反向傳播算法是深度學習的最重要的基礎,這篇博客不會詳細介紹這個算法的原理和細節。,如果想學習反向傳播算法的原理和細節請移步到這本不錯的資料。這里主要討論反向傳播算法中的一個小細節:反向傳播算法為什么要“反向”?
背景
在機器學習中,很多算法最后都會轉化為求一個目標損失函數(loss function)的最小值。這個損失函數往往很復雜,難以求出最值的解析表達式。而梯度下降法正是為了解決這類問題。直觀地說一下這個方法的思想:我們把求解損失函數最小值的過程看做“站在山坡某處去尋找山坡的最低點”。我們並不知道最低點的確切位置,“梯度下降”的策略是每次向“下坡路”的方向走一小步,經過長時間的走“下坡路”最后的停留位置也大概率在最低點附近。這個“下坡路的方向”我們選做是梯度方向的負方向,選這個方向是因為每個點的梯度負方向是在該點處函數下坡最陡的方向。至於為什么梯度負方向是函數下降最陡的方向請參考大一下的微積分教材,或者看看這個直觀的解釋。在神經網絡模型中反向傳播算法的作用就是要求出這個梯度值,從而后續用梯度下降去更新模型參數。反向傳播算法從模型的輸出層開始,利用函數求導的鏈式法則,逐層從后向前求出模型梯度,那么為什么要從后向前呢?
從一道編程題說起
這是leetcode 上的一道編程題 ,這道題不是很難,讀者可以自己先做一做。這個問題的原理跟反向傳播算法有很大的相似。
這道題目是給出一個三角形的數組,從上到下找到一條路徑使得這條路徑上數字的和最小,並且路徑層與層之間的節點左右需要相鄰。詳細的題目介紹請到leetcode上看一看。
圖1中是一個具體的例子,最小和的路徑如箭頭所示,路徑的和為11。最開始做這個題,我的想法是從上到下不斷將問題分解成小問題去處理,具體來說上圖的大三角形可以分解成3為頂點和4為頂點的兩個小三角形,如圖2所示,求出這兩個子問題的最小路徑,選擇這兩個路徑和較小者加上頂點2的值就為整個問題的最小值,而對於每個分解的子問題,再故伎重演地繼續分解來處理。具體的代碼如下:
class Solution:
def minPoint(self,triangel,i,j):
if i == len(triangel)-1: #最底層,遞歸基
return triangel[i][j]
else:
return triangel[i][j] + \ #頂點值
min(self.minPoint(triangel,i+1,j),self.minPoint(triangel,i+1,j+1)) #子問題的較小者
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
return self.minPoint(triangle,0,0)
將以上代碼提交后發現Time Limit Exceeded 的超時錯誤,究其原因是這種從上到下的計算方式中存在大量的重復計算,如2圖所示:將一個大的問題分解成兩個子問題(紅藍兩個三角形)時,這兩個三角形之間有交疊,即圖2中的紫色圈,這些交疊部分會導致重復計算,使得計算超時。圖中僅僅是一個子問題的重復計算,實際上每一次子問題的分解都有這樣的重復計算,因此整個問題的重復計算量非常的大。
實際上這個問題的正確解決方法是使用動態規划算法,動態規划要解決的正是這種包含重復子問題的情況。其實現方法一般分為兩種,一種是將計算過的子問題都緩存起來,當遇到了相同的問題就直接取出緩存的值,避免重復計算;另一種是方式是換一個“計算方向”(或者說計算的先后次序),使得在這個計算方向上沒有重復的子問題。對於上述編程題我們的解法就是將這一算法換一個計算方向,由從上到下變換為從下到上。從最底層開始由下到上計算以當前點為頂點的“三角形”最小路徑值,下層的頂點先計算,上層可以利用下層的計算結果,這就避免了重復計算。具體代碼如下:
class Solution:
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
for i in range(len(triangle)-1)[::-1]:
for j in range(len(triangle[i])):
triangle[i][j] = triangle[i][j] + min(triangle[i+1][j],triangle[i+1][j+1])
return triangle[0][0]
上面的代碼就能夠通過測試,我們看到僅僅是改變計算方向就能有效地避免重復的計算,從而加快計算速度。理解了這個算法,我們再看反向傳播算法需要“反向”的原因也就覺得理所當然了。
反向傳播算法
現在來講反向傳播算法,其目的就是要求神經網絡模型的輸出相對於各個參數的梯度值。以圖3為例子講解,圖中輸入向量x經過神經網絡的輸出為y,模型的參數為wi和bi,在輸入值x為xi時,將模型的參數看做自變量,於是所謂的求梯度就是求出所有的\(\frac{\partial y}{\partial wi}|_{x=xi}\)和\(\frac{\partial y}{\partial bi}|_{x=xi}\)。這些導數要怎么求能?如果我不知道反向傳播算法,我應該會用如下式子的近似求導方法:
即要求某個參數的導數就讓這個參數微變一點點,然后求出結果相對於參數變化量的比值。那么為何我們的神經網絡算法沒有采用這種方法求導呢?
讓我們再看一看圖3的例子,現在假設輸入向量經過正向傳播后,現在要求出參數w1和w2的導數,按照上述方法計算時,對w1微擾后,需要重新計算紅框內的節點;對w2微擾后,需要重新計算綠框內的節點。這兩次計算中也有大量的“重復單元”即圖中的藍框,實際上神經網絡的每一個參數的計算都包含着這樣大量的重復單眼,那么神經網絡規模一旦變大,這種算法的計算量一定爆炸,沒有適用價值。我們再回過頭去看我們的編程題,是否有一些似曾相識呢,尤其是圖2和圖3之間。