NLP 中的Mask全解


轉載來源:https://zhuanlan.zhihu.com/p/139595546
alt
Mask 在NLP中是一個很常規的操作,也有多種應用的場景和形式,下面嘗試從以下幾個方面去全(用了誇張的修辭手法)解Mask,並儘可能地輔以圖片說明和代碼解釋:

  • Mask的作用:
    • 處理非定長序列
      • RNN中的Mask
      • Attention中Mask
    • 防止標籤泄露
      • Transformer中的Mask
      • BERT中的Mask
      • XLNet中的Mask

Mask的作用

對於NLP中mask的作用,先上結論:

1、padding mask:處理非定長序列,區分padding和非padding部分,如在RNN等模型和Attention機制中的應用等
2、sequence mask:防止標籤泄露,如:Transformer decoder中的mask矩陣,BERT中的[Mask]位,XLNet中的mask矩陣等
PS:padding mask 和 sequence mask非官方命名

處理非定長序列

在NLP中,文本一般是不定長的,所以在進行 batch訓練之前,要先進行長度的統一,過長的句子可以通過truncating 截斷到固定的長度,過短的句子可以通過 padding 增加到固定的長度,但是 padding 對應的字符只是爲了統一長度,並沒有實際的價值,因此希望在之後的計算中屏蔽它們,這時候就需要 Mask。
alt
圖片參考
上圖爲中文場景下,一個 batch=5 的,以字爲單位的輸入矩陣(也可以在分詞後以詞爲單位)和 mask 矩陣,左圖已經將文本 padding 到統一長度了,右圖中的1表示有效字,0代表無效字。

RNN中的Mask

對於RNN等模型,本身是可以直接處理不定長數據的,因此它不需要提前告知 sequence length,如下是pytorch下的LSTM定義:

nn.LSTM(input_size, hidden_size, *args, **kwargs)

但是在實踐中,爲了 batch 訓練,一般會把不定長的序列 padding 到相同長度,再用 mask 去區分非 padding 部分和 padding 部分。

區分的目的是使得RNN只作用到它實際長度的句子,而不會處理無用的 padding 部分,這樣RNN的輸出和隱狀態都會是對應句子實際的最後一位。另外,對於token級別的任務,也可以通過mask去忽略 padding 部分對應的loss。

不過,在 pytorch 中,對 mask 的具體實現形式不是mask矩陣,而是通過一個句子長度列表來實現的,但本質一樣。實現如下,sentence_lens 表示的是這個batch中每一個句子的實際長度。參考

embed_input_x_packed = pack_padded_sequence(embed_input_x, sentence_lens, batch_first=True)
encoder_outputs_packed, (h_last, c_last) = self.lstm(embed_input_x_packed)
encoder_outputs, _ = pad_packed_sequence(encoder_outputs_packed, batch_first=True)

btw,在 pytorch 的 Embedding 和 Loss 中也有對 padding 值的設置:

# padding_idx (int, optional): If given, pads the output with the embedding vector at 
# `padding_idx` (initialized to zeros) whenever it encounters the index.
embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)

# ignore_index (int, optional): Specifies a target value that is ignored
# and does not contribute to the input gradient.
criterion = nn.CrossEntropyLoss(ignore_index=0)

Attention中Mask

在 Attention 機制中,同樣需要忽略 padding 部分的影響,這裏以transformer encoder中的self-attention爲例:

self-attention中,Q和K在點積之後,需要先經過mask再進行softmax,因此,對於要屏蔽的部分,mask之後的輸出需要爲負無窮,這樣softmax之後輸出才爲0。
alt
圖片參考

def attention(query, key, value, mask=None, dropout=None):
    "Compute 'Scaled Dot Product Attention'"
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-2, -1)) \
             / math.sqrt(d_k)
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -1e9) # mask步驟,用 -1e9 代表負無窮
    p_attn = F.softmax(scores, dim = -1)
    if dropout is not None:
        p_attn = dropout(p_attn)
    return torch.matmul(p_attn, value), p_attn

代碼參考

PS:上述兩個參考都是非常好的 transformer 介紹文章,參考1,圖片與文字相得益彰,參考2,代碼與講解相輔相成。

防止標籤泄露

在語言模型中,常常需要從上一個詞預測下一個詞,但如果要在LM中應用 self attention 或者是同時使用上下文的信息,要想不泄露要預測的標籤信息,就需要 mask 來“遮蓋”它。不同的mask方式,也對應了一篇篇的paper,這裏選取典型的幾個。

Transformer中的Mask

Transformer 是包括 Encoder和 Decoder的,Encoder中 self-attention 的 padding mask 如上,而 Decoder 還需要防止標籤泄露,即在 t 時刻不能看到 t 時刻之後的信息,因此在上述 padding mask的基礎上,還要加上 sequence mask。

sequence mask 一般是通過生成一個上三角矩陣來實現的,上三角區域對應要mask的部分。

在Transformer 的 Decoder中,先不考慮 padding mask,一個包括四個詞的句子[A,B,C,D]在計算了相似度scores之後,得到下面第一幅圖,將scores的上三角區域mask掉,即替換爲負無窮,再做softmax得到第三幅圖。這樣,比如輸入 B 在self-attention之後,也只和A,B有關,而與後序信息無關。

因爲在softmax之後的加權平均中: B’ = 0.48A+0.52B,而 C,D 對 B’不做貢獻。
alt
圖片參考

實際應用中,Decoder 需要結合 padding mask 和 sequence mask,下面在pytorch框架下以一個很簡化的例子展示 Transformer 中 的兩種 Mask。

import torch

