Copyright © Microsoft Corporation. All rights reserved.
適用於License版權許可
更多微軟人工智能學習資源,請見微軟人工智能教育與學習共建社區python
下面咱們舉一個簡單的線性迴歸的例子來講明實際的反向傳播和梯度降低的過程。徹底看懂此文後,會對理解後續的文章有很大的幫助。git
簡單回憶一下什麼是線性迴歸:github
迴歸的目的是經過幾個已知數據來預測另外一個數值型數據的目標值。假設特徵和結果知足線性關係,即知足一個計算公式y(x),這個公式的自變量就是已知的數據x,函數值y(x)就是要預測的目標值。這個計算公式稱爲迴歸方程,獲得這個方程的過程就稱爲迴歸。線性迴歸就是假設這個方式是一個線性方程,一個多元一次方程,其形式爲:
\[y=a_0+a_1x_1+a_2x_2+\dots+a_kx_k\]
爲了簡化起見,咱們用一元一次的線性迴歸來舉例,即\(z = wx+b\)(z,w,x,b都是標量),由於這個函數的形式和神經網絡中的\(Z = WX + B\)(Z,W,X,B等都是矩陣)很是近似,能夠起到用簡單的原理理解複雜的事情的做用。算法
讓咱們先自力更生創造一些模擬數據:數組
import numpy as np import matplotlib.pyplot as plt from pathlib import Path def create_sample_data(m): # check if saved before Xfile = Path("XData.npy") Yfile = Path("YData.npy") # load data from file if Xfile.exists() & Yfile.exists(): X = np.load(Xfile) Y = np.load(Yfile) else: # generate new data X = np.random.random(m) # create some offset as noise to simulate real data noise = np.random.normal(0,0.1,X.shape) # genarate Y data W = 2 B = 3 Y = X * W + B + noise np.save("XData.npy", X) np.save("YData.npy", Y) return X, Y
因爲使用了文件存儲,因此在第二次運行本程序時,或者在調試代碼時,先後的結果是可比的,由於是同一批數據。網絡
獲得200個數據點以下:app
好了,模擬數據製做好了,目前X是一個200個元素的集合,裏面有0~1之間的隨機x點,Y是一個200個元素的集合,裏面有對應到每一個x上的\(y=2x+3\)的值,而後再加一個或正或負的上下偏移做爲噪音,來知足對實際數據的模擬效果(由於大部分真實世界的生產數據歷來都不是精確的,精確只存在於數學領域)。框架
如今咱們要忘記這些模擬數據(樣本值)是如何製做出來的,也就是要忘記W,B的值。咱們就假設這是實際應用中收集到的模擬數據,可是咱們並不知道它的原始函數是什麼參數,只知道是公式\(y = wx + b\),咱們的任務就是要根據這些樣本值,經過神經網絡訓練的方式,獲得w和b的值。注意這裏x和y是樣本的輸入和輸出,不是目標變量,這一點和常見的初等數學題不同,要及時轉變概念。dom
最終,樣本數據的樣子是:機器學習
\[ \begin{pmatrix} x_1\\ x_2\\ \dots\\ x_m\\ \end{pmatrix} , \begin{pmatrix} y_1\\ y_2\\ \dots\\ y_m\\ \end{pmatrix} \]
其中,x就是上圖中藍色點的橫座標值,y是縱座標值。
線性迴歸試圖學得 \(z(x_i)=wx_i+b\),使得\(z(x_i) \simeq y_i\)。如何學得w和b呢?均方差(MSE - mean squared error)是迴歸任務中經常使用的手段:
\[ Error = \frac{1}{m}\sum_{i=1}^m(z(x_i)-y_i)^2 = \frac{1}{m}\sum_{i=1}^m(y_i-wx_i-b)^2 \]
其中,\(x_i和y_i\)是樣本值,\(z_i\)是預測值。
實際上就是試圖找到一條直線,使全部樣本到直線上的歐氏距離之和最小。
假設咱們計算出初步的結果是紅色虛線所示,這條直線是否合適呢?咱們來計算一下圖中每一個點到這條直線的距離(黃色線),把這些距離的值都加起來(都是正數,不存在互相抵消的問題)成爲loss,而後想辦法不斷改變紅色直線的角度和位置,讓loss最小,就意味着總體誤差最小,那麼最終的那條紅色直線就是咱們要的結果。
若是想讓Error的值最小,經過對w和b求導,再令導數爲0(到達最小極值),就是w和b的最優解:
\[ w = \frac{\sum{y_i(x_i-\bar{x})}}{\sum{x_i^2}-\frac{1}{m}(\sum{x_i})^2}\tag{求和均爲i=1到m} \]
\[ b=\frac{1}{m}\sum_{i=1}^m(y_i-wx_i) \]
咱們先試一下上面這兩個公式是否好用:
x_sum = sum(X) # 求x之和 x_mean = x_sum/m # 求x平均 x_square = sum(X*X) # 求x平方之和 x_square_mean = x_sum * x_sum / m # 求x之和之平方之均 xy = sum(Y*(X-x_mean)) # 求w的公式的分子部分 w = xy / (x_square - x_square_mean) # 求w print(w) b = sum(Y-w*X) / m # 求b print(b) 結果爲: w=1.9983541668129974 b=3.0128994960012876
能夠看到很是接近w=2,b=3的原始值。
既然咱們已經能夠用純數學方法的最小二乘法獲得w,b的值,爲何還要學機器學習的方法呢?由於最小二乘法能作的事情有兩種:
\[y=a_0+a_1x+a_2x^2+ \dots + a_mx^m \tag{單元x屢次方程}\]
\[y=a_0+a_1x_1+a_2x_2+ \dots + a_mx_m \tag{多元x線性方程}\]
前提條件是咱們預測到方程的形式。可是更復雜的形式就比較吃力甚至無能爲力了,好比:
\[y=0.4x^2 + 0.3xsin(15x) + 0.01cos(50x)-0.3\]
\[y=3x_1^2 + 4x_2\]
而在客觀世界中或實際的生產環境中,咱們其實根本不知道要擬合的曲線是什麼形式,就根本無從下手,這時只能用神經網絡來擬合了,而擬合的結果也不是一個公式,而是一個神經網絡模型。
咱們是首次嘗試創建神經網絡,先搞一個最簡單的單點神經元:
對於簡單的線性迴歸問題,咱們使用單層網絡單個神經元就足夠了。並且因爲是線性的,咱們不須要定義激活函數,這就大大簡化了程序,並且便於你們按部就班地理解。
樣本數據x,乘以相同的w值後相加,再加上偏移b,輸出z。
def forward_calculation(w,b,X): z = w * x + b return z
咱們用傳統的均方差函數: \(loss = \frac{1}{2}(Z-Y)^2\),其中,Z是每一次迭代的預測輸出,Y是樣本標籤數據。咱們使用全部樣本參與訓練,所以損失函數實際爲:
\[loss = \frac{1}{2m}\sum_{i=1}^{m}(Z_i - Y_i) ^ 2\]
其中的分母中有個2,其實是想在求導數時把這個2約掉,沒有什麼原則上的區別。
因爲loss是全部樣本的集合,咱們先對其中的全部值求總和,樣本數量是m,而後除以m來求一個平均值。
下面是Python的code,用於計算損失:
# w:weight, b:bias, X,Y:sample data, count: count of sample, prev_loss:last time's loss 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
咱們計算這個loss值的目的是計算先後兩次迭代的loss值差別,當足夠小時,就結束訓練。
由於:
\[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}} \]
其中:
\[ \frac{\partial{loss}}{\partial{z}} = \frac{\partial{(\frac{1}{2}(z-y)^2)}}{\partial{z}} = z-y \]
而:
\[ \frac{\partial{z}}{\partial{w}} = \frac{\partial{}}{\partial{w}}(wx+b) = x \]
因此:
\[ \frac{\partial{loss}}{\partial{w}} = \frac{\partial{loss}}{\partial{z}}*\frac{\partial{z}}{\partial{w}} = (z-y)x \]
因此咱們用loss的值做爲基準,去求w對它的影響,也就是loss對w的偏導數:
\[ \frac{\partial{loss}}{\partial{b}} = \frac{\partial{loss}}{\partial{z}}*\frac{\partial{z}}{\partial{b}} \]
其中第一項前面算w的時候已經有了,而:
\[ \frac{\partial{z}}{\partial{b}} = \frac{\partial{(wx+b)}}{\partial{b}} = 1 \]
因此:
\[ \frac{\partial{loss}}{\partial{b}} = \frac{\partial{loss}}{\partial{z}}*\frac{\partial{z}}{\partial{b}} = z-y \]
# z:predication value, y:sample data label, x:sample data, count:count of sample data def dJwb_batch(X,Y,Z,count): p = Z - Y db = sum(p)/count q = p * X dw = sum(q)/count return dw, db def dJwb_single(x,y,z): p = z - y db = p dw = p * x return dw, db
上面有兩個求梯度函數,第一個用於數組數據(當輸入的X/Y/Z都是數組時),第二個用於標量數據(x/y/z都是標量),但最後輸出的dw/db都是標量,由於只有一個神經元。
def update_weights(w, b, dw, db, eta): w = w - eta*dw b = b - eta*db return w,b
第一個show_result函數用於最後輸出結果。第二個print_progress函數用於訓練過程當中的輸出。
def show_result(X, Y, w, b, iteration, loss_his, w_his, b_his, n): # draw sample data # plt.figure(1) plt.subplot(121) plt.plot(X, Y, "b.") # draw predication data Z = w*X +b plt.plot(X, Z, "r") plt.subplot(122) plt.plot(loss_his[0:n], "r") plt.plot(w_his[0:n], "b") plt.plot(b_his[0:n], "g") plt.grid(True) plt.show() print(iteration) print(w,b) def print_progress(iteration, loss, diff_loss, w, b, loss_his, w_his, b_his): if iteration % 10 == 0: print(iteration, diff_loss, w, b) loss_his = np.append(loss_his, loss) w_his = np.append(w_his, w) b_his = np.append(b_his, b) return loss_his, w_his, b_his
# count of samples m = 200 # initialize_data eta = 0.01 # set w,b=0, you can set to others values to have a try w, b = 0, 0 eps = 1e-10 iteration, max_iteration = 0, 10000 # calculate loss to decide the stop condition prev_loss, loss, diff_loss = 0,0,0 # create mock up data X, Y = create_sample_data(m) # create list history loss_his, w_his, b_his = list(), list(), list()
接下來,咱們會用三種方式來訓練神經網絡(神經元):
Pseudo code以下:
第一種方式:逐個樣本訓練即隨機梯度降低
repeat: for 每一個樣本x,y: 標量計算獲得z的單值 z = w * x + b 計算w的梯度 計算b的梯度 更新w,b的值 計算本次損失 與上一次的損失值比較,足夠小的話就中止訓練 end for until stop condition
第二種方式:批量樣本訓練即批量梯度降低
repeat: 矩陣前向計算獲得Z值 = w * X + b(其中X是全部樣本的數組) 計算w的梯度 計算b的梯度 更新w,b的值 計算本批損失 與上一批的損失值比較,足夠小的話就中止訓練 until stop condition
第三種方式:小批量樣本訓練即批量梯度降低
repeat: 從樣本集X中得到一小批量樣本Xn 矩陣前向計算獲得Z值 = w * Xn + b(其中Xn是一小批樣本的數組) 計算w的梯度 計算b的梯度 更新w,b的值 計算本批損失 與上一批的損失值比較,足夠小的話就中止訓練 until stop condition
咱們看完它們的訓練結構後再來比較它門的好壞。
針對200個數據,每次迭代只使用一個樣本進行訓練,每次都更新梯度值。
while iteration < max_iteration: for i in range(m): # get x and y value for one sample x = X[i] y = Y[i] # get z from x,y z = forward_calculation(w, b, x) # calculate gradient of w and b dw, db = dJwb_single(x, y, z) # update w,b w, b = update_weights(w, b, dw, db, eta) # calculate loss for this batch loss, diff_loss = check_diff(w,b,X,Y,m,prev_loss) # condition 1 to stop if diff_loss < eps: break prev_loss = loss iteration += 1 loss_his, w_his, b_his = print_progress(iteration, loss, diff_loss, w, b, loss_his, w_his, b_his) if diff_loss < eps: break show_result(X, Y, w, b, iteration, loss_his, w_his, b_his, 200)
1 0.0013946089980010831 1.7082689753500857 2.8635473444149815 2 1.2964547916170625e-05 1.8540100768184453 3.06775776515801 3 7.79019593934345e-07 1.8807160337440225 3.0745103188170186 ...... 19 8.734980997196495e-09 1.9871421670235265 3.0189893623564035 20 6.770768725197773e-09 1.9888753203686051 3.0180574393623383 21 1.4217967081453509e-13 1.9909568305589769 3.0231282539481192 21 1.9909568305589769 3.0231282539481192
一共迭代了21次(實際是21*200次),因爲diff_loss小於1e-10,因此中止了。可是,能夠看到w=1.99..,b=3.023...,與實際值w=2, b=3還有差距。
下圖右側圖,紅色線是loss值的變化,藍色線是w值的變化,綠色線是b值的變化。這三個值都很快從初始值趨近於理想值,可見這種方式的收斂速度較快。
# condition 2 to stop while iteration < max_iteration: # using current w,b to calculate Z Z = forward_calculation(w,b,X) # get gradient value dW, dB = dJwb_batch(X, Y, Z, m) # update w and b w, b = update_weights(w, b, dW, dB, eta) # print(iteration,w,b) iteration += 1 # condition 1 to stop loss, diff_loss = check_diff(w,b,X,Y,m,prev_loss) if diff_loss < eps: break prev_loss = loss iteration += 1 loss_his, w_his, b_his = print_progress(iteration, loss, diff_loss, w, b, loss_his, w_his, b_his) show_result(X, Y, w, b, iteration, loss_his, w_his, b_his, 200)
15580 1.0078619969849933e-10 1.9970527059142416 3.013622182124774 15590 1.0010891421385892e-10 1.9970570862183055 3.0136197497927952 15591 1.9970579605084025 3.013619264309657
訓練過程迭代了15591次,loss的先後差值小於1e-10了,達到了中止條件。能夠看到最後w = 1.997, b = 3.0136。
下圖右側圖,紅色線是loss值的變化,藍色線是w值的變化,綠色線是b值的變化。到第400次迭代時,w/b兩個值尚未到達理想值。loss的降低速度很快,可是在後期的變化很小,不能給w/b提供有效的反饋。
batchNumber = 20 # 設置每批的數據量爲20 # condition 2 to stop while iteration < max_iteration: # generate current batch batchX, batchY = generate_batch(X, Y, iteration, batchNumber, m) # using current w,b to calculate Z Z = forward_calculation(w,b,batchX) # get gradient value dW, dB = dJwb_batch(batchX, batchY, Z, batchNumber) # update w and b w, b = update_weights(w, b, dW, dB, eta) # calculate loss loss, diff_loss = check_diff(w,b,X,Y,m,prev_loss) # condition 1 to stop if diff_loss < eps: break prev_loss = loss iteration += 1 loss_his, w_his, b_his = print_progress(iteration, loss, diff_loss, w, b, loss_his, w_his, b_his) show_result(X, Y, w, b, iteration, loss_his, w_his, b_his, 300)
4450 1.2225522157127688e-10 1.9753608229361126 3.0087345373193264 4460 1.1451271614976166e-10 1.9753871799671467 3.008717691199392 4470 1.068962461950318e-10 1.975413384400498 3.0087009426124025 4479 1.975439437119652 3.0086842909936786
訓練過程迭代了4479次,最後w=1.9754, b=3.0086。
下圖右側圖,紅色線是loss值的變化,藍色線是w值的變化,綠色線是b值的變化。到第300次迭代時,w/b值已經接近理想值了。loss的降低速度慢,可是在後期仍然能夠給w/b有效的反饋。
下圖是三種方式在向目標解迭代靠近時的示意圖:
孔子說:點贊是人類的美德!若是以爲有用,關閉網頁前,麻煩您給點個贊!而後準備學習下一週的內容。
點擊這裏提交問題與建議
聯繫咱們: msraeduhub@microsoft.com
學習了這麼多,還沒過癮怎麼辦?歡迎加入「微軟 AI 應用開發實戰交流羣」,跟你們一塊兒暢談AI,答疑解惑。掃描下方二維碼,回覆「申請入羣」,即刻邀請你入羣。