深度神經網路(DNN)的訓練過程

深度神經網路(DNN)的訓練過程

來自專欄人工智慧圖像識別技術與計算機視覺(CV)3 人贊了文章

翻山越嶺

披荊斬棘

我只為你

一個不起眼的領地

——題記

又跑錯片場了……


回顧

激活函數就是神經元對接收信息的處理。神經元的接收值為對上一層神經元輸出值的加權求和,而這個「權」就是兩層神經元之間的邊權。

激活函數有很多,常用的有Sigmoid、Tanh、ReLU、LReLU、ELU、PReLU,其中PReLU為網路引入了另一個可以學習的參數。

網路中可以有多個激活函數,但是每個神經元必須有且只有一個激活函數。

激活函數也可以自己根據網路需要來創造,但是函數的效果可能只有訓練完畢之後我們才能知道,所以前人的經驗也很重要。

那麼,如何進行深度神經網路的訓練呢?

訓練是什麼

神經網路的訓練就是尋找誤差函數最小值的過程(誤差接近最小值也叫做網路的收斂)。

好了,開頭那首詩解釋完了,今天的內容也講完了。

哈哈,當然沒這麼簡單。

不過,真的!神經網路的所有訓練演算法都是在做同一件事:逼近誤差函數關於權值、偏置量等參數的最小值。所以訓練是什麼就非常容易理解了。

為什麼訓練

那我們為什麼要訓練深度神經網路呢?

那還用問嘛!當然是為了擬合函數,或者是目標分類啊!

如何訓練

是什麼、為什麼、怎麼做,解決了前兩個了,剩下的就是如何訓練深度神經網路。

1.首先,我們構建網路結構,然後將權值初始化成隨機的實數(盡量保證均值為0,為了提高訓練速度一般設置為絕對值較小的實數,如區間[-0.25,0.25])

2.對樣本進行前饋運算,也就是輸入數據,然後逐層加權求和再激活,一直傳播到輸出層。

3.然後計算網路誤差,然後進行反饋運算,也就是將誤差分攤到每一個神經元上。

4.接下來,根據神經元的誤差,計算每一條邊的邊權、每一層的偏置量的改進量,然後更新權值、偏置量。

5.最後,重複上述2~4過程直到網路收斂。

這就是誤差梯度下降的過程。之所以叫梯度下降,是因為每一次誤差可能會下降一定值(有時可能上升,但最終網路大概率會收斂),而且不是連續的,就像下圖:

梯度下降的過程,就像小球在誤差曲面中受重力作用向下滾

每次沿著誤差下降的方向滾小球,這就是最早提出的誤差梯度下降的演算法:BP演算法,又名反向傳播演算法。

但是,BP演算法有可能出現下面的情況——陷入局部最小值

梯度下降陷入局部最小值,而不能達到全局最小值

這裡人們陷入了討論:該怎麼辦才能避開局部最小值呢?

這時,有人提出:改進BP演算法,比如增加小球的慣性,或者讓小球越滾越快。於是BP演算法發展成了今天的樣子:增加動量、自適應學習率。

還有人提出:乾脆摒棄BP演算法、或者先找到全局最小值的「大坑」,然後再用BP演算法加速收斂。於是模擬退火、遺傳演算法也加入到神經網路訓練演算法的行列。

簡單來說,BP演算法、模擬退火、遺傳演算法的主要思想如下:

BP演算法:一個球,受重力作用不斷往下滾,最後滾到最低點。

模擬退火:這個球抽風了,不受重力作用了,四處亂滾,但這個球還是逐漸恢復了冷靜,繼續受重力作用往下滾,但是滾滾停停。

遺傳演算法:有很多的球,全都抽風了,四處亂滾。但是有些往上滾的因為高處不勝寒,被凍死了;剩下往下滾的,它們交配繁殖產生了很多很多球,繼續亂滾,最終找到了全局最小值,在最小值的坑裡幸福地生活在了一起。

這是我那次直播手寫反向傳播的原話,回放我發到B站上了:

