轉載來源:https://zhuanlan.zhihu.com/p/139595546
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。
圖片參考
上圖爲中文場景下,一個 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。
圖片參考
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’不做貢獻。
圖片參考
實際應用中,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操作,思想很直接,實現很簡單,效果很驚人。
BERT之後,也有不少在Mask的範圍和形式上做文章的,比如:ERNIE,但大同小異,不多贅述。
而XLNet的Mask操作非常的巧(nan)妙(dong),如下。
XLNet中的Mask
XLNet通過Permutation Language Modeling實現了不在輸入中加[Mask],同樣可以利用上下文信息,關鍵的操作就是下面所示的 Attention Mask 機制。
但並不是那麼好理解,要理解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 的加權平均計算中,以上圖中的爲例:
self-attention 計算之後Content stream中的 ,其中表示第2個詞對應的向量,其他同理。這樣在中就看到了它的下文,就好像是把放到了它的上文位置一樣,但實際順序並沒有改變。
對序列進行排列的目的是爲了生成這個 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。