原文寫于2018年5月。修改于2019年11月17。
最近在學習《Deep Learning》這本書,書中在前饋神經網絡、全連接配接神經網絡以及卷積神經網絡等内容中,都有提到反向傳播算法,這一算法可以說是神經網絡中求解參數比較核心的部分了。為了更好地了解神經網絡工作的原理,認識反向傳播在神經網絡中的運算機制,在綜合《Deep Learning》書中的有關部分并且學習了b站講解神經網絡的相關視訊及一些有關于BP算法的部落格文章之後,筆者将自己的了解寫下來,希望能為初學者了解反向傳播算法起到一定的幫助。在此,對已為BP算法提供了學習思路的各位前輩表示敬意,特别是幫助我思考和了解BP算法的三位部落客。
關于反向傳播算法,我們首先需要清楚它的應用途徑;其次,做一些神經網絡知識的前期儲備;之後,學習BP算法的工作原理;最後,認識到BP算法的局限性,了解改進方法。是以,本文從這4個點來講解,劃分為6部分:
1、 反向傳播算法應用領域
反向傳播算法應用較為廣泛,從字面意思了解,與前向傳播互相對應。在簡單的神經網絡中,反向傳播算法,可以了解為最優化損失函數過程,求解每個參與運算的參數的梯度的方法。在前饋神經網絡中,反向傳播從求解損失函數偏導過程中,步步向前求解每一層的參數梯度。在卷積神經網絡中,反向傳播可以求解全連接配接層的參數梯度。在循環神經網絡中,反向傳播算法可以求解每一個時刻t或者狀态t的參數梯度(在RNN\LSTM\GRU中,反向傳播更多是BPTT)。筆者如今對于BP的了解,認為是在優化損失函數或者目标函數過程中,求解參與運算的參數的梯度方法,是一種比較普遍的說法。
2、準備知識--反向傳播(BP)算法應用于神經網絡
反向傳播(BP)算法在深度學習中,應用廣泛。這裡僅以前饋神經網絡中的BP算法作為介紹。神經網絡是一個由輸入層、隐藏層、輸出層三部分組成的網絡,如圖(1):資料從輸入層,經過權重值和偏置項的線性變換處理,再通過激活層,得到隐藏層的輸出,也即下一層的輸入;隐藏層到輸出層之間是,經過權重值和偏置項的線性變換,之後通過激活層,得到輸出層。
圖2表示一個網絡層為2的前饋神經網絡:一個隐藏層,一個輸出層;隐藏單元為5,記輸入層到隐藏層的權重值為W,偏置項為b1,激活函數為g1,隐藏層到輸出層的權重值為V,偏置項為b2,激活函數為g2,則圖2的模型即為:
圖2是一個比較簡單的神經網絡,通常,我們見到的神經網絡,是具有多個隐藏層的網絡,如圖3:這是一個隐藏層個數為N個,每層隐藏單元數為5的神經網絡。(PS:隐藏層設計,可以考慮層數設計和隐藏單元設計,可根據自己的需要自行設計。)
從輸入層到隐藏層再到輸出層,這一向前傳遞的過程,我們稱之為前向傳播。前向傳播過程,往往是我們設定模型的過程,也可以了解為設定數學表達式或者列方程的過程。
3、BP算法原理及其實施步驟
BP算法的核心思想:使用梯度下降來搜尋可能的權向量的假設空間,以找到最佳的拟合樣例的權向量。具體而言,即利用損失函數,每次向損失函數負梯度方向移動,直到損失函數取得最小值。
或者說,反向傳播算法,是根據損失函數,求出損失函數關于每一層的權值及偏置項的偏導數,也稱為梯度,用該值更新初始的權值和偏置項,一直更新到損失函數取得最小值或是設定的疊代次數完成為止。以此來計算神經網絡中的最佳的參數。
由此,正式介紹BP算法前,我們需要知道前向傳播過程,确定網絡的設計。為此先設定一個隻有一層的神經網絡,作為講解,如圖4.
設定:從輸入層資料為X,輸入層到隐藏層參數為w,b1,隐藏層到輸出層參數為v,b2,激活函數用為g1,g2。于是模型設定為:
輸入層到隐藏層:
(3-1)
隐藏層到輸出層:
(3-2)
模型:
(3-3)
損失函數:
(3-4)
其中:
以上述的模型設定為例,下面介紹BP算法步驟,通過BP算法的步驟,了解反向傳播,是如何實作模型的參數更新。
實施步驟:
1)初始化網絡中的權值和偏置項,分别記為
(3-5)
2)激活前向傳播,得到各層輸出和損失函數的期望值
(3-6)
其中,
表示參數集合,
表示真實值,
表示預測值,
表示對總的誤內插補點取平均,是以一般情況下,輸出單元多少維,誤內插補點求平均就除以多少;本模型設定中,輸出值為2維列資料,故用誤內插補點除以2。一般情況下,損失函數的期望值表示為:
(3-6-1)
這是一組n維資料的輸出,若是有m組這樣的資料,損失函數的期望值為:
(3-6-2)
若真實值與輸出值表示為
,上式可表示為:
(3-6-3)
一般情況下,輸出資料為1維或是2維,輸出的資料有多組。
3)根據損失函數,計算輸出單元的誤差項和隐藏單元的誤差項
輸出單元的誤差項,即計算損失函數關于輸出單元的梯度值或偏導數,根據鍊式法則有:
(3-7)
隐藏單元的誤差項,即計算損失函數關于隐藏單元的梯度值或偏導數,根據鍊式法則有:
(3-8)
PS: 對于複合函數中的向量或矩陣求偏導,複合函數内部函數的偏導總是左乘;對于複合函數中的标量求偏導,複合函數内部函數的偏導左乘或者右乘都可以。
4) 更新神經網路中的權值和偏置項
輸出單元參數更新:
(3-9)
隐藏單元參數更新:
(3-10)
其中,
表示學習率,k=1,2,...,n表示更新次數或疊代次數,k=1表示第一次更新,以此類推。此處可能和别處部落格不太一樣,但實質是一樣的,此處的'+'或者'-'主要取決于損失函數.
如何定義損失函數或者定義參數更新均可,但參數的更新一定是向參數的負梯度方向。
5) 重複步驟2-4,直到損失函數小于事先給定的門檻值或疊代次數用完為止,輸出此時的參數即為目前最佳參數。
這便是BP算法的一個具體步驟,下面我們詳細介紹BP算法步驟中的每一步:
步驟1)初始化參數值(輸出單元權值、偏置項和隐藏單元權值、偏置項均為模型的參數),是為激活前向傳播,得到每一層元素的輸出值,進而得到損失函數的值。參數初始化,可以自己設定,也可以選擇随機生成;一般情況下,自己寫代碼或者調用tensorflow或keras時,都是随機生成參數。因為初始參數對最終的參數影響不大,隻會影響疊代的次數。
步驟2)在步驟1的基礎上,激活前向傳播,得到
的值,進而得到的值;其中的計算,根據前面模型設定中的公式計算。計算這些值是為計算步驟3中的誤差項。
步驟3)計算各項誤差,即計算參數關于損失函數的梯度或偏導數,之是以稱之為誤差,是因為損失函數本身為真實值與預測值之間的差異。計算參數的偏導數,根據的是微積分中的鍊式法則。具體推導如下:
輸出單元的誤差項:輸出單元v與損失函數E,不是直接相關,而是通過複合函數的形式關聯,以設定的模型為例:
(3-11)
其中
表示損失函數化為與參數v,b2相關的表達式。
根據鍊式法則,輸出單元v與損失函數E的誤差項為:
(3-12)
求出上式中每一個偏導:
(3-13)
(3-14)
(3-15)
(3-16)
其中,
關于激活函數
求偏導,需要根據具體的激活函數而定,每一層的激活函數可以選擇不同的函數,一般情況下,為簡單化模型設計和求導友善,會設定為同一個函數。此處假設選擇激活函數為sigmoid函數,那麼有:
(3-17)
PS:因為sigmoid(z)中z是标量,對z求偏導,有:
本文定義了z為向量,對于向量就有了式(3-17)的逐元素相乘的式子。
于是,為簡化後面的計算,記
(3-18)
其中,
表示第k次求損失函數關于
的偏導;
表示逐元素相乘,即兩個向量或兩個矩陣對應的元素相乘,例如:
于是,輸出單元的誤差項為:
(3-19)
(3-20)
此處說明:若遇式(3-15)的偏導(對權值求偏導),鍊式法則處理方式均如式(3-19);若遇式(3-16)的偏導(對偏置項求偏導),鍊式法則處理方式均如式(3-20)。
隐藏單元的誤差項:隐藏單元w與損失函數E,通過複合函數的形式關聯,以設定的模型整理為:
(3-21)
根據鍊式法則,隐藏單元w與損失函數E的誤差項為:
(3-22)
同樣的求導法則,得到隐藏單元的誤差項為:
(3-23)
(3-24)
其中:
(3-25)
(3-26)
(3-27)
(3-28)
說明:若遇式(3-25)(對隐藏單元求偏導),鍊式法則處理如式(3-23);式(3-15)和(3-26)同,故有相同的處理方式;式(3-16)和(3-27)同,故有相同的處理方式。
補充:若有多個隐藏層時,逐漸計算隐藏層的權值和偏置項誤差,推導的規則同上。例如:一個隐藏層為2,隐藏單元為5的神經網絡:
輸出層到隐藏層2的誤差項同式(3-19)
隐藏層2到隐藏層1的誤差項為:
(3-29)
記:
(3-30)
隐藏層1到輸入層的誤差項為:
(3-31)
從上述中,容易看出,無論多少層隐藏層,其誤差項都是同樣的結構。
步驟4) 更新神經網路中的權值和偏置項。學習率自己設定,學習率太大,容易跳過最佳的參數;學習率太小,容易陷入局部極小值。
步驟5) 設定門檻值e或者設定疊代次數,當損失函數值小于門檻值e時,或當疊代次數用完時,輸出最終參數。
4、執行個體運用
為能更好了解BP算法和知道如何運用BP算法,下面以一個實際的例子來說明運用BP算法的具體操作。
有一組資料
,目的是訓練這兩組資料,找到輸入X計算得到Y的預測值盡可能接近于真實值的參數。設定模型:設計一個隐藏層為1,隐藏單元數為2,激活函數為sigmod函數的模型,運用反向傳播算法,得到參數。網絡如圖5:
于是有:
(4-1)
式(4-1)中,x表示net1後的h。
根據BP算法步驟:
1)初始化網絡中的所有參數并給出學習率
:
2)激活前向傳播,将參數帶入式(4-1),并計算損失函數:
輸入層-->隐藏層:
(4-2)
(4-3)
隐藏層-->輸出層:
(4-4)
(上式中x表示4-3中的h)
(4-5)
損失函數:
(4-6)
3)計算輸出單元的誤差項和隐藏單元的誤差項
輸出單元的誤差項:根據公式(3-19),将
帶入其中,得到需更新的梯度誤差:
如果對v中每一個元素求偏導,有:
用公式(3-19)和對v中每一個元素求偏導,得到的結果一緻。
隐藏單元的誤差項:根據公式(3-23),将
帶入其中,得到需更新的梯度誤差
若對w中每一個元素求偏導,有:
用公式(3-23)和對v中每一個元素求偏導,得到的結果一緻。
注意:一般情況下,不會對偏置項更新
4)更新神經網絡中的權值
于是,得到第一次更新的參數值w,v。
5)重複步驟2-4,直到損失值達到了預先設定的門檻值或疊代次數用完,得到最終的權值。
以上即為BP算法的更新權值的過程,下面将上述執行個體的推導過程用代碼實作:
5、執行個體程式設計實作(運作環境python3)
根據BP算法的步驟,将上述例子對應的代碼寫出如下:
# encoding:utf-8
# ********* 導入相應的子產品***********
import math
import numpy as np
from numpy import *
#**********設定模型所需的激活函數,運作此代碼時,帶'*'部分請删除*********
# 激活函數
def sigmoids(z):
a = []
for each in z:
b = 1/(1+math.exp(-each[0]))
a.append(b)
return a
**********設定前向傳播過程,即模型的設定部分,此處均根據模型第3部分的模型設定部分的公式編寫對應的代碼*********
# 前向傳播,傳回預測值
def forwordmd(X,W,V,B1,B2):
net1 = W.T*X+B1
H = matrix(sigmoids(np.array(net1))).T # 隐藏層單元
net2 = V.T*H+B2
pred_y = matrix(sigmoids(np.array(net2))).T # 預測值
return pred_y,H,net1,net2
#**********設定模型反向傳播,按照步驟4的公式編輯*********
# 反向傳播,更新權重
def Bpaugorith(Y,pred_y,H,V,aph,W):
Errorterm = 0.5*(Y-pred_y).T*(Y-pred_y)# 給出誤差公式
# 計算輸出單元的誤差項
a1 = multiply(pred_y-Y,pred_y) # 矩陣對應元素相乘,即逐元素相乘
a2 = multiply(a1,1-pred_y)
Verror = H*a2.T
# 計算隐藏單元的誤差項
Werror = X*(multiply(multiply(H,1-H),(V*a2))).T
# 更新權重
Vupdate = V - aph*Verror
Wupdate = W - aph*Werror
return Vupdate,Wupdate,Errorter
#**********主程式部分,此處設定了步驟1中的參數初始化和輸入值及輸出值的真實值,及步驟5中設定疊代次數和設定門檻值停止疊代的代碼*********
if __name__ =='__main__':
X = matrix([0.05,0.10]).T
Y = matrix([0.01,0.99]).T
# 給出初始權重
W = matrix([[0.15,0.20],[0.25,0.30]])
B1 = matrix([0.1,0.1]).T
V = matrix([[0.40,0.45],[0.50,0.55]])
B2 = matrix([0.2,0.2]).T
#***********初始權重亦可随機生成***********
# 随機生成參數
# np.random.seed(0)
# W = matrix(np.random.normal(0,1,[2,2]))
# B1 = matrix(np.random.normal(0, 1, [1, 2]))
# V = matrix(np.random.normal(0, 1, [2, 2]))
# B2 = matrix(np.random.normal(0, 1, [1, 2]))
#***********随機生成參數部分,若有自己設定,将此部分注釋*********
aph = 0.5 # 學習率
#*********從此處為疊代次數設定部分***********
# 疊代10次
n = 10 # 疊代次數
for i in range(n):
# 激活前向算法
pred_y, H,net1,net2 = forwordmd(X,W,V,B1,B2) # 得到預測值和隐藏層值
# 更新權重
Vupdate, Wupdate,Errorvalue = Bpaugorith(Y,pred_y,H,V,net1,net2,aph,W) # 得到更新的權重
W,V = Wupdate,Vupdate
print ('損失函數e:%.2f'%e)
print ('預測值:')
print (pred_y)
print ('更新的權重V:')
print (Vupdate)
print ('更新的權重W:')
print (Wupdate)
print ('損失值:')
print (Errorvalue)
# 門檻值E,可根據需要自行更改,若需要運作此部分,請将疊代次數部分注釋後運作
# 門檻值E
# e,m = 0.19,1
# pred_y, H, net1, net2 = forwordmd(X,W,V,B1,B2) # 得到預測值和隐藏層值
# 更新權重
# Vupdate, Wupdate, Errorvalue = Bpaugorith(Y,pred_y,H,V,net1,net2,aph,W) # 得到更新的權重
# W,V = Wupdate,Vupdate
# while Errorvalue>e:
# 激活前向算法
# pred_y, H, net1, net2 = forwordmd(X,W,V,B1,B2) # 得到預測值和隐藏層值
# 更新權重
# Vupdate, Wupdate, Errorvalue = Bpaugorith(Y,pred_y,H,V,net1,net2,aph,W) # 得到更新的權重
# W, V = Wupdate, Vupdate
# m = m+1
# print ('疊代次數:%d'%n)
# print ('更新權重:%d次'% m)
# print ('預測值:')
# print (pred_y)
# print ('更新的權重V:')
# print (Vupdate)
# print ('更新的權重W:')
# print (Wupdate)
# print ('損失值:')
# print (Errorvalue)
#*********門檻值設定部分結束***********
以上部分為本例中的代碼部分。設定了激活函數,前向傳播(即模型的設定)及反向傳播過程,步驟5中有門檻值設定和疊代步數,這一部分的程式如主程式中。前向傳播部分和反向傳播部分,以上内容均根據推導的公式一句句編寫出來的。感興趣的朋友可以自己嘗試編寫這部分程式。
代碼連結:https://gitee.com/someone317/backpropagation_algorithm_test/blob/master/BPtest1.py
PS:此代碼可直接粘貼在python3寫成檔案運作,帶'****'部分需要删除。
6、BP算法缺陷與改進
BP算法缺陷:
1)局部極小值
對于多層網絡,誤差曲面可能含有多個不同的局部極小值,梯度下降可能導緻陷入局部極小值。
2)權值過多
當隐藏節點過多,層數越多時,權值成倍增長。權值的增長意味着對應的空間維數的增加,過高的維數易導緻訓練後期的過拟合。
3)容易過拟合
訓練的次數過多、空間維數過高均容易過拟合。
BP算法改進:
1)利用動量法改進BP算法
動量法權值調整算法的具體做法是:将上一次權值調整量的一部分疊加到按本次誤差計算所得的權值調整量上,作為本次的實際權值調整量,即:
其中,
表示動量系數,
表示學習率。
2)自适應調整學習率
調整的基本指導思想是:在學習收斂的情況下,增大
以縮短學習時間;當
偏大緻使不能收斂時,要及時減小它的值,知道收斂為止。此方法适用于設定門檻值的情況下。
3)動量-自适應學習速率調整算法
采用動量法,BP算法可以找到更優的解;采用自适應學習速率法時,BP算法可以縮短訓練時間。将以上兩種方法結合起來,就得到動量-自适應學習率調整算法。
上述2)和3)都适應于設定門檻值來停止程式的方法。
參考部落格:
[1] https://www.cnblogs.com/liuwu265/p/4696388.html
[2] https://blog.csdn.net/zhaomengszu/article/details/77834845
[3] https://blog.csdn.net/g11d111/article/details/7094346