梯度裁剪
之前的文章向大家介紹了解決梯度問題的一個(gè)常用方法,批標(biāo)準(zhǔn)化贯莺。今天我們來(lái)一個(gè)能夠減輕梯度爆炸問題的手段风喇,梯度裁剪。所謂梯度裁剪缕探,是在反向傳播的過(guò)程中魂莫,對(duì)梯度進(jìn)行修剪,使得梯度永遠(yuǎn)不會(huì)達(dá)到某個(gè)閾值爹耗。這個(gè)技術(shù)通常用在循環(huán)神經(jīng)網(wǎng)絡(luò)中耙考,因?yàn)榕鷺?biāo)準(zhǔn)化在RNN中不太易用谜喊。但是在其他類型的神經(jīng)網(wǎng)絡(luò)中,批標(biāo)準(zhǔn)化總是表現(xiàn)得非常出色倦始。
在Keras里斗遏,實(shí)現(xiàn)梯度裁剪只需要在創(chuàng)建優(yōu)化器時(shí),設(shè)置clipvalue或者clipnorm參數(shù)就好了鞋邑,就像這樣:
optimizer = keras.optimizers.SGD(clipvalue=1.0)
model.compile(loss="mse", optimizer=optimizer)
這個(gè)設(shè)置好的優(yōu)化器會(huì)將梯度向量的每個(gè)值裁剪至-1.0到1.0區(qū)間內(nèi)诵次,也就是說(shuō)所有損失函數(shù)的偏導(dǎo)數(shù)(對(duì)于每個(gè)可訓(xùn)練參數(shù))被裁剪至-1.0到1.0之間。其中clipvalue參數(shù)枚碗,作為閾值逾一,是可以調(diào)的超參,但是要注意這可能會(huì)改變梯度向量的方向视译。比如說(shuō)嬉荆,如果原梯度向量是[0.9, 100.0],那么它的方向基本上就指向第二個(gè)軸酷含。但是當(dāng)你裁剪了梯度鄙早,那么梯度就變成了[0.9, 1.0],裁剪后的向量基本上就指向兩個(gè)軸之間椅亚。在實(shí)際操作當(dāng)中限番,這個(gè)方法的結(jié)果不錯(cuò)。但是如果你希望保證梯度裁剪法不改變梯度向量的方向呀舔,那么就不能設(shè)置clipvalue參數(shù)弥虐,而需要設(shè)置clipnorm參數(shù)。這個(gè)方法會(huì)將l2范數(shù)大于閾值的梯度進(jìn)行裁剪媚赖。比如我們?cè)O(shè)置clipnorm = 1.0霜瘪,那么向量[0.9, 100.0]就會(huì)被裁剪為[0.0089964, 0.9999595],保持其方向不變惧磺,但是第一個(gè)值幾乎要被消除了颖对。如果我們?cè)谟?xùn)練過(guò)程中觀察到了梯度爆炸的現(xiàn)象(可以用TensorBoard進(jìn)行梯度追蹤),那么就可以試著用不同閾值的按值裁剪和按方向裁剪磨隘,然后看哪個(gè)方案在驗(yàn)證集上的表現(xiàn)更好缤底。
預(yù)訓(xùn)練層重用
一般來(lái)說(shuō),從頭開始訓(xùn)練一個(gè)非常大的DNN都不是什么好主意:我們應(yīng)該總是試著找一個(gè)已有的能夠完成類似任務(wù)的神經(jīng)網(wǎng)絡(luò)番捂,然后重用這個(gè)網(wǎng)絡(luò)的底層个唧。這個(gè)技術(shù)被稱為遷移學(xué)習(xí)。這不僅會(huì)使訓(xùn)練速度極大提高设预,并且還減小了對(duì)訓(xùn)練數(shù)據(jù)的需求徙歼。
假設(shè)我們已經(jīng)有一個(gè)訓(xùn)練好的DNN可以將圖片分成100種不同類別,包括動(dòng)物、植物鲁沥、車輛和日常物品『艄桑現(xiàn)在我們又需要訓(xùn)練一個(gè)DNN,用來(lái)分類特定類型的車輛画恰。這兩個(gè)任務(wù)非常相似彭谁,甚至部分重疊,那么我們就可以嘗試重用第一個(gè)網(wǎng)絡(luò)的部分內(nèi)容允扇。
如果新任務(wù)的輸入圖片與原始任務(wù)中使用的圖片大小不一缠局,那么就需要一個(gè)預(yù)處理步驟來(lái)將它們調(diào)整至原始模型所期望的尺寸。更普遍的說(shuō)法是考润,當(dāng)輸入具有相似的低水平特征時(shí)狭园,遷移學(xué)習(xí)的效果最好。
原始模型的輸出層一般都是要被替換掉的糊治,因?yàn)樗话銓?duì)新任務(wù)一點(diǎn)用都沒有唱矛,很可能輸出的數(shù)量都不對(duì)。
類似的井辜,原始模型的頂層也不像底層那樣有用绎谦,因?yàn)閷?duì)新任務(wù)最有用的高級(jí)特性很可能與原始任務(wù)的最有用的高級(jí)特性完全不同。所以需要找到可以重用的層的正確數(shù)量粥脚。
任務(wù)越相似窃肠,可以重用的層就越多(從較低的層開始)。對(duì)于非常相似的任務(wù)來(lái)說(shuō)刷允,就可以嘗試保留所有的隱藏層冤留,只替換輸出層。
首先應(yīng)該嘗試凍結(jié)所有重用的層(即將它們的權(quán)重設(shè)置為不可訓(xùn)練树灶,這樣梯度下降就不會(huì)修改它們)纤怒,然后訓(xùn)練模型,觀察它的表現(xiàn)如何天通。然后嘗試解凍一個(gè)或兩個(gè)頂部隱藏層泊窘,然后讓反向傳播調(diào)整它們的參數(shù),觀察模型性能是否有提升土砂。擁有的訓(xùn)練數(shù)量越多,可以解凍的層就越多谜洽。當(dāng)層被解凍之后萝映,它還有助于降低學(xué)習(xí)率:這可以避免破壞它們的微調(diào)權(quán)重。
如果模型的性能表現(xiàn)依然不理想阐虚,并且訓(xùn)練數(shù)據(jù)很少序臂,那么可以嘗試刪除頂部的隱藏層,并再次凍結(jié)所有剩余的隱藏層“赂眩可以反復(fù)迭代上述操作逊彭,直到找到要重用的層數(shù)為止。但如果你有大量的訓(xùn)練數(shù)據(jù)构订,就可以嘗試替換頂層隱藏層結(jié)構(gòu)侮叮,而不是刪除它們,甚至還可以添加更多的隱藏層悼瘾。
使用Keras進(jìn)行遷移學(xué)習(xí)
讓我們來(lái)看個(gè)例子囊榜。假設(shè)時(shí)尚MNIST數(shù)據(jù)集除去涼鞋和襯衫,只有8個(gè)類別『ニ蓿現(xiàn)有一個(gè)基于上述數(shù)據(jù)集的已經(jīng)訓(xùn)練完成的Keras模型卸勺,并且性能表現(xiàn)良好(準(zhǔn)確度>90%),我們稱之為模型A√潭螅現(xiàn)在曙求,我們需要完成另一項(xiàng)任務(wù):我們擁有涼鞋和襯衫的圖像,需要訓(xùn)練一個(gè)二進(jìn)制分類器(正=襯衫映企,負(fù)=涼鞋)悟狱。然而現(xiàn)在的數(shù)據(jù)集規(guī)模相當(dāng)小,假設(shè)只有200個(gè)帶標(biāo)簽的圖像卑吭。當(dāng)我們?yōu)檫@個(gè)任務(wù)訓(xùn)練一個(gè)新模型(我們稱之為模型B)時(shí)芽淡,它的模型架構(gòu)與模型A相同,并且運(yùn)行得相當(dāng)好(97.2%的準(zhǔn)確率)豆赏。但由于這是個(gè)簡(jiǎn)單得多的分類任務(wù)(只有兩個(gè)類)挣菲,我們希望模型能夠有更加強(qiáng)大的能力。由于任務(wù)B和任務(wù)A其實(shí)十分相似掷邦,那么我們是不是可以利用遷移學(xué)習(xí)做一點(diǎn)事情白胀?讓我們來(lái)試一下。
首先抚岗,我們需要加載模型A或杠,并且基于模型A的層來(lái)創(chuàng)建一個(gè)新的模型。我們先來(lái)重用除了輸出層以外的所有層:
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"))
現(xiàn)在model_A和model_B_on_A現(xiàn)在有了相同的層宣蔚。當(dāng)訓(xùn)練model_B_on_A時(shí)向抢,它也會(huì)影響model_A。如果你希望避免這樣的問題胚委,就需要在重用model_A的層之前挟鸠,克隆一個(gè)model_A出來(lái)。那么如何克隆模型呢亩冬?首先需要克隆模型A的結(jié)構(gòu)艘希,然后復(fù)制它的權(quán)重(clone_model()方法并不會(huì)復(fù)制權(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ī)初始化的,它會(huì)在訓(xùn)練最初的幾個(gè)epoch期間有很大的誤差覆享。因此它會(huì)有很大的錯(cuò)誤梯度佳遂,這可能會(huì)破壞重用層的權(quán)重。為避免這種情況撒顿,我們可以在訓(xùn)練最初的幾個(gè)epoch期間將重用層凍結(jié)丑罪,這樣使得新加的層在此期間學(xué)習(xí)到相對(duì)合理的權(quán)重。在Keras中核蘸,只需將層的trainable參數(shù)設(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"])
注意,對(duì)于模型的層進(jìn)行了凍結(jié)或者解凍的操作之后客扎,必須要編譯一次祟峦。模型在訓(xùn)練了幾個(gè)epoch之后再解凍重用層(需要再編譯一次模型),隨后再繼續(xù)訓(xùn)練以精調(diào)重用層徙鱼。在解凍重用的層之后宅楞,一般需要降低學(xué)習(xí)率,避免再次破壞重用的權(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) # 默認(rèn)lr為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é)果如何呢厌衙?結(jié)果是模型的測(cè)試準(zhǔn)確度為99.25%,也就是說(shuō)遷移學(xué)習(xí)將錯(cuò)誤率從2.8%降低到了0.7%绞绒!這是1/4的錯(cuò)誤率婶希。
>>> model_B_on_A.evaluate(X_test_B, y_test_B)
[0.06887910133600235, 0.9925]
看完上面的例子,你相信遷移學(xué)習(xí)的力量了嗎蓬衡?其實(shí)你根本不應(yīng)該相信喻杈,因?yàn)槲以谏厦娴睦永镒鞅琢耍∥覈L試了很多模型配置狰晚,直到找到了其中一種配置筒饰,使模型看起來(lái)提升了非常多。其實(shí)如果改變了選擇的服飾類別壁晒,或者改變隨機(jī)種子瓷们,那么模型性能就不會(huì)提升那么多,甚至不提升或者性能更差了秒咐。這種做法被稱為:“折磨數(shù)據(jù)谬晕,直到它坦白⌒。”當(dāng)一篇論文的結(jié)果看上去過(guò)于出色攒钳,其實(shí)你應(yīng)該持懷疑態(tài)度:那些看上去很炫的技術(shù)可能并沒有什么效果(甚至可能降低性能),但是作者嘗試了很多變體歹茶,然后只報(bào)道了最好的結(jié)果(可能是由于運(yùn)氣得到了這樣的結(jié)果)夕玩,根本沒有說(shuō)在試驗(yàn)過(guò)程中有失敗的例子。很多時(shí)候研究人員可能并不是有意這樣做的惊豺,但這就是很多科學(xué)成果無(wú)法復(fù)現(xiàn)的原因之一燎孟。
那么我為什么需要作弊呢?因?yàn)檫w移學(xué)習(xí)在小而密集型的神經(jīng)網(wǎng)絡(luò)上并不能發(fā)揮作用尸昧,可能是因?yàn)樾⌒蜕窠?jīng)網(wǎng)絡(luò)學(xué)習(xí)的模式很少揩页,而密集型的網(wǎng)絡(luò)學(xué)習(xí)的模式又十分具體,那么這樣的模型對(duì)于別的任務(wù)就不太有用烹俗。遷移學(xué)習(xí)更適合深度卷積神經(jīng)網(wǎng)絡(luò)爆侣,它能夠?qū)W習(xí)更加通用的特征(特別是在較低的層中)。我們會(huì)在未來(lái)的文章當(dāng)中進(jìn)一步討論遷移學(xué)習(xí)幢妄,當(dāng)然下次不會(huì)再作弊了兔仰。
本文介紹了最后一種常用的解決梯度爆炸問題的方法:梯度裁剪,還簡(jiǎn)單介紹了遷移學(xué)習(xí)的概念和基本方法蕉鸳。但是乎赴,如果我們想用遷移學(xué)習(xí),卻又找不到類似的模型可以用來(lái)遷移該怎么辦呢潮尝?下一篇文章會(huì)介紹如何解決榕吼。
敬請(qǐng)期待吧!