泛化性亂彈:從隨機噪聲、梯度懲罰到虛擬對抗訓練

 

©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 裏邊加入梯度懲罰項 ,這又跟前一節的關於噪聲積分的結果類似。這表明梯度懲罰應該是通用的能提高模型性能的手段之一。

虛擬對抗

在前面我們提到, 這一項不需要標籤信號,因此可以用來做無監督學習,並且關於它的展開高斯積分我們得到了梯度懲罰 (13)

如果沿着對抗訓練的思想,我們不去計算積分,而是去尋找讓  儘可能大的擾動噪聲,這就構成了“虛擬對抗訓練(VAT)”,首次出現在文章 Virtual Adversarial Training: A Regularization Method for Supervised and Semi-Supervised Learning [3] 中。

基於前面對損失函數 l 的性質的討論,我們知道  關於  的一階梯度爲 0,所以要算對抗擾動,還必須將它展開到二階:

這裏用  表示不需要對裏邊的 x 求梯度。這樣一來,我們需要解決兩個問題:

1. 如何高效計算 Hessian 矩陣 

2. 如何求單位向量 u 使得  最大?

事實上,不難證明 u 的最優解實際上就是“ 的最大特徵根對應的特徵向量”,也稱爲“ 的主特徵向量”,而要近似求主特徵向量,一個行之有效的方法就是“冪迭代法 [4]”:

從一個隨機向量  出發,迭代執行 。相關推導可以參考深度學習中的Lipschitz約束:泛化與生成模型的“主特徵根”和“冪迭代”兩節。

在冪迭代中,我們發現並不需要知道  具體值,只需要知道  的值,這可以通過差分來近似計算:

其中  是一個標量常數。根據這個近似結果,我們就可以得到如下的 VAT 流程:

初始化向量 、標量  和 

迭代 r 次:

用  作爲 loss 執行常規梯度下降。

實驗表明一般迭代 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 的交流羣裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章