©PaperWeekly 原創 · 作者|蘇劍林
單位|追一科技
研究方向|NLP、神經網絡
提高模型的泛化性能是機器學習致力追求的目標之一。常見的提高泛化性的方法主要有兩種:第一種是添加噪聲,比如往輸入添加高斯噪聲、中間層增加 Dropout 以及進來比較熱門的對抗訓練等,對圖像進行隨機平移縮放等數據擴增手段某種意義上也屬於此列;第二種是往 loss 裏邊添加正則項,比如 , 懲罰、梯度懲罰等。本文試圖探索幾種常見的提高泛化性能的手段的關聯。
隨機噪聲
我們記模型爲 f(x), 爲訓練數據集合,l(f(x), y) 爲單個樣本的 loss,那麼我們的優化目標是:
是 f(x) 裏邊的可訓練參數。假如往模型輸入添加噪聲 ,其分佈爲 ,那麼優化目標就變爲:
當然,可以添加噪聲的地方不僅僅是輸入,也可以是中間層,也可以是權重 ,甚至可以是輸出 y(等價於標籤平滑),噪聲也不一定是加上去的,比如 Dropout 是乘上去的。對於加性噪聲來說, 的常見選擇是均值爲 0、方差固定的高斯分佈;而對於乘性噪聲來說,常見選擇是均勻分佈 U([0,1]) 或者是伯努利分佈。
添加隨機噪聲的目的很直觀,就是希望模型能學會抵禦一些隨機擾動,從而降低對輸入或者參數的敏感性,而降低了這種敏感性,通常意味着所得到的模型不再那麼依賴訓練集,所以有助於提高模型泛化性能。
提高效率
添加隨機噪聲的方式容易實現,而且在不少情況下確實也很有效,但它有一個明顯的缺點:不夠“特異性”。噪聲 是隨機的,而不是針對 x 構建的,這意味着多數情況下 可能只是一個平凡樣本,也就是沒有對原模型造成比較明顯的擾動,所以對泛化性能的提高幫助有限。
增加採樣
從理論上來看,加入隨機噪聲後,單個樣本的 loss 變爲:
但實踐上,對於每個特定的樣本 (x,y),我們一般只採樣一個噪聲,所以並沒有很好地近似上式。當然,我們可以採樣多個噪聲 ,然後更好地近似:
但這樣相當於 batch_size 擴大爲原來的 k 倍,增大了計算成本,並不是那麼友好。
近似展開
一個直接的想法是,如果能事先把式 (3) 中的積分算出來,那就用不着低效率地採樣了(或者相當於一次性採樣無限多的噪聲)。我們就往這個方向走一下試試。當然,精確的顯式積分基本上是做不到的,我們可以做一下近似展開:
然後兩端乘以 積分,這裏假設 的各個分量是獨立同分布的,並且均值爲 0、方差爲 ,那麼積分結果就是:
這裏的 是拉普拉斯算子,即 。這個結果在形式上很簡單,就是相當於往 loss 裏邊加入正則項 ,然而實踐上卻相當困難,因爲這意味着要算 l 的二階導數,再加上梯度下降,那麼就一共要算三階導數,這是現有深度學習框架難以高效實現的。
轉移目標
直接化簡 的積分是行不通了,但我們還可以試試將優化目標換成:
也就是變成同時縮小 的差距,兩者雙管齊下,一定程度上也能達到縮小 差距的目標。關鍵的是,這個目標能得到更有意思的結果。
思路解析
用數學的話來講,如果 l 是某種形式的距離度量,那麼根據三角不等式就有:
如果 l 不是度量,那麼通常根據詹森不等式也能得到一個類似的結果,比如 ,那麼我們有:
這也就是說,目標 (7)(的若干倍)可以認爲是 的上界,原始目標不大好優化,所以我們改爲優化它的上界。
注意到,目標 (7) 的兩項之中, 衡量了模型本身的平滑程度,跟標籤沒關係,用無標籤數據也可以對它進行優化,這意味着它可以跟帶標籤的數據一起,構成一個半監督學習流程。
勇敢地算
對於目標 (7) 來說,它的積分結果是:
還是老路子,近似展開 :
很恐怖?不着急,我們回顧一下,作爲 loss 函數的 l,它一般會有如下幾個特點:
1. l是光滑的;
2. l(x, x)=0;
3. 。
這其實就是說 l 是光滑的,並且在 x=y 的時候取到極(小)值,且極(小)值爲 0,這幾個特點幾乎是所有 loss 的共性了。基於這幾個特點,恐怖的 (11) 式的前三項就直接爲 0 了,所以最後的積分結果是:
梯度懲罰
看上去依然讓人有些心悸,但總比 (11) 好多了。上式也是一個正則項,其特點是隻包含一階梯度項,而對於特定的損失函數, 可以提前算出來,我們記爲 ,那麼:
這其實就是對每個 f(x) 的每個分量都算一個梯度懲罰項 ,然後按 加權求和。
對於 MSE 來說,,這時候可以算得 ,所以對應的正則項爲 ;對於 KL 散度來說,,這時候 ,那麼對應的正則項爲 。
這些結果大家多多少少可以從著名的“花書”《深度學習》[1] 中找到類似的,所以並不是什麼新的結果。類似的推導還可以參考文獻 Training with noise is equivalent to Tikhonov regularization [2]。
採樣近似
當然,雖然能求出只帶有一階梯度的正則項 ,但事實上這個計算量也不低,因爲需要對每個 都要求梯度,如果輸出的分量數太大,這個計算量依然難以承受。
這時候可以考慮的方案是通過採樣近似計算:假設 是均值爲 0、方差爲 1 的分佈,那麼我們有:
這樣一來,每步我們只需要算 的梯度,不需要算多次梯度。 的一個最簡單的取法是空間爲 的均勻分佈,也就是 等概率地從 中選取一個。
對抗訓練
回顧前面的流程,我們先是介紹了添加隨機噪聲這一增強泛化性能的手段,然後指出隨機加噪聲可能太沒特異性,所以想着先把積分算出來,纔有了後面推導的關於近似展開與梯度懲罰的一些結果。那麼換個角度來想,如果我們能想辦法更特異性地構造噪聲信號,那麼也能提高訓練效率,增強泛化性能了。
監督對抗
有監督的對抗訓練,關注的是原始目標 (3),優化的目標是讓 loss 儘可能小,所以如果我們要選擇更有代表性的噪聲,那麼應該選擇能讓 loss 變得更大的噪聲,而:
所以讓 儘可能大就意味着 要跟 同向,換言之擾動要往梯度上升方向走,即:
這便構成了對抗訓練中的 FGM 方法,之前在對抗訓練淺談:意義、方法和思考(附Keras實現)就已經介紹過了。
值得注意的是,在對抗訓練淺談:意義、方法和思考(附Keras實現)一文中我們也推導過,對抗訓練在一定程度上也等價於往 loss 裏邊加入梯度懲罰項
虛擬對抗
在前面我們提到,
如果沿着對抗訓練的思想,我們不去計算積分,而是去尋找讓
基於前面對損失函數 l 的性質的討論,我們知道
這裏用
1. 如何高效計算 Hessian 矩陣
2. 如何求單位向量 u 使得
事實上,不難證明 u 的最優解實際上就是“
從一個隨機向量
在冪迭代中,我們發現並不需要知道
其中
初始化向量
迭代 r 次:
用
實驗表明一般迭代 1 次就不錯了,而如果迭代 0 次,那麼就是本文開頭提到的添加高斯噪聲。這表明虛擬對抗訓練就是通過
參考實現
關於對抗訓練的 Keras 實現,在對抗訓練淺談:意義、方法和思考(附Keras實現)一文中已經給出過,這裏筆者給出 Keras 下虛擬對抗訓練的參考實現:
def virtual_adversarial_training(
model, embedding_name, epsilon=1, xi=10, iters=1
):
"""給模型添加虛擬對抗訓練
其中model是需要添加對抗訓練的keras模型,embedding_name
則是model裏邊Embedding層的名字。要在模型compile之後使用。
"""
if model.train_function is None: # 如果還沒有訓練函數
model._make_train_function() # 手動make
old_train_function = model.train_function # 備份舊的訓練函數
# 查找Embedding層
for output in model.outputs:
embedding_layer = search_layer(output, embedding_name)
if embedding_layer is not None:
break
if 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
) # 所有輸入層
model_outputs = K.function(
inputs=inputs,
outputs=model.outputs,
name='model_outputs',
) # 模型輸出函數
embedding_gradients = K.function(
inputs=inputs,
outputs=[gradients],
name='embedding_gradients',
) # 模型梯度函數
def l2_normalize(x):
return x / (np.sqrt((x**2).sum()) + 1e-8)
def train_function(inputs): # 重新定義訓練函數
outputs = model_outputs(inputs)
inputs = inputs[:2] + outputs + inputs[3:]
delta1, delta2 = 0.0, np.random.randn(*K.int_shape(embeddings))
for _ in range(iters): # 迭代求擾動
delta2 = xi * l2_normalize(delta2)
K.set_value(embeddings, K.eval(embeddings) - delta1 + delta2)
delta1 = delta2
delta2 = embedding_gradients(inputs)[0] # Embedding梯度
delta2 = epsilon * l2_normalize(delta2)
K.set_value(embeddings, K.eval(embeddings) - delta1 + delta2)
outputs = old_train_function(inputs) # 梯度下降
K.set_value(embeddings, K.eval(embeddings) - delta2) # 刪除擾動
return outputs
model.train_function = train_function # 覆蓋原訓練函數
# 寫好函數後,啓用虛擬對抗訓練只需要一行代碼
virtual_adversarial_training(model_vat, 'Embedding-Token')
完整的使用腳本請參考:
https://github.com/bojone/bert4keras/blob/master/examples/task_sentiment_virtual_adversarial_training.py
大概是將模型建立兩次,一個模型通過標註數據正常訓練,一個模型通過無標註數據虛擬對抗訓練,兩者交替執行,請讀懂源碼後再使用,不要亂套代碼。實驗任務爲情況分類,大約有 2 萬的標註數據,取前 200 個作爲標註樣本,剩下的作爲無標註數據,VAT 和非 VAT 的表現對比如下(每個實驗都重複了三次,取平均):
文章小結
本文先介紹了添加隨機噪聲這一常規的正則化手段,然後通過近似展開與積分的過程,推導了它與梯度懲罰之間的聯繫,並從中引出了可以用於半監督訓練的模型平滑損失,接着進一步聯繫到了監督式的對抗訓練和半監督的虛擬對抗訓練,最後給出了 Keras 下虛擬對抗訓練的實現和例子。
參考鏈接
[1] https://book.douban.com/subject/27087503/
[2] https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/bishop-tikhonov-nc-95.pdf
[3] https://arxiv.org/abs/1704.03976
[4] https://en.wikipedia.org/wiki/Power_iteration
更多閱讀
#投 稿 通 道#
讓你的論文被更多人看到
如何才能讓更多的優質內容以更短路徑到達讀者羣體,縮短讀者尋找優質內容的成本呢?答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成爲一座橋樑,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學習心得或技術乾貨。我們的目的只有一個,讓知識真正流動起來。
???? 來稿標準:
• 稿件確係個人原創作品,來稿需註明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向)
• 如果文章並非首發,請在投稿時提醒並附上所有已發佈鏈接
• PaperWeekly 默認每篇文章都是首發,均會添加“原創”標誌
???? 投稿郵箱:
• 投稿郵箱:[email protected]
• 所有文章配圖,請單獨在附件中發送
• 請留下即時聯繫方式(微信或手機),以便我們在編輯發佈時和作者溝通
????
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關於PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公衆號後臺點擊「交流羣」,小助手將把你帶入 PaperWeekly 的交流羣裏。