原創(chuàng):鄭佳偉
在NLP任務(wù)中欠拾,會(huì)有很多為了提升模型效果而提出的優(yōu)化胰锌,為了方便記憶,所以就把這些方法都整理出來(lái)藐窄,也有助于大家學(xué)習(xí)资昧。為了理解,文章并沒(méi)有引入公式推導(dǎo)荆忍,只是介紹這些方法是怎么回事格带,如何使用。
一东揣、對(duì)抗訓(xùn)練
近幾年践惑,隨著深度學(xué)習(xí)的發(fā)展,對(duì)抗樣本得到了越來(lái)越多的關(guān)注嘶卧。通常尔觉,我們通過(guò)對(duì)模型的對(duì)抗攻擊和防御來(lái)增強(qiáng)模型的穩(wěn)健性,比如自動(dòng)駕駛系統(tǒng)中的紅綠燈識(shí)別芥吟,要防止模型因?yàn)橐恍╇S機(jī)噪聲就將紅燈識(shí)別為綠燈侦铜。在NLP領(lǐng)域,類(lèi)似的對(duì)抗訓(xùn)練也是存在的钟鸵。
簡(jiǎn)單來(lái)說(shuō)钉稍,“對(duì)抗樣本” 是指對(duì)于人類(lèi)來(lái)說(shuō)“看起來(lái)”幾乎一樣、但對(duì)于模型來(lái)說(shuō)預(yù)測(cè)結(jié)果卻完全不一樣的樣本棺耍,比如圖中的例子贡未,一只熊貓的圖片在加了一點(diǎn)擾動(dòng)之后被識(shí)別成了長(zhǎng)臂猿。
“對(duì)抗攻擊”蒙袍,就是生成更多的對(duì)抗樣本俊卤,而“對(duì)抗防御”,就是讓模型能正確識(shí)別更多的對(duì)抗樣本害幅。對(duì)抗訓(xùn)練消恍,最初由 Goodfellow 等人提出,是對(duì)抗防御的一種以现,其思路是將生成的對(duì)抗樣本加入到原數(shù)據(jù)集中用來(lái)增強(qiáng)模型對(duì)對(duì)抗樣本的魯棒性狠怨,Goodfellow還總結(jié)了對(duì)抗訓(xùn)練的除了提高模型應(yīng)對(duì)惡意對(duì)抗樣本的魯棒性之外约啊,還可以作為一種正則化,減少過(guò)擬合佣赖,提高模型泛化能力恰矩。
在CV任務(wù)中,輸入是連續(xù)的RGB的值茵汰,而NLP問(wèn)題中枢里,輸入是離散的單詞序列,一般以one-hot向量的形式呈現(xiàn)蹂午,如果直接在raw text上進(jìn)行擾動(dòng)栏豺,那么擾動(dòng)的大小和方向可能都沒(méi)什么意義。Goodfellow在17年的ICLR中 提出了可以在連續(xù)的Embedding上做擾動(dòng)豆胸,但對(duì)比圖像領(lǐng)域中直接在原始輸入加擾動(dòng)的做法奥洼,擾動(dòng)后的Embedding向量不一定能匹配上原來(lái)的Embedding向量表,這樣一來(lái)對(duì)Embedding層的擾動(dòng)就無(wú)法對(duì)應(yīng)上真實(shí)的文本輸入晚胡,這就不是真正意義上的對(duì)抗樣本了灵奖,因?yàn)閷?duì)抗樣本依然能對(duì)應(yīng)一個(gè)合理的原始輸入。那么估盘,在Embedding層做對(duì)抗擾動(dòng)還有沒(méi)有意義呢瓷患?有!實(shí)驗(yàn)結(jié)果顯示遣妥,在很多任務(wù)中擅编,在Embedding層進(jìn)行對(duì)抗擾動(dòng)能有效提高模型的性能。之所以能提高性能箫踩,主要是因?yàn)閷?duì)抗訓(xùn)練可以作為正則化爱态,一定程度上等價(jià)于在loss里加入了梯度懲罰,提升了模型泛化能力境钟。
接下來(lái)看一下NLP對(duì)抗訓(xùn)練中常用的兩個(gè)方法和具體實(shí)現(xiàn)代碼锦担。
第一種是FGM, Goodfellow在17年的ICLR中對(duì)他自己在15年提出的FGSM方法進(jìn)行了優(yōu)化,主要是在計(jì)算擾動(dòng)的部分做了一點(diǎn)簡(jiǎn)單的修改慨削。其偽代碼如下:
對(duì)于每個(gè)樣本x:
1.計(jì)算x的前向loss洞渔、反向傳播得到梯度
2.根據(jù)Embedding矩陣的梯度計(jì)算出擾動(dòng)項(xiàng)r,并加到當(dāng)前Embedding上缚态,相當(dāng)于x+r
3.計(jì)算x+r的前向loss磁椒,反向傳播得到對(duì)抗的梯度,累加到(1)的梯度上
4.將embedding恢復(fù)為(1)時(shí)的值
5.根據(jù)(3)的梯度對(duì)參數(shù)進(jìn)行更新
具體pytorch代碼如下:
1. import torch
2. class FGM():
3. def __init__(self, model):
4. self.model = model
5. self.backup = {}
6.
7. def attack(self, epsilon=1., emb_name='embedding'):
8. # emb_name這個(gè)參數(shù)要換成你模型中embedding的參數(shù)名
9. for name, param in self.model.named_parameters():
10. if param.requires_grad and emb_name in name:
11. self.backup[name] = param.data.clone()
12. norm = torch.norm(param.grad)
13. if norm != 0 and not torch.isnan(norm):
14. r_at = epsilon * param.grad / norm
15. param.data.add_(r_at)
16.
17. def restore(self, emb_name='embedding'):
18. # emb_name這個(gè)參數(shù)要換成你模型中embedding的參數(shù)名
19. for name, param in self.model.named_parameters():
20. if param.requires_grad and emb_name in name:
21. assert name in self.backup
22. param.data = self.backup[name]
23. self.backup = {}
需要使用對(duì)抗訓(xùn)練的時(shí)候猿规,只需要添加五行代碼:
需要使用對(duì)抗訓(xùn)練的時(shí)候衷快,只需要添加五行代碼:
1. # 初始化
2. fgm = FGM(model)
3. for batch_input, batch_label in data:
4. # 正常訓(xùn)練
5. loss = model(batch_input, batch_label)
6. loss.backward() # 反向傳播宙橱,得到正常的grad
7. # 對(duì)抗訓(xùn)練
8. fgm.attack() # 在embedding上添加對(duì)抗擾動(dòng)
9. loss_adv = model(batch_input, batch_label)
10. loss_adv.backward() # 反向傳播姨俩,并在正常的grad基礎(chǔ)上蘸拔,累加對(duì)抗訓(xùn)練的梯度
11. fgm.restore() # 恢復(fù)embedding參數(shù)
12. # 梯度下降,更新參數(shù)
13. optimizer.step()
14. model.zero_grad()
第二種是PGD, FGM直接通過(guò)epsilon參數(shù)一下算出了對(duì)抗擾動(dòng)环葵,這樣得到的對(duì)抗擾動(dòng)可能不是最優(yōu)的调窍。因此PGD進(jìn)行了改進(jìn),多迭代幾次张遭,“小步走邓萨,多走幾步”,慢慢找到最優(yōu)的擾動(dòng)菊卷。偽代碼如下
對(duì)于每個(gè)樣本x:
1.計(jì)算x的前向loss缔恳、反向傳播得到梯度并備份
對(duì)于每步t:
2.根據(jù)Embedding矩陣的梯度計(jì)算出擾動(dòng)項(xiàng)r,并加到當(dāng)前Embedding上洁闰,相當(dāng)于x+r(超出范圍則投影回epsilon內(nèi))
3.t不是最后一步: 將梯度歸0歉甚,根據(jù)1的x+r計(jì)算前后向并得到梯度
4.t是最后一步: 恢復(fù)(1)的梯度,計(jì)算最后的x+r并將梯度累加到(1)上
5.將Embedding恢復(fù)為(1)時(shí)的值
6.根據(jù)(4)的梯度對(duì)參數(shù)進(jìn)行更新
可以看到扑眉,在循環(huán)中r是逐漸累加的纸泄,要注意的是最后更新參數(shù)只使用最后一個(gè)x+r算出來(lái)的梯度。具體代碼如下:
1. import torch
2. class PGD():
3. def __init__(self, model):
4. self.model = model
5. self.emb_backup = {}
6. self.grad_backup = {}
7.
8. def attack(self, epsilon=1., alpha=0.3, emb_name='emb.', is_first_attack=False):
9. # emb_name這個(gè)參數(shù)要換成你模型中embedding的參數(shù)名
10. for name, param in self.model.named_parameters():
11. if param.requires_grad and emb_name in name:
12. if is_first_attack:
13. self.emb_backup[name] = param.data.clone()
14. norm = torch.norm(param.grad)
15. if norm != 0 and not torch.isnan(norm):
16. r_at = alpha * param.grad / norm
17. param.data.add_(r_at)
18. param.data = self.project(name, param.data, epsilon)
19.
20. def restore(self, emb_name='emb.'):
21. # emb_name這個(gè)參數(shù)要換成你模型中embedding的參數(shù)名
22. for name, param in self.model.named_parameters():
23. if param.requires_grad and emb_name in name:
24. assert name in self.emb_backup
25. param.data = self.emb_backup[name]
26. self.emb_backup = {}
27.
28. def project(self, param_name, param_data, epsilon):
29. r = param_data - self.emb_backup[param_name]
30. if torch.norm(r) > epsilon:
31. r = epsilon * r / torch.norm(r)
32. return self.emb_backup[param_name] + r
33.
34. def backup_grad(self):
35. for name, param in self.model.named_parameters():
36. if param.requires_grad:
37. self.grad_backup[name] = param.grad.clone()
38.
39. def restore_grad(self):
40. for name, param in self.model.named_parameters():
41. if param.requires_grad:
42. param.grad = self.grad_backup[name]
使用的時(shí)候腰素,步驟要多一點(diǎn):
1. pgd = PGD(model)
2. K = 3
3. for batch_input, batch_label in data:
4. # 正常訓(xùn)練
5. loss = model(batch_input, batch_label)
6. loss.backward() # 反向傳播聘裁,得到正常的grad
7. pgd.backup_grad()
8. # 對(duì)抗訓(xùn)練
9. for t in range(K):
10. pgd.attack(is_first_attack=(t==0)) # 在embedding上添加對(duì)抗擾動(dòng), first attack時(shí)備份param.data
11. if t != K-1:
12. model.zero_grad()
13. else:
14. pgd.restore_grad()
15. loss_adv = model(batch_input, batch_label)
16. loss_adv.backward() # 反向傳播,并在正常的grad基礎(chǔ)上弓千,累加對(duì)抗訓(xùn)練的梯度
17. pgd.restore() # 恢復(fù)embedding參數(shù)
18. # 梯度下降衡便,更新參數(shù)
19. optimizer.step()
20. model.zero_grad()
二、Lookahead
Lookahead是近幾年新多倫多大學(xué)向量學(xué)院的研究者提出的一種優(yōu)化器计呈,它與已有的方法完全不同砰诵,它迭代更新兩組權(quán)重。直觀來(lái)說(shuō)捌显,Lookahead 算法通過(guò)提前觀察另一個(gè)優(yōu)化器生成的「fast weights」序列茁彭,來(lái)選擇搜索方向。該研究發(fā)現(xiàn)扶歪, Lookahead 算法能夠提升學(xué)習(xí)穩(wěn)定性理肺,不僅降低了調(diào)參需要的時(shí)間,同時(shí)還能提升收斂速度與效果善镰。此外妹萨,我們可以使用 Lookahead 加強(qiáng)已有最優(yōu)化方法的性能。 Lookahead 的直觀過(guò)程如圖所示炫欺,它維護(hù)兩組權(quán)重乎完。Lookahead 首先使用SGD 等標(biāo)準(zhǔn)優(yōu)化器,更新 k 次「Fast weights」品洛,然后以最后一個(gè) Fast weights 的方向更新「slow weights」树姨。如下 Fast Weights 每更新 5 次摩桶,slow weights 就會(huì)更新一次。
這種更新機(jī)制不僅能夠有效地降低方差帽揪,而且Lookahead 對(duì)次優(yōu)超參數(shù)沒(méi)那么敏感硝清,以至于它對(duì)大規(guī)模調(diào)參的需求沒(méi)有那么強(qiáng)。此外转晰,使用 Lookahead 及其內(nèi)部?jī)?yōu)化器(如 SGD 或 Adam)芦拿,還能實(shí)現(xiàn)更快的收斂速度,計(jì)算開(kāi)銷(xiāo)也比較小查邢。 Lookahead的思路比較簡(jiǎn)答蔗崎,準(zhǔn)確來(lái)說(shuō)它并不是一個(gè)優(yōu)化器,而是一個(gè)使用現(xiàn)有優(yōu)化器的方案扰藕。簡(jiǎn)單來(lái)說(shuō)它就是下面三個(gè)步驟的循環(huán)執(zhí)行:
1)蚁趁、備份模型現(xiàn)有的權(quán)重θ;
2)实胸、從θ出發(fā)他嫡,用指定優(yōu)化器更新k步,得到新權(quán)重θ? 庐完;
3)钢属、更新模型權(quán)重為θ←θ+α(θ? ?θ)。
三、 Warmup
warm up是一種學(xué)習(xí)率優(yōu)化方法。一般情況下屹逛,我們?cè)谟?xùn)練模型過(guò)程中盖高,學(xué)習(xí)率是不會(huì)變化的共郭,而warm up是在不同階段采用不同的學(xué)習(xí)策略。比如 在模型訓(xùn)練之初選用較小的學(xué)習(xí)率,訓(xùn)練一段時(shí)間之后(如10 epoches或10000steps)使用預(yù)設(shè)的學(xué)習(xí)率進(jìn)行訓(xùn)練。 warm up的意義在于荷憋,在模型訓(xùn)練的初始階段:模型對(duì)數(shù)據(jù)很陌生,需要使用較小的學(xué)習(xí)率學(xué)習(xí)褐望,不斷修正權(quán)重分布勒庄,如果開(kāi)始階段,使用很大的學(xué)習(xí)率瘫里,訓(xùn)練出現(xiàn)偏差后实蔽,后續(xù)需要很多個(gè)epoch才能修正過(guò)來(lái),或者修正不過(guò)來(lái)谨读,導(dǎo)致訓(xùn)練過(guò)擬合局装。
在模型訓(xùn)練的中間階段,當(dāng)使用較小的學(xué)習(xí)率學(xué)習(xí)一段時(shí)間后,模型已經(jīng)根據(jù)之前的數(shù)據(jù)形成了先驗(yàn)知識(shí)铐尚,這時(shí)使用較大的學(xué)習(xí)率加速學(xué)習(xí)阶冈,前面學(xué)習(xí)到的先驗(yàn)知識(shí)可以使模型的方向正確,加速收斂速度塑径。
在模型訓(xùn)練的學(xué)習(xí)率衰減階段:模型參數(shù)在學(xué)習(xí)到一定階段,參數(shù)分布已經(jīng)在小范圍內(nèi)波動(dòng)填具,整體分布變化不大统舀,這時(shí)如果繼續(xù)沿用較大的學(xué)習(xí)率,可能會(huì)破壞模型權(quán)值分布的穩(wěn)定性劳景。
常用的warm up策略介紹三種 (1)constant warm up:學(xué)習(xí)率從比較小的數(shù)值線性增加到預(yù)設(shè)值之后保持不變 (2)linear warm up:學(xué)習(xí)率從非常小的數(shù)值線性增加到預(yù)設(shè)值之后誉简,然后再線性減小 (3)Cosine Warmup:學(xué)習(xí)率先從很小的數(shù)值線性增加到預(yù)設(shè)學(xué)習(xí)率,然后按照cos函數(shù)值進(jìn)行衰減盟广。
四闷串、混合精度訓(xùn)練
使用混合精度訓(xùn)練并不能提高模型效果,而是為了提高訓(xùn)練速度筋量∨氤常混合精度訓(xùn)練時(shí)一種在盡可能減少精度損失的情況下利用半精度浮點(diǎn)數(shù)加速訓(xùn)練的方法。它使用FP16即半精度浮點(diǎn)數(shù)存儲(chǔ)權(quán)重和梯度桨武。在減少占用內(nèi)存的同時(shí)起到了加速訓(xùn)練的效果肋拔。 通常訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型的時(shí)候默認(rèn)使用的數(shù)據(jù)類(lèi)型為單精度FP32,混合精度訓(xùn)練是指在訓(xùn)練的過(guò)程中呀酸,同時(shí)使用單精度(FP32)和半精度(FP16)凉蜂。
IEEE標(biāo)準(zhǔn)中的FP16和FP32格式如圖所示,float16表示FP6性誉,float表示FP32:
從圖中可以看出窿吩,與FP32相比,F(xiàn)P16的存儲(chǔ)空間是FP32的一半错览。因此使用FP16訓(xùn)練神經(jīng)網(wǎng)絡(luò)可以使權(quán)重等參數(shù)所占用的內(nèi)存是原來(lái)的一半纫雁,節(jié)省下來(lái)的內(nèi)存可以放更大的網(wǎng)絡(luò)模型或者使用更多的數(shù)據(jù)進(jìn)行訓(xùn)練。并且在分布式訓(xùn)練倾哺,特別是大模型的訓(xùn)練過(guò)程中先较,半精度可以加快數(shù)據(jù)的流通。但使用FP16同樣會(huì)帶來(lái)一些問(wèn)題悼粮,其中最重要的是1)精度溢出和2)舍入誤差闲勺。
精度溢出:Float16的有效的動(dòng)態(tài)范圍約為 [5.96×10^-8,65504], 而Float32的范圍是[1.4x10^-45, 1.7x10^38]】勖ǎ可以看到FP16相比FP32的有效范圍要小很多菜循,使用FP16替換FP32會(huì)出現(xiàn)上溢和下溢的情況。而在神經(jīng)網(wǎng)絡(luò)中申尤,由于激活函數(shù)的梯度通常要比權(quán)重的梯度小癌幕,更容易出現(xiàn)下溢的情況衙耕。
舍入誤差:0.00006666666在FP32中能正常表示,轉(zhuǎn)換到FP16后會(huì)表示成為0.000067勺远,不滿足FP16最小間隔的數(shù)會(huì)強(qiáng)制舍入橙喘。
為了讓深度學(xué)習(xí)訓(xùn)練,可以使用FP16的好處胶逢,并且避免精度溢出和舍入誤差厅瞎,F(xiàn)P16和FP32混合精度訓(xùn)練采用了三種有效的方法:
1.權(quán)重備份:權(quán)重備份主要是為了解決舍入誤差的問(wèn)題。其主要思路是把神經(jīng)網(wǎng)絡(luò)訓(xùn)練過(guò)程中產(chǎn)生的激活函數(shù)初坠、梯度和簸、以及中間變量等數(shù)據(jù),在訓(xùn)練中都利用FP16來(lái)存儲(chǔ)碟刺,同時(shí)復(fù)制一份FP32的權(quán)重參數(shù)锁保,用于訓(xùn)練時(shí)候的更新。
從圖中可以看到半沽,前向傳播過(guò)程中產(chǎn)生的權(quán)重爽柒,激活函數(shù),以及梯度都是用FP16進(jìn)行存儲(chǔ)和計(jì)算的者填。參數(shù)更新的公式為:
lr表示學(xué)習(xí)率霉赡,gradient表示梯度,在深度模型中幔托,的值可能會(huì)很小穴亏,如果利用FP16的權(quán)重進(jìn)行更新,可能會(huì)導(dǎo)致誤差問(wèn)題重挑,以至于權(quán)重更新無(wú)效嗓化。因此要使用FP32的權(quán)重參數(shù)進(jìn)行更新,即:
在這里需要注意的是谬哀,雖然對(duì)權(quán)重用FP32格式拷貝增加了內(nèi)存刺覆,但是對(duì)于整體的內(nèi)存占用還是很小的。訓(xùn)練內(nèi)存的消耗主要是激活史煎,這是因?yàn)槊恳粚拥呐炕蚣せ顣?huì)保存下來(lái)用于重復(fù)使用谦屑。激活也使用半精度存儲(chǔ),整體的內(nèi)存基本減半篇梭。
2.損失縮放:僅使用FP32進(jìn)行訓(xùn)練氢橙,模型可以收斂的很好,但是如果使用FP32和FP16混合進(jìn)行訓(xùn)練恬偷,會(huì)存在模型不收斂悍手。主要原因是梯度的太小,使用FP16表示之后會(huì)造成數(shù)據(jù)下溢的問(wèn)題,導(dǎo)致模型不收斂坦康,
所以神經(jīng)網(wǎng)絡(luò)模型為了匹配FP32的準(zhǔn)確性竣付,對(duì)前向傳播計(jì)算出來(lái)的Loss值進(jìn)行放大,例如:對(duì)FP32的參數(shù)乘以一個(gè)因子系數(shù)滞欠,把可能溢出的數(shù)據(jù)古胆,轉(zhuǎn)換到FP16可表示的范圍。根據(jù)鏈?zhǔn)角髮?dǎo)法則筛璧,放大Loss后會(huì)作用在反向傳播的每一層梯度逸绎,這樣比在每一層梯度上進(jìn)行放大更加高效。
損失縮放實(shí)現(xiàn)的主要過(guò)程:
(1)在神經(jīng)網(wǎng)絡(luò)模型在前向傳播之后隧哮,將得到loss增大2^K倍。
(2)在反向傳播之后座舍,將權(quán)重梯度縮小2^K倍沮翔,使用FP32進(jìn)行表示。 這種損失縮放是使用一個(gè)默認(rèn)值對(duì)損失進(jìn)行縮放曲秉,是靜態(tài)的采蚀。動(dòng)態(tài)損失縮放算法是在梯度溢出的時(shí)候減少損失縮放規(guī)模,并且階段性的嘗試增加損失規(guī)模承二,從而實(shí)現(xiàn)在不引起溢出的情況下使用最高損失縮放因子榆鼠,更好地恢復(fù)精度。
具體實(shí)現(xiàn)過(guò)程如下:
(1)動(dòng)態(tài)損失縮放會(huì)在開(kāi)始的時(shí)候使用較高的縮放因子(如2^24)亥鸠,然后在訓(xùn)練迭代中檢測(cè)數(shù)值是否存在溢出妆够;
(2)如果沒(méi)有數(shù)值溢出,則不進(jìn)行縮放负蚊,繼續(xù)進(jìn)行迭代神妹,如果檢測(cè)到數(shù)值溢出,則縮放因子會(huì)減半家妆,重新確認(rèn)數(shù)值更新情況鸵荠,直到數(shù)值不會(huì)溢出;
(3)在訓(xùn)練的后期伤极,loss已經(jīng)趨近收斂蛹找,梯度更新的幅度往往小了,這個(gè)時(shí)候使用更高的損失縮放因子來(lái)防止數(shù)據(jù)下溢哨坪。
3.運(yùn)算精度:為了有效減少計(jì)算過(guò)程中的舍入誤差庸疾,混合精度訓(xùn)練在訓(xùn)練過(guò)程中,使用FP16進(jìn)行矩陣乘法運(yùn)算当编,使用FP32來(lái)進(jìn)行矩陣乘法中間的累加部分彼硫,然后將FP32格式的值轉(zhuǎn)換成FP16格式的數(shù)值。
混合精度訓(xùn)練是減少內(nèi)存占用、運(yùn)算時(shí)間和運(yùn)算量的方法∨±海現(xiàn)在已經(jīng)證明了很多深度模型都可以用這個(gè)方法訓(xùn)練词渤,并且在不修改模型參數(shù)的情況下,準(zhǔn)確率不會(huì)下降串绩。Pytorch1.6版本已經(jīng)實(shí)現(xiàn)了NVIDIA的APEX混合精度訓(xùn)練的功能缺虐,看一下具體代碼:
1. from apex import amp
2. model, optimizer = amp.initialize(model, optimizer, opt_level="o1")
3. with amp.scale_loss(loss, optimizer) as scaled_loss:
4. scaled_loss.backward()
opt_level有4種選擇,分別為"o0","o1","o2","o3"礁凡,是APEX混合精度庫(kù)支持的4種混合精度訓(xùn)練策略高氮。"o0"和"o3"策略分別表示FP32和FP16的純精度方式。"o1"策略表示使用混合精度訓(xùn)練顷牌,但會(huì)根據(jù)實(shí)際Tensor和操作之間的關(guān)系建立黑白名單來(lái)決定是否使用FP16剪芍。例如使用FP16進(jìn)行GEMM和CNN卷積運(yùn)算會(huì)特別友好,則會(huì)把輸入的數(shù)據(jù)和權(quán)重轉(zhuǎn)換成FP16進(jìn)行運(yùn)算窟蓝,而使用FP32計(jì)算Softmax罪裹、Batchnorm等標(biāo)量和向量。此外运挫,默認(rèn)使用動(dòng)態(tài)損失縮放状共。而"o2"策略也是混合精度訓(xùn)練,但沒(méi)有黑白名單谁帕,它是將模型權(quán)重參數(shù)以及輸入模型的參數(shù)都轉(zhuǎn)換為FP16峡继,Batch norm使用FP32,另外模型權(quán)重文件復(fù)制一份FP32匈挖,用于跟優(yōu)化器更新梯度保持一致碾牌。同樣提供了動(dòng)態(tài)損失縮放。使用權(quán)重備份是為了減少舍入誤差儡循,使用損失縮放是為了避免數(shù)據(jù)溢出小染。
五、Label smoothing
1)使用cross-entropy的問(wèn)題
傳統(tǒng)one-hot編碼標(biāo)簽的網(wǎng)絡(luò)學(xué)習(xí)過(guò)程中贮折,鼓勵(lì)模型預(yù)測(cè)為目標(biāo)類(lèi)別的概率趨近1裤翩,非目標(biāo)類(lèi)別的概率趨近0,即最終預(yù)測(cè)的logits向量(logits向量經(jīng)過(guò)softmax后輸出的就是預(yù)測(cè)的所有類(lèi)別的概率分布)中目標(biāo)類(lèi)別????zi的值會(huì)趨于無(wú)窮大调榄,使得模型向預(yù)測(cè)正確與錯(cuò)誤標(biāo)簽的logit差值無(wú)限增大的方向?qū)W習(xí)踊赠,而過(guò)大的logit差值會(huì)使模型缺乏適應(yīng)性,對(duì)它的預(yù)測(cè)過(guò)于自信每庆。在訓(xùn)練數(shù)據(jù)不足以覆蓋所有情況下筐带,這就會(huì)導(dǎo)致網(wǎng)絡(luò)過(guò)擬合,泛化能力差缤灵,而且實(shí)際上有些標(biāo)注數(shù)據(jù)不一定準(zhǔn)確伦籍,這時(shí)候使用交叉熵?fù)p失函數(shù)作為目標(biāo)函數(shù)也不一定是最優(yōu)的了蓝晒。
2)計(jì)算公式
3) 舉例說(shuō)明
p(cat) = 1
p(pig) = 0
p(dog) = 0
三分類(lèi)k=3,假設(shè)smoothing parameter
Cat = (1-0.1)[1,0,0]+0.1/3
=[0.9,0,0]+0.03
=[0.933,0.03,0.03]
Label smoothing就是使用[0.933,0.03,0.03]代替[1,0,0]
六帖鸦、Focal loss
focal loss 定義
在交叉熵的基礎(chǔ)上添加了一個(gè)可調(diào)節(jié)因子, 且
1)芝薇、當(dāng)一個(gè)樣本被誤分類(lèi)并且pt很小時(shí),調(diào)節(jié)因子接近1作儿,, 并且loss不受影響洛二。
2)、當(dāng) 攻锰,調(diào)節(jié)因子變?yōu)?晾嘶,F(xiàn)L=0, 降低分類(lèi)良好樣本的權(quán)重娶吞。 焦點(diǎn)參數(shù)γ平滑的調(diào)整了簡(jiǎn)單樣本被降權(quán)的概率垒迂。當(dāng)γ=0是,F(xiàn)L=CE妒蛇。 簡(jiǎn)單樣本:可以理解為正確分類(lèi)的樣本
3)机断、 alpha-focal loss
??調(diào)整正負(fù)樣本的權(quán)重。
七材部、梯度累加(加快訓(xùn)練速度)
如果只有單卡毫缆,且可以加載模型唯竹,但batch受限的話可以使用梯度累加乐导,進(jìn)行N次前向后反向更新一次參數(shù),相當(dāng)于擴(kuò)大了N倍的batch size浸颓。
正常的訓(xùn)練代碼是這樣的:
for i, (inputs, labels) in enumerate(training_set):
loss = model(inputs, labels) # 計(jì)算loss
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向計(jì)算梯度
optimizer.step() # 更新參數(shù)
加入梯度累加后:
for i, (inputs, labels) in enumerate(training_set):
loss = model(inputs, labels) # 計(jì)算loss
loss = loss / accumulation_steps
# Normalize our loss (if averaged)
loss.backward() # 反向計(jì)算梯度物臂,累加到之前梯度上
if (i+1) % accumulation_steps == 0:
optimizer.step() # 更新參數(shù)
model.zero_grad() # 清空梯度
要注意的是,batch擴(kuò)大后产上,如果想保持樣本權(quán)重相等棵磷,學(xué)習(xí)率也要線性擴(kuò)大或者適當(dāng)調(diào)整 。另外batchnorm也會(huì)受到影響晋涣,小batch下的均值和方差肯定不如大batch的精準(zhǔn)仪媒,可以調(diào)整BN中的momentum參數(shù)解決。
八谢鹊、LAMB(Layer-wise Adaptive Moments optimizer for Batch training)
LAMB:模型在進(jìn)行大批量數(shù)據(jù)訓(xùn)練時(shí)算吩,能夠維持梯度更新的精度。
LAMB主要是綜合了Adam和LARS(Layerwise Adaptive Rate Scaling)佃扼,對(duì)學(xué)習(xí)率進(jìn)行調(diào)整偎巢。上文提到當(dāng)batch變大時(shí)學(xué)習(xí)率也需要變大,這樣會(huì)導(dǎo)致收斂不穩(wěn)定兼耀,LARS通過(guò)給LR乘上權(quán)重與梯度的norm比值來(lái)解決這個(gè)問(wèn)題:
8.1)WarmUp
模型剛開(kāi)始訓(xùn)練的時(shí)候压昼,先使用一個(gè)較小的學(xué)習(xí)率求冷,訓(xùn)練一些epochs,等模型穩(wěn)定時(shí)再修改為預(yù)先設(shè)置的學(xué)習(xí)率窍霞。
為什么使用Warmup? 模型隨機(jī)初始化匠题,若選擇一個(gè)較大的學(xué)習(xí)率,可能會(huì)帶來(lái)模型的不穩(wěn)定官撼,選擇Warmup先訓(xùn)練幾個(gè)epochs, 之后梧躺,模型趨于穩(wěn)定,等模型穩(wěn)定之后在選擇預(yù)先設(shè)置的學(xué)習(xí)率可以加快模型的收斂速度傲绣,模型效果最佳掠哥。
九、文本數(shù)據(jù)增強(qiáng)
- 基礎(chǔ)的文本數(shù)據(jù)增強(qiáng)(EDA)[同義詞替換!]
- 閉包數(shù)據(jù)增強(qiáng)
- 無(wú)監(jiān)督數(shù)據(jù)增強(qiáng)(UDA)
- 對(duì)偶數(shù)據(jù)增強(qiáng)
EDA對(duì)于訓(xùn)練集中的給定句子秃诵,隨機(jī)選擇并執(zhí)行以下操作之一:
- 同義詞替換(SR):從句子中隨機(jī)選擇 n 個(gè)不是停用詞的詞续搀。 用隨機(jī)選擇的同義詞之一替換這些單詞中的每一個(gè)。
- 隨機(jī)插入 (RI):在句子中隨機(jī)找到一個(gè)詞菠净,并找出其同義詞禁舷,且該同義詞不是停用詞。 將該同義詞插入句子中的隨機(jī)位置毅往。 這樣做n次牵咙。
- 隨機(jī)交換(RS):隨機(jī)選擇句子中的兩個(gè)單詞并交換它們的位置。 這樣做n次攀唯。
- 隨機(jī)刪除(RD):以概率 p 隨機(jī)刪除句子中的每個(gè)單詞
閉包數(shù)據(jù)增強(qiáng)
數(shù)據(jù)集中每條數(shù)據(jù)有兩個(gè)句子 a, b, 1 a, c, 1 a, d, 0 a~b, a~c => b~c a~b, ad不相似 => bd不相
UDA(Unsupervised Data Augmentation for Consistency Training)用于一致性訓(xùn)練的無(wú)監(jiān)督數(shù)據(jù)增
十洁桌、sharpen
讓預(yù)測(cè)標(biāo)簽更接近真實(shí)標(biāo)簽,增大概率大的值侯嘀,減小概率小的值)
import torch.nn as nn
logits = torch.randn(2,3)
print(logits)
t_softmax = torch.softmax(logits, dim=1)
print(t_softmax)
t_sharpen = torch.softmax(logits/0.4, dim=1)
print(t_sharpen)
十一另凌、EMA
EMA在深度學(xué)習(xí)的優(yōu)化過(guò)程中,是t時(shí)刻的模型權(quán)重weights,
是t時(shí)刻的影子權(quán)重(shadow weights)戒幔。在梯度下降的過(guò)程中吠谢,會(huì)一直維護(hù)著這個(gè)影子權(quán)重,但是這個(gè)影子權(quán)重并不會(huì)參與訓(xùn)練诗茎」し唬基本的假設(shè)是,模型權(quán)重在最后的n步內(nèi)敢订,會(huì)在實(shí)際的最優(yōu)點(diǎn)處抖動(dòng)王污,所以我們?nèi)∽詈髇步的平均,能使得模型更加的魯棒枢析。
在保存模型或者評(píng)估模型時(shí)玉掸,會(huì)利用影子權(quán)重進(jìn)行評(píng)估,如果效果比當(dāng)前效果好醒叁,則保存影子權(quán)重的參數(shù)司浪,但是之后在繼續(xù)訓(xùn)練的時(shí)候會(huì)還原之前的參數(shù)進(jìn)行訓(xùn)練泊业。
class EMA():
def __init__(self, model, decay):
self.model = model
self.decay = decay
self.shadow = {}
self.backup = {}
def register(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
self.shadow[name] = param.data.clone()
def update(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
assert name in self.shadow
new_average = (1.0 - self.decay) * param.data + self.decay * self.shadow[name]
self.shadow[name] = new_average.clone()
def apply_shadow(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
assert name in self.shadow
self.backup[name] = param.data
param.data = self.shadow[name]
def restore(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
assert name in self.backup
param.data = self.backup[name]
self.backup = {}
# 初始化
ema = EMA(model, 0.999)
ema.register()
# 訓(xùn)練過(guò)程中,更新完參數(shù)后啊易,同步update shadow weights
def train():
optimizer.step()
ema.update()
# eval前吁伺,apply shadow weights;eval之后租谈,恢復(fù)原來(lái)模型的參數(shù)
def evaluate():
ema.apply_shadow()
# evaluate
ema.restore()
參考
[1]Explaining and Harnessing Adversarial Examples(https://arxiv.org/pdf/1412.6572.pdf)
[2]【煉丹技巧】功守道:NLP中的對(duì)抗訓(xùn)練 + PyTorch實(shí)現(xiàn)(https://zhuanlan.zhihu.com/p/91269728)
[3]蘇劍林. (Mar. 01, 2020). 《對(duì)抗訓(xùn)練淺談:意義篮奄、方法和思考(附Keras實(shí)現(xiàn)) 》[Blog post]. Retrieved from https://spaces.ac.cn/archives/7234
[4]Mixed Precision Training(https://arxiv.org/pdf/1710.03740.pdf)
[5]When Does Label Smoothing Help?(https://arxiv.org/pdf/1906.02629.pdf)
[6]Label Smoothing分析(https://zhuanlan.zhihu.com/p/302843504)
[7]Focal Loss for Dense Object Detection(https://arxiv.org/pdf/1708.02002.pdf)
[8]【煉丹技巧】指數(shù)移動(dòng)平均(EMA)的原理及PyTorch實(shí)現(xiàn)https://zhuanlan.zhihu.com/p/68748778