神经网络基本原理简明教程之线性回归预测房价
1.1 提出問題
問題:在北京通州,距離通州區中心15公里的一套93平米的房子,大概是多少錢?
房價預測問題,成為了機器學習的一個入門話題,著名的波士頓的房價數據及相關的比賽已經很多了,但是美國的房子都是獨棟的,前院后院停車庫游泳池等等參數非常多,初學者可能理解起來有困難。我們不妨用簡化版的北京通州的房價來舉例,感受一下房價預測的過程。
影響北京通州房價的因素有很多,居住面積、地理位置、朝向、學區房、周邊設施、建筑年份等等,其中,面積和地理位置是兩個比較重要的因素。地理位置信息一般采用經緯度方式表示,但是經緯度是兩個特征值,聯合起來才有意義,因此,我們把它轉換成了到通州區中心的距離。
我們有1000個樣本,每個樣本有兩個特征值,一個標簽值,示例如下:
特征值1 - 地理位置,統計得到:
最大值:21.96公里
最小值:2.02公里
平均值:12.13公里
特征值2 - 房屋面積,統計得到:
最大值:119平米
最小值:40平米
平均值:78.9平米
標簽值 - 房價,單位為百萬元:
最大值:674.37
最小值:181.38
平均值:420.64
這個數據是三維的,所以可以用兩個特征值作為x和y,用標簽值作為z,在xyz坐標中展示:
正向
側向
從正向看,很像一塊草坪,似乎是一個平面。再從側向看,和第4章中的直線擬合數據很像。所以,對于這種三維的線性擬合,我們可以把它想象成為擬合一個平面,這個平面會位于這塊“草坪”的中位,把“草坪”分割成上下兩塊更薄的“草坪”,最終使得所有樣本點到這個平面的距離的平方和最小。
1.2 多元線性回歸模型
由于表中可能沒有恰好符合15公里、93平米條件的數據,因此我們需要根據1000個樣本值來建立一個模型,來解決預測問題。
通過圖示,我們基本可以確定這個問題是個線性回歸問題,而且是典型的多元線性回歸,即包括兩個或兩個以上自變量的回歸。多元線性回歸的函數模型如下:
y=a0+a1x1+a2x2+?+akxk
具體化到房價預測問題,上面的公式可以簡化成:
z=x1?w1+x2?w2+b
拋開本例的房價問題,對于一般的應用問題,建立多元線性回歸模型時,為了保證回歸模型具有優良的解釋能力和預測效果,應首先注意自變量的選擇,其準則是:
1.自變量對因變量必須有顯著的影響,并呈密切的線性相關;
2.自變量與因變量之間的線性相關必須是真實的,而不是形式上的;
3.自變量之間應具有一定的互斥性,即自變量之間的相關程度不應高于自變量與因變量之因的相關程度;
4.自變量應具有完整的統計數據,其預測值容易確定。
1.3 解決方案
如果用傳統的數學方法解決這個問題,我們可以使用正規方程,從而可以得到數學解析解,然后再使用神經網絡方式來求得近似解,從而比較兩者的精度,再進一步調試神經網絡的參數,達到學習的目的。
我們不妨先把兩種方式在這里做一個對比,讀者閱讀并運行代碼,得到結果后,再回到這里來仔細體會下面這個表格中的比較項:
2.1 正規方程解法
對于線性回歸問題,除了前面提到的最小二乘法可以解決一元線性回歸的問題外,也可以解決多元線性回歸問題。
對于多元線性回歸,可以用正規方程來解決,也就是得到一個數學上的解析解。它可以解決下面這個公式描述的問題:
2.2 簡單的推導方法
在做函數擬合(回歸)時,我們假設函數H為:
令b=w0,則:
公式3中的x是一個樣本的n個特征值,如果我們把m個樣本一起計算,將會得到下面這個矩陣:
公式5中的X和W的矩陣形狀如下:
然后我們期望假設函數的輸出與真實值一致,則有:
其中,Y的形狀如下:
直觀上看,W = Y/X,但是這里三個值都是矩陣,而矩陣沒有除法,所以需要得到X的逆矩陣,用Y乘以X的逆矩陣即可。但是又會遇到一個問題,只有方陣才有逆矩陣,而X不一定是方陣,所以要先把左側變成方陣,就可能會有逆矩陣存在了。所以,先把等式兩邊同時乘以X的轉置矩陣,以便得到X的方陣:
其中,XT是X的轉置矩陣,XTX一定是個方陣,并且假設其存在逆矩陣,把它移到等式右側來:’
至此可以求出W的正規方程。
2.3 復雜的推導方法
我們仍然使用均方差損失函數:
把b看作是一個恒等于1的feature,并把z=XW計算公式帶入,并變成矩陣形式:
對w求導,令導數為0,就是W的最小值解:
求導后:
第一項的結果是:2XTXW
第二項和第三項的結果都是:XTY
第四項的結果是:0
再令導數為0:
結論和公式10一樣。
以上推導的基本公式可以參考第0章的公式60-69。
逆矩陣(XTX)?1可能不存在的原因是:
1.特征值冗余,比如x2=x21,即正方形的邊長與面積的關系,不能做為兩個特征同時存在
2.特征數量過多,比如特征數n比樣本數m還要大
以上兩點在我們這個具體的例子中都不存在。
2.4 代碼實現
我們再看一下樣本數據的樣子:
根據公式(5),我們應該建立如下的X,Y矩陣:
根據公式(10):
1.X是1000x3的矩陣,X的轉置是3x1000,XTX生成(3x3的矩陣
2.(XTX)?1也是3x3
3.再乘以XT,即(3x3)x(3x1000)的矩陣,變成3x1000
4.再乘以Y,Y是1000x1,所以(3x1000)x(1000x1)變成3x1,就是W的解,其中包括一個偏移值b和兩個權重值w,3個值在一個向量里
資源包:HelperClass
3.1 神經網絡解法
與單特征值的線性回歸問題類似,多變量(多特征值)的線性回歸可以被看做是一種高維空間的線性擬合。以具有兩個特征的情況為例,這種線性擬合不再是用直線去擬合點,而是用平面去擬合點。
3.2 定義神經網絡結構
我們定義一個一層的神經網絡,輸入層為2或者更多,反正大于2了就沒區別。這個一層的神經網絡的特點是:
1.沒有中間層,只有輸入項和輸出層(輸入項不算做一層),
2.輸出層只有一個神經元,
3.神經元有一個線性輸出,不經過激活函數處理,即在下圖中,經過Σ求和得到Z值之后,直接把Z值輸出。
輸入層
單獨看第一個樣本是這樣的:
一共有1000個樣本,每個樣本2個特征值,X就是一個1000×2的矩陣:
x1 表示第一個樣本,x1,1表示第一個樣本的一個特征值,y1是第一個樣本的標簽值。
權重W和B
由于我們只想完成一個回歸(擬合)任務,所以輸出層只有一個神經元。由于是線性的,所以沒有用激活函數。
寫成矩陣形式:
上述公式中括號中的數字表示該矩陣的(行x列)數。
對于擬合,可以想象成用一支筆在一堆點中畫一條直線或者曲線,而那一個神經元就是這支筆。如果有多個神經元,可以畫出多條線來,就不是擬合了,而是分類。
損失函數
因為是線性回歸問題,所以損失函數使用均方差函數。
其中,zi是樣本預測值,yi是樣本的標簽值。
3.3 反向傳播
單樣本多特征計算
與上一章不同,本章中的前向計算是多特征值的公式:
因為x有兩個特征值,對應的W也有兩個權重值。xi1表示第i個樣本的第1個特征值,所以無論是x還是w都是一個向量或者矩陣了,那么我們在反向傳播方法中的梯度計算公式還有效嗎?答案是肯定的,我們來一起做個簡單推導。
由于W被分成了w1和w2兩部分,根據公式1和公式2,我們單獨對它們求導:
求損失函數對W矩陣的偏導,是無法求的,所以要變成求各個W的分量的偏導。由于W的形狀是:
所以求loss對W的偏導,由于W是個矩陣,所以應該這樣寫:
多樣本多特征計算
當進行多樣本計算時,我們用m=3個樣本做一個實例化推導:
3.4 代碼實現
我們依然采用第四章中已經寫好的HelperClass目錄中的那些類,來表示我們的神經網絡。雖然此次神經元多了一個輸入,但是不用改代碼就可以適應這種變化,因為在前向計算代碼中,使用的是矩陣乘的方式,可以自動適應x的多個列的輸入,只要對應的w的矩陣形狀是正確的即可。
但是在初始化時,我們必須手動指定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))在參數中,指定了學習率0.1,最大循環次數100輪,批大小1個樣本,以及停止條件損失函數值1e-5。
在神經網絡初始化時,指定了input_size=2,且output_size=1,即一個神經元可以接收兩個輸入,最后是一個輸出。
最后的inference部分,是把兩個條件(15公里,93平方米)代入,查看輸出結果。
在下面的神經網絡的初始化代碼中,W的初始化是根據input_size和output_size的值進行的。
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))正向計算的代碼
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 運行結果
在Visual Studio 2017中,可以使用Ctrl+F5運行Level2的代碼,但是,會遇到一個令人沮喪的打印輸出:
減法怎么會出問題?什么是nan?
nan的意思是數值異常,導致計算溢出了,出現了沒有意義的數值。現在是每500個迭代監控一次,我們把監控頻率調小一些,再試試看:
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次迭代,損失函數值已經達到了6.83e+66,而且越往后運行值越大,最后終于溢出了。下面的損失函數歷史記錄也表明了這一過程。
3.6 尋找失敗的原因
我們可以在NeuralNet.py文件中,在下述代碼行上設置斷點,跟蹤一下訓練過程,以便找到問題所在:
在VS2017中用F5運行debug模式,看第50行的結果:
返回的樣本數據是正常的。再看下一行:
batch_z array([[0.]])第一次運行前向計算,由于W和B初始值都是0,所以z也是0,這是正常的。再看下一行:
dW array([[ -1210.80475712],[-10007.22118309]]) dB array([[-244.07856544]])dW和dB的值都非常大,這是因為下面這行代碼:
batch_z是0,batch_y是244.078,二者相減,是-244.078,因此dB就是-244.078,dW因為矩陣乘了batch_x,值就更大了。
再看W和B的更新值,一樣很大:
self.W array([[ 121.08047571],[1000.72211831]]) self.B array([[24.40785654]])如果W和B的值很大,那么再下一輪進行前向計算時,會得到更糟糕的結果:
batch_z array([[82459.53752331]])果不其然,這次的z值飆升到了8萬多,如此下去,幾輪以后數值溢出是顯而易見的事情了。
那么我們到底遇到了什么情況?
4.1 樣本特征數據歸一化
發現問題的根源
仔細分析一下屏幕打印信息,前兩次迭代的損失值已經是天文數字了,后面的W和B的值也在不斷變大,說明網絡發散了。難度我們遇到了傳說中的梯度爆炸!數值太大,導致計算溢出了。第一次遇到這個情況,但相信不會是最后一次,因為這種情況在神經網絡中太常見了。
回想一個問題:為什么在第4章中,我們沒有遇到這種情況?把第4章的數據樣本拿來看一看:
所有的X值(服務器數量除以1000后的值)都是在[0,1]之間的,而本章中的房價數據有兩個特征值,一個是公里數,一個是平米數,全都是不是在[0,1]之間的,并且取值范圍還不相同。我們不妨把本次樣本數據也做一下這樣的處理,亦即“歸一化”。
其實,數據歸一化是深度學習的必要步驟之一,已經是大師們的必殺技能,也因此它很少被各種博客/文章所提及,以至于初學者們經常被坑。
根據5.0.1中對數據的初步統計,我們是不是也可以把公里數都除以100,而平米數都除以1000呢,這樣也會得到[0,1]之間的數字?公里數的取值范圍是[2,22],除以100后變成了[0.02,0.22]。平米數的取值范圍是[40,120],除以1000后變成了[0.04,0.12]。
對本例來說這樣做肯定是可以正常工作的,但是下面我們要介紹一種更科學合理的做法。
4.2 為什么要做歸一化
理論層面上,神經網絡是以樣本在事件中的統計分布概率為基礎進行訓練和預測的,所以它對樣本數據的要求比較苛刻。具體說明如下:
1.樣本的各個特征的取值要符合概率分布,即[0,1]
2.樣本的度量單位要相同。我們并沒有辦法去比較1米和1公斤的區別,但是,如果我們知道了1米在整個樣本中的大小比例,以及1公斤在整個樣本中的大小比例,比如一個處于0.2的比例位置,另一個處于0.3的比例位置,就可以說這個樣本的1米比1公斤要小!
3.神經網絡假設所有的輸入輸出數據都是標準差為1,均值為0,包括權重值的初始化,激活函數的選擇,以及優化算法的的設計。
4.數值問題
歸一化可以避免一些不必要的數值問題。因為激活函數sigmoid/tanh的非線性區間大約在[-1.7,1.7]。意味著要使神經元有效,線性計算輸出的值的數量級應該在1(1.7所在的數量級)左右。這時如果輸入較大,就意味著權值必須較小,一個較大,一個較小,兩者相乘,就引起數值問題了。
5.梯度更新
若果輸出層的數量級很大,會引起損失函數的數量級很大,這樣做反向傳播時的梯度也就很大,這時會給梯度的更新帶來數值問題。
6.學習率
知道梯度非常大,學習率就必須非常小,因此,學習率(學習率初始值)的選擇需要參考輸入的范圍,不如直接將數據歸一化,這樣學習率就不必再根據數據范圍作調整。 對w1適合的學習率,可能相對于w2來說會太小,若果使用適合w1的學習率,會導致在w2方向上步進非常慢,會消耗非常多的時間,而使用適合w2的學習率,對w1來說又太大,搜索不到適合w1的解。
4.3 從損失函數等高線圖分析歸一化的必要性
在房價數據中,地理位置的取值范圍是[2,20],而房屋面積的取值范圍為[40,120],二者相差太遠,根本不可以放在一起計算了?
根據公式z=x1w1+x2w2+b,神經網絡想學習w1和w2,但是數值范圍問題導致神經網絡來說很難“理解”。下圖展示了歸一化前后的情況Loss值的等高圖,意思是地理位置和房屋面積取不同的值時,作為組合來計算損失函數值時,形成的類似地圖的等高圖。左側為歸一化前,右側為歸一化后:
房屋面積的取值范圍是[40,120],而地理位置的取值范圍是[2,20],二者會形成一個很扁的橢圓,如左側。這樣在尋找最優解的時候,過程會非常曲折。運氣不好的話,根本就沒法訓練。
4.4 歸一化的基本概念
有三個類似的概念,歸一化,標準化,中心化。
歸一化
把數據線性地變成[0,1]或[-1,1]之間的小數,把帶單位的數據(比如米,公斤)變成無量綱的數據,區間縮放。
歸一化有三種方法:
1.Min-Max歸一化:
2.平均值歸一化
3.非線性歸一化
對數轉換:
反余切轉換:
標準化
把每個特征值中的所有數據,變成平均值為0,標準差為1的數據,最后為正態分布。Z-score規范化(標準差標準化 / 零均值標準化,其中std是標準差):
中心化
平均值為0,無標準差要求:
4.5 如何做數據歸一化
我們再看看樣本的數據:
按照歸一化的定義,我們只要把地理位置列和居住面積列分別做歸一化就達到要求了,即:
注意:
1.我們并沒有歸一化樣本的標簽Y數據,所以最后一行的價格還是保持不變
2.我們是對兩列特征值分別做歸一化處理的
4.6 代碼實現
在HelperClass目錄的SimpleDataReader.py文件中,給該類增加一個方法:
返回值X_new是歸一化后的樣本,和原始數據的形狀一樣。
再把主程序修改一下,在ReadData()方法后,緊接著調用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 運行結果
運行上述代碼,看打印結果:
雖然損失函數值沒有像我們想象的那樣趨近于0,但是卻穩定在了400左右震蕩,這也算是收斂!看一下損失函數圖像:
再看看W和B的輸出值和z的預測值:
w1 = -41.71417524 w2 = 395.84701164 b = 242.15205099 z = 37366.53336103回憶一下正規方程的輸出值:
w1= -2.0184092853092226 w2= 5.055333475112755 b= 46.235258613837644 z= 486.1051325196855正規方程預測房價結果:
Z=?2.018×15+5.055×93+46.235=486.105(萬元)
神經網絡預測房價結果:
Z=?14.714×15+395.847×93+242.152=37366(萬元)
好吧,我們遇到了天價房!這是怎么回事兒?難道和我們做數據歸一化有關系?
在5.0.1中,我們想象神經網絡會尋找一個平面,來擬合這些空間中的樣本點,是不是這樣呢?我們通過下面的函數來實現這個可視化:
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')前半部分代碼先是把所有的點顯示在三維空間中,我們曾經描述它們像一塊厚厚的草坪。后半部分的代碼在[0,1]空間內形成了一個50x50的網格,亦即有2500個點,這些點都是有橫縱坐標的。然后把這些點送入神經網絡中做預測,得到了2500個Z值,相當于第三維的坐標值。最后把這2500個三維空間的點,以網格狀顯示在空間中,就形成了下面的可視化的結果:
正向
側向
從正向圖可以看到,真的形成了一個平面;從側向圖可以看到,這個平面也確實穿過了那些點,并且把它們分成了上下兩個部分。只不過由于訓練精度的問題,沒有做到平分,而是斜著穿過了點的區域,就好像第4章中的那個精度不夠的線性回歸的結果。
細心的讀者可能會問兩個問題:
1.為什么要在[0,1]空間中形成50x50的網格呢?
2.50這個數字從哪里來的?
NumPy庫的np.linspace(0,1)的含義,就是在[0,1]空間中生成50個等距的點,第三個參數不指定時,缺省是50。因為我們前面對樣本數據做過歸一化,統一到了[0,1]空間中,這就方便了我們對問題的分析,不用考慮每個特征值的實際范圍是多大了。
這下子我們可以大致放心了,神經網絡的訓練結果并沒有錯,一定是別的地方出了什么問題。在下一節中我們來一起看看問題出在哪里!
5.1 歸一化的后遺癥
對比結果
在上一節中,我們使用了如下超參進行神經網絡的訓練:
我們再把每次checkpoint的W和B的值打印出來:
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]]打印結果中每列的含義:
1.epoch
2.iteration
3.loss
4.w1
5.w2
6.b
可以看到loss值、w1、w2、b的值,每次跳躍都很大,懷疑是學習率過高導致的梯度下降在最優解附近徘徊。所以,我們先把超參修改一下:
做了三處修改:
1.學習率縮小10倍,變成0.01
2.max_epoch擴大50倍,讓網絡得到充分訓練
3.batch_size=10,使用mini-batch批量樣本訓練,提高精度,減緩個別樣本引起的跳躍程度
運行結果:
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]]可以看到達到了我們的目的,loss、w1、w2、b的值都很穩定。我們使用這批結果做為分析基礎。首先列出W和B的訓練結果:
再列出歸一化后的數據:
通過對比我發現,關于W的結果,第一張表最后一行的數據,和第二張表最后一行的數據,有驚人的相似之處!這是為什么呢?
5.2 還原真實的W,B值
我們唯一修改的地方,就是樣本數據特征值的歸一化,我們并沒有修改標簽值。可以大概猜到W的值和樣本特征值的縮放有關系,而且縮放倍數非常相似,甚至可以說一致。下面推導一下這種現象的數學基礎。
假設在歸一化之前,真實的樣本值是X,真實的權重值是W;在歸一化之后,樣本值變成了X′,訓練出來的權重值是W′:
由于訓練時標簽值(房價)并沒有做歸一化,意味著我們是用真實的房價做的訓練,所以預測值和標簽值應該相等,所以:
歸一化的公式是:
為了簡化書寫,我們令xm=xmax?xmin,把公式2代入公式1:
公式3中,x1,x2是變量,其它都是常量,如果想讓公式3等式成立,則變量項和常數項分別相等,即:
下面我們用實際數值代入公式4,5,6:
可以看到公式7、8、9的計算結果(神經網絡的訓練結果的變換值)與正規方程的計算結果(-2.018, 5.055, 46.235)基本相同,基于神經網絡是一種近似解的考慮,可以認為這種推導是有理論基礎和試驗證明的。
5.3 代碼實現
下面的代碼實現了公式4,5,6:
X_Norm是我們在做歸一化時保留下來的樣本的兩個特征向量的最小值和數值范圍(最大值減去最小值)。
修改主程序如下: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()方法返回之后,訓練好的W和B的值就保存在NeuralNet類的屬性里了。然后通過調用DeNormalizeWeightsBias()函數,把它們轉換成真實的W_real和B_real值,就好比我們不做歸一化而能訓練出來的權重值一樣。
最后在推理預測時,我們直接使用了np.dot()公式,而沒有使用net.inference()方法,是因為在net實例中的W和B是還原前的值,做前向計算時還是會有問題,所以我們直接把前向計算公式拿出來,代入W_real和B_real,就可以得到真實的預測值了。
5.4 運行結果
運行上述代碼,觀察最后部分的打印輸出:
把結果與正規方程的結果對比一下:
二者幾乎一樣,可以認為我們成功了!但是這一套代碼下來,總覺得有些啰嗦,對于簡單的線性問題來說,這么做可以,如果遇到非線性問題,或者深層網絡,這么做是不是也可以呢?
正確的推理預測方法
6.1 預測數據的歸一化
在上一節中,我們在用訓練出來的模型預測房屋價格之前,還需要先還原W和B的值,這看上去比較麻煩,下面我們來介紹一種正確的推理方法。
既然我們在訓練時可以把樣本數據歸一化,那么在預測時,把預測數據也做相同方式的歸一化,不是就可以和訓練數據一樣進行預測了嗎?且慢!這里有一個問題,訓練時的樣本數據是批量的,至少是成百成千的數量級。但是預測時,一般只有一個或幾個數據,如何做歸一化?
我們在針對訓練數據做歸一化時,得到的最重要的數據是訓練數據的最小值和最大值,我們只需要把這兩個值記錄下來,在預測時使用它們對預測數據做歸一化,這就相當于把預測數據“混入”訓練數據。前提是預測數據的特征值不能超出訓練數據的特征值范圍,否則有可能影響準確程度。
6.2 代碼實現
基于這種想法,我們先給SimpleDataReader類增加一個方法NormalizePredicateData(),如下述代碼:
X_norm數組中的數據,是在訓練時從樣本數據中得到的最大值最小值,比如:
所以,最后X_new就是按照訓練樣本的規格歸一化好的預測歸一化數據,然后我們把這個預測歸一化數據放入網絡中進行預測:
6.3 運行結果
...... 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]]比較一下正規方程的結果:
z= 486.1051325196855二者非常接近,可以說這種方法的確很方便,把預測數據看作訓練數據的一個記錄,先做歸一化,再做預測,這樣就不需要把權重矩陣還原了。
看上去我們已經完美地解決了這個問題,但是且慢,仔細看看loss值,還有w和b的值,都是幾十幾百的數量級,這和神經網絡的概率計算的優點并不吻合,實際上它們的值都應該在[0,1]之間的。
大數量級的數據有另外一個問題,就是它的波動有可能很大。目前我們還沒有使用激活函數,一旦網絡復雜了,開始使用激活函數時,像486.166這種數據,一旦經過激活函數就會發生梯度飽和的現象,輸出值總為1,這樣對于后面的網絡就沒什么意義了,因為輸入值都是1。
好吧,看起來問題解決得并不完美,我們看看還能有什么更好的解決方案!
對標簽值歸一化
7.1發現問題
這一節里我們重點解決在訓練過程中的數值的數量級的問題。
我們既然已經對樣本數據特征值做了歸一化,那么如此大數值的損失函數值是怎么來的呢?看一看損失函數定義:
其中,zi是預測值,yi是標簽值。初始狀態時,W和B都是0,所以,經過前向計算函數Z=X?W+B的結果是0,但是Y值很大,處于[181.38, 674.37]之間,再經過平方計算后,一下子就成為至少5位數的數值了。
再看反向傳播時的過程:
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,與房價是同一數量級的,這樣經過反向傳播后,dW和dB的值也會很大,導致整個反向傳播鏈的數值都很大。我們可以debug一下,得到第一反向傳播時的數值是:
dW array([[-142.59982906],[-283.62409678]]) dB array([[-443.04543906]]) 上述數值又可能在讀者的機器上是不一樣的,因為樣本做了shuffle,但是不影響我們對問題的分析。這么大的數值,需要把學習率設置得很小,比如0.001,才可以落到[0,1]區間,但是損失函數值還是不能變得很小。
如果我們像對特征值做歸一化一樣,把標簽值也歸一化到[0,1]之間,是不是有幫助呢?
7.2 代碼實現
參照X的歸一化方法,對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:原始數據中,Y的數值范圍是:
最大值:674.37
最小值:181.38
平均值:420.64
歸一化后,Y的數值范圍是:
最大值:1.0
最小值:0.0
平均值:0.485
注意,我們同樣記住了Y_norm的值便于以后使用。
修改主程序代碼,增加對Y歸一化的方法調用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 運行結果
運行上述代碼得到的結果其實并不令人滿意:
雖然W和B的值都已經處于[-1,1]之間了,但是z的值也在[0,1]之間,一套房子不可能賣0.61萬元!
聰明的讀者可能會想到:既然對標簽值做了歸一化,那么我們在得到預測結果后,需要對這個結果應該做反歸一化。
根據公式2,反歸一化的公式應該是:
代碼如下:
倒數第二行代碼,就是公式3。運行…結果如下:
W= [[-0.08149004][ 0.81022449]] B= [[0.12801985]] z= [[0.61856996]] Z_real= [[486.33591769]]看Z_real的值,完全滿足要求!
總結一下從本章中學到的正確的方法:
1.X必須歸一化,否則無法訓練;
2.Y值不在[0,1]之間時,要做歸一化,好處是迭代次數少;
3.如果Y做了歸一化,對得出來的預測結果做關于Y的反歸一化
至此,我們完美地解決了北京通州地區的房價預測問題!但是還沒有解決自己可以有能力買一套北京通州的房子的問題…
完整代碼:
1.ch5.npz是訓練集:可以用自己的訓練集
2.掃描二維碼獲取HelperClass包,封裝了一些函數
3.主函數代碼
總結
以上是生活随笔為你收集整理的神经网络基本原理简明教程之线性回归预测房价的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VC2008的运行库问题。
- 下一篇: datatables 基本增删改查(ph