对抗训练浅谈:意义、方法和思考(附Keras实现)
?PaperWeekly 原創 ·?作者|蘇劍林
單位|追一科技
研究方向|NLP、神經網絡
當前,說到深度學習中的對抗,一般會有兩個含義:一個是生成對抗網絡(Generative Adversarial Networks,GAN),代表著一大類先進的生成模型;另一個則是跟對抗攻擊、對抗樣本相關的領域,它跟 GAN 相關,但又很不一樣,它主要關心的是模型在小擾動下的穩健性。
本人之前所涉及的對抗話題,都是前一種含義,而今天,我們來聊聊后一種含義中的“對抗訓練”。
本文包括如下內容:
對抗樣本、對抗訓練等基本概念的介紹;
介紹基于快速梯度上升的對抗訓練及其在 NLP 中的應用;
給出了對抗訓練的 Keras 實現(一行代碼調用);
討論了對抗訓練與梯度懲罰的等價性;
基于梯度懲罰,給出了一種對抗訓練的直觀的幾何理解。
方法介紹
近年來,隨著深度學習的日益發展和落地,對抗樣本也得到了越來越多的關注。
在 CV 領域,我們需要通過對模型的對抗攻擊和防御來增強模型的穩健型,比如在自動駕駛系統中,要防止模型因為一些隨機噪聲就將紅燈識別為綠燈。
在 NLP 領域,類似的對抗訓練也是存在的,不過 NLP 中的對抗訓練更多是作為一種正則化手段來提高模型的泛化能力。
這使得對抗訓練成為了 NLP 刷榜的“神器”之一,前有微軟通過 RoBERTa+ 對抗訓練在 GLUE [1] 上超過了原生 RoBERTa,后有我司的同事通過對抗訓練刷新了 CoQA [2] 榜單。這也成功引起了筆者對它的興趣,遂學習了一番,分享在此。
基本概念
要認識對抗訓練,首先要了解“對抗樣本”,它首先出現在論文 Intriguing properties of neural networks [3] 之中。
簡單來說,它是指對于人類來說“看起來”幾乎一樣、但對于模型來說預測結果卻完全不一樣的樣本,比如下面的經典例子:
理解對抗樣本之后,也就不難理解各種相關概念了,比如“對抗攻擊”,其實就是想辦法造出更多的對抗樣本,而“對抗防御”,就是想辦法讓模型能正確識別更多的對抗樣本。
所謂對抗訓練,則是屬于對抗防御的一種,它構造了一些對抗樣本加入到原數據集中,希望增強模型對對抗樣本的魯棒性;同時,如本文開篇所提到的,在 NLP 中它通常還能提高模型的表現。
Min-Max
總的來說,對抗訓練可以統一寫成如下格式:
其中?代表訓練集,代表輸入,代表標簽,是模型參數,是單個樣本的 loss,是對抗擾動,是擾動空間。這個統一的格式首先由論文 Towards Deep Learning Models Resistant to Adversarial Attacks [4]?提出。
這個式子可以分步理解如下:
往屬于里邊注入擾動?,的目標是讓?越大越好,也就是說盡可能讓現有模型的預測出錯;
當然?也不是無約束的,它不能太大,否則達不到“看起來幾乎一樣”的效果,所以?要滿足一定的約束,常規的約束是?,其中?是一個常數;
每個樣本都構造出對抗樣本之后,用作為數據對去最小化loss來更新參數?(梯度下降);
反復交替執行 1、2、3 步。
由此觀之,整個優化過程是?和?交替執行,這確實跟 GAN 很相似,不同的是,GAN 所?的自變量也是模型的參數,而這里?的自變量則是輸入(的擾動量),也就是說要對每一個輸入都定制一步?。
快速梯度
現在的問題是如何計算?,它的目標是增大?,而我們知道讓 loss 減少的方法是梯度下降,那反過來,讓 loss 增大的方法自然就是梯度上升,因此可以簡單地取:
當然,為了防止?過大,通常要對?做些標準化,比較常見的方式是:
有了?之后,就可以代回式 (1) 進行優化:
這就構成了一種對抗訓練方法,被稱為?Fast Gradient Method(FGM),它由 GAN 之父 Goodfellow 在論文 Explaining and Harnessing Adversarial Examples [5] 首先提出。
此外,對抗訓練還有一種方法,叫做?Projected Gradient Descent(PGD),其實就是通過多迭代幾步來達到讓?更大的?。
如果迭代過程中模長超過了?,就縮放回去,細節請參考Towards Deep Learning Models Resistant to Adversarial Attacks [6]。
但本文不旨在對對抗學習做完整介紹,而且筆者認為它不如 FGM 漂亮有效,所以本文還是以 FGM 為重點。關于對抗訓練的補充介紹,建議有興趣的讀者閱讀富邦同學寫的功守道:NLP中的對抗訓練 + PyTorch實現。
回到NLP
對于 CV 領域的任務,上述對抗訓練的流程可以順利執行下來,因為圖像可以視為普通的連續實數向量,也是一個實數向量,因此?依然可以是有意義的圖像。
但 NLP 不一樣,NLP 的輸入是文本,它本質上是 one hot 向量(如果還沒認識到這一點,歡迎閱讀詞向量與 Embedding 究竟是怎么回事?[7],而兩個不同的 one hot 向量,其歐氏距離恒為?,因此對于理論上不存在什么“小擾動”。
一個自然的想法是像論文 Adversarial Training Methods for Semi-Supervised Text Classification [8] 一樣,將擾動加到 Embedding 層。
這個思路在操作上沒有問題,但問題是,擾動后的 Embedding 向量不一定能匹配上原來的 Embedding 向量表,這樣一來對 Embedding 層的擾動就無法對應上真實的文本輸入,這就不是真正意義上的對抗樣本了,因為對抗樣本依然能對應一個合理的原始輸入。
那么,在 Embedding 層做對抗擾動還有沒有意義呢?有!實驗結果顯示,在很多任務中,在 Embedding 層進行對抗擾動能有效提高模型的性能。
實驗結果
既然有效,那我們肯定就要親自做實驗驗證一下了。怎么通過代碼實現對抗訓練呢?怎么才能做到用起來盡可能簡單呢?最后用起來的效果如何呢?
思路分析
對于 CV 任務來說,一般輸入張量的 shape 是?,這時候我們需要固定模型的 batch size(即),然后給原始輸入加上一個 shape 同樣為?、全零初始化的Variable。
比如就叫做?,那么我們可以直接求 loss 對?的梯度,然后根據梯度給?賦值,來實現對輸入的干擾,完成干擾之后再執行常規的梯度下降。
對于 NLP 任務來說,原則上也要對 Embedding 層的輸出進行同樣的操作,Embedding 層的輸出 shape 為?,所以也要在 Embedding 層的輸出加上一個 shape 為?的Variable,然后進行上述步驟。但這樣一來,我們需要拆解、重構模型,對使用者不夠友好。
不過,我們可以退而求其次。Embedding 層的輸出是直接取自于 Embedding 參數矩陣的,因此我們可以直接對 Embedding 參數矩陣進行擾動。
這樣得到的對抗樣本的多樣性會少一些(因為不同樣本的同一個 token 共用了相同的擾動),但仍然能起到正則化的作用,而且這樣實現起來容易得多。
代碼參考
基于上述思路,這里給出 Keras 下基于 FGM 方式對 Embedding 層進行對抗訓練的參考實現:
https://github.com/bojone/keras_adversarial_training
核心代碼如下:
def?adversarial_training(model,?embedding_name,?epsilon=1):"""給模型添加對抗訓練其中model是需要添加對抗訓練的keras模型,embedding_name則是model里邊Embedding層的名字。要在模型compile之后使用。"""if?model.train_function?is?None:??#?如果還沒有訓練函數model._make_train_function()??#?手動makeold_train_function?=?model.train_function??#?備份舊的訓練函數#?查找Embedding層for?output?in?model.outputs:embedding_layer?=?search_layer(output,?embedding_name)if?embedding_layer?is?not?None:breakif?embedding_layer?is?None:raise?Exception('Embedding?layer?not?found')#?求Embedding梯度embeddings?=?embedding_layer.embeddings??#?Embedding矩陣gradients?=?K.gradients(model.total_loss,?[embeddings])??#?Embedding梯度gradients?=?K.zeros_like(embeddings)?+?gradients[0]??#?轉為dense?tensor#?封裝為函數inputs?=?(model._feed_inputs?+model._feed_targets?+model._feed_sample_weights)??#?所有輸入層embedding_gradients?=?K.function(inputs=inputs,outputs=[gradients],name='embedding_gradients',)??#?封裝為函數def?train_function(inputs):??#?重新定義訓練函數grads?=?embedding_gradients(inputs)[0]??#?Embedding梯度delta?=?epsilon?*?grads?/?(np.sqrt((grads**2).sum())?+?1e-8)??#?計算擾動K.set_value(embeddings,?K.eval(embeddings)?+?delta)??#?注入擾動outputs?=?old_train_function(inputs)??#?梯度下降K.set_value(embeddings,?K.eval(embeddings)?-?delta)??#?刪除擾動return?outputsmodel.train_function?=?train_function??#?覆蓋原訓練函數?
定義好上述函數后,給 Keras 模型增加對抗訓練就只需要一行代碼了:
?
需要指出的是,由于每一步算對抗擾動也需要計算梯度,因此每一步訓練一共算了兩次梯度,因此每步的訓練時間會翻倍。
效果比較
為了測試實際效果,筆者選了中文 CLUE 榜?[9] 的兩個分類任務:IFLYTEK和TNEWS,模型選擇了中文 BERT base。
在 CLUE 榜單上,BERT base 模型在這兩個數據上的成績分別是 60.29% 和56.58%,經過對抗訓練后,成績為 62.46%、57.66%,分別提升了 2% 和 1%!
訓練腳本請參考:
https://github.com/bojone/bert4keras/blob/master/examples/task_iflytek_adversarial_training.py
當然,同所有正則化手段一樣,對抗訓練也不能保證每一個任務都能有提升,但從目前大多數“戰果”來看,它是一種非常值得嘗試的技術手段。
此外,BERT 的 finetune 本身就是一個非常玄乎(靠人品)的過程,前些時間論文Fine-Tuning Pretrained Language Models: Weight Initializations, Data Orders, and Early Stopping [10] 換用不同的隨機種子跑了數百次 finetune 實驗,發現最好的結果能高出好幾個點,所以如果你跑了一次發現沒提升,不妨多跑幾次再下結論。
延伸思考
在這一節中,我們從另一個視角對上述結果進行分析,從而推出對抗訓練的另一種方法,并且得到一種關于對抗訓練的更直觀的幾何理解。
梯度懲罰
假設已經得到對抗擾動?,那么我們在更新?時,考慮對?的展開:
對應的?的梯度為:
代入?,得到:
這個結果表示,對輸入樣本施加?的對抗擾動,一定程度上等價于往 loss 里邊加入梯度懲罰。
如果對抗擾動是?,那么對應的梯度懲罰項則是?(少了個,也少了個2次方)。
事實上,這個結果不是新的,據筆者所知,它首先出現論文 Improving the Adversarial Robustness and Interpretability of Deep Neural Networks by Regularizing their Input Gradients?[11] 里。
只不過這篇文章不容易搜到,因為你一旦搜索“adversarial training gradient penalty”等關鍵詞,出來的結果幾乎都是 WGAN-GP 相關的東西。
幾何圖像
事實上,關于梯度懲罰,我們有一個非常直觀的幾何圖像。以常規的分類問題為例,假設有?個類別,那么模型相當于挖了?個坑,然后讓同類的樣本放到同一個坑里邊去:
梯度懲罰則說同類樣本不僅要放在同一個坑內,還要放在坑底,這就要求每個坑的內部要長這樣:
為什么要在坑底呢?因為物理學告訴我們,坑底最穩定呀,所以就越不容易受干擾呀,這不就是對抗訓練的目的么?
那坑底意味著什么呢?極小值點呀,導數(梯度)為零呀,所以不就是希望?越小越好么?
這便是梯度懲罰 (8) 的幾何意義了。類似的“挖坑”、“坑底”與梯度懲罰的幾何圖像,還可以參考能量視角下的GAN模型:GAN=“挖坑”+“跳坑”。
L約束
我們還可以從 L 約束(Lipschitz 約束)的角度來看梯度懲罰。所謂對抗樣本,就是輸入的小擾動導致輸出的大變化,而關于輸入輸出的控制問題,我們之前在文章深度學習中的Lipschitz約束:泛化與生成模型就已經探討過。
一個好的模型,理論上應該是“輸入的小擾動導致導致輸出的小變化”,而為了保證這一遍,一個很常用的方案是讓模型滿足 L 約束,即存在常數?,使得
這樣一來只要兩個輸出的差距?足夠小,那么就能保證輸出的差距也足夠小。
而深度學習中的Lipschitz約束:泛化與生成模型已經討論了,實現 L 約束的方案之一就是譜歸一化(Spectral Normalization),所以往神經網絡里邊加入譜歸一化,就可以增強模型的對抗防御性能。
相關的工作已經被發表在 Generalizable Adversarial Training via Spectral Normalization [12]。
美中不足的是,譜歸一化是對模型的每一層權重都進行這樣的操作,結果就是神經網絡的每一層都滿足 L 約束,這是不必要的(我們只希望整個模型滿足 L 約束,不必強求每一層都滿足),因此理論上來說 L 約束會降低模型表達能力,從而降低模型性能。
而在 WGAN 系列模型中,為了讓判別器滿足 L 約束,除了譜歸一化外,還有一種常見的方案,那就是梯度懲罰。因此,梯度懲罰也可以理解為一個促使模型滿足 L 約束的正則項,而滿足 L 約束則能有效地抵御對抗樣本的攻擊。
代碼實現
既然梯度懲罰號稱能有類似的效果,那必然也是要接受實驗驗證的了。相比前面的 FGM 式對抗訓練,其實梯度懲罰實現起來還容易一些,因為它就是在 loss 里邊多加一項罷了,而且實現方式是通用的,不用區分 CV 還是 NLP。
Keras 參考實現如下:
def?sparse_categorical_crossentropy(y_true,?y_pred):"""自定義稀疏交叉熵這主要是因為keras自帶的sparse_categorical_crossentropy不支持求二階梯度。"""y_true?=?K.reshape(y_true,?K.shape(y_pred)[:-1])y_true?=?K.cast(y_true,?'int32')y_true?=?K.one_hot(y_true,?K.shape(y_pred)[-1])return?K.categorical_crossentropy(y_true,?y_pred)def?loss_with_gradient_penalty(y_true,?y_pred,?epsilon=1):"""帶梯度懲罰的loss"""loss?=?K.mean(sparse_categorical_crossentropy(y_true,?y_pred))embeddings?=?search_layer(y_pred,?'Embedding-Token').embeddingsgp?=?K.sum(K.gradients(loss,?[embeddings])[0].values**2)return?loss?+?0.5?*?epsilon?*?gpmodel.compile(loss=loss_with_gradient_penalty,optimizer=Adam(2e-5),metrics=['sparse_categorical_accuracy'], )?
可以看到,定義帶梯度懲罰的 loss 非常簡單,就兩行代碼而已。需要指出的是,梯度懲罰意味著參數更新的時候需要算二階導數,但是 Tensorflow 和 Keras 自帶的 loss 函數不一定支持算二階導數。
比如K.categorical_crossentropy支持而K.sparse_categorical_crossentropy不支持,遇到這種情況時,需要自定重新定義 loss。
效果比較
還是前面兩個任務,結果如下表。可以看到,梯度懲罰能取得跟 FGM 基本一致的結果。
完整的代碼請參考:
https://github.com/bojone/bert4keras/blob/master/examples/task_iflytek_gradient_penalty.py
本文小結
本文簡單介紹了對抗訓練的基本概念和推導,著重講了其中的 FGM 方法并給出了 Keras 實現,實驗證明它能提高一些 NLP 模型的泛化性能。此外,本文還討論了對抗學習與梯度懲罰的聯系,并給出了梯度懲罰的一種直觀的幾何理解。
相關鏈接
[1] https://gluebenchmark.com/leaderboard
[2] https://stanfordnlp.github.io/coqa/
[3] http://https://arxiv.org/abs/1312.6199
[4] https://arxiv.org/abs/1706.06083
[5] https://arxiv.org/abs/1412.6572
[6] https://arxiv.org/abs/1706.06083
[7] https://kexue.fm/archives/4122
[8] https://arxiv.org/abs/1605.07725
[9] https://www.cluebenchmarks.com/
[10] https://arxiv.org/abs/2002.06305
[11] https://arxiv.org/abs/1711.09404
[12] https://arxiv.org/abs/1811.07457
點擊以下標題查看更多往期內容:?
圖自編碼器的起源和應用
圖神經網絡三劍客:GCN、GAT與GraphSAGE
如何快速理解馬爾科夫鏈蒙特卡洛法?
深度學習預訓練模型可解釋性概覽
ICLR 2020 | 隱空間的圖神經網絡
????
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
總結
以上是生活随笔為你收集整理的对抗训练浅谈:意义、方法和思考(附Keras实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软 Xbox Game Pass 和
- 下一篇: 曲奇云盘因平台运营问题停服:腾讯云关闭服