Copyright © Microsoft Corporation. All rights reserved.
適用於License版權許可
更多微軟人工智能學習資源,請見微軟人工智能教育與學習共建社區python
咱們定義一個兩層的神經網絡,輸入層不算,一個隱藏層,含128個神經元,一個輸出層。git
數學理論證實:具備足夠數量神經元的兩層神經網絡可以擬合任意精度的連續函數。因此,今天我們就用實際數據來驗證一下這個理論。咱們假設一個連續函數的形式爲:github
\[y=0.4x^2 + 0.3xsin(15x) + 0.01cos(50x)-0.3\]算法
輸入層就是一個標量X值。網絡
它是鏈接兩層之間的紐帶,有的人理解它應該屬於輸入層,有的人理解應該屬於隱藏層,各有各的道理,我我的傾向於把它歸到隱藏層,理由是\(Z1=W1*X+B1\),在X固定的前提下,W1決定了Z1的值。另一個理由是B1的存在位置,在本例中B1是一個128x1的矩陣,它是隱藏層128個神經元的偏移,因此它應該屬於隱藏層。框架
其實這裏的B1所在的圓圈裏應該是個常數1,而B1鏈接到Z1-1...Z1-128的權重線B1-1...B1-128應該是個浮點數。咱們爲了說明問題方便,就寫了個B1,而實際的B1是指B1-1...B1-128的矩陣/向量。dom
W1的尺寸是128x1,B1的尺寸是128x1。函數
咱們用一個128個神經元的網絡來模擬函數,這個你們能夠本身試驗一下,把代碼中的神經元數量修改一下,而後在保持迭代次數和其它(超)參數不變的狀況,看看最終的精確度有何區別,訓練時間的差別,以及內存佔用有何差別。學習
每一個神經元的輸入\(Z1 = W1 * X + B1\),咱們在這裏使用雙曲sigmoid正切函數,因此輸出是\(A1 = sigmoid(Z1)\)。固然也可使用其它激活函數若是tanh, Relu等等。測試
與W1/B1相似,我我的認爲它屬於輸出層。W2的尺寸是1x128,B2的尺寸是1x1。
因爲咱們只想完成一個擬合任務,因此輸出層只有一個神經元。它們的左側是\(Z2=W2*A1+B2\),右側是\(A2=Z2\)。
爲何在最後一步沒有用激活函數,而是直接令A2=Z2呢?咱們後面再說。
讓咱們先自力更生創造一些模擬數據:
import numpy as np import matplotlib.pyplot as plt from pathlib import Path def TargetFunction(x): p1 = 0.4 * (x**2) p2 = 0.3 * x * np.sin(15 * x) p3 = 0.01 * np.cos(50 * x) y = p1 + p2 + p3 - 0.3 return y def CreateSampleDataXY(m): S = np.random.random((m,2)) S[:,1] = TargetFunction(S[:,0]) return S def CreateTestData(n): TX = np.linspace(0,1,100) TY = TargetFunction(TX) TZ = np.zeros(n) return TX, TY, TZ
其函數圖像在[0,1]之間的樣子是:
生成的數據格式以下:
\[ \begin{pmatrix} x_1, y_1\\ x_2, y_2\\ \dots\\ x_m, y_m\\ \end{pmatrix} \]
其中,x就是上圖中藍色點的橫座標值,y是縱座標值。在[0,1]以外的函數曲線沒這麼複雜,彷佛擬合起來沒什麼難度,因此咱們特色選擇了[0,1]之間這一段來作試驗。
至此,咱們獲得瞭如下一串公式:
\[Z1=W1*X+B1\]
\[A1=sigmoid(Z1)\]
\[Z2=W2*A1+B2\]
\[A2=Z2 \tag{這一步能夠省略}\]
def ForwardCalculation(x, dictWeights): W1 = dictWeights["W1"] B1 = dictWeights["B1"] W2 = dictWeights["W2"] B2 = dictWeights["B2"] Z1 = np.dot(W1,x) + B1 A1 = sigmoid(Z1) Z2 = np.dot(W2,A1) + B2 A2 = Z2 # 這一步能夠省略 dictCache ={"A1": A1, "A2": A2} return A2, dictCache
因爲參數較多,因此咱們用一個dictionary(dictWeights)來保存W,B這些參數,若是是更多層的神經網絡,就會有更多的參數,咱們這裏使用的仍是一些最基本的參數。
咱們用傳統的均方差函數: \(loss = \frac{1}{2}(Z-Y)^2\),其中,Z是每一次迭代的預測輸出,Y是樣本標籤數據。咱們使用全部樣本參與訓練,所以損失函數實際爲:
\[Loss = \frac{1}{2}(Z - Y) ^ 2\]
其中的分母中有個2,其實是想在求導數時把這個2約掉,沒有什麼原則上的區別。
看一下計算圖,而後用鏈式求導法則反推:
由於:
\[Z2 = W2*A1+B2\]
\[Loss = \frac{1}{2}(Z2-Y2)^2\]
因此咱們用Loss的值做爲基準,去求w對它的影響,也就是loss對w的偏導數:
\[ \frac{\partial{Loss}}{\partial{W2}} = \frac{\partial{Loss}}{\partial{Z2}}*\frac{\partial{Z2}}{\partial{W2}} \]
其中:
\[ \frac{\partial{Loss}}{\partial{Z2}} = \frac{\partial{}}{\partial{Z2}}[\frac{(Z2-Y)^2}{2}] = Z2-Y \]
而:
\[ \frac{\partial{Z2}}{\partial{W2}} = \frac{\partial{}}{\partial{W2}}(W2*A1+B2) = A1^T \]
因此:
\[ \frac{\partial{Loss}}{\partial{W2}} = \frac{\partial{Loss}}{\partial{Z2}}*\frac{\partial{Z2}}{\partial{W2}} = (Z2-Y)*A1^T \]
矩陣求導的理論部分較爲複雜,請你們參考咱們的《基本數學導數公式》章節。
\[ \frac{\partial{Loss}}{\partial{B2}} = \frac{\partial{Loss}}{\partial{Z2}}*\frac{\partial{Z2}}{\partial{B2}} \]
其中第一項前面算w的時候已經有了,而:
\[ \frac{\partial{Z2}}{\partial{B2}} = \frac{\partial{(W2*A1+B2)}}{\partial{B2}} = 1 \]
因此:
\[ \frac{\partial{Loss}}{\partial{B2}} = \frac{\partial{Loss}}{\partial{Z2}}*\frac{\partial{Z2}}{\partial{B2}} = Z2-Y \]
由於:
\[A1 = sigmoid(Z1)\]
\[Z1 = W1*X+B1\]
對Z1求導:
\[ \frac{\partial{Loss}}{\partial{Z1}} = \frac{\partial{Loss}}{\partial{Z2}}*\frac{\partial{Z2}}{\partial{A1}}*\frac{\partial{A1}}{\partial{Z1}} \]
其中前面推導過:
\[ \frac{\partial{Loss}}{\partial{Z2}} = Z2-Y = dZ2 \]
而:
\[ \frac{\partial{Z2}}{\partial{A1}} = \frac{\partial{}}{\partial{A1}}(W2*A1+B2) = W2^T \]
\[ \frac{\partial{A1}}{\partial{Z1}} = \frac{\partial{}}{\partial{Z1}}(sigmoid(Z1)) = A1*(1-A1) \]
因此:
\[ \frac{\partial{Loss}}{\partial{Z1}} = W2^T * dZ2 * A1 * (1-A1) = dZ1 \]
而W1,B1的求導結果和W2,B2相似:
\[ \frac{\partial{Loss}}{\partial{W1}} = \frac{\partial{Loss}}{\partial{Z1}}*\frac{\partial{Z1}}{\partial{W1}}=dZ1*\frac{\partial{(W1*X+B1)}}{\partial{W1}}=dZ1*X^T \]
\[ \frac{\partial{Loss}}{\partial{B1}} = \frac{\partial{Loss}}{\partial{Z1}}*\frac{\partial{Z1}}{\partial{B1}}=dZ1*\frac{\partial{(W1*X+B1)}}{\partial{B1}}=dZ1 \]
變成代碼:
def BackPropagation(x, y, dictCache, dictWeights): A1 = dictCache["A1"] A2 = dictCache["A2"] W2 = dictWeights["W2"] dLoss_Z2 = A2 - y dZ2 = dLoss_Z2 dW2 = dZ2 * A1.T dB2 = dZ2 dZ2_A1 = W2.T * dZ2 dA1_Z1 = A1 * (1 - A1) # dZ1 is dLoss_Z1 dZ1 = dZ2_A1 * dA1_Z1 dW1 = dZ1 * x dB1 = dZ1 dictGrads = {"dW1":dW1, "dB1":dB1, "dW2":dW2, "dB2":dB2} return dictGrads
def UpdateWeights(dictWeights, dictGrads, learningRate): W1 = dictWeights["W1"] B1 = dictWeights["B1"] W2 = dictWeights["W2"] B2 = dictWeights["B2"] dW1 = dictGrads["dW1"] dB1 = dictGrads["dB1"] dW2 = dictGrads["dW2"] dB2 = dictGrads["dB2"] W1 = W1 - learningRate * dW1 W2 = W2 - learningRate * dW2 B1 = B1 - learningRate * dB1 B2 = B2 - learningRate * dB2 dictWeights = {"W1": W1,"B1": B1,"W2": W2,"B2": B2} return dictWeights
第一個show_result函數用於最後輸出結果。第二個print_progress函數用於訓練過程當中的輸出。
def sigmoid(x): s=1/(1+np.exp(-x)) return s def initialize_with_zeros(n_x,n_h,n_y): np.random.seed(2) # W1=np.random.randn(n_h,n_x)*0.00000001 # W1=np.random.randn(n_h,n_x) W1=np.random.uniform(-np.sqrt(6)/np.sqrt(n_x+n_h),np.sqrt(6)/np.sqrt(n_h+n_x),size=(n_h,n_x)) # W1=np.reshape(32,784) B1=np.zeros((n_h,1)) # W2=np.random.randn(n_y,n_h)*0.00000001 # W2=np.random.randn(n_y,n_h) W2=np.random.uniform(-np.sqrt(6)/np.sqrt(n_y+n_h),np.sqrt(6)/np.sqrt(n_y+n_h),size=(n_y,n_h)) B2=np.zeros((n_y,1)) assert (W1.shape == (n_h, n_x)) assert (B1.shape == (n_h, 1)) assert (W2.shape == (n_y, n_h)) assert (B2.shape == (n_y, 1)) dictWeights = {"W1": W1,"B1": B1,"W2": W2,"B2": B2} return dictWeights
m = 1000 S = CreateSampleDataXY(m) #plt.scatter(S[:,0], S[:,1], 1) #plt.show() n_input, n_hidden, n_output = 1, 128, 1 learning_rate = 0.1 eps = 1e-10 dictWeights = initialize_with_zeros(n_input, n_hidden, n_output) max_iteration = 1000 loss, prev_loss, diff_loss = 0, 0, 0
for iteration in range(max_iteration): for i in range(m): x = S[i,0] y = S[i,1] A2, dictCache = ForwardCalculation(x, dictWeights) dictGrads = BackPropagation(x,y,dictCache,dictWeights) dictWeights = UpdateWeights(dictWeights, dictGrads, learning_rate) print("iteration", iteration)
tm = 100 TX, TY, TZ = CreateTestData(tm) correctCount = 0 for i in range(tm): x = TX[i] y = TY[i] a2, dict = ForwardCalculation(x, dictWeights) TZ[i] = a2 plt.scatter(TX, TY) plt.plot(TX, TZ, 'r') str = str.format("cell:{0} sample:{1} iteration:{2} rate:{3}", n_hidden, m, max_iteration, learning_rate) plt.title(str) plt.show()
上面的TX是[0,1]之間的連續數,共100個,間隔相同。TY是更加被模擬的函數計算出來的精確值。TZ是咱們訓練的模型的預測值。咱們的目的就是要比較TY和TZ之間的差距。
下圖就是擬合結果,還比較使人滿意。
常常聽人提及「調參」,此次我們親身經歷一下調參的痛(快)苦(樂)!咱們下面一切的比較都是如下面這組參數爲基準:
以上這些標準值如何獲得呢?試了不少組合後獲得的,這就是所謂「試錯」的過程了。
神經元數量=64
神經元數量=96
神經元數量=256,迭代次數=500
基準神經元數爲128,在96時,擬合效果不好,在64時,儘管咱們增長了迭代次數爲2000,仍然不好。
第三張圖,儘管神經元數量翻了一倍,成爲256個,可是迭代次數爲500,少了一倍,也會形成奇怪的結果。
輸入數據量=500
輸入數據量=1500
樣本數據量不夠時,擬合效果很差。可是當樣本數據量超過必定值後,就沒多大做用了。
迭代次數=500
輸入數據量=1500
迭代次數少,擬合效果很差。迭代次數超過必定值後,容易形成過擬合,效果不大。
步長=0.5
步長=0.01
步長值太大或者過小,都會形成很差的效果。
總結以下(效果5分爲最好):
神經元數量 | 樣本量 | 迭代次數 | 步長值 | 效果 | |
---|---|---|---|---|---|
0 | 128 | 1000 | 1000 | 0.1 | 5 |
1 | 64 | 1000 | 2000 | 0.1 | 3 |
2 | 96 | 1000 | 1000 | 0.1 | 2.5 |
3 | 256 | 1000 | 500 | 0.1 | 0 |
4 | 128 | 500 | 1000 | 0.1 | 2 |
5 | 128 | 1500 | 1000 | 0.1 | 5 |
6 | 128 | 1000 | 500 | 0.1 | 2.5 |
7 | 128 | 1000 | 1500 | 0.1 | 5 |
8 | 128 | 1000 | 1000 | 0.5 | 1 |
9 | 128 | 1000 | 1000 | 0.05 | 2 |
點擊這裏提交問題與建議
聯繫咱們: msraeduhub@microsoft.com
學習了這麼多,還沒過癮怎麼辦?歡迎加入「微軟 AI 應用開發實戰交流羣」,跟你們一塊兒暢談AI,答疑解惑。掃描下方二維碼,回覆「申請入羣」,即刻邀請你入羣。