Copyright © Microsoft Corporation. All rights reserved.
適用於License版權許可
更多微軟人工智能學習資源,請見微軟人工智能教育與學習共建社區python
假設有以下這樣一個問題:有1000個樣本,每一個樣本有三個特徵值,一個標籤值。想讓你預測一下給定任意三個特徵值組合,其y值是多少?git
樣本序號 | 1 | 2 | 3 | 4 | ... | 1000 |
---|---|---|---|---|---|---|
樣本特徵值1 | 1 | 4 | 2 | 4 | ... | 2 |
樣本特徵值2 | 3 | 2 | 6 | 3 | ... | 3 |
樣本特徵值3 | 96 | 100 | 54 | 72 | ... | 69 |
樣本標籤值y | 434 | 500 | 321 | 482 | ... | 410 |
這就是典型的多元線性迴歸。函數模型以下:github
\[y=a_0+a_1x_1+a_2x_2+\dots+a_kx_k\]算法
爲了方便你們理解,我們具體化一下上面的公式,按照本系列文章的符號約定就是:網絡
\[ Z = w_1x_1+w_2x_2+w_3x_3+b = WX + B \]app
咱們定義一個一層的神經網絡,輸入層爲3或者更多,反正大於2了就沒區別。這個一層的神經網絡沒有中間層,只有輸出層,並且只有一個神經元,而且神經元有一個線性輸出,不通過激活函數處理。亦即在下圖中,通過\(\Sigma\)求和獲得Z值以後,直接把Z值輸出。框架
這樣的一個神經網絡能作什麼事情呢?dom
假設一共有m個樣本,每一個樣本n個特徵值,X就是一個\(n \times m\)的矩陣,模樣是這樣紫的(n=3,m=1000,亦即3行1000列):機器學習
\[ X = \begin{pmatrix} x1_1 & x2_1 & \dots & xm_1 \\ \\ x1_2 & x2_2 & \dots & xm_2 \\ \\ x1_3 & x2_3 & \dots & xm_3 \end{pmatrix} = \begin{pmatrix} 3 & 2 & \dots & 3 \\ \\ 1 & 4 & \dots & 2 \\ \\ 96 & 100 & \dots & 54 \end{pmatrix} \]函數
\[ Y = \begin{pmatrix} y1 & y2 & \dots & ym \\ \end{pmatrix}= \begin{pmatrix} 434 & 500 & \dots & 410 \\ \end{pmatrix} \]
單獨看一個樣本是這樣的:
\[ x1 = \begin{pmatrix} x1_1 \\ \\ x1_2 \\ \\ x1_3 \end{pmatrix} = \begin{pmatrix} 3 \\ \\ 1 \\ \\ 96 \end{pmatrix} \]
\[ y1 = \begin{pmatrix} 434 \end{pmatrix} \]
\(x1\)表示第一個樣本,\(x1_1\)表示第一個樣本的一個特徵值。
有人問了,爲什麼不把這個表格轉一下,變成橫向是樣本特徵值,縱向是樣本數量?那樣好像更符合思惟習慣?
確實是!可是在實際的矩陣運算時,因爲是\(Z=W*X+B\),W在前面,X在後面,因此必須是這個樣子的:
\[ \begin{pmatrix} w1 & w2 & w3 \end{pmatrix} \times \begin{pmatrix} x1_1 \\ \\ x1_2 \\ \\ x1_3 \end{pmatrix}+B= w1*x1_1+w2*x1_2+w3*x1_3+B \]
假設每一個樣本X有n個特徵向量,上式中的W就是一個\(1 \times n\)行向量,讓每一個w都對應一個x:
\[ \begin{pmatrix}w_1 & w_2 \dots w_n\end{pmatrix} \]
B是個單值,由於只有一個神經元,因此只有一個bias,每一個神經元對應一個bias,若是有多個神經元,它們都會有各自的b值。
因爲咱們只想完成一個迴歸(擬合)任務,因此輸出層只有一個神經元。因爲是線性的,因此沒有用激活函數。
房價預測問題,成爲了機器學習的一個入門話題。咱們也不能免俗,可是,不要用美國的什麼多少平方英尺,多少個房間的例子來講事兒了,中國人不能理解。咱們來個北京的例子!
影響北京房價的因素有不少,幾個最重要的因子和它們的取值範圍是:
因爲一些緣由,收集的數據不能用於教學,因此我們根據以上規則創造一些數據:
import numpy as np import matplotlib.pyplot as plt from pathlib import Path # y = w1*x1 + w2*x2 + w3*x3 + b # W1 = 朝向:1,2,3,4 = N,W,E,S # W2 = 位置幾環:2,3,4,5,6 # W3 = 面積:平米 def TargetFunction(x1,x2,x3): w1,w2,w3,b = 2,10,5,10 return w1*x1 + w2*(10-x2) + w3*x3 + b def CheckFileData(): Xfile = Path("HouseXData.npy") Yfile = Path("HouseYData.npy") if Xfile.exists() & Yfile.exists(): XData = np.load(Xfile) YData = np.load(Yfile) return True,XData,YData return False,None,None def create_sample_data(m): flag,XData,YData = CheckFileData() if flag == False: X0 = np.random.randint(1,5,m) X1 = np.random.randint(2,7,m) X2 = np.random.randint(40,120,m) XData = np.zeros((3,m)) XData[0] = X0 XData[1] = X1 XData[2] = X2 Y = TargetFunction(X0,X1,X2) noise = 20 Noise = np.random.randint(1,noise,(1,m)) - noise/2 YData = Y.reshape(1,m) np.save("HouseXData.npy", XData) np.save("HouseYData.npy", YData) return XData, YData
在TargetFunction函數中,\(w2*(10-x2)\),是由於越靠近二環的房子越貴,因此當x2=2時(二環),10-2=8;當x2=6時(六環),10-6=4。
令w1=2, w2=10, w3=5, b=10,因此最後的公式是:
\[z = w1·x_1 + w2·(10-x_2)+w3·x_3+b \\ = 2x_1 - 10(10-x_2)+5x_3+10 \\ = 2x_1 - 10x_2+5x_3+110 \]
def forward_calculation(Xm,W,b): z = np.dot(W, Xm) + b return
咱們用傳統的均方差函數: \(loss = \frac{1}{2}(Z-Y)^2\),其中,Z是每一次迭代的預測輸出,Y是樣本標籤數據。咱們使用全部樣本參與訓練,所以損失函數實際爲:
\[Loss = \frac{1}{2}(Z - Y) ^ 2\]
其中的分母中有個2,其實是想在求導數時把這個2約掉,沒有什麼原則上的區別。
def check_diff(w, b, X, Y, count, prev_loss): Z = w * X + b LOSS = (Z - Y)**2 loss = LOSS.sum()/count/2 diff_loss = abs(loss - prev_loss) return loss, diff_loss
求解W和B的梯度方法與咱們前面的文章「單入單出的一層神經網絡」徹底同樣,因此再也不贅述,只說一下結論:
由於:
\[z = wx+b\]
\[loss = \frac{1}{2}(z-y)^2\]
因此咱們用loss的值做爲基準,去求w對它的影響,也就是loss對w的偏導數:
\[ \frac{\partial{loss}}{\partial{w}} = \frac{\partial{loss}}{\partial{z}}*\frac{\partial{z}}{\partial{w}} = (z-y)x \]
\[ \frac{\partial{loss}}{\partial{b}} = \frac{\partial{loss}}{\partial{z}}*\frac{\partial{z}}{\partial{b}} = z-y \]
變成代碼:
def dJwb_single(Xm,Y,Z): dloss_z = Z - Y db = dloss_z dw = np.dot(dloss_z, Xm.T) return dw, db
def update_weights(w, b, dw, db, eta): w = w - eta*dw b = b - eta*db return w,b
m = 1000 # 創造1000個樣本 XData, Y = create_sample_data(m) X = XData n = X.shape[0] # 每一個樣本的特徵數 eta = 0.1 # 學習率0.1 loss, diff_loss, prev_loss = 10, 10, 5 eps = 1e-10 max_iteration = 100 # 最多100次循環 # 初始化w,b b = np.zeros((1,1)) w = np.zeros((1,n))
for iteration in range(max_iteration): for i in range(m): Xm = X[0:n,i].reshape(n,1) Ym = Y[0,i].reshape(1,1) Z = forward_calculation(Xm, W, B) dw, db = dJwb_single(Xm, Ym, Z) W, B = update_weights(W, B, dw, db, eta) loss, diff_loss = check_diff(w,b,xxx,y,1,prev_loss) if diff_loss < eps: print(i) break prev_loss = loss print(iteration, w, b, diff_loss) if diff_loss < eps: break
懷着期待的心情用顫抖的右手按下了運行鍵......but......what happened?
C:\Users\Python\LinearRegression\MultipleInputSingleOutput.py:61: RuntimeWarning: overflow encountered in square LOSS = (Z - Y)**2 C:\Users\Python\LinearRegression\MultipleInputSingleOutput.py:63: RuntimeWarning: invalid value encountered in double_scalars diff_loss = abs(loss - prev_loss) C:\Users\Python\LinearRegression\MultipleInputSingleOutput.py:55: RuntimeWarning: invalid value encountered in subtract w = w - eta*dw 0 [[nan nan nan]] [[nan]] nan 1 [[nan nan nan]] [[nan]] nan 2 [[nan nan nan]] [[nan]] nan 3 [[nan nan nan]] [[nan]] nan
怎麼會overflow呢?因而右手的顫抖沒有中止,左手也開始顫抖了。
咱們遇到了傳說中的梯度爆炸!數值太大,致使計算溢出了。第一次遇到這個狀況,但相信不會是最後一次,由於這種狀況在神經網絡中太常見了。技術只服務於相信技術的人,技能只給與培養技能的人!別慌,讓咱們debug一下。
先把迭代中的關鍵值打印出來:
0 ----------- Z: [[0.]] Y: [[469]] dLoss/dZ: [[-469.]] dw: [[ -938. -1876. -37051.]] db: [[-469.]] W: [[ 93.8 187.6 3705.1]] B: [[46.9]] 1 ----------- Z: [[289982.7]] Y: [[464]] dLoss/dZ: [[289518.7]] dw: [[ 579037.4 1158074.8 22582458.60000001]] db: [[289518.7]] W: [[ -57809.94 -115619.88 -2254540.76]] B: [[-28904.97]] 2 ----------- Z: [[-2.62364972e+08]] Y: [[634]] dLoss/dZ: [[-2.62365606e+08]] dw: [[-5.24731213e+08 -1.57419364e+09 -3.04344103e+10]] db: [[-2.62365606e+08]] W: [[5.24153113e+07 1.57303744e+08 3.04118649e+09]] B: [[26207655.65900001]] ......
最開始的W,B的值都是0,三次迭代後,W,B的值已經大的超乎想象了。能夠中止運行程序了,想想爲何。
難道是由於學習率太大嗎?目前是0.1,設置成0.01試試看:
0 ---------- Z: [[0.]] Y: [[469]] dLoss/dZ: [[-469.]] dw: [[ -938. -1876. -37051.]] db: [[-469.]] W: [[ 0.938 1.876 37.051]] B: [[0.469]] 1 ----------- Z: [[2899.827]] Y: [[464]] dLoss/dZ: [[2435.827]] dw: [[ 4871.654 9743.308 189994.506]] db: [[2435.827]] W: [[ -3.933654 -7.867308 -152.943506]] B: [[-1.966827]] 2 ---------- Z: [[-17798.484679]] Y: [[634]] dLoss/dZ: [[-18432.484679]] dw: [[ -36864.969358 -110594.908074 -2138168.222764]] db: [[-18432.484679]] W: [[ 32.93131536 102.72760007 1985.22471676]] B: [[16.46565768]]
沒啥改進。
回想一個問題:爲何在「單入淡出的一層神經網絡」一文的代碼中,咱們沒有遇到這種狀況?由於全部的X值都是在[0,1]之間的,而神經網絡是以樣本在事件中的統計分佈機率爲基礎進行訓練和預測的,也就是說,樣本的各個特徵的度量單位要相同。咱們並無辦法去比較1米和1公斤的區別,可是,若是咱們知道了1米在整個樣本中的大小比例,以及1公斤在整個樣本中的大小比例,好比一個處於0.2的比例位置,另外一個處於0.3的比例位置,就能夠說這個樣本的1米比1公斤要小。這就提出了樣本的歸一化或者正則化的理論。
更多的數據歸一化的問題,咱們會另文給出,下面只提對咱們解決當前問題有用的方法。
也叫離差標準化,是對原始數據的線性變換,使結果落到[0,1]區間,轉換函數以下:
\[ x_{new} = \frac{x-x_{min}}{x_{max}-x_{min}} \]
其中max爲樣本數據的最大值,min爲樣本數據的最小值。
若是想要將數據映射到[-1,1],則將公式換成:
\[ x_{new} = \frac{x-x_{mean}}{x_{max}-x_{min}} \]
mean表示數據的均值。
再把這個表拿出來分析一下:
樣本序號 | 1 | 2 | 3 | 4 | ... | 1000 |
---|---|---|---|---|---|---|
樣本特徵值1:窗戶朝向 | 1 | 3 | 2 | 4 | ... | 2 |
樣本特徵值2:地理位置 | 3 | 2 | 6 | 3 | ... | 4 |
樣本特徵值3:居住面積 | 96 | 100 | 54 | 72 | ... | 69 |
樣本標籤值y:房價(萬元) | 434 | 500 | 321 | 482 | ... | 410 |
特徵值1 - 窗戶朝向
一共有」東」「南」「西」「北「四個值,用數字化表示是:
東:3
南:4
西:2
北:1
由於超南的房子比較貴,其次爲東,西,北。爲啥東比西貴?由於夏天時朝西的窗戶西曬時間長,比較熱。
特徵值2 - 地理位置
二環:2 - 單價最貴
三環:3
四環:4
五環:5
六環:6 - 單價最便宜
特徵值3 - 房屋面積
統計全部樣本數據獲得房屋的面積範圍是[40,120]
咱們用min-max標準化來歸一以上數據,獲得下表:
樣本序號 | 1 | 2 | 3 | 4 | ... | 1000 |
---|---|---|---|---|---|---|
樣本特徵值1:窗戶朝向 | 0 | 0.667 | 0.333 | 1 | ... | 0.33 |
樣本特徵值2:地理位置 | 0.25 | 0 | 1 | 0.25 | ... | 0.75 |
樣本特徵值3:居住面積 | 0.7 | 0.75 | 0.175 | 0.4 | ... | 0.36 |
樣本標籤值y:房價(萬元) | 434 | 500 | 321 | 482 | ... | 410 |
還有一個問題:標籤值y是否須要歸一化?沒有任何理論說須要歸一化標籤值,因此咱們先把這個問題放一放。
下圖展現了歸一化先後的狀況,左側爲前,右側爲後:
房屋面積的取值範圍是[40,120],而地理位置的取值範圍是[2,6],兩者會造成一個很扁的橢圓,如左側。這樣在尋找最優解的時候,過程會很是曲折。運氣很差的話,如同咱們上面的代碼,根本就無法訓練。
歸一化後,地理位置和房屋面積兩者都歸一到[0,1]之間,變成了可比的了,並且尋找最優解的路徑也很直接,節省了時間。
咱們把歸一化的函數寫好:
def Normalize(X): X_new = np.zeros(X.shape) n = X.shape[0] w_num = np.zeros((1,n)) for i in range(n): v = X[i,:] max = np.max(v) min = np.min(v) w_num[0,i] = max - min v = (v - min)/(max-min) X_new[i,:] = v return X_new, w_num
而後再改一下主程序,加上歸一化的調用:
m = 1000 XData, Y = create_sample_data(m) X, W_num = Normalize(XData) n = X.shape[0] eta = 0.1 loss, diff_loss, prev_loss = 10, 10, 5 eps = 1e-10 max_iteration = 1 B = np.zeros((1,1)) W = np.zeros((1,n)) for iteration in range(max_iteration): for i in range(m): Xm = X[0:n,i].reshape(n,1) Ym = Y[0,i].reshape(1,1) Z = forward_calculation(Xm, W, B) dw, db = dJwb_single(Xm, Ym, Z) W, B = update_weights(W, B, dw, db, eta) loss, diff_loss = check_diff(W,B,Xm,Ym,1,prev_loss) if diff_loss < eps: print(i) break prev_loss = loss print(iteration, W, B, diff_loss) if diff_loss < eps: break
用顫抖的雙手同時按下Ctrl+F5,運行開始,結束,一眨眼!仔細看打印結果:
[[ 5.94417016 -40.05847929 394.83396979]] [[292.15951172]]
\[ w1=5.94417016 \\ w2=-40.05847929 \\ w3=394.83396979 \\ b=292.15951172 \]
比較一下原始公式:
\[z = w1·x_1 + w2·(10-x_2)+w3·x_3+b \\ = 2x_1 - 10(10-x_2)+5x_3+10 \\ = 2x_1 - 10x_2+5x_3+110 \\ w1=2 \\ w2=-10 \\ w3=5 \\ b=110 \]
什麼鬼!怎麼相差這麼多?!
仔細想一想,訓練竟然收斂了,出結果了,可是和咱們預期的不太一致。咱們沒有作歸一化前,根本沒結果。歸一化後有結果,那麼就是歸一化起做用了,但它有什麼反作用呢?一塊兒逐個分析一下4個值。
w1是窗戶朝向,取值範圍[1,4],即:\(x_{min}=1, x_{max}=4\),目前\(w1=5.94417\),試着變換一下:
\[ \frac{w1}{x_{max}-x_{min}} = \frac{5.94417}{4-1}=1.98139 \simeq 2 \]
是否是和w1=2這個值很是的接近呢?!WoW!我從澡盆裏跳了出來,給阿基米德打了一個電話,說:「我找到啦!」
再看W2是地理位置:
\[ \frac{w2}{x_{max}-x_{min}} = \frac{-40.058}{6-2}=-10.0145 \simeq -10 \]
再看W3是房屋面積:
\[ \frac{w3}{x_{max}-x_{min}} = \frac{394.8339}{119-40}=4.997 \simeq 5 \]
代碼以下:
W_rel = np.zeros((1,n)) for i in range(n): W_rel[0,i] = W[0,i] / W_num[0,i] print(W_rel)
其中W_num是在調用Normalize函數時返回的三個特徵值的範圍,亦即(3,4,79)。
再看b值......"元芳,你怎麼看?"
「卑職覺得,b值不是樣本值,沒有通過歸一化。」
「你說得很對!但咱們怎麼解出b呢?」
「大人,這個好辦,咱們如此這樣這樣......把一堆樣本值代入w1,w2,w3的公式,令b=0,看看計算出來的結果是什麼,再和樣本標籤值去比較就能夠了!」
\[ z = w1*x_1+w2*x_2+w3*x_3 +0\\ b = y - z \]
爲了不單個的樣本偏差,用100個樣本作比較,獲得差值之和,再平均,代碼以下:
B = 0 for i in range(10): xm = XData[0:n,i].reshape(n,1) zm = forward_calculation(xm, W_rel, 0) ym = Y[0,i].reshape(1,1) B = B + (ym - zm) b = B / 10 print(b)
咱們在計算z的時候,故意把第三個參數設置爲0,即令b=0。ym是每一個樣本的標籤值,它與z的差值天然就是真正的b值。
上述兩段代碼獲得的輸出以下:
[[ 1.98139005 -10.01461982 4.99789835]] [[110.29389945]]
至此,咱們完美地解決了北京地區的房價預測問題!可是尚未解決本身能夠有能力買一套北京的房子的問題......
最後遺留一個問題,若是標籤值y也歸一化的話,也能夠獲得訓練結果,可是怎麼解釋呢?你們有興趣的話能夠研究一下,分享出來。反正筆者是沒研究出來。
點擊這裏提交問題與建議
聯繫咱們: msraeduhub@microsoft.com
學習了這麼多,還沒過癮怎麼辦?歡迎加入「微軟 AI 應用開發實戰交流羣」,跟你們一塊兒暢談AI,答疑解惑。掃描下方二維碼,回覆「申請入羣」,即刻邀請你入羣。