def padding_mask(seq, pad_idx):
    return (seq != pad_idx).unsqueeze(-2)   # [B, 1, L]

def sequence_mask(seq):
    batch_size, seq_len = seq.size()
    mask = 1- torch.triu(torch.ones((seq_len, seq_len), dtype=torch.uint8),diagonal=1)
    mask = mask.unsqueeze(0).expand(batch_size, -1, -1)  # [B, L, L]
    return mask

def test():
    # 以最簡化的形式測試Transformer的兩種mask
    seq = torch.LongTensor([[1,2,0]]) # batch_size=1, seq_len=3,padding_idx=0
    embedding = torch.nn.Embedding(num_embeddings=3, embedding_dim=10, padding_idx=0)
    query, key = embedding(seq), embedding(seq)
    scores = torch.matmul(query, key.transpose(-2, -1))

    mask_p = padding_mask(seq, 0)
    mask_s = sequence_mask(seq)
    mask_decoder = mask_p & mask_s # 結合 padding mask 和 sequence mask

    scores_encoder = scores.masked_fill(mask_p==0, -1e9) # 對於scores,在mask==0的位置填充
    scores_decoder = scores.masked_fill(mask_decoder==0, -1e9)

test()

對應的各mask值爲:

# mask_p
[[[1 1 0]]]
# mask_s
[[[1 0 0]
  [1 1 0]
  [1 1 1]]]
# mask_decoder
[[[1 0 0]
  [1 1 0]
  [1 1 0]]]

可以看到 mask_decoder 的第三列爲0 ,對應padding mask,上三角爲0,對應sequence mask。

BERT中的Mask

BERT實際上是Transformer的Encoder,爲了在語言模型的訓練中,使用上下文信息又不泄露標籤信息,採用了Masked LM,簡單來說就是隨機的選擇序列的部分token用 [Mask] 標記代替。

這波Mask操作,思想很直接,實現很簡單,效果很驚人。
alt
BERT之後,也有不少在Mask的範圍和形式上做文章的,比如:ERNIE,但大同小異,不多贅述。

而XLNet的Mask操作非常的巧(nan)妙(dong),如下。

XLNet中的Mask

XLNet通過Permutation Language Modeling實現了不在輸入中加[Mask],同樣可以利用上下文信息,關鍵的操作就是下面所示的 Attention Mask 機制。
alt
但並不是那麼好理解,要理解XLNet中的Mask,一定要先看張俊林老師的:XLNet:運行機制及和Bert的異同比較,再來看下面的內容。上圖也是引自該文,這裏再引用一句我認爲非常關鍵的一段話:

在Transformer內部,通過Attention掩碼,從X的輸入單詞裏面,也就是Ti的上文和下文單詞中,隨機選擇i-1個,放到Ti的上文位置中,把其它單詞的輸入通過Attention掩碼隱藏掉,於是就能夠達成我們期望的目標(當然這個所謂放到Ti的上文位置,只是一種形象的說法,其實在內部,就是通過Attention Mask,把其它沒有被選到的單詞Mask掉,不讓它們在預測單詞Ti的時候發生作用,如此而已。看着就類似於把這些被選中的單詞放到了上文Context_before的位置了)

對於排列序列:3->2->4->1,通過 Attention Mask,在 self-attention 的加權平均計算中,以上圖中的E2E_{2{}'}爲例:

self-attention 計算之後Content stream中的 E2=a2E2+a3E3E_{2'}=a_{2}E_{2}+a_{3}E_{3},其中E2E_{2}表示第2個詞對應的向量,其他同理。這樣在E2E_{2'}中就看到了它的下文E3E_{3},就好像是把E3E_{3}放到了它的上文位置一樣,但實際順序並沒有改變。

對序列進行排列的目的是爲了生成這個 Attention Mask,再加上雙流注意力去解決預測歧義的問題,可以說就是Permutation Language Modeling 的全部了。

在評論中有一個非常好的問題:爲什麼不直接在attention掩碼矩陣中只把當前的單詞掩蓋住來獲取上下文的特徵呢?直接mask住左上到右下的對角線構建雙向語言模型不行嗎?

XLNet實際上仍然遵循語言模型的預測模式,即從左往右依次預測,如對於排列序列:3->2->4->1,預測 2 時用到了 3 信息,預測 4 時用到了3、2信息.……因此,它本質還是需要一個上三角/下三角mask矩陣,在上圖的content stream矩陣中,把第4行的左邊兩個紅點移到第4列中去,就變成了一個三角矩陣了,和Transformer decoder中的mask矩陣一樣。

那爲什麼要這樣的三角矩陣呢?直接mask住左上到右下的對角線可以嘛?答案是不行,mask掉對角線在預測時可以不看到當前詞,但是會提前看到後面的詞,這樣在預測後面詞的時候就相當於提前發生了泄露。

另外,要提及的一點是,XLNet的從左到右和一般語言模型(如GPT)的從左到右稍微有一點區別,GPT的輸入和輸出是錯位一個時間步的,即讀入 1,2,3,然後在 3 的位置預測 4;而 XLNet 的輸入和輸出時間步數是相同的(這一點類似於 BERT),輸入 1,2,3,4 並在 4 的位置預測 4。當然,XLNet 不會讀取第 4 個時間步的單詞(否則有信息泄露),僅僅利用位置 4 的 position embedding,告訴模型現在想要預測第 4 個位置,所以最終預測是用的query stream,不含當前時間步信息。這一小點參考

到這裏,就是本文全部的Mask,但這肯定不是NLP中Mask的全部,但希望能幫助你去更好地理解Mask。

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