BP神經網路第一講_嗶哩嗶哩 (゜-゜)つロ 乾杯~-bilibili?

www.bilibili.com圖標

實際上,遺傳演算法可以實現全局搜索,但是最慢;模擬退火可以實現部分搜索,較慢,配合BP演算法效果還是比較好的,但是同樣有可能找不到全局最小值;BP演算法最快,可是它是個貪婪策略,有可能根本找不到全局最小值。

在這裡,我們只介紹BP演算法的實現過程。

BP演算法的過程

BP演算法只有三個過程:前向傳播反向傳播更新權值。當然一般都會再跟一步:降低學習率

下面,就下圖4層網路(兩層隱含層,包含偏置共12個神經元),分別分析這四步。

前向傳播

將輸入的兩個值作為 out_{I1}out_{I2} ,完成I層傳播;

在隱含層的H層,三個神經元的接收值分別是:

net_{H1} = w_{1} · out_{I1} + w_{4} · out_{I2} + b_{1}

net_{H2} = w_{2} · out_{I1} + w_{5} · out_{I2} + b_{2}

net_{H3} = w_{3} · out_{I1} + w_{6} · out_{I2} + b_{3}

然後用激活函數激活神經元:

out_{H1} = A_{H1}(net_{H1})

out_{H2} = A_{H2}(net_{H2})

out_{H3} = A_{H3}(net_{H3})

然後傳播到了隱含層的D層,D層神經元的接收值和輸出值如下:

net_{D1} = w_{7} · out_{H1} + w_{10} · out_{H2} + w_{13} · out_{H3} + b_{4}out_{D1} = A_{D1}(net_{D1})

net_{D2} = w_{8} · out_{H1} + w_{11} · out_{H2} + w_{14} · out_{H3} + b_{5}out_{D2} = A_{D2}(net_{D2})

net_{D3} = w_{9} · out_{H1} + w_{12} · out_{H2} + w_{15} · out_{H3} + b_{6}out_{D3} = A_{D3}(net_{D3})

最後到了輸出層O層,最終處理結果並輸出:

net_{O1} = w_{16} · out_{D1} + w_{17} · out_{D2} + w_{18} · out_{D3} + b_{7}out_{O1} = A_{O1}(net_{O1})

於是前向傳播結束了,下面進行反向傳播。

反向傳播

在這裡,為了方便理解,筆者在給出公式時,先給出模板化的公式,再給出適用於本網路的具體公式。

首先我們計算前向傳播的輸出值與期望輸出之間的誤差。

我們的目標是擬合,採用差值平方和公式:

E_{i} = frac{1}{2} · (out_{Oi} - ans_{i})^{2}

總誤差就是:

E_{tot} = sum_{i = 1}^{size_{layerspace O}}{E_{i}} = frac{1}{2} · sum_{i = 1}^{size_{layerspace O}}{(out_{Oi} - ans_{i})^{2}}

因為這個網路我們只有一個輸出神經元,於是誤差就是 E_{tot} = E_{1} = frac{1}{2}(out_{O1} - ans_{1})^{2}

然後我們求誤差分攤到每一個輸出層神經元上的值,也就是:

delta_{Oi} = frac{partial{E_{tot}}}{partial{net_{Oi}}} = frac{partial{E_{i}}}{partial{out_{Oi}}} 	imes frac{partial{out_{Oi}}}{partial{net_{Oi}}}

第二個等號利用了鏈式法則和微積分加法定理,其中

微積分加法定理: frac{partial{(f(x) + g(x))}}{partial{x}} = frac{partial{f(x)}}{partial{x}} + frac{partial{g(x)}}{partial{x}}

鏈式法則: frac{partial{f(g(x))}}{partial{x}} = frac{partial{f(g(x))}}{partial{g(x)}} 	imes frac{partial{g(x)}}{partial{x}}

其中 frac{partial{E_{i}}}{partial{out_{Oi}}} 是求差值平方和公式關於 out_{Oi} 的導數,而 frac{partial{out_{Oi}}}{partial{net_{Oi}}} 是對激活函數求導。很容易知道:

