神经网络基本原理简明教程之线性回归预测房价
1.1 提出問(wèn)題
問(wèn)題:在北京通州,距離通州區(qū)中心15公里的一套93平米的房子,大概是多少錢(qián)?
房?jī)r(jià)預(yù)測(cè)問(wèn)題,成為了機(jī)器學(xué)習(xí)的一個(gè)入門(mén)話題,著名的波士頓的房?jī)r(jià)數(shù)據(jù)及相關(guān)的比賽已經(jīng)很多了,但是美國(guó)的房子都是獨(dú)棟的,前院后院停車庫(kù)游泳池等等參數(shù)非常多,初學(xué)者可能理解起來(lái)有困難。我們不妨用簡(jiǎn)化版的北京通州的房?jī)r(jià)來(lái)舉例,感受一下房?jī)r(jià)預(yù)測(cè)的過(guò)程。
影響北京通州房?jī)r(jià)的因素有很多,居住面積、地理位置、朝向、學(xué)區(qū)房、周邊設(shè)施、建筑年份等等,其中,面積和地理位置是兩個(gè)比較重要的因素。地理位置信息一般采用經(jīng)緯度方式表示,但是經(jīng)緯度是兩個(gè)特征值,聯(lián)合起來(lái)才有意義,因此,我們把它轉(zhuǎn)換成了到通州區(qū)中心的距離。
我們有1000個(gè)樣本,每個(gè)樣本有兩個(gè)特征值,一個(gè)標(biāo)簽值,示例如下:
特征值1 - 地理位置,統(tǒng)計(jì)得到:
最大值:21.96公里
最小值:2.02公里
平均值:12.13公里
特征值2 - 房屋面積,統(tǒng)計(jì)得到:
最大值:119平米
最小值:40平米
平均值:78.9平米
標(biāo)簽值 - 房?jī)r(jià),單位為百萬(wàn)元:
最大值:674.37
最小值:181.38
平均值:420.64
這個(gè)數(shù)據(jù)是三維的,所以可以用兩個(gè)特征值作為x和y,用標(biāo)簽值作為z,在xyz坐標(biāo)中展示:
正向
側(cè)向
從正向看,很像一塊草坪,似乎是一個(gè)平面。再?gòu)膫?cè)向看,和第4章中的直線擬合數(shù)據(jù)很像。所以,對(duì)于這種三維的線性擬合,我們可以把它想象成為擬合一個(gè)平面,這個(gè)平面會(huì)位于這塊“草坪”的中位,把“草坪”分割成上下兩塊更薄的“草坪”,最終使得所有樣本點(diǎn)到這個(gè)平面的距離的平方和最小。
1.2 多元線性回歸模型
由于表中可能沒(méi)有恰好符合15公里、93平米條件的數(shù)據(jù),因此我們需要根據(jù)1000個(gè)樣本值來(lái)建立一個(gè)模型,來(lái)解決預(yù)測(cè)問(wèn)題。
通過(guò)圖示,我們基本可以確定這個(gè)問(wèn)題是個(gè)線性回歸問(wèn)題,而且是典型的多元線性回歸,即包括兩個(gè)或兩個(gè)以上自變量的回歸。多元線性回歸的函數(shù)模型如下:
y=a0+a1x1+a2x2+?+akxk
具體化到房?jī)r(jià)預(yù)測(cè)問(wèn)題,上面的公式可以簡(jiǎn)化成:
z=x1?w1+x2?w2+b
拋開(kāi)本例的房?jī)r(jià)問(wèn)題,對(duì)于一般的應(yīng)用問(wèn)題,建立多元線性回歸模型時(shí),為了保證回歸模型具有優(yōu)良的解釋能力和預(yù)測(cè)效果,應(yīng)首先注意自變量的選擇,其準(zhǔn)則是:
1.自變量對(duì)因變量必須有顯著的影響,并呈密切的線性相關(guān);
2.自變量與因變量之間的線性相關(guān)必須是真實(shí)的,而不是形式上的;
3.自變量之間應(yīng)具有一定的互斥性,即自變量之間的相關(guān)程度不應(yīng)高于自變量與因變量之因的相關(guān)程度;
4.自變量應(yīng)具有完整的統(tǒng)計(jì)數(shù)據(jù),其預(yù)測(cè)值容易確定。
1.3 解決方案
如果用傳統(tǒng)的數(shù)學(xué)方法解決這個(gè)問(wèn)題,我們可以使用正規(guī)方程,從而可以得到數(shù)學(xué)解析解,然后再使用神經(jīng)網(wǎng)絡(luò)方式來(lái)求得近似解,從而比較兩者的精度,再進(jìn)一步調(diào)試神經(jīng)網(wǎng)絡(luò)的參數(shù),達(dá)到學(xué)習(xí)的目的。
我們不妨先把兩種方式在這里做一個(gè)對(duì)比,讀者閱讀并運(yùn)行代碼,得到結(jié)果后,再回到這里來(lái)仔細(xì)體會(huì)下面這個(gè)表格中的比較項(xiàng):
2.1 正規(guī)方程解法
對(duì)于線性回歸問(wèn)題,除了前面提到的最小二乘法可以解決一元線性回歸的問(wèn)題外,也可以解決多元線性回歸問(wèn)題。
對(duì)于多元線性回歸,可以用正規(guī)方程來(lái)解決,也就是得到一個(gè)數(shù)學(xué)上的解析解。它可以解決下面這個(gè)公式描述的問(wèn)題:
2.2 簡(jiǎn)單的推導(dǎo)方法
在做函數(shù)擬合(回歸)時(shí),我們假設(shè)函數(shù)H為:
令b=w0,則:
公式3中的x是一個(gè)樣本的n個(gè)特征值,如果我們把m個(gè)樣本一起計(jì)算,將會(huì)得到下面這個(gè)矩陣:
公式5中的X和W的矩陣形狀如下:
然后我們期望假設(shè)函數(shù)的輸出與真實(shí)值一致,則有:
其中,Y的形狀如下:
直觀上看,W = Y/X,但是這里三個(gè)值都是矩陣,而矩陣沒(méi)有除法,所以需要得到X的逆矩陣,用Y乘以X的逆矩陣即可。但是又會(huì)遇到一個(gè)問(wèn)題,只有方陣才有逆矩陣,而X不一定是方陣,所以要先把左側(cè)變成方陣,就可能會(huì)有逆矩陣存在了。所以,先把等式兩邊同時(shí)乘以X的轉(zhuǎn)置矩陣,以便得到X的方陣:
其中,XT是X的轉(zhuǎn)置矩陣,XTX一定是個(gè)方陣,并且假設(shè)其存在逆矩陣,把它移到等式右側(cè)來(lái):’
至此可以求出W的正規(guī)方程。
2.3 復(fù)雜的推導(dǎo)方法
我們?nèi)匀皇褂镁讲顡p失函數(shù):
把b看作是一個(gè)恒等于1的feature,并把z=XW計(jì)算公式帶入,并變成矩陣形式:
對(duì)w求導(dǎo),令導(dǎo)數(shù)為0,就是W的最小值解:
求導(dǎo)后:
第一項(xiàng)的結(jié)果是:2XTXW
第二項(xiàng)和第三項(xiàng)的結(jié)果都是:XTY
第四項(xiàng)的結(jié)果是:0
再令導(dǎo)數(shù)為0:
結(jié)論和公式10一樣。
以上推導(dǎo)的基本公式可以參考第0章的公式60-69。
逆矩陣(XTX)?1可能不存在的原因是:
1.特征值冗余,比如x2=x21,即正方形的邊長(zhǎng)與面積的關(guān)系,不能做為兩個(gè)特征同時(shí)存在
2.特征數(shù)量過(guò)多,比如特征數(shù)n比樣本數(shù)m還要大
以上兩點(diǎn)在我們這個(gè)具體的例子中都不存在。
2.4 代碼實(shí)現(xiàn)
我們?cè)倏匆幌聵颖緮?shù)據(jù)的樣子:
根據(jù)公式(5),我們應(yīng)該建立如下的X,Y矩陣:
根據(jù)公式(10):
1.X是1000x3的矩陣,X的轉(zhuǎn)置是3x1000,XTX生成(3x3的矩陣
2.(XTX)?1也是3x3
3.再乘以XT,即(3x3)x(3x1000)的矩陣,變成3x1000
4.再乘以Y,Y是1000x1,所以(3x1000)x(1000x1)變成3x1,就是W的解,其中包括一個(gè)偏移值b和兩個(gè)權(quán)重值w,3個(gè)值在一個(gè)向量里
資源包:HelperClass
3.1 神經(jīng)網(wǎng)絡(luò)解法
與單特征值的線性回歸問(wèn)題類似,多變量(多特征值)的線性回歸可以被看做是一種高維空間的線性擬合。以具有兩個(gè)特征的情況為例,這種線性擬合不再是用直線去擬合點(diǎn),而是用平面去擬合點(diǎn)。
3.2 定義神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)
我們定義一個(gè)一層的神經(jīng)網(wǎng)絡(luò),輸入層為2或者更多,反正大于2了就沒(méi)區(qū)別。這個(gè)一層的神經(jīng)網(wǎng)絡(luò)的特點(diǎn)是:
1.沒(méi)有中間層,只有輸入項(xiàng)和輸出層(輸入項(xiàng)不算做一層),
2.輸出層只有一個(gè)神經(jīng)元,
3.神經(jīng)元有一個(gè)線性輸出,不經(jīng)過(guò)激活函數(shù)處理,即在下圖中,經(jīng)過(guò)Σ求和得到Z值之后,直接把Z值輸出。
輸入層
單獨(dú)看第一個(gè)樣本是這樣的:
一共有1000個(gè)樣本,每個(gè)樣本2個(gè)特征值,X就是一個(gè)1000×2的矩陣:
x1 表示第一個(gè)樣本,x1,1表示第一個(gè)樣本的一個(gè)特征值,y1是第一個(gè)樣本的標(biāo)簽值。
權(quán)重W和B
由于我們只想完成一個(gè)回歸(擬合)任務(wù),所以輸出層只有一個(gè)神經(jīng)元。由于是線性的,所以沒(méi)有用激活函數(shù)。
寫(xiě)成矩陣形式:
上述公式中括號(hào)中的數(shù)字表示該矩陣的(行x列)數(shù)。
對(duì)于擬合,可以想象成用一支筆在一堆點(diǎn)中畫(huà)一條直線或者曲線,而那一個(gè)神經(jīng)元就是這支筆。如果有多個(gè)神經(jīng)元,可以畫(huà)出多條線來(lái),就不是擬合了,而是分類。
損失函數(shù)
因?yàn)槭蔷€性回歸問(wèn)題,所以損失函數(shù)使用均方差函數(shù)。
其中,zi是樣本預(yù)測(cè)值,yi是樣本的標(biāo)簽值。
3.3 反向傳播
單樣本多特征計(jì)算
與上一章不同,本章中的前向計(jì)算是多特征值的公式:
因?yàn)閤有兩個(gè)特征值,對(duì)應(yīng)的W也有兩個(gè)權(quán)重值。xi1表示第i個(gè)樣本的第1個(gè)特征值,所以無(wú)論是x還是w都是一個(gè)向量或者矩陣了,那么我們?cè)诜聪騻鞑シ椒ㄖ械奶荻扔?jì)算公式還有效嗎?答案是肯定的,我們來(lái)一起做個(gè)簡(jiǎn)單推導(dǎo)。
由于W被分成了w1和w2兩部分,根據(jù)公式1和公式2,我們單獨(dú)對(duì)它們求導(dǎo):
求損失函數(shù)對(duì)W矩陣的偏導(dǎo),是無(wú)法求的,所以要變成求各個(gè)W的分量的偏導(dǎo)。由于W的形狀是:
所以求loss對(duì)W的偏導(dǎo),由于W是個(gè)矩陣,所以應(yīng)該這樣寫(xiě):
多樣本多特征計(jì)算
當(dāng)進(jìn)行多樣本計(jì)算時(shí),我們用m=3個(gè)樣本做一個(gè)實(shí)例化推導(dǎo):
3.4 代碼實(shí)現(xiàn)
我們依然采用第四章中已經(jīng)寫(xiě)好的HelperClass目錄中的那些類,來(lái)表示我們的神經(jīng)網(wǎng)絡(luò)。雖然此次神經(jīng)元多了一個(gè)輸入,但是不用改代碼就可以適應(yīng)這種變化,因?yàn)樵谇跋蛴?jì)算代碼中,使用的是矩陣乘的方式,可以自動(dòng)適應(yīng)x的多個(gè)列的輸入,只要對(duì)應(yīng)的w的矩陣形狀是正確的即可。
但是在初始化時(shí),我們必須手動(dòng)指定x和w的形狀,如下面的代碼所示:
from HelperClass.SimpleDataReader import *if __name__ == '__main__':# datareader = SimpleDataReader()reader.ReadData()# netparams = HyperParameters(2, 1, eta=0.1, max_epoch=100, batch_size=1, eps = 1e-5)net = NeuralNet(params)net.train(reader)# inferencex1 = 15x2 = 93x = np.array([x1,x2]).reshape(1,2)print(net.inference(x))在參數(shù)中,指定了學(xué)習(xí)率0.1,最大循環(huán)次數(shù)100輪,批大小1個(gè)樣本,以及停止條件損失函數(shù)值1e-5。
在神經(jīng)網(wǎng)絡(luò)初始化時(shí),指定了input_size=2,且output_size=1,即一個(gè)神經(jīng)元可以接收兩個(gè)輸入,最后是一個(gè)輸出。
最后的inference部分,是把兩個(gè)條件(15公里,93平方米)代入,查看輸出結(jié)果。
在下面的神經(jīng)網(wǎng)絡(luò)的初始化代碼中,W的初始化是根據(jù)input_size和output_size的值進(jìn)行的。
class NeuralNet(object):def __init__(self, params):self.params = paramsself.W = np.zeros((self.params.input_size, self.params.output_size))self.B = np.zeros((1, self.params.output_size))正向計(jì)算的代碼
class NeuralNet(object):def __forwardBatch(self, batch_x):Z = np.dot(batch_x, self.W) + self.Breturn Z誤差反向傳播的代碼
class NeuralNet(object):def __backwardBatch(self, batch_x, batch_y, batch_z):m = batch_x.shape[0]dZ = batch_z - batch_ydB = dZ.sum(axis=0, keepdims=True)/mdW = np.dot(batch_x.T, dZ)/mreturn dW, dB3.5 運(yùn)行結(jié)果
在Visual Studio 2017中,可以使用Ctrl+F5運(yùn)行Level2的代碼,但是,會(huì)遇到一個(gè)令人沮喪的打印輸出:
減法怎么會(huì)出問(wèn)題?什么是nan?
nan的意思是數(shù)值異常,導(dǎo)致計(jì)算溢出了,出現(xiàn)了沒(méi)有意義的數(shù)值?,F(xiàn)在是每500個(gè)迭代監(jiān)控一次,我們把監(jiān)控頻率調(diào)小一些,再試試看:
epoch=0 0 10 6.838664338516814e+66 0 20 2.665505502247752e+123 0 30 1.4244204612680962e+179 0 40 1.393993758296751e+237 0 50 2.997958629609441e+290 NeuralNet.py:76: RuntimeWarning: overflow encountered in squareLOSS = (Z - Y)**2 0 60 inf 0 70 inf 0 80 inf 0 90 inf 0 100 inf 0 110 inf NeuralNet.py:32: RuntimeWarning: invalid value encountered in subtractself.W = self.W - self.params.eta * dW 0 120 nan 0 130 nan前10次迭代,損失函數(shù)值已經(jīng)達(dá)到了6.83e+66,而且越往后運(yùn)行值越大,最后終于溢出了。下面的損失函數(shù)歷史記錄也表明了這一過(guò)程。
3.6 尋找失敗的原因
我們可以在NeuralNet.py文件中,在下述代碼行上設(shè)置斷點(diǎn),跟蹤一下訓(xùn)練過(guò)程,以便找到問(wèn)題所在:
在VS2017中用F5運(yùn)行debug模式,看第50行的結(jié)果:
返回的樣本數(shù)據(jù)是正常的。再看下一行:
batch_z array([[0.]])第一次運(yùn)行前向計(jì)算,由于W和B初始值都是0,所以z也是0,這是正常的。再看下一行:
dW array([[ -1210.80475712],[-10007.22118309]]) dB array([[-244.07856544]])dW和dB的值都非常大,這是因?yàn)橄旅孢@行代碼:
batch_z是0,batch_y是244.078,二者相減,是-244.078,因此dB就是-244.078,dW因?yàn)榫仃嚦肆薭atch_x,值就更大了。
再看W和B的更新值,一樣很大:
self.W array([[ 121.08047571],[1000.72211831]]) self.B array([[24.40785654]])如果W和B的值很大,那么再下一輪進(jìn)行前向計(jì)算時(shí),會(huì)得到更糟糕的結(jié)果:
batch_z array([[82459.53752331]])果不其然,這次的z值飆升到了8萬(wàn)多,如此下去,幾輪以后數(shù)值溢出是顯而易見(jiàn)的事情了。
那么我們到底遇到了什么情況?
4.1 樣本特征數(shù)據(jù)歸一化
發(fā)現(xiàn)問(wèn)題的根源
仔細(xì)分析一下屏幕打印信息,前兩次迭代的損失值已經(jīng)是天文數(shù)字了,后面的W和B的值也在不斷變大,說(shuō)明網(wǎng)絡(luò)發(fā)散了。難度我們遇到了傳說(shuō)中的梯度爆炸!數(shù)值太大,導(dǎo)致計(jì)算溢出了。第一次遇到這個(gè)情況,但相信不會(huì)是最后一次,因?yàn)檫@種情況在神經(jīng)網(wǎng)絡(luò)中太常見(jiàn)了。
回想一個(gè)問(wèn)題:為什么在第4章中,我們沒(méi)有遇到這種情況?把第4章的數(shù)據(jù)樣本拿來(lái)看一看:
所有的X值(服務(wù)器數(shù)量除以1000后的值)都是在[0,1]之間的,而本章中的房?jī)r(jià)數(shù)據(jù)有兩個(gè)特征值,一個(gè)是公里數(shù),一個(gè)是平米數(shù),全都是不是在[0,1]之間的,并且取值范圍還不相同。我們不妨把本次樣本數(shù)據(jù)也做一下這樣的處理,亦即“歸一化”。
其實(shí),數(shù)據(jù)歸一化是深度學(xué)習(xí)的必要步驟之一,已經(jīng)是大師們的必殺技能,也因此它很少被各種博客/文章所提及,以至于初學(xué)者們經(jīng)常被坑。
根據(jù)5.0.1中對(duì)數(shù)據(jù)的初步統(tǒng)計(jì),我們是不是也可以把公里數(shù)都除以100,而平米數(shù)都除以1000呢,這樣也會(huì)得到[0,1]之間的數(shù)字?公里數(shù)的取值范圍是[2,22],除以100后變成了[0.02,0.22]。平米數(shù)的取值范圍是[40,120],除以1000后變成了[0.04,0.12]。
對(duì)本例來(lái)說(shuō)這樣做肯定是可以正常工作的,但是下面我們要介紹一種更科學(xué)合理的做法。
4.2 為什么要做歸一化
理論層面上,神經(jīng)網(wǎng)絡(luò)是以樣本在事件中的統(tǒng)計(jì)分布概率為基礎(chǔ)進(jìn)行訓(xùn)練和預(yù)測(cè)的,所以它對(duì)樣本數(shù)據(jù)的要求比較苛刻。具體說(shuō)明如下:
1.樣本的各個(gè)特征的取值要符合概率分布,即[0,1]
2.樣本的度量單位要相同。我們并沒(méi)有辦法去比較1米和1公斤的區(qū)別,但是,如果我們知道了1米在整個(gè)樣本中的大小比例,以及1公斤在整個(gè)樣本中的大小比例,比如一個(gè)處于0.2的比例位置,另一個(gè)處于0.3的比例位置,就可以說(shuō)這個(gè)樣本的1米比1公斤要小!
3.神經(jīng)網(wǎng)絡(luò)假設(shè)所有的輸入輸出數(shù)據(jù)都是標(biāo)準(zhǔn)差為1,均值為0,包括權(quán)重值的初始化,激活函數(shù)的選擇,以及優(yōu)化算法的的設(shè)計(jì)。
4.數(shù)值問(wèn)題
歸一化可以避免一些不必要的數(shù)值問(wèn)題。因?yàn)榧せ詈瘮?shù)sigmoid/tanh的非線性區(qū)間大約在[-1.7,1.7]。意味著要使神經(jīng)元有效,線性計(jì)算輸出的值的數(shù)量級(jí)應(yīng)該在1(1.7所在的數(shù)量級(jí))左右。這時(shí)如果輸入較大,就意味著權(quán)值必須較小,一個(gè)較大,一個(gè)較小,兩者相乘,就引起數(shù)值問(wèn)題了。
5.梯度更新
若果輸出層的數(shù)量級(jí)很大,會(huì)引起損失函數(shù)的數(shù)量級(jí)很大,這樣做反向傳播時(shí)的梯度也就很大,這時(shí)會(huì)給梯度的更新帶來(lái)數(shù)值問(wèn)題。
6.學(xué)習(xí)率
知道梯度非常大,學(xué)習(xí)率就必須非常小,因此,學(xué)習(xí)率(學(xué)習(xí)率初始值)的選擇需要參考輸入的范圍,不如直接將數(shù)據(jù)歸一化,這樣學(xué)習(xí)率就不必再根據(jù)數(shù)據(jù)范圍作調(diào)整。 對(duì)w1適合的學(xué)習(xí)率,可能相對(duì)于w2來(lái)說(shuō)會(huì)太小,若果使用適合w1的學(xué)習(xí)率,會(huì)導(dǎo)致在w2方向上步進(jìn)非常慢,會(huì)消耗非常多的時(shí)間,而使用適合w2的學(xué)習(xí)率,對(duì)w1來(lái)說(shuō)又太大,搜索不到適合w1的解。
4.3 從損失函數(shù)等高線圖分析歸一化的必要性
在房?jī)r(jià)數(shù)據(jù)中,地理位置的取值范圍是[2,20],而房屋面積的取值范圍為[40,120],二者相差太遠(yuǎn),根本不可以放在一起計(jì)算了?
根據(jù)公式z=x1w1+x2w2+b,神經(jīng)網(wǎng)絡(luò)想學(xué)習(xí)w1和w2,但是數(shù)值范圍問(wèn)題導(dǎo)致神經(jīng)網(wǎng)絡(luò)來(lái)說(shuō)很難“理解”。下圖展示了歸一化前后的情況Loss值的等高圖,意思是地理位置和房屋面積取不同的值時(shí),作為組合來(lái)計(jì)算損失函數(shù)值時(shí),形成的類似地圖的等高圖。左側(cè)為歸一化前,右側(cè)為歸一化后:
房屋面積的取值范圍是[40,120],而地理位置的取值范圍是[2,20],二者會(huì)形成一個(gè)很扁的橢圓,如左側(cè)。這樣在尋找最優(yōu)解的時(shí)候,過(guò)程會(huì)非常曲折。運(yùn)氣不好的話,根本就沒(méi)法訓(xùn)練。
4.4 歸一化的基本概念
有三個(gè)類似的概念,歸一化,標(biāo)準(zhǔn)化,中心化。
歸一化
把數(shù)據(jù)線性地變成[0,1]或[-1,1]之間的小數(shù),把帶單位的數(shù)據(jù)(比如米,公斤)變成無(wú)量綱的數(shù)據(jù),區(qū)間縮放。
歸一化有三種方法:
1.Min-Max歸一化:
2.平均值歸一化
3.非線性歸一化
對(duì)數(shù)轉(zhuǎn)換:
反余切轉(zhuǎn)換:
標(biāo)準(zhǔn)化
把每個(gè)特征值中的所有數(shù)據(jù),變成平均值為0,標(biāo)準(zhǔn)差為1的數(shù)據(jù),最后為正態(tài)分布。Z-score規(guī)范化(標(biāo)準(zhǔn)差標(biāo)準(zhǔn)化 / 零均值標(biāo)準(zhǔn)化,其中std是標(biāo)準(zhǔn)差):
中心化
平均值為0,無(wú)標(biāo)準(zhǔn)差要求:
4.5 如何做數(shù)據(jù)歸一化
我們?cè)倏纯礃颖镜臄?shù)據(jù):
按照歸一化的定義,我們只要把地理位置列和居住面積列分別做歸一化就達(dá)到要求了,即:
注意:
1.我們并沒(méi)有歸一化樣本的標(biāo)簽Y數(shù)據(jù),所以最后一行的價(jià)格還是保持不變
2.我們是對(duì)兩列特征值分別做歸一化處理的
4.6 代碼實(shí)現(xiàn)
在HelperClass目錄的SimpleDataReader.py文件中,給該類增加一個(gè)方法:
返回值X_new是歸一化后的樣本,和原始數(shù)據(jù)的形狀一樣。
再把主程序修改一下,在ReadData()方法后,緊接著調(diào)用NormalizeX()方法:
if __name__ == '__main__':# datareader = SimpleDataReader()reader.ReadData()reader.NormalizeX()# netparams = HyperParameters(eta=0.1, max_epoch=10, batch_size=1, eps = 1e-5)net = NeuralNet(params, 2, 1)net.train(reader)4.7 運(yùn)行結(jié)果
運(yùn)行上述代碼,看打印結(jié)果:
雖然損失函數(shù)值沒(méi)有像我們想象的那樣趨近于0,但是卻穩(wěn)定在了400左右震蕩,這也算是收斂!看一下?lián)p失函數(shù)圖像:
再看看W和B的輸出值和z的預(yù)測(cè)值:
w1 = -41.71417524 w2 = 395.84701164 b = 242.15205099 z = 37366.53336103回憶一下正規(guī)方程的輸出值:
w1= -2.0184092853092226 w2= 5.055333475112755 b= 46.235258613837644 z= 486.1051325196855正規(guī)方程預(yù)測(cè)房?jī)r(jià)結(jié)果:
Z=?2.018×15+5.055×93+46.235=486.105(萬(wàn)元)
神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)房?jī)r(jià)結(jié)果:
Z=?14.714×15+395.847×93+242.152=37366(萬(wàn)元)
好吧,我們遇到了天價(jià)房!這是怎么回事兒?難道和我們做數(shù)據(jù)歸一化有關(guān)系?
在5.0.1中,我們想象神經(jīng)網(wǎng)絡(luò)會(huì)尋找一個(gè)平面,來(lái)擬合這些空間中的樣本點(diǎn),是不是這樣呢?我們通過(guò)下面的函數(shù)來(lái)實(shí)現(xiàn)這個(gè)可視化:
def ShowResult(net, reader):X,Y = reader.GetWholeTrainSamples()fig = plt.figure()ax = Axes3D(fig)ax.scatter(X[:,0],X[:,1],Y)p = np.linspace(0,1)q = np.linspace(0,1)P,Q = np.meshgrid(p,q)R = np.hstack((P.ravel().reshape(2500,1), Q.ravel().reshape(2500,1)))Z = net.inference(R)Z = Z.reshape(50,50)ax.plot_surface(P,Q,Z, cmap='rainbow')前半部分代碼先是把所有的點(diǎn)顯示在三維空間中,我們?cè)?jīng)描述它們像一塊厚厚的草坪。后半部分的代碼在[0,1]空間內(nèi)形成了一個(gè)50x50的網(wǎng)格,亦即有2500個(gè)點(diǎn),這些點(diǎn)都是有橫縱坐標(biāo)的。然后把這些點(diǎn)送入神經(jīng)網(wǎng)絡(luò)中做預(yù)測(cè),得到了2500個(gè)Z值,相當(dāng)于第三維的坐標(biāo)值。最后把這2500個(gè)三維空間的點(diǎn),以網(wǎng)格狀顯示在空間中,就形成了下面的可視化的結(jié)果:
正向
側(cè)向
從正向圖可以看到,真的形成了一個(gè)平面;從側(cè)向圖可以看到,這個(gè)平面也確實(shí)穿過(guò)了那些點(diǎn),并且把它們分成了上下兩個(gè)部分。只不過(guò)由于訓(xùn)練精度的問(wèn)題,沒(méi)有做到平分,而是斜著穿過(guò)了點(diǎn)的區(qū)域,就好像第4章中的那個(gè)精度不夠的線性回歸的結(jié)果。
細(xì)心的讀者可能會(huì)問(wèn)兩個(gè)問(wèn)題:
1.為什么要在[0,1]空間中形成50x50的網(wǎng)格呢?
2.50這個(gè)數(shù)字從哪里來(lái)的?
NumPy庫(kù)的np.linspace(0,1)的含義,就是在[0,1]空間中生成50個(gè)等距的點(diǎn),第三個(gè)參數(shù)不指定時(shí),缺省是50。因?yàn)槲覀兦懊鎸?duì)樣本數(shù)據(jù)做過(guò)歸一化,統(tǒng)一到了[0,1]空間中,這就方便了我們對(duì)問(wèn)題的分析,不用考慮每個(gè)特征值的實(shí)際范圍是多大了。
這下子我們可以大致放心了,神經(jīng)網(wǎng)絡(luò)的訓(xùn)練結(jié)果并沒(méi)有錯(cuò),一定是別的地方出了什么問(wèn)題。在下一節(jié)中我們來(lái)一起看看問(wèn)題出在哪里!
5.1 歸一化的后遺癥
對(duì)比結(jié)果
在上一節(jié)中,我們使用了如下超參進(jìn)行神經(jīng)網(wǎng)絡(luò)的訓(xùn)練:
我們?cè)侔衙看蝐heckpoint的W和B的值打印出來(lái):
9 0 437.5399553941636 [[-35.46926435] [399.01136072]] [[252.69305588]] 9 100 420.78580862641473 [[-36.93198181] [400.03047293]] [[251.26503706]] 9 200 398.58439997901917 [[-39.90602892] [390.9923031 ]] [[253.77229392]] 9 300 393.4058623386585 [[-31.26023019] [389.38500924]] [[247.81021777]] 9 400 380.95014666219294 [[-41.71204444] [400.49621558]] [[243.90381925]] 9 500 402.3345372333071 [[-50.16424871] [400.57038807]] [[242.88921572]] 9 600 419.2032196399209 [[-38.64935779] [397.40267036]] [[235.76347754]] 9 700 388.91219270279 [[-41.87540883] [406.51486971]] [[245.11439119]] 9 800 387.30767281965444 [[-40.57188118] [407.41384495]] [[237.77896547]] 9 900 413.7210407763991 [[-36.67601742] [406.55322285]] [[246.8067483]]打印結(jié)果中每列的含義:
1.epoch
2.iteration
3.loss
4.w1
5.w2
6.b
可以看到loss值、w1、w2、b的值,每次跳躍都很大,懷疑是學(xué)習(xí)率過(guò)高導(dǎo)致的梯度下降在最優(yōu)解附近徘徊。所以,我們先把超參修改一下:
做了三處修改:
1.學(xué)習(xí)率縮小10倍,變成0.01
2.max_epoch擴(kuò)大50倍,讓網(wǎng)絡(luò)得到充分訓(xùn)練
3.batch_size=10,使用mini-batch批量樣本訓(xùn)練,提高精度,減緩個(gè)別樣本引起的跳躍程度
運(yùn)行結(jié)果:
499 9 380.9733976063486 [[-40.0502582 ] [399.59874166]] [[245.01472597]] 499 19 380.91972396603296 [[-39.96834496] [399.55957677]] [[244.92705677]] 499 29 380.6255377532388 [[-40.31047769] [399.26167586]] [[244.19126217]] 499 39 380.6057213728372 [[-40.2563536 ] [399.35785505]] [[244.53062721]] 499 49 380.657163633654 [[-40.16087354] [399.36180641]] [[244.67728494]] 499 59 380.59442069555746 [[-40.32063337] [399.48881984]] [[244.37834746]] 499 69 380.92999531800933 [[-40.57175379] [399.16255261]] [[243.81211148]] 499 79 380.687742276159 [[-40.4266247 ] [399.30514719]] [[244.0496554]] 499 89 380.62299460835936 [[-40.2782923 ] [399.34224968]] [[244.14309928]] 499 99 380.5935045560184 [[-40.26440193] [399.39472352]] [[244.3928586]]可以看到達(dá)到了我們的目的,loss、w1、w2、b的值都很穩(wěn)定。我們使用這批結(jié)果做為分析基礎(chǔ)。首先列出W和B的訓(xùn)練結(jié)果:
再列出歸一化后的數(shù)據(jù):
通過(guò)對(duì)比我發(fā)現(xiàn),關(guān)于W的結(jié)果,第一張表最后一行的數(shù)據(jù),和第二張表最后一行的數(shù)據(jù),有驚人的相似之處!這是為什么呢?
5.2 還原真實(shí)的W,B值
我們唯一修改的地方,就是樣本數(shù)據(jù)特征值的歸一化,我們并沒(méi)有修改標(biāo)簽值。可以大概猜到W的值和樣本特征值的縮放有關(guān)系,而且縮放倍數(shù)非常相似,甚至可以說(shuō)一致。下面推導(dǎo)一下這種現(xiàn)象的數(shù)學(xué)基礎(chǔ)。
假設(shè)在歸一化之前,真實(shí)的樣本值是X,真實(shí)的權(quán)重值是W;在歸一化之后,樣本值變成了X′,訓(xùn)練出來(lái)的權(quán)重值是W′:
由于訓(xùn)練時(shí)標(biāo)簽值(房?jī)r(jià))并沒(méi)有做歸一化,意味著我們是用真實(shí)的房?jī)r(jià)做的訓(xùn)練,所以預(yù)測(cè)值和標(biāo)簽值應(yīng)該相等,所以:
歸一化的公式是:
為了簡(jiǎn)化書(shū)寫(xiě),我們令xm=xmax?xmin,把公式2代入公式1:
公式3中,x1,x2是變量,其它都是常量,如果想讓公式3等式成立,則變量項(xiàng)和常數(shù)項(xiàng)分別相等,即:
下面我們用實(shí)際數(shù)值代入公式4,5,6:
可以看到公式7、8、9的計(jì)算結(jié)果(神經(jīng)網(wǎng)絡(luò)的訓(xùn)練結(jié)果的變換值)與正規(guī)方程的計(jì)算結(jié)果(-2.018, 5.055, 46.235)基本相同,基于神經(jīng)網(wǎng)絡(luò)是一種近似解的考慮,可以認(rèn)為這種推導(dǎo)是有理論基礎(chǔ)和試驗(yàn)證明的。
5.3 代碼實(shí)現(xiàn)
下面的代碼實(shí)現(xiàn)了公式4,5,6:
X_Norm是我們?cè)谧鰵w一化時(shí)保留下來(lái)的樣本的兩個(gè)特征向量的最小值和數(shù)值范圍(最大值減去最小值)。
修改主程序如下:if __name__ == '__main__':# datareader = SimpleDataReader()reader.ReadData()reader.NormalizeX()# netparams = HyperParameters(eta=0.01, max_epoch=500, batch_size=10, eps = 1e-5)net = NeuralNet(params, 2, 1)net.train(reader, checkpoint=0.1)# inferenceW_real, B_real = DeNormalizeWeightsBias(net, reader)print("W_real=", W_real)print("B_real=", B_real)x1 = 15x2 = 93x = np.array([x1,x2]).reshape(1,2)z = np.dot(x, W_real) + B_realprint("Z=", z)ShowResult(net, reader)在net.train()方法返回之后,訓(xùn)練好的W和B的值就保存在NeuralNet類的屬性里了。然后通過(guò)調(diào)用DeNormalizeWeightsBias()函數(shù),把它們轉(zhuǎn)換成真實(shí)的W_real和B_real值,就好比我們不做歸一化而能訓(xùn)練出來(lái)的權(quán)重值一樣。
最后在推理預(yù)測(cè)時(shí),我們直接使用了np.dot()公式,而沒(méi)有使用net.inference()方法,是因?yàn)樵趎et實(shí)例中的W和B是還原前的值,做前向計(jì)算時(shí)還是會(huì)有問(wèn)題,所以我們直接把前向計(jì)算公式拿出來(lái),代入W_real和B_real,就可以得到真實(shí)的預(yù)測(cè)值了。
5.4 運(yùn)行結(jié)果
運(yùn)行上述代碼,觀察最后部分的打印輸出:
把結(jié)果與正規(guī)方程的結(jié)果對(duì)比一下:
二者幾乎一樣,可以認(rèn)為我們成功了!但是這一套代碼下來(lái),總覺(jué)得有些啰嗦,對(duì)于簡(jiǎn)單的線性問(wèn)題來(lái)說(shuō),這么做可以,如果遇到非線性問(wèn)題,或者深層網(wǎng)絡(luò),這么做是不是也可以呢?
正確的推理預(yù)測(cè)方法
6.1 預(yù)測(cè)數(shù)據(jù)的歸一化
在上一節(jié)中,我們?cè)谟糜?xùn)練出來(lái)的模型預(yù)測(cè)房屋價(jià)格之前,還需要先還原W和B的值,這看上去比較麻煩,下面我們來(lái)介紹一種正確的推理方法。
既然我們?cè)谟?xùn)練時(shí)可以把樣本數(shù)據(jù)歸一化,那么在預(yù)測(cè)時(shí),把預(yù)測(cè)數(shù)據(jù)也做相同方式的歸一化,不是就可以和訓(xùn)練數(shù)據(jù)一樣進(jìn)行預(yù)測(cè)了嗎?且慢!這里有一個(gè)問(wèn)題,訓(xùn)練時(shí)的樣本數(shù)據(jù)是批量的,至少是成百成千的數(shù)量級(jí)。但是預(yù)測(cè)時(shí),一般只有一個(gè)或幾個(gè)數(shù)據(jù),如何做歸一化?
我們?cè)卺槍?duì)訓(xùn)練數(shù)據(jù)做歸一化時(shí),得到的最重要的數(shù)據(jù)是訓(xùn)練數(shù)據(jù)的最小值和最大值,我們只需要把這兩個(gè)值記錄下來(lái),在預(yù)測(cè)時(shí)使用它們對(duì)預(yù)測(cè)數(shù)據(jù)做歸一化,這就相當(dāng)于把預(yù)測(cè)數(shù)據(jù)“混入”訓(xùn)練數(shù)據(jù)。前提是預(yù)測(cè)數(shù)據(jù)的特征值不能超出訓(xùn)練數(shù)據(jù)的特征值范圍,否則有可能影響準(zhǔn)確程度。
6.2 代碼實(shí)現(xiàn)
基于這種想法,我們先給SimpleDataReader類增加一個(gè)方法NormalizePredicateData(),如下述代碼:
X_norm數(shù)組中的數(shù)據(jù),是在訓(xùn)練時(shí)從樣本數(shù)據(jù)中得到的最大值最小值,比如:
所以,最后X_new就是按照訓(xùn)練樣本的規(guī)格歸一化好的預(yù)測(cè)歸一化數(shù)據(jù),然后我們把這個(gè)預(yù)測(cè)歸一化數(shù)據(jù)放入網(wǎng)絡(luò)中進(jìn)行預(yù)測(cè):
6.3 運(yùn)行結(jié)果
...... 199 69 380.66017104568533 [[-40.46214107][399.22941114]] [[244.17767124]] 199 79 380.74980617596043 [[-40.54801022][399.27413915]] [[244.00581217]] 199 89 380.5933565144328 [[-40.24324555][399.35384485]] [[244.398389]] 199 99 380.5942402877278 [[-40.23494571][399.40443921]] [[244.388824]] W= [[-40.23494571][399.40443921]] B= [[244.388824]] Z= [[486.16645199]]比較一下正規(guī)方程的結(jié)果:
z= 486.1051325196855二者非常接近,可以說(shuō)這種方法的確很方便,把預(yù)測(cè)數(shù)據(jù)看作訓(xùn)練數(shù)據(jù)的一個(gè)記錄,先做歸一化,再做預(yù)測(cè),這樣就不需要把權(quán)重矩陣還原了。
看上去我們已經(jīng)完美地解決了這個(gè)問(wèn)題,但是且慢,仔細(xì)看看loss值,還有w和b的值,都是幾十幾百的數(shù)量級(jí),這和神經(jīng)網(wǎng)絡(luò)的概率計(jì)算的優(yōu)點(diǎn)并不吻合,實(shí)際上它們的值都應(yīng)該在[0,1]之間的。
大數(shù)量級(jí)的數(shù)據(jù)有另外一個(gè)問(wèn)題,就是它的波動(dòng)有可能很大。目前我們還沒(méi)有使用激活函數(shù),一旦網(wǎng)絡(luò)復(fù)雜了,開(kāi)始使用激活函數(shù)時(shí),像486.166這種數(shù)據(jù),一旦經(jīng)過(guò)激活函數(shù)就會(huì)發(fā)生梯度飽和的現(xiàn)象,輸出值總為1,這樣對(duì)于后面的網(wǎng)絡(luò)就沒(méi)什么意義了,因?yàn)檩斎胫刀际?。
好吧,看起來(lái)問(wèn)題解決得并不完美,我們看看還能有什么更好的解決方案!
對(duì)標(biāo)簽值歸一化
7.1發(fā)現(xiàn)問(wèn)題
這一節(jié)里我們重點(diǎn)解決在訓(xùn)練過(guò)程中的數(shù)值的數(shù)量級(jí)的問(wèn)題。
我們既然已經(jīng)對(duì)樣本數(shù)據(jù)特征值做了歸一化,那么如此大數(shù)值的損失函數(shù)值是怎么來(lái)的呢?看一看損失函數(shù)定義:
其中,zi是預(yù)測(cè)值,yi是標(biāo)簽值。初始狀態(tài)時(shí),W和B都是0,所以,經(jīng)過(guò)前向計(jì)算函數(shù)Z=X?W+B的結(jié)果是0,但是Y值很大,處于[181.38, 674.37]之間,再經(jīng)過(guò)平方計(jì)算后,一下子就成為至少5位數(shù)的數(shù)值了。
再看反向傳播時(shí)的過(guò)程:
def __backwardBatch(self, batch_x, batch_y, batch_z):m = batch_x.shape[0]dZ = batch_z - batch_ydB = dZ.sum(axis=0, keepdims=True)/mdW = np.dot(batch_x.T, dZ)/mreturn dW, dB第二行代碼求得的dZ,與房?jī)r(jià)是同一數(shù)量級(jí)的,這樣經(jīng)過(guò)反向傳播后,dW和dB的值也會(huì)很大,導(dǎo)致整個(gè)反向傳播鏈的數(shù)值都很大。我們可以debug一下,得到第一反向傳播時(shí)的數(shù)值是:
dW array([[-142.59982906],[-283.62409678]]) dB array([[-443.04543906]]) 上述數(shù)值又可能在讀者的機(jī)器上是不一樣的,因?yàn)闃颖咀隽藄huffle,但是不影響我們對(duì)問(wèn)題的分析。這么大的數(shù)值,需要把學(xué)習(xí)率設(shè)置得很小,比如0.001,才可以落到[0,1]區(qū)間,但是損失函數(shù)值還是不能變得很小。
如果我們像對(duì)特征值做歸一化一樣,把標(biāo)簽值也歸一化到[0,1]之間,是不是有幫助呢?
7.2 代碼實(shí)現(xiàn)
參照X的歸一化方法,對(duì)Y的歸一化公式如下:
在SimpleDataReader類中增加新方法如下class
SimpleDataReader(object):def NormalizeY(self):self.Y_norm = np.zeros((1,2))max_value = np.max(self.YRaw)min_value = np.min(self.YRaw)# min valueself.Y_norm[0, 0] = min_value # range valueself.Y_norm[0, 1] = max_value - min_value y_new = (self.YRaw - min_value) / self.Y_norm[0, 1]self.YTrain = y_new:原始數(shù)據(jù)中,Y的數(shù)值范圍是:
最大值:674.37
最小值:181.38
平均值:420.64
歸一化后,Y的數(shù)值范圍是:
最大值:1.0
最小值:0.0
平均值:0.485
注意,我們同樣記住了Y_norm的值便于以后使用。
修改主程序代碼,增加對(duì)Y歸一化的方法調(diào)用NormalizeY():
# main if __name__ == '__main__':# datareader = SimpleDataReader()reader.ReadData()reader.NormalizeX()reader.NormalizeY()# netparams = HyperParameters(eta=0.01, max_epoch=200, batch_size=10, eps=1e-5)net = NeuralNet(params, 2, 1)net.train(reader, checkpoint=0.1)# inferencex1 = 15x2 = 93x = np.array([x1,x2]).reshape(1,2)x_new = reader.NormalizePredicateData(x)z = net.inference(x_new)print("z=", z)7.3 運(yùn)行結(jié)果
運(yùn)行上述代碼得到的結(jié)果其實(shí)并不令人滿意:
雖然W和B的值都已經(jīng)處于[-1,1]之間了,但是z的值也在[0,1]之間,一套房子不可能賣0.61萬(wàn)元!
聰明的讀者可能會(huì)想到:既然對(duì)標(biāo)簽值做了歸一化,那么我們?cè)诘玫筋A(yù)測(cè)結(jié)果后,需要對(duì)這個(gè)結(jié)果應(yīng)該做反歸一化。
根據(jù)公式2,反歸一化的公式應(yīng)該是:
代碼如下:
倒數(shù)第二行代碼,就是公式3。運(yùn)行…結(jié)果如下:
W= [[-0.08149004][ 0.81022449]] B= [[0.12801985]] z= [[0.61856996]] Z_real= [[486.33591769]]看Z_real的值,完全滿足要求!
總結(jié)一下從本章中學(xué)到的正確的方法:
1.X必須歸一化,否則無(wú)法訓(xùn)練;
2.Y值不在[0,1]之間時(shí),要做歸一化,好處是迭代次數(shù)少;
3.如果Y做了歸一化,對(duì)得出來(lái)的預(yù)測(cè)結(jié)果做關(guān)于Y的反歸一化
至此,我們完美地解決了北京通州地區(qū)的房?jī)r(jià)預(yù)測(cè)問(wèn)題!但是還沒(méi)有解決自己可以有能力買一套北京通州的房子的問(wèn)題…
完整代碼:
1.ch5.npz是訓(xùn)練集:可以用自己的訓(xùn)練集
2.掃描二維碼獲取HelperClass包,封裝了一些函數(shù)
3.主函數(shù)代碼
總結(jié)
以上是生活随笔為你收集整理的神经网络基本原理简明教程之线性回归预测房价的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: VC2008的运行库问题。
- 下一篇: 构造函数、析构函数