第10章 使用Keras搭建人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)
第12章 使用TensorFlow自定義模型并訓(xùn)練
第13章 使用TensorFlow加載和預(yù)處理數(shù)據(jù)
第14章 使用卷積神經(jīng)網(wǎng)絡(luò)實現(xiàn)深度計算機(jī)視覺
第15章 使用RNN和CNN處理序列
第16章 使用RNN和注意力機(jī)制進(jìn)行自然語言處理
第17章 使用自編碼器和GAN做表征學(xué)習(xí)和生成式學(xué)習(xí)
第18章 強化學(xué)習(xí)
第19章 規(guī)南夷簦化訓(xùn)練和部署TensorFlow模型
第 10 章介紹了人工神經(jīng)網(wǎng)絡(luò)鸟辅,并訓(xùn)練了第一個深度神經(jīng)網(wǎng)絡(luò)。 但它非常淺莺葫,只有兩個隱藏層匪凉。 如果你需要解決非常復(fù)雜的問題,例如檢測高分辨率圖像中的數(shù)百種類型的對象捺檬,該怎么辦洒缀? 你可能需要訓(xùn)練更深的 DNN,也許有 10 層或更多欺冀,每層包含數(shù)百個神經(jīng)元树绩,通過數(shù)十萬個連接相連。 這可不像公園散步那么簡單隐轩,可能碰到下面這些問題:
- 你將面臨棘手的梯度消失問題(或相關(guān)的梯度爆炸問題):在反向傳播過程中饺饭,梯度變得越來越小或越來越大。二者都會使較淺層難以訓(xùn)練职车;
- 要訓(xùn)練一個龐大的神經(jīng)網(wǎng)絡(luò)瘫俊,但是數(shù)據(jù)量不足鹊杖,或者標(biāo)注成本很高;
- 訓(xùn)練可能非常慢扛芽;
- 具有數(shù)百萬參數(shù)的模型將會有嚴(yán)重的過擬合訓(xùn)練集的風(fēng)險骂蓖,特別是在訓(xùn)練實例不多或存在噪音時。
在本章中川尖,我們將依次討論這些問題登下,并給出解決問題的方法。 我們將從梯度消失/爆炸問題開始叮喳,并探討解決這個問題的一些最流行的解決方案被芳。 接下來會介紹遷移學(xué)習(xí)和無監(jiān)督預(yù)訓(xùn)練,這可以在即使標(biāo)注數(shù)據(jù)不多的情況下馍悟,也能應(yīng)對復(fù)雜問題畔濒。然后我們將看看各種優(yōu)化器,可以加速大型模型的訓(xùn)練锣咒。 最后侵状,我們將瀏覽一些流行的大型神經(jīng)網(wǎng)絡(luò)正則化方法。
使用這些工具毅整,你將能夠訓(xùn)練非常深的網(wǎng)絡(luò):歡迎來到深度學(xué)習(xí)的世界趣兄!
梯度消失/爆炸問題
正如我們在第 10 章中所討論的那樣,反向傳播算法的工作原理是從輸出層到輸入層毛嫉,傳播誤差的梯度糙俗。 一旦該算法已經(jīng)計算了網(wǎng)絡(luò)中每個參數(shù)的損失函數(shù)的梯度锻霎,它就通過梯度下降使用這些梯度來更新每個參數(shù)蔚出。
不幸的是谈秫,隨著算法進(jìn)展到較低層章办,梯度往往變得越來越小剃氧。 結(jié)果娱据,梯度下降更新使得低層連接權(quán)重實際上保持不變仅叫,并且訓(xùn)練永遠(yuǎn)不會收斂到最優(yōu)解房交。 這被稱為梯度消失問題彻舰。 在某些情況下,可能會發(fā)生相反的情況:梯度可能變得越來越大候味,許多層得到了非常大的權(quán)重更新刃唤,算法發(fā)散。這是梯度爆炸的問題白群,在循環(huán)神經(jīng)網(wǎng)絡(luò)中最為常見(見第 145章)尚胞。 更一般地說,深度神經(jīng)網(wǎng)絡(luò)面臨梯度不穩(wěn)定; 不同的層可能有非常不同的學(xué)習(xí)率帜慢。
雖然很早就觀察到這種現(xiàn)象了(這是造成深度神經(jīng)網(wǎng)絡(luò)在2000年早期被拋棄的原因之一)笼裳,但直到 2010 年左右唯卖,人們才才略微清楚了導(dǎo)致梯度消失/爆炸的原因。 Xavier Glorot 和 Yoshua Bengio 發(fā)表的題為《Understanding the Difficulty of Training Deep Feedforward Neural Networks》(http://proceedings.mlr.press/v9/glorot10a/glorot10a.pdf)的論文發(fā)現(xiàn)了一些疑點躬柬,包括流行的 sigmoid 激活函數(shù)和當(dāng)時最受歡迎的權(quán)重初始化方法的組合拜轨,即隨機(jī)初始化時使用平均值為 0,標(biāo)準(zhǔn)差為 1 的正態(tài)分布允青。簡而言之橄碾,他們表明,用這個激活函數(shù)和這個初始化方案昧廷,每層輸出的方差遠(yuǎn)大于其輸入的方差堪嫂。隨著網(wǎng)絡(luò)前向傳播,每層的方差持續(xù)增加木柬,直到激活函數(shù)在頂層飽和皆串。logistic函數(shù)的平均值為 0.5 而不是 0(雙曲正切函數(shù)的平均值為 0,表現(xiàn)略好于深層網(wǎng)絡(luò)中的logistic函數(shù))眉枕,使得情況更壞恶复。
看一下logistic 激活函數(shù)(參見圖 11-1),可以看到當(dāng)輸入變大(負(fù)或正)時速挑,函數(shù)飽和在 0 或 1谤牡,導(dǎo)數(shù)非常接近 0。因此姥宝,當(dāng)反向傳播開始時翅萤, 它幾乎沒有梯度通過網(wǎng)絡(luò)傳播回來,而且由于反向傳播通過頂層向下傳遞腊满,所以存在的小梯度不斷地被稀釋套么,因此較低層得到的改善很小。
Glorot 和 He 初始化
Glorot 和 Bengio 在他們的論文中提出了一種顯著緩解這個問題的方法碳蛋。 我們需要信號在兩個方向上正確地流動:在進(jìn)行預(yù)測時是前向的胚泌,在反向傳播梯度時是逆向的。 我們不希望信號消失肃弟,也不希望它爆炸并飽和玷室。 為了使信號正確流動,作者認(rèn)為笤受,我們需要每層輸出的方差等于其輸入的方差穷缤,并且反向傳播時,流經(jīng)一層的前后箩兽,梯度的方差也要相同(如果對數(shù)學(xué)細(xì)節(jié)感興趣的話津肛,請查看論文)。實際上不可能保證兩者都是一樣的比肄,除非這個層具有相同數(shù)量的輸入和神經(jīng)元(這兩個數(shù)被稱為該層的扇入fan-in
和扇出fan-out
)快耿,但是他們提出了一個很好的折衷辦法囊陡,在實踐中證明這個折中辦法非常好:隨機(jī)初始化連接權(quán)重必須如公式 11-1 這樣,其中fanavg = (fanin + fanout) / 2掀亥。 這種初始化策略通常被稱為Xavier初始化或Glorot初始化撞反。
如果將公式11-1中的fanavg替換為fanin,就得到了Yann LeCun在1990年代提出的初始化策略搪花,他稱其為LeCun初始化遏片。Genevieve Orr 和 Klaus-Robert Müller 在1998年出版的書《Neural Networks: Tricks of the Trade (Springer)》中推薦了LeCun初始化。當(dāng)fanin = fanout 時撮竿,LeCun初始化等同于 Glorot 初始化吮便。研究者們經(jīng)歷了十多年才意識到初始化策略的重要性。使用 Glorot 初始化可以大大加快訓(xùn)練幢踏,這是促成深度學(xué)習(xí)成功的技術(shù)之一髓需。
一些論文針對不同的激活函數(shù)提供了類似的策略。這些策略的區(qū)別在于方差大小和使用fanavg或fanout房蝉,如表 11-1 所示僚匆。 ReLU 激活函數(shù)(及其變體,包括簡稱 ELU 激活)的初始化策略有時稱為 He 初始化搭幻。本章后面會介紹SELU激活函數(shù)咧擂,它應(yīng)該與LeCun初始化(最好是正態(tài)分布)一起使用。
默認(rèn)情況下檀蹋,Keras使用均勻分布的Glorot初始化函數(shù)松申。創(chuàng)建層時,可以通過設(shè)置kernel_initializer="he_uniform"
或kernel_initializer="he_normal"
變更為He初始化俯逾,如下所示:
keras.layers.Dense(10, activation="relu", kernel_initializer="he_normal")
如果想讓均勻分布的He初始化是基于fanavg而不是fanin贸桶,可以使用VarianceScaling初始化器:
he_avg_init = keras.initializers.VarianceScaling(scale=2., mode='fan_avg',
distribution='uniform')
keras.layers.Dense(10, activation="sigmoid", kernel_initializer=he_avg_init)
非飽和激活函數(shù)
Glorot 和 Bengio 在 2010 年的論文中的一個見解是,消失/爆炸的梯度問題部分是由于激活函數(shù)的選擇不好造成的纱昧。 在那之前刨啸,大多數(shù)人都認(rèn)為堡赔,如果大自然選擇在生物神經(jīng)元中使用 sigmoid 激活函數(shù)识脆,它們必定是一個很好的選擇。 但事實證明善已,其他激活函數(shù)在深度神經(jīng)網(wǎng)絡(luò)中表現(xiàn)得更好灼捂,特別是 ReLU 激活函數(shù),主要是因為它對正值不會飽和(也因為它的計算速度很快)换团。
但是悉稠,ReLU激活功能并不完美。 它有一個被稱為 “ReLU 死區(qū)” 的問題:在訓(xùn)練過程中艘包,一些神經(jīng)元會“死亡”的猛,即它們停止輸出 0 以外的任何東西耀盗。在某些情況下,你可能會發(fā)現(xiàn)你網(wǎng)絡(luò)的一半神經(jīng)元已經(jīng)死亡卦尊,特別是使用大學(xué)習(xí)率時叛拷。 在訓(xùn)練期間,如果神經(jīng)元的權(quán)重得到更新岂却,使得神經(jīng)元輸入的加權(quán)和為負(fù)忿薇,則它將開始輸出 0 。當(dāng)這種情況發(fā)生時躏哩,由于當(dāng)輸入為負(fù)時署浩,ReLU函數(shù)的梯度為0,神經(jīng)元就只能輸出0了扫尺。
為了解決這個問題筋栋,你可能需要使用 ReLU 函數(shù)的一個變體,比如 leaky ReLU正驻。這個函數(shù)定義為LeakyReLUα(z)= max(αz二汛,z)(見圖 11-2)。超參數(shù)α
定義了函數(shù)“l(fā)eak”的程度:它是z < 0
時函數(shù)的斜率拨拓,通常設(shè)置為 0.01肴颊。這個小斜率保證 leaky ReLU 永不死亡;他們可能會長期昏迷渣磷,但他們有機(jī)會最終醒來婿着。2015年的一篇論文(https://arxiv.org/abs/1505.00853)比較了幾種 ReLU 激活功能的變體,其中一個結(jié)論是 leaky Relu 總是優(yōu)于嚴(yán)格的 ReLU 激活函數(shù)醋界。事實上竟宋,設(shè)定α= 0.2
(大leak)似乎比α= 0.01
(小 leak)有更好的性能。這篇論文還評估了隨機(jī)化 leaky ReLU(RReLU)形纺,其中α
在訓(xùn)練期間在給定范圍內(nèi)隨機(jī)丘侠,并在測試期間固定為平均值。它表現(xiàn)相當(dāng)好逐样,似乎是一個正則項(減少訓(xùn)練集的過擬合風(fēng)險)蜗字。最后,文章還評估了參數(shù)化的 leaky ReLU(PReLU)脂新,其中α
被授權(quán)在訓(xùn)練期間參與學(xué)習(xí)(而不是作為超參數(shù)挪捕,α
變成可以像任何其他參數(shù)一樣被反向傳播修改的參數(shù))。據(jù)報道争便,PReLU在大型圖像數(shù)據(jù)集上的表現(xiàn)強于 ReLU级零,但是對于較小的數(shù)據(jù)集,其具有過度擬合訓(xùn)練集的風(fēng)險滞乙。
最后鉴嗤,Djork-Arné Clevert 等人在 2015 年的一篇論文(https://arxiv.org/abs/1511.07289)中提出了一種稱為指數(shù)線性單元(exponential linear unit,ELU)的新激活函數(shù)序调,在他們的實驗中躬窜,ELU的表現(xiàn)優(yōu)于所有 ReLU 變體:訓(xùn)練時間減少,神經(jīng)網(wǎng)絡(luò)在測試集上表現(xiàn)的更好炕置。 如圖 11-3 所示荣挨,公式 11-2 給出了它的定義。
ELU看起來很像 ReLU 函數(shù)朴摊,但有一些區(qū)別默垄,主要區(qū)別在于:
- 它在
z < 0
時取負(fù)值,這使得該單元的平均輸出接近于 0甚纲。這有助于減輕梯度消失問題口锭。 超參數(shù)α
定義為當(dāng)z
是一個大的負(fù)數(shù)時,ELU 函數(shù)接近的值介杆。它通常設(shè)置為 1鹃操,但是如果你愿意,你可以像調(diào)整其他超參數(shù)一樣調(diào)整它春哨。 - 它對
z < 0
有一個非零的梯度荆隘,避免了神經(jīng)元死亡的問題。 - 如果
α
等于1赴背,則函數(shù)在任何地方都是平滑的椰拒,包括z = 0
附近,這有助于加速梯度下降凰荚,因為它不會在z = 0
附近回彈燃观。
ELU 激活函數(shù)的主要缺點是計算速度慢于 ReLU 及其變體(由于使用指數(shù)函數(shù)),但是在訓(xùn)練過程中便瑟,這是通過更快的收斂速度來補償?shù)摹?然而缆毁,在測試時間,ELU 網(wǎng)絡(luò)將比 ReLU 網(wǎng)絡(luò)慢到涂。
2017年的一篇文章(https://arxiv.org/abs/1706.02515)中脊框,Günter Klambauer等人介紹了一種Scaled ELU(SELU)激活函數(shù):正如它的名字所示,它是ELU的伸縮變體养盗。作者證明缚陷,只要神經(jīng)網(wǎng)絡(luò)中都是緊密層适篙,并且所有隱藏層都是用的SELU激活函數(shù)往核,則這個網(wǎng)絡(luò)是自歸一的:訓(xùn)練過程中,每層輸出的平均值是0嚷节,標(biāo)準(zhǔn)差是1聂儒,這樣就解決了梯度消失爆炸問題虎锚。對于全緊密層的網(wǎng)絡(luò)(尤其是很深的),SELU的效果常常優(yōu)于其他激活函數(shù)衩婚。但是自歸一是需要條件的(數(shù)學(xué)論證見論文):
輸入特征必須是標(biāo)準(zhǔn)的(平均值是0窜护,標(biāo)準(zhǔn)差是1);
每個隱藏層的權(quán)重必須是LeCun正態(tài)初始化的非春。在Keras中柱徙,要設(shè)置
kernel_initializer="lecun_normal"
;網(wǎng)絡(luò)架構(gòu)必須是順序的奇昙。但是护侮,如果要在非順序網(wǎng)絡(luò)(比如RNN)或有跳連接的網(wǎng)絡(luò)(跳過層的連接,比如Wide&Deep)中使用SELU储耐,就不能保證是自歸一的羊初,所以SELU就不會比其它激活函數(shù)更優(yōu);
這篇論文只是說如果所有層都是緊密層才保證自歸一什湘,但有些研究者發(fā)現(xiàn)SELU激活函數(shù)也可以提高卷積神經(jīng)網(wǎng)絡(luò)的性能长赞。
提示:那么深層神經(jīng)網(wǎng)絡(luò)的隱藏層應(yīng)該使用哪個激活函數(shù)呢? 雖然可能會有所不同闽撤,一般來說 SELU > ELU > leaky ReLU(及其變體)> ReLU > tanh > sigmoid得哆。 如果網(wǎng)絡(luò)架構(gòu)不能保證自歸一,則ELU可能比SELU的性能更好(因為SELU在z=0時不是平滑的)哟旗。如果關(guān)心運行延遲柳恐,則 leaky ReLU 更好。 如果你不想多調(diào)整另一個超參數(shù)热幔,你可以使用前面提到的默認(rèn)的
α
值(leaky ReLU 為 0.3)乐设。 如果有充足的時間和計算能力,可以使用交叉驗證來評估其他激活函數(shù)绎巨,如果神經(jīng)網(wǎng)絡(luò)過擬合近尚,則使用RReLU; 如果您擁有龐大的訓(xùn)練數(shù)據(jù)集,則為 PReLU场勤。但是戈锻,因為ReLU是目前應(yīng)用最廣的激活函數(shù),許多庫和硬件加速器都使用了針對ReLU的優(yōu)化和媳,如果速度是首要的格遭,ReLU可能仍然是首選。
要使用leaky ReLU留瞳,需要創(chuàng)建一個LeakyReLU
層拒迅,并將它加到需要追加的層后面:
model = keras.models.Sequential([
[...]
keras.layers.Dense(10, kernel_initializer="he_normal"),
keras.layers.LeakyReLU(alpha=0.2),
[...]
])
對于PReLU,用PReLU()
替換LeakyRelu(alpha=0.2)
。目前還沒有RReLU的Keras官方實現(xiàn)璧微,但很容易自己實現(xiàn)(方法見第12章的練習(xí))作箍。
對于SELU,當(dāng)創(chuàng)建層時設(shè)置activation="selu"
前硫,kernel_initializer="lecun_normal"
:
layer = keras.layers.Dense(10, activation="selu",
kernel_initializer="lecun_normal")
批歸一化(Batch Normalization)
盡管使用 He初始化和 ELU(或任何 ReLU 變體)可以顯著減少訓(xùn)練開始階段的梯度消失/爆炸問題胞得,但不能保證在訓(xùn)練期間問題不會再次出現(xiàn)。
在 2015 年的一篇論文(https://arxiv.org/abs/1502.03167)中屹电,Sergey Ioffe 和 Christian Szegedy 提出了一種稱為批歸一化(Batch Normalization阶剑,BN)的方法來解決梯度消失/爆炸問題。該方法包括在每層的激活函數(shù)之前或之后在模型中添加操作危号。操作就是將輸入平均值變?yōu)?个扰,方差變?yōu)?,然后用兩個新參數(shù)葱色,一個做縮放递宅,一個做偏移。換句話說苍狰,這個操作可以讓模型學(xué)習(xí)到每層輸入值的最佳縮放值和平均值办龄。大大多數(shù)情況下,如果模型的第一層使用了BN層淋昭,則不用標(biāo)準(zhǔn)化訓(xùn)練集(比如使用StandardScaler
)俐填;BN層做了標(biāo)準(zhǔn)化工作(雖然是近似的,每次每次只處理一個批次翔忽,但能做縮放和平移)英融。
為了對輸入進(jìn)行零居中(平均值是0)和歸一化,算法需要估計輸入的均值和標(biāo)準(zhǔn)差歇式。 它通過評估當(dāng)前小批量輸入的均值和標(biāo)準(zhǔn)差(因此命名為“批歸一化”)來實現(xiàn)驶悟。 整個操作在公式 11-3 中。
其中材失,
μB是整個小批量B的均值矢量
σB是輸入標(biāo)準(zhǔn)差矢量痕鳍,也是根據(jù)整個小批量估算的。
mB是小批量中的實例數(shù)量龙巨。
(i)是以為零中心和標(biāo)準(zhǔn)化的實例i的輸入矢量艺沼。
γ是層的縮放參數(shù)的矢量(每個輸入一個縮放參數(shù))绝淡。
?表示元素級別的相乘(每個輸入乘以對應(yīng)的縮放參數(shù))
β是層的偏移參數(shù)(偏移量)矢量(每個輸入一個偏移參數(shù))
?是一個很小的數(shù)字埃脏,以避免被零除(通常為
10^-5
)揽惹。 這被稱為平滑項(拉布拉斯平滑,Laplace Smoothing)秸弛。z(i) 是BN操作的輸出:它是輸入的縮放和移位版本铭若。
在訓(xùn)練時洪碳,BN將輸入標(biāo)準(zhǔn)化,然后做了縮放和平移奥喻。測試時又如何呢偶宫?因為需要對實例而不是批次實例做預(yù)測非迹,所以就不能計算每個輸入的平均和標(biāo)準(zhǔn)差环鲤。另外,即使有批量實例憎兽,批量也可能太小冷离,或者實例并不是獨立同分布的,所以在批量上計算是不可靠的纯命。一種解決方法是等到訓(xùn)練結(jié)束西剥,用模型再運行一次訓(xùn)練集,算出每個BN層的平均值和標(biāo)準(zhǔn)差亿汞。然后就可以用這些數(shù)據(jù)做預(yù)測瞭空,而不是批輸入的平均值和標(biāo)準(zhǔn)差。但是疗我,大部分批歸一化實現(xiàn)是通過層輸入的平均值和標(biāo)準(zhǔn)差的移動平均值來計算的咆畏。這也是Keras在BatchNormalization
中使用的方法∥饪悖總的來說旧找,每個批歸一化的層都通過指數(shù)移動平均學(xué)習(xí)了四個參數(shù):γ
(輸出縮放矢量),β
(輸出偏移矢量)麦牺,μ
(最終輸入平均值矢量)和σ
(最終輸入標(biāo)準(zhǔn)差矢量)钮蛛。μ
和σ
都是在訓(xùn)練過程中計算的,但只在訓(xùn)練后使用(用于替換公式11-3中批輸入平均和標(biāo)準(zhǔn)差)剖膳。
Ioffe和Szegedy證明魏颓,批歸一化大大改善了他們試驗的所有深度神經(jīng)網(wǎng)絡(luò),極大提高了ImageNet分類的效果(ImageNet是一個圖片分類數(shù)據(jù)集吱晒,用于評估計算機(jī)視覺系統(tǒng))琼开。梯度消失問題大大減少了,他們可以使用飽和激活函數(shù)枕荞,如 tanh 甚至邏輯激活函數(shù)柜候。網(wǎng)絡(luò)對權(quán)重初始化也不那么敏感。他們能夠使用更大的學(xué)習(xí)率躏精,顯著加快了學(xué)習(xí)過程渣刷。具體地,他們指出矗烛,“應(yīng)用于最先進(jìn)的圖像分類模型辅柴,批標(biāo)準(zhǔn)減少了 14 倍的訓(xùn)練步驟實現(xiàn)了相同的精度箩溃,以顯著的優(yōu)勢擊敗了原始模型。[...] 使用批量標(biāo)準(zhǔn)化的網(wǎng)絡(luò)集合碌嘀,我們改進(jìn)了 ImageNet 分類上的最佳公布結(jié)果:達(dá)到4.9% 的前5個驗證錯誤(和 4.8% 的測試錯誤)涣旨,超出了人類評估者的準(zhǔn)確性。批量標(biāo)準(zhǔn)化也像一個正則化項一樣股冗,減少了對其他正則化技術(shù)的需求(如本章稍后描述的 dropout).
然而霹陡,批量標(biāo)準(zhǔn)化的確會增加模型的復(fù)雜性(盡管它不需要對輸入數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化,因為第一個隱藏層會照顧到這一點止状,只要它是批量標(biāo)準(zhǔn)化的)烹棉。 此外,還存在運行時間的損失:由于每層所需的額外計算怯疤,神經(jīng)網(wǎng)絡(luò)的預(yù)測速度較慢浆洗。 但是,可以在訓(xùn)練之后集峦,處理在BN層的前一層伏社,就可以加快速度。方法是更新前一層的權(quán)重和偏置項塔淤,使其直接輸出合適的縮放值和偏移值摘昌。例如,如果前一層計算的是XW + b
凯沪,BN層計算的是γ?(XW + b – μ)/σ + β
(忽略了分母中的平滑項ε
)第焰。如果定義W′ = γ?W/σ
和b′ = γ?(b – μ)/σ + β
,公式就能簡化為XW′ + b′
妨马。因此如果替換前一層的權(quán)重和偏置項(W
和b
)為W'
和b'
挺举,就可以不用BN層了(TFLite的優(yōu)化器就干了這件事,見第19章)烘跺。
注意:你可能會發(fā)現(xiàn)湘纵,訓(xùn)練相當(dāng)緩慢,這是因為每個周期都因為使用BN而延長了時間滤淳。但是有了BN梧喷,收斂的速度更快,需要的周期數(shù)更少脖咐。綜合來看铺敌,需要的總時長變短了。
使用 Keras 實現(xiàn)批歸一化
和Keras大部分功能一樣屁擅,實現(xiàn)批歸一化既簡單又直觀偿凭。只要每個隱藏層的激活函數(shù)前面或后面添加一個BatchNormalization
層就行,也可以將BN層作為模型的第一層派歌。例如弯囊,這個模型在每個隱藏層的后面使用了BN痰哨,第一層也用了BN(在打平輸入之后):
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.BatchNormalization(),
keras.layers.Dense(300, activation="elu", kernel_initializer="he_normal"),
keras.layers.BatchNormalization(),
keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal"),
keras.layers.BatchNormalization(),
keras.layers.Dense(10, activation="softmax")
])
這樣就成了!在這個只有兩個隱藏層的例子中匾嘱,BN的作用不會那么大斤斧,但對于更深的網(wǎng)絡(luò),作用就特別大霎烙。
打印一下模型的摘要:
>>> model.summary()
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten_3 (Flatten) (None, 784) 0
_________________________________________________________________
batch_normalization_v2 (Batc (None, 784) 3136
_________________________________________________________________
dense_50 (Dense) (None, 300) 235500
_________________________________________________________________
batch_normalization_v2_1 (Ba (None, 300) 1200
_________________________________________________________________
dense_51 (Dense) (None, 100) 30100
_________________________________________________________________
batch_normalization_v2_2 (Ba (None, 100) 400
_________________________________________________________________
dense_52 (Dense) (None, 10) 1010
=================================================================
Total params: 271,346
Trainable params: 268,978
Non-trainable params: 2,368
可以看到每個BN層添加了四個參數(shù):γ
撬讽、 β
、 μ
和 σ
(例如吼过,第一個BN層添加了3136個參數(shù)锐秦,即4 × 784)咪奖。后兩個參數(shù)μ
和 σ
是移動平均盗忱,不受反向傳播影響,Keras稱其“不可訓(xùn)練”(如果將BN的總參數(shù)3,136 + 1,200 + 400除以2羊赵,得到2368趟佃,就是模型中總的不可訓(xùn)練的參數(shù)量)。
看下第一個BN層的參數(shù)昧捷。兩個參數(shù)是可訓(xùn)練的(通過反向傳播)闲昭,兩個不可訓(xùn)練:
>>> [(var.name, var.trainable) for var in model.layers[1].variables]
[('batch_normalization_v2/gamma:0', True),
('batch_normalization_v2/beta:0', True),
('batch_normalization_v2/moving_mean:0', False),
('batch_normalization_v2/moving_variance:0', False)]
當(dāng)在Keras中創(chuàng)建一個BN層時,訓(xùn)練過程中靡挥,還會創(chuàng)建兩個Keras在迭代時的操作序矩。該操作會更新移動平均值。因為后端使用的是TensorFlow跋破,這些操作就是TensorFlow操作(第12章會討論TF操作):
>>> model.layers[1].updates
[<tf.Operation 'cond_2/Identity' type=Identity>,
<tf.Operation 'cond_3/Identity' type=Identity>]
BN的論文作者建議在激活函數(shù)之前使用BN層簸淀,而不是像前面的例子添加到后面。到底是前面還是后面好存在爭議毒返,取決于具體的任務(wù) —— 你最好在數(shù)據(jù)集上試驗一下哪種選擇好租幕。要在激活函數(shù)前添加BN層,必須將激活函數(shù)從隱藏層拿出來拧簸,單獨做成一層劲绪。另外,因為BN層對每個輸入有一個偏移參數(shù)盆赤,可以將前一層的偏置項去掉(設(shè)置use_bias=False
):
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.BatchNormalization(),
keras.layers.Dense(300, kernel_initializer="he_normal", use_bias=False),
keras.layers.BatchNormalization(),
keras.layers.Activation("elu"),
keras.layers.Dense(100, kernel_initializer="he_normal", use_bias=False),
keras.layers.BatchNormalization(),
keras.layers.Activation("elu"),
keras.layers.Dense(10, activation="softmax")
])
BatchNormalization
類可供調(diào)節(jié)的參數(shù)不多贾富。默認(rèn)值通常就可以,但有時需要調(diào)節(jié)momentum
牺六,這個超參數(shù)是BatchNormalization
在更新指數(shù)移動平均時使用的颤枪。給定一個新值v
(即,一個當(dāng)前批次的輸入平均或標(biāo)準(zhǔn)差新矢量)兔乞,BN層使用下面的等式更新平均:
momentum
的最優(yōu)值通常接近于1:比如汇鞭,0.9凉唐、0.99、0.999(大數(shù)據(jù)的9更多霍骄,小數(shù)據(jù)集的9少)台囱。
另一個重要的超參數(shù)是axis
:它確定了在哪個軸上歸一。默認(rèn)是-1读整,即歸一化最后一個軸(使用其它軸的平均值和標(biāo)準(zhǔn)差)簿训。當(dāng)輸入是2D時(即批的形狀是[batch size
,features
]),也就是說每個輸入特征都會根據(jù)批次全部實例的平均值和標(biāo)準(zhǔn)差做歸一米间。例如强品,前面例子的第一個BN層會分別對784個輸入特征的每個特征做歸一化(還有縮放和偏移);因此屈糊,BN層會計算28個平均值和28個標(biāo)準(zhǔn)差(每列1個值的榛,根據(jù)每行的所有實例計算),用同樣的平均值和標(biāo)準(zhǔn)差歸一化給定列的所有像素逻锐。還會有28個縮放值和28個偏移值夫晌。如果仍想對784個像素獨立處理,要設(shè)置axis=[1, 2]
昧诱。
在訓(xùn)練和訓(xùn)練之后晓淀,BN層不會做同樣的計算:BN會使用訓(xùn)練中的批次數(shù)據(jù)和訓(xùn)練后的最終數(shù)據(jù)(即移動平均值的最終值)≌档担看看源碼中是如何實現(xiàn)的:
class BatchNormalization(keras.layers.Layer):
[...]
def call(self, inputs, training=None):
[...]
call()
方法具體實現(xiàn)了方法凶掰,它有一個參數(shù)training
,默認(rèn)是None蜈亩,但fit()
方法在訓(xùn)練中將其設(shè)為1懦窘。如果你需要寫一個自定義層,要求自定義層在訓(xùn)練和測試中的功能不同勺拣,就可以在call()
方法中添加一個參數(shù)training
奶赠,用這個參數(shù)決定該計算什么(第12張會討論自定義層)。
BatchNormalization
已經(jīng)成為了深度神經(jīng)網(wǎng)絡(luò)中最常使用的層药有,以至于計算圖中經(jīng)常省略毅戈,默認(rèn)嘉定在每個層后面加一個BN層。但是Hongyi Zhang的一篇文章(https://arxiv.org/abs/1901.09321)可能改變了這種做法:通過使用一個新的fixed-update
(fixup)權(quán)重初始化方法愤惰,作者沒有使用BN苇经,訓(xùn)練了一個非常深的神經(jīng)網(wǎng)絡(luò)(多達(dá)10000層),在復(fù)雜圖片分類任務(wù)上表現(xiàn)驚艷宦言。但這個結(jié)論很新扇单,最好還是再等一等,現(xiàn)在還是使用批歸一化奠旺。
梯度裁剪
減少梯度爆炸問題的一種常用技術(shù)是在反向傳播過程中剪切梯度蜘澜,使它們不超過某個閾值施流,這種方法稱為梯度裁剪。梯度裁剪在循環(huán)神經(jīng)網(wǎng)絡(luò)中用的很多鄙信,因為循環(huán)神經(jīng)網(wǎng)絡(luò)中用BN很麻煩瞪醋,參見第 15 章。 對于其它類型的網(wǎng)絡(luò)装诡,BN就足夠了银受。在Keras中,梯度裁剪只需在創(chuàng)建優(yōu)化器時設(shè)置clipvalue
或clipnorm
參數(shù)鸦采,如下:
optimizer = keras.optimizers.SGD(clipvalue=1.0)
model.compile(loss="mse", optimizer=optimizer)
優(yōu)化器會將梯度矢量中的每個值裁剪到-1.0和1.0之間宾巍。這意味著損失(對每個可訓(xùn)練參數(shù))的所有偏導(dǎo)數(shù)會被裁剪到-1.0和1.0之間。閾值是一個可以調(diào)節(jié)的超參數(shù)渔伯,可能影響到梯度矢量的方向顶霞。例如,如果原始梯度矢量是[0.9, 100.0]咱旱,它大體指向第二個軸确丢;但在裁剪之后變?yōu)閇0.9, 1.0]绷耍,方向就大體指向?qū)蔷€了吐限。在實際中,梯度裁剪的效果不錯褂始。如果想確保梯度裁剪不改變梯度矢量的方向诸典,就需要設(shè)置clipnorm
靠范數(shù)裁剪,這樣如果梯度的l2范數(shù)超過了閾值崎苗,就能對整個梯度裁剪狐粱。例如,如果設(shè)置clipnorm = 1.0
胆数,矢量[0.9, 100.0]就會被裁剪為[0.00899964, 0.9999595]肌蜻,方向沒變,但第一個量幾乎被抹去了必尼。如果再訓(xùn)練過程中發(fā)現(xiàn)了梯度爆炸(可以用TensorBoard跟蹤梯度)蒋搜,最好的方法是既用值也用范數(shù)裁剪,設(shè)置不同的閾值判莉,看看哪個在驗證集上表現(xiàn)最好豆挽。
復(fù)用預(yù)訓(xùn)練層
從零開始訓(xùn)練一個非常大的 DNN 通常不是一個好主意,相反券盅,您應(yīng)該總是嘗試找到一個現(xiàn)有的神經(jīng)網(wǎng)絡(luò)來完成與您正在嘗試解決的任務(wù)類似的任務(wù)(第14章會介紹如何找)帮哈,然后復(fù)用這個網(wǎng)絡(luò)的較低層:這就是所謂的遷移學(xué)習(xí)。這樣不僅能大大加快訓(xùn)練速度锰镀,還將需要更少的訓(xùn)練數(shù)據(jù)娘侍。
例如咖刃,假設(shè)你有一個經(jīng)過訓(xùn)練的 DNN,能將圖片分為 100 個不同的類別憾筏,包括動物僵缺,植物,車輛和日常物品踩叭。 現(xiàn)在想要訓(xùn)練一個 DNN 來對特定類型的車輛進(jìn)行分類磕潮。 這些任務(wù)非常相似,甚至部分重疊容贝,因此應(yīng)該嘗試重新使用第一個網(wǎng)絡(luò)的一部分(請參見圖 11-4)自脯。
筆記:如果新任務(wù)的輸入圖像與原始任務(wù)中使用的輸入圖像的大小不一致,則必須添加預(yù)處理步驟以將其大小調(diào)整為原始模型的預(yù)期大小斤富。 更一般地說膏潮,如果輸入具有類似的低級層次的特征,則遷移學(xué)習(xí)將很好地工作满力。
原始模型的輸出層通常要替換掉焕参,因為對于新任務(wù)可能一點用也沒有,輸出的數(shù)量可能就不對油额。相似的叠纷,原始模型的上層也不如淺層管用,因為高階特征可能相差很大潦嘶。需要確定好到底用幾層涩嚣。
提示:任務(wù)越相似,可復(fù)用的層越多掂僵。對于非常相似的任務(wù)航厚,可以嘗試保留所有的吟唱層,替換輸出層锰蓬。
先將所有復(fù)用的層凍結(jié)(即幔睬,使其權(quán)重不可訓(xùn)練,梯度下降不能修改權(quán)重)芹扭,然后訓(xùn)練模型麻顶,看其表現(xiàn)如何。然后將復(fù)用的最上一或兩層解凍冯勉,讓反向傳播可以調(diào)節(jié)它們澈蚌,再查看性能有無提升。訓(xùn)練數(shù)據(jù)越多灼狰,可以解凍的層越多宛瞄。解凍時減小學(xué)習(xí)率也有幫助,可以避免破壞微調(diào)而得的權(quán)重。
如果效果不好份汗,或者訓(xùn)練數(shù)據(jù)不多盈电,可以嘗試去除頂層,將其余的層都解凍杯活。不斷嘗試匆帚,直到找到合適的層,如果訓(xùn)練數(shù)據(jù)很多旁钧,可以嘗試替換頂層吸重,或者加入更多的隱藏層。
用Keras進(jìn)行遷移學(xué)習(xí)
看一個例子歪今。假設(shè)Fashion MNIST只有八個類嚎幸,不包括拖鞋和T恤。一些人在這個數(shù)據(jù)集上搭建并訓(xùn)練了一個Keras模型寄猩,且效果不錯(準(zhǔn)確率大于90%)嫉晶,將其稱為模型A。現(xiàn)在想處理另一個問題:有拖鞋和T恤的圖片田篇,要訓(xùn)練一個二分類器(positive=shirt, negative=sandal)替废。數(shù)據(jù)集不大,只有200張打了標(biāo)簽的圖片泊柬。當(dāng)訓(xùn)練架構(gòu)與模型A相同的新模型時(稱其為模型B)椎镣,表現(xiàn)非常好(準(zhǔn)確率97.2%)。但因為這是一個非常簡單的任務(wù)(只有兩類)彬呻,所以準(zhǔn)確率應(yīng)該還可以更高衣陶。因為和任務(wù)A很像,所以可以嘗試一下遷移學(xué)習(xí)。
首先侠鳄,加載模型A漆弄,創(chuàng)建一個新模型,除了輸出層不要钟病,保留所有的層:
model_A = keras.models.load_model("my_model_A.h5")
model_B_on_A = keras.models.Sequential(model_A.layers[:-1])
model_B_on_A.add(keras.layers.Dense(1, activation="sigmoid"))
model_A
和 model_B_on_A
公用了一些層。當(dāng)你訓(xùn)練model_B_on_A
時,也會影響model_A
或悲。如果想避免,需要在復(fù)用前克隆model_A
堪唐。要這么做巡语,可以使用clone.model()
,然后復(fù)制權(quán)重(clone.model()
不能克隆權(quán)重):
model_A_clone = keras.models.clone_model(model_A)
model_A_clone.set_weights(model_A.get_weights())
現(xiàn)在就可以訓(xùn)練model_B_on_A
了淮菠,但是因為新輸出層是隨機(jī)初始化的男公,誤差較大,較大的誤差梯度可能會破壞復(fù)用的權(quán)重合陵。為了避免枢赔,一種方法是在前幾次周期中澄阳,凍結(jié)復(fù)用的層,讓新層有時間學(xué)到合理的權(quán)重踏拜。要實現(xiàn)的話碎赢,將每層的trainable
屬性設(shè)為False
,然后編譯模型:
for layer in model_B_on_A.layers[:-1]:
layer.trainable = False
model_B_on_A.compile(loss="binary_crossentropy", optimizer="sgd",
metrics=["accuracy"])
筆記:凍結(jié)或解凍模型之后速梗,都需要編譯肮塞。
訓(xùn)練幾個周期之后,就可以解凍復(fù)用層(需要再次編譯模型)姻锁,然后接著訓(xùn)練以微調(diào)模型峦嗤。解凍之后,最好降低學(xué)習(xí)率屋摔,目的還是避免破壞復(fù)用層的權(quán)重:
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=4,
validation_data=(X_valid_B, y_valid_B))
for layer in model_B_on_A.layers[:-1]:
layer.trainable = True
optimizer = keras.optimizers.SGD(lr=1e-4) # the default lr is 1e-2
model_B_on_A.compile(loss="binary_crossentropy", optimizer=optimizer,
metrics=["accuracy"])
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=16,
validation_data=(X_valid_B, y_valid_B))
最終結(jié)果烁设,新模型的測試準(zhǔn)確率達(dá)到了99.25%。遷移學(xué)習(xí)將誤差率從2.8%降低到了0.7%钓试,減小了4倍装黑!
>>> model_B_on_A.evaluate(X_test_B, y_test_B)
[0.06887910133600235, 0.9925]
你相信這個結(jié)果嗎?不要相信:因為作者作弊了弓熏!作者嘗試了許多方案恋谭,才找到一組配置提升了效果。如果你嘗試改變類或隨機(jī)種子挽鞠,就能發(fā)現(xiàn)效果下降疚颊。作者這里做的是“拷問數(shù)據(jù),直到數(shù)據(jù)招供”信认。當(dāng)某篇論文的結(jié)果太好了材义,你應(yīng)該懷疑下:也許新方法實際沒什么效果(甚至降低了表現(xiàn)),只是作者嘗試了許多變量嫁赏,只報告了最好的結(jié)果(可能只是運氣)其掂,踩的坑都沒說。大部分時候潦蝇,這不是惡意款熬,但確實是科學(xué)中許多結(jié)果無法復(fù)現(xiàn)的原因。作者為什么要作弊呢攘乒?因為遷移學(xué)習(xí)對小網(wǎng)絡(luò)幫助不大贤牛,小型網(wǎng)絡(luò)只能學(xué)到幾個模式,緊密網(wǎng)絡(luò)學(xué)到的具體模式则酝,可能在其他任務(wù)中用處不大殉簸。遷移學(xué)習(xí)在深度卷積網(wǎng)絡(luò)中表現(xiàn)最好,CNN學(xué)到的特征更通用(特別是淺層)。第14章會用剛討論的喂链,回顧遷移學(xué)習(xí)(下次保證不作弊)返十。
無監(jiān)督預(yù)訓(xùn)練
假設(shè)你想要解決一個復(fù)雜的任務(wù),但沒有多少的打了標(biāo)簽的訓(xùn)練數(shù)據(jù)椭微,也找不到一個類似的任務(wù)訓(xùn)練模型洞坑。 不要失去希望! 首先蝇率,應(yīng)該嘗試收集更多的有標(biāo)簽的訓(xùn)練數(shù)據(jù)迟杂,但是如果做不到,仍然可以進(jìn)行無監(jiān)督的訓(xùn)練(見圖 11-5)本慕。 通常排拷,獲得無標(biāo)簽的訓(xùn)練數(shù)據(jù)成本低,但打標(biāo)簽成本很高锅尘。如果收集了大量無標(biāo)簽數(shù)據(jù)监氢,可以嘗試訓(xùn)練一個無監(jiān)督模型,比如自編碼器或生成式對抗網(wǎng)絡(luò)(見第17章)藤违。然后可以復(fù)用自編碼器或GAN的淺層浪腐,加上輸出層,使用監(jiān)督學(xué)習(xí)微調(diào)網(wǎng)絡(luò)(使用標(biāo)簽數(shù)據(jù))顿乒。
這是 Geoffrey Hinton 和他的團(tuán)隊在 2006 年使用的技術(shù)议街,導(dǎo)致了神經(jīng)網(wǎng)絡(luò)的復(fù)興和深度學(xué)習(xí)的成功。 直到 2010 年璧榄,無監(jiān)督預(yù)訓(xùn)練(通常使用受限玻爾茲曼機(jī) RBM)是深度網(wǎng)絡(luò)的標(biāo)準(zhǔn)特漩,只有在梯度消失問題得到緩解之后,監(jiān)督訓(xùn)練 DNN 才更為普遍骨杂。 然而涂身,當(dāng)你有一個復(fù)雜的任務(wù)需要解決時,沒有類似的模型可以重復(fù)使用腊脱,而且標(biāo)記的訓(xùn)練數(shù)據(jù)很少访得,但是大量的未標(biāo)記的訓(xùn)練數(shù)據(jù)時,無監(jiān)督訓(xùn)練(現(xiàn)在通常使用自動編碼器陕凹、GAN而不是 RBM)仍然是一個很好的選擇。在深度學(xué)習(xí)的早期鳄炉,訓(xùn)練深度模型很困難杜耙,人們使用了一種逐層預(yù)訓(xùn)練的方法(見圖11-5)。先訓(xùn)練一個單層無監(jiān)督模型拂盯,通常是RBM佑女,然后凍結(jié)該層,加另一個層,再訓(xùn)練模型(只訓(xùn)練新層)团驱,然后凍住新層摸吠,再加一層,再次訓(xùn)練模型『炕ǎ現(xiàn)在變得簡單了寸痢,直接跳到圖11-5中的步驟3,訓(xùn)練完整的無監(jiān)督模型紊选,使用的是自編碼器或GAN啼止。
在輔助任務(wù)上預(yù)訓(xùn)練
如果沒有多少標(biāo)簽訓(xùn)練數(shù)據(jù),最后的選擇是在輔助任務(wù)上訓(xùn)練第一個神經(jīng)網(wǎng)絡(luò)兵罢,在輔助任務(wù)上可以輕松獲取或生成標(biāo)簽的訓(xùn)練數(shù)據(jù)献烦,然后重新使用該網(wǎng)絡(luò)的較低層來完成實際任務(wù)。 第一個神經(jīng)網(wǎng)絡(luò)的較低層將學(xué)習(xí)可能被第二個神經(jīng)網(wǎng)絡(luò)重復(fù)使用的特征檢測器卖词。
例如巩那,如果你想建立一個識別面孔的系統(tǒng),你可能只有幾個人的照片 - 顯然不足以訓(xùn)練一個好的分類器此蜈。 收集每個人的數(shù)百張照片將是不實際的即横。 但是,您可以在互聯(lián)網(wǎng)上收集大量隨機(jī)人員的照片舶替,并訓(xùn)練第一個神經(jīng)網(wǎng)絡(luò)來檢測兩張不同的照片是否屬于同一個人令境。 這樣的網(wǎng)絡(luò)將學(xué)習(xí)面部優(yōu)秀的特征檢測器,所以重復(fù)使用它的較低層將允許你使用很少的訓(xùn)練數(shù)據(jù)來訓(xùn)練一個好的面部分類器顾瞪。
對于自然語言處理(NLP)舔庶,可以下載大量文本,然后自動生成標(biāo)簽數(shù)據(jù)陈醒。例如惕橙,可以隨機(jī)遮擋一些詞,然后訓(xùn)練一個模型預(yù)測缺失詞钉跷。如果能在這個任務(wù)上訓(xùn)練一個表現(xiàn)不錯的模型弥鹦,則該模型已經(jīng)在語言層面學(xué)到不少了,就可以復(fù)用它到實際任務(wù)中爷辙,再用標(biāo)簽數(shù)據(jù)微調(diào)(第15章會討論更多預(yù)訓(xùn)練任務(wù))彬坏。
筆記:自監(jiān)督學(xué)習(xí)是當(dāng)你從數(shù)據(jù)自動生成標(biāo)簽,然后在標(biāo)簽數(shù)據(jù)上使用監(jiān)督學(xué)習(xí)訓(xùn)練模型膝晾。因為這種方法無需人工標(biāo)注栓始,最好將其分類為無監(jiān)督學(xué)習(xí)。
更快的優(yōu)化器
訓(xùn)練一個非常大的深度神經(jīng)網(wǎng)絡(luò)可能會非常緩慢血当。 到目前為止幻赚,我們已經(jīng)看到了四種加速訓(xùn)練的方法(并且達(dá)到更好性能的方法):對連接權(quán)重應(yīng)用良好的初始化策略禀忆,使用良好的激活函數(shù),使用批歸一化以及重用預(yù)訓(xùn)練網(wǎng)絡(luò)的部分(使用輔助任務(wù)或無監(jiān)督學(xué)習(xí))落恼。 另一個速度提升的方法是使用更快的優(yōu)化器箩退,而不是常規(guī)的梯度下降優(yōu)化器。 在本節(jié)中佳谦,我們將介紹最流行的算法:動量優(yōu)化戴涝,Nesterov 加速梯度,AdaGrad吠昭,RMSProp喊括,最后是 Adam 和Nadam優(yōu)化。
劇透:本節(jié)的結(jié)論是矢棚,幾乎總是應(yīng)該使用
Adam_optimization
郑什,所以如果不關(guān)心它是如何工作的,只需使用AdamOptimizer
替換GradientDescentOptimizer
蒲肋,然后跳到下一節(jié)蘑拯! 只需要這么小的改動,訓(xùn)練通常會快幾倍兜粘。 但是申窘,Adam 優(yōu)化確實有三個可以調(diào)整的超參數(shù)(加上學(xué)習(xí)率)。 默認(rèn)值通常工作的不錯孔轴,但如果您需要調(diào)整它們剃法,知道他們怎么實現(xiàn)的可能會有幫助。 Adam 優(yōu)化結(jié)合了來自其他優(yōu)化算法的幾個想法路鹰,所以先看看這些算法是有用的贷洲。
動量優(yōu)化
想象一下,一個保齡球在一個光滑的表面上平緩的斜坡上滾動:它會緩慢地開始晋柱,但是它會很快地達(dá)到最終的速度(如果有一些摩擦或空氣阻力的話)优构。 這是 Boris Polyak 在 1964 年提出的動量優(yōu)化背后的一個非常簡單的想法。相比之下雁竞,普通的梯度下降只需要沿著斜坡進(jìn)行小的有規(guī)律的下降步驟钦椭,所以需要更多的時間才能到達(dá)底部。
回想一下碑诉,梯度下降只是通過直接減去損失函數(shù)J(θ)
相對于權(quán)重θ
的梯度(?θJ(θ)
)彪腔,乘以學(xué)習(xí)率η
來更新權(quán)重θ
。 等式是:θ ← θ – η?θJ(θ)进栽。它不關(guān)心早期的梯度是什么漫仆。 如果局部梯度很小,則會非常緩慢泪幌。
動量優(yōu)化很關(guān)心以前的梯度:在每次迭代時盲厌,它將動量矢量m
(乘以學(xué)習(xí)率η
)與局部梯度相加,并且通過簡單地減去該動量矢量來更新權(quán)重(參見公式 11-4)祸泪。 換句話說吗浩,梯度用作加速度,不用作速度没隘。 為了模擬某種摩擦機(jī)制懂扼,避免動量過大,該算法引入了一個新的超參數(shù)β
右蒲,簡稱為動量阀湿,它必須設(shè)置在 0(高摩擦)和 1(無摩擦)之間。 典型的動量值是 0.9瑰妄。
可以很容易驗證陷嘴,如果梯度保持不變,則最終速度(即间坐,權(quán)重更新的最大大性职ぁ)等于該梯度乘以學(xué)習(xí)率η
乘以1/(1-β)
。 例如竹宋,如果β = 0.9
劳澄,則最終速度等于學(xué)習(xí)率的梯度乘以 10 倍,因此動量優(yōu)化比梯度下降快 10 倍蜈七! 這使動量優(yōu)化比梯度下降快得多秒拔。 特別是,我們在第四章中看到飒硅,當(dāng)輸入量具有非常不同的尺度時砂缩,損失函數(shù)看起來像一個細(xì)長的碗(見圖 4-7)。 梯度下降速度很快狡相,但要花很長的時間才能到達(dá)底部梯轻。 相反,動量優(yōu)化會越來越快地滾下山谷底部尽棕,直到達(dá)到底部(最佳)喳挑。在不使用批歸一化的深度神經(jīng)網(wǎng)絡(luò)中,較高層往往會得到具有不同的尺度的輸入滔悉,所以使用動量優(yōu)化會有很大的幫助伊诵。 它也可以幫助滾過局部最優(yōu)值。
筆記:由于動量的原因回官,優(yōu)化器可能會超調(diào)一些曹宴,然后再回來,再次超調(diào)歉提,并在穩(wěn)定在最小值之前多次振蕩笛坦。 這就是為什么在系統(tǒng)中有一點摩擦的原因之一:它消除了這些振蕩区转,從而加速了收斂。
在 Keras 中實現(xiàn)動量優(yōu)化很簡單:只需使用SGD
優(yōu)化器版扩,設(shè)置momentum
超參數(shù)废离,然后就可以躺下賺錢了!
optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9)
動量優(yōu)化的一個缺點是它增加了另一個超參數(shù)來調(diào)整礁芦。 然而蜻韭,0.9 的動量值通常在實踐中運行良好,幾乎總是比梯度下降快柿扣。
Nesterov 加速梯度
Yurii Nesterov 在 1983 年提出的動量優(yōu)化的一個小變體幾乎總是比普通的動量優(yōu)化更快肖方。 Nesterov 動量優(yōu)化或 Nesterov 加速梯度(Nesterov Accelerated Gradient,NAG)的思想是測量損失函數(shù)的梯度不是在局部位置未状,而是在動量方向稍微靠前(見公式 11-5)俯画。 與普通的動量優(yōu)化的唯一區(qū)別在于梯度是在θ+βm
而不是在θ
處測量的。
這個小小的調(diào)整是可行的娩践,因為一般來說活翩,動量矢量將指向正確的方向(即朝向最優(yōu)方向),所以使用在該方向上測得的梯度稍微更精確翻伺,而不是使用 原始位置的梯度材泄,如圖11-6所示(其中?1
代表在起點θ
處測量的損失函數(shù)的梯度,?2
代表位于θ+βm
的點處的梯度)吨岭。
可以看到拉宗,Nesterov 更新稍微靠近最佳值。 過了一段時間辣辫,這些小的改進(jìn)加起來旦事,NAG 最終比常規(guī)的動量優(yōu)化快得多。 此外急灭,當(dāng)動量推動權(quán)重橫跨山谷時姐浮,▽1繼續(xù)推進(jìn)越過山谷,而▽2推回山谷的底部葬馋。 這有助于減少振蕩卖鲤,從而更快地收斂。
與常規(guī)的動量優(yōu)化相比畴嘶,NAG 幾乎總能加速訓(xùn)練蛋逾。 要使用它,只需在創(chuàng)建SGD
時設(shè)置`nesterov=True:
optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9, nesterov=True)
AdaGrad
再次考慮細(xì)長碗的問題:梯度下降從最陡峭的斜坡快速下降窗悯,然后緩慢地下到谷底区匣。 如果算法能夠早期檢測到這個問題并且糾正它的方向來指向全局最優(yōu)點,那將是非常好的蒋院。AdaGrad 算法通過沿著最陡的維度縮小梯度向量來實現(xiàn)這一點(見公式 11-6):
第一步將梯度的平方累加到矢量s
中(?符號表示元素級別相乘)亏钩。 這個向量化形式相當(dāng)于向量s
的每個元素si
計算si ← si + (? / ? θi J(θ))2莲绰。換一種說法,每個 si 累加損失函數(shù)對參數(shù)θi的偏導(dǎo)數(shù)的平方铸屉。 如果損失函數(shù)沿著第i
維陡峭钉蒲,則在每次迭代時, si 將變得越來越大彻坛。
第二步幾乎與梯度下降相同,但有一個很大的不同:梯度矢量按比例(s+ε)^0.5縮小 (?符號表示元素分割踏枣,ε
是避免被零除的平滑項昌屉,通常設(shè)置為10-10。 這個矢量化的形式相當(dāng)于所有θi同時計算
簡而言之茵瀑,這種算法會降低學(xué)習(xí)速度间驮,但對于陡峭的維度,其速度要快于具有溫和的斜率的維度马昨。 這被稱為自適應(yīng)學(xué)習(xí)率竞帽。 它有助于將更新的結(jié)果更直接地指向全局最優(yōu)(見圖 11-7)。 另一個好處是它不需要那么多的去調(diào)整學(xué)習(xí)率超參數(shù)η
鸿捧。
對于簡單的二次問題屹篓,AdaGrad 經(jīng)常表現(xiàn)良好,但不幸的是匙奴,在訓(xùn)練神經(jīng)網(wǎng)絡(luò)時堆巧,它經(jīng)常停止得太早。 學(xué)習(xí)率被縮減得太多泼菌,以至于在達(dá)到全局最優(yōu)之前谍肤,算法完全停止。 所以哗伯,即使 Keras 有一個Adagrad
優(yōu)化器荒揣,你也不應(yīng)該用它來訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(雖然對線性回歸這樣簡單的任務(wù)可能是有效的)。但是焊刹,理解AdaGrad對掌握其它自適應(yīng)學(xué)習(xí)率還是很有幫助的系任。
RMSProp
前面看到,AdaGrad 的風(fēng)險是降速太快伴澄,可能無法收斂到全局最優(yōu)赋除。RMSProp 算法通過僅累積最近迭代(而不是從訓(xùn)練開始以來的所有梯度)的梯度來修正這個問題。 它通過在第一步中使用指數(shù)衰減來實現(xiàn)(見公式 11-7)非凌。
它的衰變率β
通常設(shè)定為 0.9举农。 是的,它又是一個新的超參數(shù)敞嗡,但是這個默認(rèn)值通常運行良好颁糟,所以你可能根本不需要調(diào)整它航背。
正如所料,Keras 擁有一個RMSProp
優(yōu)化器:
optimizer = keras.optimizers.RMSprop(lr=0.001, rho=0.9)
除了非常簡單的問題棱貌,這個優(yōu)化器幾乎總是比 AdaGrad 執(zhí)行得更好玖媚。 它通常也比動量優(yōu)化和 Nesterov 加速梯度表現(xiàn)更好。 事實上婚脱,這是許多研究人員首選的優(yōu)化算法今魔,直到 Adam 優(yōu)化出現(xiàn)。
Adam 和 Nadam 優(yōu)化
Adam障贸,代表自適應(yīng)矩估計错森,結(jié)合了動量優(yōu)化和 RMSProp 的思想:就像動量優(yōu)化一樣,它追蹤過去梯度的指數(shù)衰減平均值篮洁,就像 RMSProp 一樣涩维,它跟蹤過去平方梯度的指數(shù)衰減平均值 (見方程式 11-8)。
T 代表迭代次數(shù)(從 1 開始)袁波。
如果你只看步驟 1, 2 和 5瓦阐,你會注意到 Adam 與動量優(yōu)化和 RMSProp 的相似性。 唯一的區(qū)別是第 1 步計算指數(shù)衰減的平均值篷牌,而不是指數(shù)衰減的和睡蟋,但除了一個常數(shù)因子(衰減平均值只是衰減和的1 - β1
倍)之外,它們實際上是等效的娃磺。 步驟 3 和步驟 4 是一個技術(shù)細(xì)節(jié):由于m
和s
初始化為 0薄湿,所以在訓(xùn)練開始時它們會偏向0,所以這兩步將在訓(xùn)練開始時幫助提高m
和s
偷卧。
動量衰減超參數(shù)β1
通常初始化為 0.9豺瘤,而縮放衰減超參數(shù)β2
通常初始化為 0.999。 如前所述听诸,平滑項ε
通常被初始化為一個很小的數(shù)坐求,例如10-7褐奥。這些是 TensorFlow 的Adam
類的默認(rèn)值(更具體地碴开,ε默認(rèn)為None,Keras將使用keras.backend.epsilon()
埂伦,默認(rèn)為10-7仔蝌,可以通過keras.backend.set_epsilon()
更改)泛领,所以你可以簡單地使用:
optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)
實際上,由于 Adam 是一種自適應(yīng)學(xué)習(xí)率算法(如 AdaGrad 和 RMSProp)敛惊,所以對學(xué)習(xí)率超參數(shù)η
的調(diào)整較少渊鞋。 您經(jīng)常可以使用默認(rèn)值η= 0.001
,使 Adam 相對于梯度下降更容易使用锡宋。
提示:如果讀者對這些不同的技術(shù)感到頭暈?zāi)X脹儡湾,不用擔(dān)心,本章末尾會提供一些指導(dǎo)执俩。
最后徐钠,Adam還有兩種變體值得一看:
AdaMax
公式11-8的第2步中,Adam積累了s
的梯度平方(越近役首,權(quán)重越高)尝丐。第5步中,如果忽略了ε宋税、第3步和第4步(只是技術(shù)細(xì)節(jié)而已)摊崭,Adam是通過s
的平方根更新參數(shù)〗苋總之,Adam通過時間損耗梯度的l2范數(shù)更新參數(shù)(l2范數(shù)是平方和的平方根)矮台。AdaMax(也是在Adam的同一篇論文中介紹的)用?∞范數(shù)(max的另一種說法)代替了?2范數(shù)乏屯。更具體的,是在第2步中做了替換瘦赫,舍棄了第4步辰晕,第5步中用s
(即時間損耗的最大值)更新梯度。在實踐中确虱,這樣可以使AdaMax比Adam更穩(wěn)定含友,但也要取決于數(shù)據(jù)集,總體上校辩,Adam表現(xiàn)更好窘问。因此,AdaMax只是Adam碰到問題時的另一種選擇宜咒。
Nadam
Nadam優(yōu)化是Adam優(yōu)化加上了Nesterov技巧惠赫,所以通常比Adam收斂的快一點。在論文(http://cs229.stanford.edu/proj2015/054_report.pdf)中故黑,作者Timothy Dozat在不同任務(wù)上試驗了不同的優(yōu)化器儿咱,發(fā)現(xiàn)Nadam通常比Adam效果好,但有時不如RMSProp场晶。
警告:自適應(yīng)優(yōu)化方法(包括RMSProp混埠,Adam,Nadam)總體不錯诗轻,收斂更快钳宪。但是Ashia C. Wilson在2017年的一篇論文(https://arxiv.org/abs/1705.08292)中說,這些自適應(yīng)優(yōu)化方法在有些數(shù)據(jù)集上泛化很差。所以當(dāng)你對模型失望時使套,可以嘗試下普通的Nesterov加速梯度:你的數(shù)據(jù)集可能只是對自適應(yīng)梯度敏感罐呼。另外要調(diào)研最新的研究進(jìn)展,因為這個領(lǐng)域進(jìn)展很快侦高。
目前所有討論的優(yōu)化方法都是基于一階偏導(dǎo)(雅可比矩陣)的嫉柴。文獻(xiàn)中還介紹了基于二階導(dǎo)數(shù)(黑森矩陣,黑森矩陣是雅可比矩陣的騙到)的算法奉呛。但是计螺,后者很難應(yīng)用于深度神經(jīng)網(wǎng)絡(luò),因為每個輸出有n2個黑森矩陣(n是參數(shù)個數(shù))瞧壮,每個輸出只有n個雅可比矩陣登馒。因為DNN通常有數(shù)萬個參數(shù),二階優(yōu)化器通常超出了內(nèi)存咆槽,就算內(nèi)存能裝下陈轿,計算黑森矩陣也非常慢。
訓(xùn)練稀疏模型
所有剛剛提出的優(yōu)化算法都會產(chǎn)生緊密模型秦忿,這意味著大多數(shù)參數(shù)都是非零的麦射。 如果你在運行時需要一個非常快的模型灯谣,或者如果你需要它占用較少的內(nèi)存潜秋,你可能更喜歡用一個稀疏模型來代替。
實現(xiàn)這一點的一個微不足道的方法是像平常一樣訓(xùn)練模型胎许,然后丟掉微小的權(quán)重(將它們設(shè)置為 0)峻呛。但這通常不會生成一個稀疏的模型,而且可能使模型性能下降辜窑。
更好的選擇是在訓(xùn)練過程中應(yīng)用強 ?1 正則化钩述,因為它會推動優(yōu)化器盡可能多地消除權(quán)重(如第 4 章關(guān)于 Lasso 回歸的討論)。
如果這些技術(shù)可能仍然不成谬擦,就查看TensorFlow Model Optimization Toolkit (TF-MOT)切距,它提供了一些剪枝API,可以在訓(xùn)練中根據(jù)量級迭代去除權(quán)重惨远。
表11-2比較了討論過的優(yōu)化器(是差谜悟,是平均,**是好)北秽。
學(xué)習(xí)率調(diào)整
找到一個好的學(xué)習(xí)速率非常重要葡幸。 如果設(shè)置太高,訓(xùn)練時可能離散贺氓。如果設(shè)置得太低蔚叨,訓(xùn)練最終會收斂到最佳狀態(tài),但會花費很長時間。 如果將其設(shè)置得稍高蔑水,開始的進(jìn)度會非承暇猓快,但最終會在最優(yōu)解周圍跳動搀别,永遠(yuǎn)不會停下來丹擎。如果計算資源有限,可能需要打斷訓(xùn)練歇父,在最優(yōu)收斂之前拿到一個次優(yōu)解(見圖11-8)蒂培。
正如第10章討論過的,可以通過幾百次迭代找到一個好的學(xué)習(xí)率榜苫,學(xué)習(xí)率一開始設(shè)的很小护戳,然后指數(shù)級提高,查看學(xué)習(xí)曲線垂睬,找到那條要要開始抬高的曲線媳荒,要找的學(xué)習(xí)率比這條曲線稍低。
但除了固定學(xué)習(xí)率驹饺,還有更好的方法:如果你從一個高的學(xué)習(xí)率開始肺樟,然后一旦它停止快速的進(jìn)步就減少它,你可以比最佳的恒定學(xué)習(xí)率更快地達(dá)到一個好的解決方案逻淌。 有許多不同的策略,以減少訓(xùn)練期間的學(xué)習(xí)率疟暖。 這些策略被稱為學(xué)習(xí)率調(diào)整(我們在第 4 章中簡要介紹了這個概念)卡儒,其中最常見的是:
冪調(diào)度:
設(shè)學(xué)習(xí)率為迭代次數(shù)t的函數(shù): η(t) = η0 (1 + t/s)c。初始學(xué)習(xí)率η0俐巴, 冪c
(通常被設(shè)置為 1)骨望,步數(shù)s
是超參數(shù)。學(xué)習(xí)率在每步都會下降欣舵,s步后擎鸠,下降到η0 / 2。再經(jīng)過s步缘圈,下降到η0 / 3劣光,然后是η0 / 4糟把、η0 / 5遣疯,以此類推雄可。可以看到聪舒,策略是一開始很快,然后越來越慢虐急。冪調(diào)度需要調(diào)節(jié)η0和s(也可能有c)箱残。
指數(shù)調(diào)度:
將學(xué)習(xí)率設(shè)置為迭代次數(shù)t
的函數(shù):η(t) = η0 0.1t/s疚宇。 學(xué)習(xí)率每步都會下降10倍赏殃。冪調(diào)度的下降是越來越慢,指數(shù)調(diào)度保持10倍不變榜揖。
預(yù)定的分段恒定學(xué)習(xí)率:
先在幾個周期內(nèi)使用固定的學(xué)習(xí)率(比如5個周期內(nèi)學(xué)習(xí)率設(shè)置為 η0 = 0.1)抗蠢,然后在另一個周期內(nèi)設(shè)更小的學(xué)習(xí)率(比如50個周期η1 = 0.001)迅矛,以此類推秽褒。雖然這個解決方案可以很好地工作庐椒,但是通常需要弄清楚正確的學(xué)習(xí)速度順序以及使用時長约谈。
性能調(diào)度:
每 N 步測量驗證誤差(就像提前停止一樣),當(dāng)誤差下降時犁钟,將學(xué)習(xí)率降低λ
倍军俊。
1循環(huán)調(diào)度:
與其它方法相反粪躬,1循環(huán)調(diào)度(Leslie Smith在2018年提出)一開始在前半個周期將學(xué)習(xí)率η0 線性增加到η1提前,然后在后半個周期內(nèi)再線性下降到η0狈网,最后幾個周期學(xué)習(xí)率下降幾個數(shù)量級(仍然是線性的)。用前面的方法找到最優(yōu)學(xué)習(xí)率的方法確定η1士鸥,η0是η1的十分之一。當(dāng)使用動量時脚仔,先用一個高動量(比如0.95)鲤脏,然后在訓(xùn)練上半段下降(比如線性下降到0.85),然后在訓(xùn)練后半部分上升到最高值(0.95),最后幾個周期也用最高值完成茅坛。Smith做了許多試驗,證明這個方法可以顯著加速并能提高性能斥铺。例如邻眷,在CIFAR10圖片數(shù)據(jù)集上肆饶,這個方法在100個周期就達(dá)到了91.9%的驗證準(zhǔn)確率,而標(biāo)準(zhǔn)方法經(jīng)過800個周期才打到90.3%(模型架構(gòu)不變)板惑。
Andrew Senior 等人在2013年的論文比較了使用動量優(yōu)化訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)進(jìn)行語音識別時一些最流行的學(xué)習(xí)率調(diào)整的性能。 作者得出結(jié)論:在這種情況下往湿,性能調(diào)度和指數(shù)調(diào)度都表現(xiàn)良好,但他們更喜歡指數(shù)調(diào)度绒窑,因為它實現(xiàn)起來比較簡單,容易調(diào)整订雾,收斂速度略快于最佳解決方案。作者還之處噩峦,1周期表現(xiàn)更好。
使用 Keras 實現(xiàn)學(xué)習(xí)率冪調(diào)整非常簡單凭涂,只要在優(yōu)化器中設(shè)定decay
超參數(shù):
optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-4)
decay
是s
(更新學(xué)習(xí)率的步驟數(shù))较幌,Keras假定c
等于1乍炉。
指數(shù)調(diào)度和分段恒定學(xué)習(xí)率也很簡單。首先定義一個函數(shù)接受當(dāng)前周期槐瑞,然后返回學(xué)習(xí)率。例如悼沿,如下實現(xiàn)指數(shù)調(diào)度:
def exponential_decay_fn(epoch):
return 0.01 * 0.1**(epoch / 20)
如果不想硬實現(xiàn)η0和s,可以實現(xiàn)一個函數(shù)返回配置函數(shù):
def exponential_decay(lr0, s):
def exponential_decay_fn(epoch):
return lr0 * 0.1**(epoch / s)
return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)
然后,創(chuàng)建一個LearningRateScheduler
調(diào)回非驮,給它一個調(diào)度函數(shù),然后將調(diào)回傳遞給fit()
:
lr_scheduler = keras.callbacks.LearningRateScheduler(exponential_decay_fn)
history = model.fit(X_train_scaled, y_train, [...], callbacks=[lr_scheduler])
LearningRateScheduler
會在每個周期開始時更新優(yōu)化器的learning_rate
屬性。每個周期更新一次學(xué)習(xí)率就夠了,但如果想更新更頻繁敷矫,例如每步都更新榨汤,可以通過寫調(diào)回實現(xiàn)(看前面指數(shù)調(diào)回的例子)收壕。如果每個周期有許多步,每步都更新學(xué)習(xí)率是非常合理的“囊ぃ或者,可以使用keras.optimizers.schedules
方法栗精。
調(diào)度函數(shù)可以將當(dāng)前學(xué)習(xí)率作為第二個參數(shù)。例如薪夕,下面的調(diào)度函數(shù)將之前的學(xué)習(xí)率乘以0.11/20,同樣實現(xiàn)了指數(shù)下降:
def exponential_decay_fn(epoch, lr):
return lr * 0.1**(1 / 20)
該實現(xiàn)依靠優(yōu)化器的初始學(xué)習(xí)率(與前面的實現(xiàn)相反)姑隅,所以一定要設(shè)置對。
當(dāng)保存模型時冕房,優(yōu)化器和學(xué)習(xí)率也能保存耙册。這意味著,只要有這個新的調(diào)度函數(shù),就能加載模型接著訓(xùn)練畸悬。如果調(diào)度函數(shù)使用了周期,會稍微麻煩點:周期不會保存冷冗,每次調(diào)用fit()
方法時,周期都會重置為0。如果加載模型接著訓(xùn)練泰偿,可能會導(dǎo)致學(xué)習(xí)率很大,會破壞模型的權(quán)重调塌。一種應(yīng)對方法是手動設(shè)置fit()
方法的參數(shù)initial_epoch
,是周期從正確的值開始。
對于分段恒定學(xué)習(xí)率調(diào)度檀葛,可以使用如下的調(diào)度函數(shù),然后創(chuàng)建一個LearningRateScheduler
調(diào)回,傳遞給fit()
方法:
def piecewise_constant_fn(epoch):
if epoch < 5:
return 0.01
elif epoch < 15:
return 0.005
else:
return 0.001
對于性能調(diào)度楚殿,使用ReduceLROnPlateau
調(diào)回。例如,如果將下面的調(diào)回去傳遞給fit()
匣缘,只要驗證損失在連續(xù)5個周期內(nèi)沒有改進(jìn),就會將學(xué)習(xí)率乘以0.5:
lr_scheduler = keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5)
最后,tf.keras還提供了一種實現(xiàn)學(xué)習(xí)率調(diào)度的方法:使用keras.optimizers.schedules
中一種可用的調(diào)度定義學(xué)習(xí)率竖配。這樣可以在每步更新學(xué)習(xí)率。例如,還可以如下實現(xiàn)前面的函數(shù)exponential_decay_fn()
:
s = 20 * len(X_train) // 32 # number of steps in 20 epochs (batch size = 32)
learning_rate = keras.optimizers.schedules.ExponentialDecay(0.01, s, 0.1)
optimizer = keras.optimizers.SGD(learning_rate)
這樣又好看又簡單盯漂,另外當(dāng)保存模型時帖渠,學(xué)習(xí)率和調(diào)度(包括狀態(tài))也能保存。但是這個方法不屬于Keras API,是tf.keras專有的哼审。
對于1循環(huán)調(diào)度,實現(xiàn)也不困難:只需創(chuàng)建一個在每個迭代修改學(xué)習(xí)率的自定義調(diào)回(通過更改self.model.optimizer.lr
更新學(xué)習(xí)率)。代碼見Jupyter Notebook的例子。
總結(jié)一下离福,指數(shù)調(diào)度、性能調(diào)度和1循環(huán)調(diào)度可以極大加快收斂,不妨一試次舌!
通過正則化避免過擬合
有四個參數(shù)挪圾,我可以擬合一個大象吩案,五個我可以讓他擺動他的象鼻∑槠瘢—— John von Neumann,cited by Enrico Fermi in Nature 427
有數(shù)千個參數(shù),甚至可以擬合整個動物園。深度神經(jīng)網(wǎng)絡(luò)通常具有數(shù)以萬計的參數(shù),有時甚至是數(shù)百萬条辟。 有了這么多的參數(shù)肩袍,網(wǎng)絡(luò)擁有難以置信的自由度氛赐,可以適應(yīng)各種復(fù)雜的數(shù)據(jù)集。 但是這個很大的靈活性也意味著它很容易過擬合訓(xùn)練集粗井。所以需要正則懒构。第10章用過了最好的正則方法之一:早停醉冤。另外秩霍,雖然批歸一化是用來解決梯度不穩(wěn)定的,但也可以作為正則器蚁阳。這一節(jié)會介紹其它一些最流行的神經(jīng)網(wǎng)絡(luò)正則化技術(shù):?1 和 ?2正則铃绒、dropout和最大范數(shù)正則螺捐。
?1 和 ?2正則
就像第 4 章中對簡單線性模型所做的那樣颠悬,可以使用 ?2正則約束一個神經(jīng)網(wǎng)絡(luò)的連接權(quán)重,或?1正則得到稀疏模型(許多權(quán)重為0)定血。下面是對Keras的連接權(quán)重設(shè)置?2正則赔癌,正則因子是0.01:
layer = keras.layers.Dense(100, activation="elu",
kernel_initializer="he_normal",
kernel_regularizer=keras.regularizers.l2(0.01))
l2
函數(shù)返回的正則器會在訓(xùn)練中的每步被調(diào)用,以計算正則損失澜沟。正則損失隨后被添加到最終損失灾票。如果要使用?1 正則,可以使用keras.regularizers.l1()
茫虽;如果想使用?1 和 ?2正則铝条,可以使用keras.regularizers.l1_l2()
(要設(shè)置兩個正則因子)。
因為想對模型中的所有層使用相同的正則器席噩,還要使用相同的激活函數(shù)和相同的初始化策略班缰。參數(shù)重復(fù)使代碼很難看。為了好看悼枢,可以用循環(huán)重構(gòu)代碼埠忘。另一種方法是使用Python的函數(shù)functools.partial()
,它可以為任意可調(diào)回對象創(chuàng)建封裝類馒索,并有默認(rèn)參數(shù)值:
from functools import partial
RegularizedDense = partial(keras.layers.Dense,
activation="elu",
kernel_initializer="he_normal",
kernel_regularizer=keras.regularizers.l2(0.01))
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
RegularizedDense(300),
RegularizedDense(100),
RegularizedDense(10, activation="softmax",
kernel_initializer="glorot_uniform")
])
Dropout
dropout是深度神經(jīng)網(wǎng)絡(luò)最流行的正則化方法之一莹妒。 它由 Geoffrey Hinton 于 2012 年提出,并在 Nitish Srivastava 等人的2014年論文中進(jìn)一步詳細(xì)描述绰上,并且已被證明是非常成功的:即使是最先進(jìn)的神經(jīng)網(wǎng)絡(luò)旨怠,僅僅通過增加dropout就可以提高1-2%的準(zhǔn)確度。 這聽起來可能不是很多蜈块,但是當(dāng)一個模型已經(jīng)具有 95% 的準(zhǔn)確率時鉴腻,獲得 2% 的準(zhǔn)確度提升意味著將誤差率降低近 40%(從 5% 誤差降至大約 3%)迷扇。
這是一個相當(dāng)簡單的算法:在每個訓(xùn)練步驟中,每個神經(jīng)元(包括輸入神經(jīng)元爽哎,但不包括輸出神經(jīng)元)都有一個暫時“丟棄”的概率p
蜓席,這意味著在這個訓(xùn)練步驟中它將被完全忽略, 在下一步可能會激活(見圖 11-9)课锌。 超參數(shù)p
稱為丟失率厨内,通常設(shè)為 10%到50%之間;循環(huán)神經(jīng)網(wǎng)絡(luò)之間接近20-30%渺贤,在卷積網(wǎng)絡(luò)中接近40-50%雏胃。 訓(xùn)練后,神經(jīng)元不會再丟失志鞍。 這就是全部(除了我們將要討論的技術(shù)細(xì)節(jié))丑掺。
這個具有破壞性的方法竟然行得通,這是相當(dāng)令人驚訝的述雾。如果一個公司的員工每天早上被告知要擲硬幣來決定是否上班,公司的表現(xiàn)會不會更好呢兼丰?那么玻孟,誰知道;也許會鳍征!公司顯然將被迫適應(yīng)這樣的組織構(gòu)架黍翎;它不能依靠任何一個人操作咖啡機(jī)或執(zhí)行任何其他關(guān)鍵任務(wù),所以這個專業(yè)知識將不得不分散在幾個人身上艳丛。員工必須學(xué)會與其他的許多同事合作匣掸,而不僅僅是其中的一小部分。該公司將變得更有彈性氮双。如果一個人離開了碰酝,并沒有什么區(qū)別。目前還不清楚這個想法是否真的可以在公司實行戴差,但它確實對于神經(jīng)網(wǎng)絡(luò)是可行的送爸。神經(jīng)元被dropout訓(xùn)練不能與其相鄰的神經(jīng)元共適應(yīng);他們必須盡可能讓自己變得有用暖释。他們也不能過分依賴一些輸入神經(jīng)元;他們必須注意他們的每個輸入神經(jīng)元袭厂。他們最終對輸入的微小變化會不太敏感。最后球匕,你會得到一個更穩(wěn)定的網(wǎng)絡(luò)纹磺,泛化能力更強。
了解 dropout 的另一種方法是認(rèn)識到每個訓(xùn)練步驟都會產(chǎn)生一個獨特的神經(jīng)網(wǎng)絡(luò)亮曹。 由于每個神經(jīng)元可以存在或不存在橄杨,總共有2 ^ N
個可能的網(wǎng)絡(luò)(其中 N 是可丟棄神經(jīng)元的總數(shù))秘症。 這是一個巨大的數(shù)字,實際上不可能對同一個神經(jīng)網(wǎng)絡(luò)進(jìn)行兩次采樣讥珍。 一旦你運行了 10,000 個訓(xùn)練步驟历极,你基本上已經(jīng)訓(xùn)練了 10,000 個不同的神經(jīng)網(wǎng)絡(luò)(每個神經(jīng)網(wǎng)絡(luò)只有一個訓(xùn)練實例)。 這些神經(jīng)網(wǎng)絡(luò)顯然不是獨立的衷佃,因為它們共享許多權(quán)重趟卸,但是它們都是不同的。 由此產(chǎn)生的神經(jīng)網(wǎng)絡(luò)可以看作是所有這些較小的神經(jīng)網(wǎng)絡(luò)的平均集成氏义。
提示:在實際中锄列,可以只將dropout應(yīng)用到最上面的一到三層(不包括輸出層)。
有一個小而重要的技術(shù)細(xì)節(jié)惯悠。 假設(shè)p = 50%
邻邮,在這種情況下,在測試期間克婶,在訓(xùn)練期間神經(jīng)元將被連接到兩倍于(平均)的輸入神經(jīng)元筒严。 為了彌補這個事實,我們需要在訓(xùn)練之后將每個神經(jīng)元的輸入連接權(quán)重乘以 0.5情萤。 如果我們不這樣做鸭蛙,每個神經(jīng)元的總輸入信號大概是網(wǎng)絡(luò)訓(xùn)練的兩倍,這不太可能表現(xiàn)良好筋岛。 更一般地說娶视,我們需要將每個輸入連接權(quán)重乘以訓(xùn)練后的保持概率(1-p
)。 或者睁宰,我們可以在訓(xùn)練過程中將每個神經(jīng)元的輸出除以保持概率(這些替代方案并不完全等價肪获,但它們工作得同樣好)。
要使用 Kera 實現(xiàn)dropout柒傻,可以使用keras.layers.Dropout
層孝赫。在訓(xùn)練過程中,它隨機(jī)丟棄一些輸入(將它們設(shè)置為 0)红符,并用保留概率來劃分剩余輸入寒锚。 訓(xùn)練結(jié)束后,這個函數(shù)什么都不做违孝,只是將輸入傳給下一層刹前。下面的代碼將dropout正則化應(yīng)用于每個緊密層之前,丟失率為0.2:
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(300, activation="elu", kernel_initializer="he_normal"),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal"),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(10, activation="softmax")
])
警告:因為dropout只在訓(xùn)練時有用雌桑,比較訓(xùn)練損失和驗證損失會產(chǎn)生誤導(dǎo)喇喉。特別地,一個模型可能過擬合訓(xùn)練集校坑,但訓(xùn)練和驗證損失相近拣技。因此一定要不要帶dropout評估訓(xùn)練損失(比如訓(xùn)練后)千诬。
如果觀察到模型過擬合,則可以增加 dropout 率(即膏斤,減少keep_prob
超參數(shù))徐绑。 相反,如果模型欠擬合訓(xùn)練集莫辨,則應(yīng)嘗試降低 dropout 率(即增加keep_prob
)傲茄。 它也可以幫助增加大層的 dropout 率,并減少小層的 dropout 率沮榜。另外盘榨,許多優(yōu)秀的架構(gòu)只在最后一個隱藏層之后使用dropout,如果全都加上dropout太強了,可以這么試試。
dropout 似乎減緩了收斂速度憎夷,但通常會在調(diào)參得當(dāng)時使模型更好。 所以山憨,這通常值得花費額外的時間和精力。
提示:如果想對一個自歸一化的基于SELU的網(wǎng)絡(luò)使用正則弥喉,應(yīng)該使用alpha dropout:這是一個dropout的變體郁竟,可以保留輸入的平均值和標(biāo)準(zhǔn)差(它是在SELU的論文中提出的,因為常規(guī)的dropout會破會自歸一化)档桃。
蒙特卡洛(MC)dropout
Yarin Gal 和 Zoubin Ghahramani 在2016的一篇論文(https://arxiv.org/abs/1506.02142)中,追加了幾個使用drop的理由:
首先憔晒,這篇論文對dropout網(wǎng)絡(luò)(每個權(quán)重層前都有一個Dropout層)和貝葉斯推斷建立了理論聯(lián)系藻肄,從數(shù)學(xué)角度給予了證明。
第二拒担,作者介紹了一種稱為MC dropout的方法嘹屯,它可以提升任何訓(xùn)練過的dropout模型的性能,并且無需重新訓(xùn)練或修改从撼,對模型存在的不確定性提供了一種更好的方法州弟,也很容易實現(xiàn)。
如果這聽起來像一個廣告低零,看下面的代碼婆翔。它是MC dropout的完整實現(xiàn),可以提升前面訓(xùn)練的模型掏婶,并且沒有重新訓(xùn)練:
y_probas = np.stack([model(X_test_scaled, training=True)
for sample in range(100)])
y_proba = y_probas.mean(axis=0)
我們只是在訓(xùn)練集上做了100次預(yù)測啃奴,設(shè)置training=True
保證Dropout是活躍的,然后放到一起雄妥。因為dropout是開啟的最蕾,所有的預(yù)測都會不同依溯。predict()
返回一個矩陣,每行包含一個實例瘟则,每列是一個類黎炉。因為測試集有10000個實例和10個類,這個矩陣的形狀是[10000,10]醋拧。我們一共有100個這樣的矩陣慷嗜,因此y_proba
是一個形狀[100,10000,10]的數(shù)組。當(dāng)對以一個維度維度(axis=0
)做平均時趁仙,得到的是y_proba
洪添,形狀是[10000,10]的數(shù)組,就像和一次獨立預(yù)測的一樣雀费。對開啟dropout的多次預(yù)測做平均干奢,就得到了一個蒙特卡洛估計,會比單獨一次預(yù)測的可靠性更高盏袄。例如忿峻,看下模型對訓(xùn)練集第一個實例的預(yù)測,關(guān)閉dropout:
>>> np.round(model.predict(X_test_scaled[:1]), 2)
array([[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.01, 0. , 0.99]],
dtype=float32)
這個模型大概率認(rèn)定這張圖屬于類9(靴子)辕羽。應(yīng)該相信這個結(jié)果嗎逛尚?有無質(zhì)疑空間呢?
再看看開啟dropout的預(yù)測:
>>> np.round(y_probas[:, :1], 2)
array([[[0. , 0. , 0. , 0. , 0. , 0.14, 0. , 0.17, 0. , 0.68]],
[[0. , 0. , 0. , 0. , 0. , 0.16, 0. , 0.2 , 0. , 0.64]],
[[0. , 0. , 0. , 0. , 0. , 0.02, 0. , 0.01, 0. , 0.97]],
[...]
當(dāng)開啟dropout刁愿,模型就沒那么確定了绰寞。雖然仍偏向類9,但會在類5(涼鞋)和類7(運動鞋)猶豫铣口。對第一維做平均滤钱,我們得到了下面的MC dropout預(yù)測:
>>> np.round(y_proba[:1], 2)
array([[0. , 0. , 0. , 0. , 0. , 0.22, 0. , 0.16, 0. , 0.62]],
dtype=float32)
模型仍認(rèn)為這張圖屬于類9,但置信度只有62%脑题,這比99%可信讀了件缸。知道可能屬于其它什么類,也有用叔遂。還可以再查看下概率估計的標(biāo)準(zhǔn)差:
>>> y_std = y_probas.std(axis=0)
>>> np.round(y_std[:1], 2)
array([[0. , 0. , 0. , 0. , 0. , 0.28, 0. , 0.21, 0.02, 0.32]],
dtype=float32)
顯然他炊,概率估計的方差很大:如果搭建的是一個對風(fēng)險敏感的系統(tǒng)(比如醫(yī)療或金融),就要對這樣不確定的預(yù)測保持謹(jǐn)慎已艰。另外痊末,模型的準(zhǔn)確率從86.8提升到了86.9:
>>> accuracy = np.sum(y_pred == y_test) / len(y_test)
>>> accuracy
0.8694
筆記:蒙特卡洛樣本的數(shù)量是一個可以調(diào)節(jié)的超參數(shù)。這個數(shù)越高哩掺,預(yù)測和不準(zhǔn)確度的估計越高舌胶。但是,如果樣本數(shù)翻倍疮丛,推斷時間也要翻倍幔嫂。另外辆它,樣本數(shù)超過一定數(shù)量,提升就不大了履恩。因此要取決于任務(wù)本身锰茉,在延遲和準(zhǔn)確性上做取舍。
如果模型包含其它層行為特殊的層(比如批歸一化層)切心,則不能像剛才那樣強行訓(xùn)練模型飒筑。相反,你需要將Dropout
層替換為MCDropout
類:
class MCDropout(keras.layers.Dropout):
def call(self, inputs):
return super().call(inputs, training=True)
這里绽昏,使用了Dropout
的子類协屡,并覆蓋了方法call()
,使training
參數(shù)變?yōu)?code>True(見第12章)全谤。相似的肤晓,可以通過AlphaDropout
的子類定義一個MCAlphaDropout
。如果是從零搭建模型认然,只需使用MCDropout
而不是Dropout
补憾,你需要創(chuàng)建一個與老模型架構(gòu)相同的新模型,替換Dropout
層為MCDropout
層卷员,然后復(fù)制權(quán)重到新模型上盈匾。
總之,MC dropout是一個可以提升dropout模型毕骡、提供更加不準(zhǔn)確估計的神奇方法削饵。當(dāng)然,因為在訓(xùn)練中仍然是常規(guī)dropout未巫,它仍然是一個正則器窿撬。
最大范數(shù)正則化
另一種在神經(jīng)網(wǎng)絡(luò)中非常流行的正則化技術(shù)被稱為最大范數(shù)正則化:對于每個神經(jīng)元,它約束輸入連接的權(quán)重w
橱赠,使得 ∥ w ∥2 ≤ r尤仍,其中r
是最大范數(shù)超參數(shù)箫津,∥ · ∥2 是 l2 范數(shù)狭姨。
最大范數(shù)正則沒有添加正則損失項到總損失函數(shù)中。相反苏遥,只是計算
我們通常通過在每個訓(xùn)練步驟之后計算∥w∥2饼拍,并且如果需要的話可以如下剪切W
。
減少r
增加了正則化的量田炭,并有助于減少過擬合师抄。 最大范數(shù)正則化還可以幫助減輕梯度消失/爆炸問題(如果不使用批歸一化)。
要在Keras中實現(xiàn)最大范數(shù)正則教硫,需要設(shè)置每個隱藏層的kernel_constraint
的max_norm()
為一個合適的值叨吮,如下所示:
keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal",
kernel_constraint=keras.constraints.max_norm(1.))
每次訓(xùn)練迭代之后辆布,模型的fit()
方法會調(diào)用max_norm()
返回的對象,傳給它層的權(quán)重茶鉴,并返回縮放過的權(quán)重锋玲,再代替層的權(quán)重。第12章會看到涵叮,如果需要的話可以定義自己的約束函數(shù)惭蹂。你還可以通過設(shè)置參數(shù)bias_constraint
約束偏置項。
max_norm()
函數(shù)有一個參數(shù)axis
割粮,默認(rèn)為0盾碗。緊密層權(quán)重的形狀通常是[輸入數(shù),神經(jīng)元數(shù)]舀瓢,因此設(shè)置axis=0
廷雅,意味最大范數(shù)約束會獨立作用在每個神經(jīng)元的權(quán)重矢量上。如果你想對卷積層使用最大范數(shù)氢伟,一定要合理設(shè)置axis
(通常axis=[0,1,2]
)榜轿。
總結(jié)和實踐原則
本章介紹了許多方法,讀者可能納悶到底該用哪個呢朵锣。用哪種方法要取決于任務(wù)谬盐,并沒有統(tǒng)一的結(jié)論,表11-3的總結(jié)可用于大多數(shù)情況诚些,不需要調(diào)節(jié)太多超參數(shù)飞傀。但是,也不要死守這些默認(rèn)值诬烹!
如果網(wǎng)絡(luò)只有緊密層砸烦,則可以是自歸一化的,可以使用表11-4的配置绞吁。
不要忘了歸一化輸入特征幢痘!還應(yīng)該嘗試復(fù)用部分預(yù)訓(xùn)練模型,如果它處理的是一個想死任務(wù)家破,或者如果有許多無便數(shù)據(jù)時使用無監(jiān)督預(yù)訓(xùn)練颜说,或者有許多相似任務(wù)的標(biāo)簽數(shù)據(jù)時使用輔助任務(wù)的語序年。
雖然這些指導(dǎo)可以應(yīng)對大部分情況汰聋,但有些例外:
如果需要系數(shù)模型,你可以使用?1正則(可以在訓(xùn)練后玄妈,將部分小權(quán)重設(shè)為零)。如果需要一個再稀疏點的模型拟蜻,可以使用TensorFlow Model Optimization Toolkit绎签,它會破壞自歸一化,所以要使用默認(rèn)配置酝锅。
如果需要一個地延遲模型(預(yù)測快)辜御,層要盡量少,對前一層使用批歸一化屈张,使用更快的激活函數(shù)擒权,比如leaky ReLU或ReLU。稀疏模型也快阁谆。最后碳抄,將浮點精度從32位降到16位,甚至8位场绿。還有剖效,嘗試TF-MOT。
如果搭建的是風(fēng)險敏感的模型焰盗,或者推斷延遲不是非常重要璧尸,可以使用MC dropout提升性能,得到更可靠的概率估計和不確定估計熬拒。
有了這些原則爷光,就可以開始訓(xùn)練非常深的網(wǎng)絡(luò)了。希望你現(xiàn)在對Keras有足夠的自信活烙。隨著深入重贺,可能需要寫自定義的損失函數(shù)或調(diào)解訓(xùn)練算法气笙。對于這樣的情況贫贝,需要使用TensorFlow的低級API崇堵,見下一章。
練習(xí)
使用 He 初始化隨機(jī)選擇權(quán)重赏廓,是否可以將所有權(quán)重初始化為相同的值?
可以將偏置初始化為 0 嗎既忆?
說出SELU 激活功能與 ReLU 相比的三個優(yōu)點。
在哪些情況下,您想要使用以下每個激活函數(shù):SELU又谋,leaky ReLU(及其變體),ReLU,tanh废酷,logistic 以及 softmax?
如果將
momentum
超參數(shù)設(shè)置得太接近 1(例如睹簇,0.99999)太惠,會發(fā)生什么情況?請列舉您可以生成稀疏模型的三種方法。
dropout 是否會減慢訓(xùn)練剂癌? 它是否會減慢推斷(即預(yù)測新的實例)?MC dropout呢谐檀?
-
在CIFAR10圖片數(shù)據(jù)集上訓(xùn)練一個深度神經(jīng)網(wǎng)絡(luò):
- 建立一個 DNN刽肠,有20個隱藏層惫撰,每層 100 個神經(jīng)元坚嗜,使用 He 初始化和 ELU 激活函數(shù)诱建。
- 使用 Nadam 優(yōu)化和早停,嘗試在 CIFAR10 上進(jìn)行訓(xùn)練,可以使用
keras.datasets.cifar10.load_data()
加載數(shù)據(jù)。數(shù)據(jù)集包括60000張32x32的圖片(50000張訓(xùn)練,10000張測試)有10個類药磺,所以需要10個神經(jīng)元的softmax輸出層。記得每次調(diào)整架構(gòu)或超參數(shù)之后,尋找合適的學(xué)習(xí)率姚建。 - 現(xiàn)在嘗試添加批歸一化并比較學(xué)習(xí)曲線:它是否比以前收斂得更快? 它是否會產(chǎn)生更好的模型?對訓(xùn)練速度有何影響?
- 嘗試用SELU替換批歸一化策精,做一些調(diào)整,確保網(wǎng)絡(luò)是自歸一化的(即谜嫉,標(biāo)準(zhǔn)化輸入特征沐兰,使用LeCun正態(tài)初始化,確保DNN只含有緊密層)蔽挠。
- 使用alpha dropout正則化模型住闯。然后,不訓(xùn)練模型澳淑,使用MC Dropout能否提高準(zhǔn)確率比原。
- 用1循環(huán)調(diào)度重新訓(xùn)練模型厘线,是否能提高訓(xùn)練速度和準(zhǔn)確率展箱。
參考答案見附錄A满粗。
第10章 使用Keras搭建人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)
第12章 使用TensorFlow自定義模型并訓(xùn)練
第13章 使用TensorFlow加載和預(yù)處理數(shù)據(jù)
第14章 使用卷積神經(jīng)網(wǎng)絡(luò)實現(xiàn)深度計算機(jī)視覺
第15章 使用RNN和CNN處理序列
第16章 使用RNN和注意力機(jī)制進(jìn)行自然語言處理
第17章 使用自編碼器和GAN做表征學(xué)習(xí)和生成式學(xué)習(xí)
第18章 強化學(xué)習(xí)
第19章 規(guī)模化訓(xùn)練和部署TensorFlow模型