delta_{Oi} = frac{partial{E_{i}}}{partial{out_{Oi}}} 	imes frac{partial{out_{Oi}}}{partial{net_{Oi}}} = (out_{Oi} - ans_{i}) 	imes A_{Oi}(net_{Oi})

所以本網路O1的誤差 delta_{O1} = (out_{O1} - ans_{1}) 	imes A_{Oi}(net_{O1})

接下來把O層誤差傳播到D層:

delta_{Di} = frac{partial{E_{tot}}}{partial{net_{Di}}} = (sum_{j = 1}^{size_{layerspace O}}{(frac{partial{E_{j}}}{partial{net_{Oj}}} 	imes frac{partial{net_{Oj}}}{partial{out_{Di}}})}) 	imes frac{partial{out_{Di}}}{partial{net_{Di}}}

其中 frac{partial{E_{i}}}{partial{net_{Oi}}} 就是我們求過的 delta_{Oi}frac{partial{net_{Oj}}}{partial{out_{Di}}} 就是神經元 D_{i} 和神經元 O_{j} 之間的邊權。於是我們發現,求和記號裡面的不就是對上一層的誤差加權求和嘛!然後 frac{partial{out_{Di}}}{partial{net_{Di}}} 也是對激活函數求導。所以:

delta_{Di} = (sum_{j = 1}^{size_{layerspace O}}{(frac{partial{E_{j}}}{partial{net_{Oj}}} 	imes frac{partial{net_{Oj}}}{partial{out_{Di}}})}) 	imes frac{partial{out_{Di}}}{partial{net_{Di}}} = (sum_{j = 1}^{size_{layerspace O}}{(delta_{Oj} · w_{(Di, Oj)})}) · A_{Di}(net_{Di})

對於我們的這個網路, D_{1}D_{2}D_{3} 的誤差分別是:

delta_{D1} = w_{16} · delta_{O1} · A_{D1}(net_{D1})delta_{D2} = w_{17} · delta_{O1} · A_{D2}(net_{D2})delta_{D3} = w_{18} · delta_{O1} · A_{D3}(net_{D3})

然後我們把誤差傳播到H層,發現公式和D層公式很相似對吧:

delta_{Hi} = frac{partial{E_{tot}}}{partial{net_{Hi}}} = (sum_{j = 1}^{size_{layerspace D}}{(frac{partial{E_{j}}}{partial{net_{Dj}}} 	imes frac{partial{net_{Dj}}}{partial{out_{Hi}}})}) 	imes frac{partial{out_{Hi}}}{partial{net_{Hi}}}

沒錯,其實隱含層的公式都是一樣的,多幾層少幾層都沒關係。

因此:

delta_{Hi} = (sum_{j = 1}^{size_{layerspace D}}{(frac{partial{E_{j}}}{partial{net_{Dj}}} 	imes frac{partial{net_{Dj}}}{partial{out_{Hi}}})}) 	imes frac{partial{out_{Hi}}}{partial{net_{Hi}}} = (sum_{j = 1}^{size_{layerspace D}}{(delta_{Dj} · w_{(Hi, Dj)})}) · A_{Hi}(net_{Hi})

而在我們的網路中, H_{1}H_{2}H_{3} 的誤差分別是:

delta_{H1} = (w_{7} · delta_{D1} + w_{8} · delta_{D2} + w_{9} · delta_{D3}) · A_{H1}(net_{H1})delta_{H2} = (w_{10} · delta_{D1} + w_{11} · delta_{D2} + w_{12} · delta_{D3}) · A_{H2}(net_{H2})delta_{H3} = (w_{13} · delta_{D1} + w_{14} · delta_{D2} + w_{15} · delta_{D3}) · A_{H3}(net_{H3})

然後反向傳播就已經結束了。

沒錯,誤差不需要傳播到輸入層I層,到隱含層的最淺層就可以了,接下來就是更新權值和偏置量了。

且慢!似乎還缺點什麼……

DNN的其他超參數

深度神經網路(DNN)除了網路層數、隱含層神經元個數、激活函數之外,還有幾個超參數:

