BP算法為深度學習中參數更新的重要角色,一般基於loss對參數的偏導進行更新。
一些根據均方誤差,每層默認激活函數sigmoid(不同激活函數,則更新公式不一樣)
假設網絡如圖所示:

則更新公式為:

以上列舉了最后2層的參數更新方式,第一層的更新公式類似,即上一層的誤差來自於下一層所有的神經元,e的更新就是不斷建立在舊的e上(這里g可以當做初始的e)
下面上代碼:
1,BP算法
# 手寫BP算法 import numpy as np # 先更新參數,再繼續傳播 # layers:包括從輸入層到輸出層,每層參數為:連接權重w,閾值b,輸出y。類型為np.array # 對於輸入層,w和b隨便是啥,反正不用,只需y即原始輸入 # 基於激活函數sigmoid # loss為均方誤差 def bp(layers,labels,lr=0.001): # 翻轉layers,反向傳播 reversed_layers=layers[::-1] # 輸出層 output_w,output_b,output_y=reversed_layers[0] g=np.array([output_y[j]*(1-output_y[j])*(labels[j]-output_y[j]) for j in range(len(labels))]) # 最后一層更新較為特殊,先進行更新 delta_w=np.empty(shape=(output_w.shape[0],output_w.shape[1])) # 上一層輸出y last_y=reversed_layers[1][2] for h in range(output_w.shape[0]): for j in range(output_w.shape[1]): delta_w[h,j]=lr*g[j]*last_y[h] delta_b=-lr*g new_w=output_w+delta_w new_b=output_b+delta_b reversed_layers[0][0]=new_w reversed_layers[0][1]=new_b # 從倒數第二層到第二層進行更新,每次取3層進行計算,由公式知,需用到上一層輸出即下一層權重 for i in range(1,len(reversed_layers)-1): # 下一層w next_w=reversed_layers[i-1][0] out_w,out_b,out_y=reversed_layers[i] # 上一層y last_y=reversed_layers[i+1][2] # 更新輔助量,意思即上一層每個神經元的誤差都由下一層所有神經元的誤差反向傳播,體現在這里內循環 e=np.empty(shape=(len(out_b),1)) for h in range(len(out_b)): temp=0 for j in range(next_w.shape[1]): temp+=next_w[h,j]*g[j] e[h]=out_y[h]*(1-out_y[h])*temp delta_w=np.empty(shape=(out_w.shape[0],out_w.shape[1])) for h in range(out_w.shape[0]): for j in range(out_w.shape[1]): delta_w[h,j]=lr*e[j]*last_y[h] delta_b=-lr*e out_new_w=out_w+delta_w out_new_b=out_b+delta_b reversed_layers[i][0]=out_new_w reversed_layers[i][1]=out_new_b g=np.copy(e) return layers
以上假設每個神經元的輸出為一個實數y值
2,構建測試
構建平面上的點(x,y),將y是否大於0作為划分,進行訓練。只使用了一層網絡,sigmoid激活
X=[] Y=[] for i in range(-100,100): for j in range(-100,100): X.append([[i],[j]]) if j>=0: Y.append([1]) else: Y.append([0]) X=np.array(X) Y=np.array(Y)
3,划分訓練,驗證集
indexs=np.random.choice(range(40000),size=30000) x_train=np.array([X[i] for i in indexs]) y_train=np.array([Y[i] for i in indexs]) x_val=np.array([X[i] for i in np.setdiff1d(range(40000),indexs)) y_val=np.array([Y[i] for i in np.setdiff1d(range(40000),indexs))
4,訓練。這里只對所有樣本訓練了一輪。使用隨機初始化的w和b,每個樣本都會改變w和b
# 使用sigmoid激活函數 def output(input_x,w,b): res=0 t=np.matmul(np.transpose(w),input_x)-b return 1./(1+np.power(np.e,-t)) w1=np.random.normal(size=(2,1)) b1=np.array([[0]]) for i in range(len(x_train)): y0=x_train[i] l=y_train[i] input_layers=[] w0,b0=(0,0) input_layers.append([w0,b0,y0]) input_layers.append([w1,b1,output(y0,w1,b1)]) input_layers=bp(input_layers,l) w1=input_layers[1][0] b1=input_layers[1][1] # w: [[0.11213777] # [1.67425498]] # b: [[0.0001581]] print('w: ',w1) print('b: ',b1)
5,驗證。從分出的驗證集選取部分驗證即可
for xx in x_val[:50]: print(xx.reshape((2,)),output(xx,w1,b1).reshape((1,)))
驗證結果如下:
[63 68] [1.]
[-100 -99] [1.39636722e-77]
[-100 -98] [7.44936654e-77]
[63 69] [1.]
[-100 -96] [2.12011171e-75]
[-100 -94] [6.03390049e-74]
[-100 -93] [3.21897678e-73]
[63 74] [1.]
[-100 -91] [9.16130293e-72]
[63 75] [1.]
[63 76] [1.]
[63 77] [1.]
[63 78] [1.]
[-100 -86] [3.95872874e-68]
[-100 -85] [2.11191018e-67]
[63 79] [1.]
[-100 -83] [6.01055872e-66]
[-100 -82] [3.20652436e-65]
[63 82] [1.]
[-100 -80] [9.12586299e-64]
[-100 -79] [4.86848285e-63]
[63 83] [1.]
[-100 -77] [1.38558459e-61]
[-100 -76] [7.39184317e-61]
[63 89] [1.]
[-100 -74] [2.10374039e-59]
[63 91] [1.]
[-100 -72] [5.98730724e-58]
[-100 -71] [3.19412012e-57]
[-100 -70] [1.70400531e-56]
[-100 -69] [9.09056014e-56]
[-100 -68] [4.84964942e-55]
[-100 -67] [2.58720025e-54]
[-100 -66] [1.38022454e-53]
[63 99] [1.]
[-100 -64] [3.92815978e-52]
[-100 -63] [2.09560219e-51]
[ 64 -100] [2.53988133e-70]
[-100 -61] [5.9641457e-50]
[ 64 -97] [3.85631522e-68]
[ 64 -96] [2.05727442e-67]
[-100 -58] [9.05539386e-48]
[ 64 -90] [4.74253371e-63]
[ 64 -89] [2.53005596e-62]
[ 64 -87] [7.20061393e-61]
[-100 -52] [2.08749548e-43]
[ 64 -84] [1.09327301e-58]
[-100 -50] [5.94107377e-42]
[ 95 -13] [1.49260907e-05]
[-100 -45] [2.56722211e-38]
6,總結:可以看出,這50個驗證樣本上都沒問題,雖然想到的測試方案有點low,但一時找不到啥好數據。由此驗證BP算法的正確性。如有可疑或不足之處,敬請告知。