學習率 sigma :控制梯度下降的步長。

動量 mu :控制梯度下降過程的慣性大小。

學習率適應度 phi :控制學習率自適應程度。

第一次,我們求出總誤差分攤在權值的誤差 frac{partial{E_{tot}}}{partial{w_{i}}} 和偏置量上的誤差 frac{partial{E_{tot}}}{partial{b_{i}}} 之後,我們讓權值 w_{i} 和偏置量 b_{i} 的更新量 Delta w_{i} = sigma · frac{partial{E_{tot}}}{partial{w_{i}}}Delta b_{i} = sigma · frac{partial{E_{tot}}}{partial{b_{i}}}

接下來每次反向傳播結束之後,我們將更新的幅度 Delta w_{i}Delta b_{i} 乘以動量 mu ,然後再加上這一次算出的 sigma · frac{partial{E_{tot}}}{partial{w_{i}}} 或者 sigma · frac{partial{E_{tot}}}{partial{b_{i}}} 。即:

Delta w_{i} = mu · Delta w_{i} + sigmafrac{partial{E_{tot}}}{partial{w_{i}}}Delta b_{i} = mu · Delta b_{i} + sigmafrac{partial{E_{tot}}}{partial{b_{i}}}

將它們作為下一步權值和偏置量的更新量。問題又來了,如何求這個更新量呢?

網路參數的更新量

很顯然,我們知道: frac{partial{E_{tot}}}{partial{w_{(p, q)}}} = frac{partial{E_{tot}}}{partial{net_q}} 	imes frac{partial{net_q}}{partial{w_{(p, q)}}} ,其中p是邊(p, q)的起點,q是終點, w_{(p, q)} 是這條邊的權值。而且我們知道:  frac{partial{E_{tot}}}{partial{net_q}} = delta_{q}frac{partial{net_{q}}}{partial{w_{(p, q)}}} = out_{p} ,因此每個權值的更新量就是:

frac{partial{E_{tot}}}{partial{w_{(p, q)}}} = frac{partial{E_{tot}}}{partial{net_q}} 	imes frac{partial{net_q}}{partial{w_{(p, q)}}} = delta_{q} · out_{p}

那麼偏置量呢?其實,偏置量可以視為恆輸出1的普通神經元,而真正起作用的是它連接到下一層神經元的權值,這樣我們對偏置量的修改就是對從偏置量神經元到下一層神經元權值的修改。因此:

frac{partial{E_{tot}}}{partial{b_{q}}} = frac{partial{E_{tot}}}{partial{net_q}} 	imes frac{partial{net_q}}{partial{b_{q}}} = delta_{q}

於是整個網路就可以更新了。方法是,逆向誤差函數升高的方向:

w_{i} = w_{i} - Delta w_{i}b_{i} = b_{i} - Delta b_{i}

BP演算法的最主要的三個過程已經講完了,如果想要錦上添花的話可以加一步降低學習率。

降低學習率

因為在誤差梯度下降的過程中,誤差曲面往往是比較崎嶇的,所以我們很容易一步跨過最小值,以至於造成來回震蕩。下圖是學習率的取值與收斂速度的關係( sigma 是使用的學習率, sigma_{0} 是最合適的學習率)。

學習率取值與收斂速度的關係

我們發現,當一開始學習率就很大的時候,誤差就飈上天了;學習率較大和較小的時候,誤差都會下降,但是較大會因為震蕩收斂緩慢,較小會因為步長較短而收斂緩慢。所以我們可以一開始先設置一個較大的學習率(但不能太大,因為一開始所在的誤差曲面的位置往往很陡),然後通過動量懲罰掉震蕩,逐漸降低學習率達到合適的值。

於是,關於DNN的訓練過程已經詳細地解釋完畢了。下一篇我將模擬一個網路訓練的過程,讓大家對這一篇的理解更加深入透徹。


感謝閱讀。由於本人水平有限,文章定有疏漏。如有不足,敬請斧正!(萬能結束語)


推薦閱讀:

TAG:深度學習DeepLearning | 神經網路 | BP演算法 |