白話 NLP,3 個因果告訴你 Mask 矩陣因何而產生?


白話 NLP,3 個因果告訴你 Mask 矩陣因何而產生?

一、為什麼需要 Mask?

在此,先思考一個問題,為什麼需要 mask?

在 NLP 中,一個最常見的問題便是輸入序列長度不等,通常需要進行 PAD 操作,通常在較短的序列後面填充 0,雖然 RNN 等模型可以處理不定長輸入,但在實踐中,需要對 input 做 batchsize,轉換成固定的 tensor。

PAD 案例:

如下是兩句英文,先將文本轉換成數字

s1 = 'He likes cats'

s2 = 'He does not like cats'

s = s1.split(' ') + s2.split(' ')

word_to_id = dict(zip(s, range(len(s))))

id_to_word = dict((k,v) for v,k in word_to_id.items())

# {'He': 3, 'likes': 1, 'cats': 7, 'does': 4, 'not': 5, 'like': 6}

# {3: 'He', 1: 'likes', 7: 'cats', 4: 'does', 5: 'not', 6: 'like'}

s1_vector = [word_to_id[x] for x in s1.split(' ')]

s2_vector = [word_to_id[x] for x in s2.split(' ')]

sentBatch = [s1_vector, s2_vector]

print(sentBatch)

對文本進行數字編碼

[[3, 1, 7], [3, 4, 5, 6, 7]]

對如上兩個 vector 進行 pad 處理。

from torch.nn.utils.rnn import pad_sequence

a = torch.tensor(s1_vector)

b = torch.tensor(s2_vector)

pad = pad_sequence([a, b])

print(pad)

PAD 結果

tensor([[3, 3],

[1, 4],

[7, 5],

[0, 6],

[0, 7]])

以句子 ”He likes cats“ 的 PAD 結果舉例:[3, 1, 7, 0, 0],PAD 操作會引起以下幾個問題。

1. mean-pooling 的問題

如上述案例所示,對於矩陣:s1 = [3, 1, 7]​

對 s1 進行 mean-pooling:mean_{s1}=(3+1+7)/3=3.667​

進行 pad 之後:pad_{s1}=[3,1,7,0,0]​

對 pad_ {s1}​ 進行 mean-pooling:pad_{s1}=(3+1+7+0+0)/10=1.1​

對比 mean_ {s1}​ 和 pad_{s1}​ 發現:pad 操作影響 mean-pooling。

2. max-pooling 的問題

對於矩陣 s1: s1 = [-3, -1, -7]​,PAD 之後:​pad_{s1}= [-3,-1,-7,0,0]​

分別對 s1​ 和 pad_ {s1}​ 進行 max-pooling:​max{s1}=-1,max{pad_{s1}}=0​

對比 mean_ {s1}​ 和 pad_{s1}​ 發現:pad 操作影響 max-pooling。

3. attention 的問題

通常在 Attention 計算中最後一步是使用 softmax 進行歸一化操作,將數值轉換成概率。但如果直接對 PAD 之後的向量進行 softmax,那麼 PAD 的部分也會分攤一部分概率,這就導致有意義的部分 (非 PAD 部分) 概率之和小於等於 1。

二、Mask 為解決 PAD 問題順應而生

Mask 是相對於 PAD 而產生的技術,具備告訴模型一個向量有多長的功效。Mask 矩陣有如下特點:

  1. Mask 矩陣是與 PAD 之後的矩陣具有相同的 shape。
  2. mask 矩陣只有 1 和 0兩個值,如果值為 1 表示 PAD 矩陣中該位置的值有意義,值為 0 則表示對應 PAD 矩陣中該位置的值無意義。

在第一部分中兩個矩陣的 mask 矩陣如下所示:

mask_s1 = [1, 1, 1, 0, 0]

mask_s2 = [1, 1, 1, 1, 1]

mask = a.ne(torch.tensor(paddingIdx)).byte()

print(mask)

>>> tensor([[1, 1],

[1, 1],

[1, 1],

[0, 1],

[0, 1]], dtype=torch.uint8)

1. 解決 mean-pooling 問題

mean_s1=sum(pad_{s1}*m)/sum(m)​

2. 解決 max-pooling 問題

在進行 max-pooling 時,只需要將 pad 的部分的值足夠小即可,可以將 mask 矩陣中的值為 0 的位置替換的足夠小 ( 如: 甚至負無窮​,則不會影響 max-pooling 計算。

max_b=max(pad_b-(1-m)*10^{-10})​

3. 解決 Attention 問題

該問題的解決方式跟 max-pooling 一樣,就是將 pad 的部分足夠小,使得 ​ 的值非常接近於 0,以至於忽略。

softmax(x)=softmax(x-(1-m)*10^{10})​

二、常見的 Mask 有哪些?

有了之前的鋪墊,你應該明白 Mask 因何而產生,有什麼作用,而在 NLP 任務中,因為功能不同,Mask 也會不同。

常見的 Mask 有兩種,Padding-mask,用於處理不定長輸入,也即是上面講的第一種,另一種則是 seqence-mask,為了防止未來信息不被洩露。接下來將詳細講解這兩種 Mask。

padding mask - 處理輸入不定長

在 NLP 中,一個常見的問題是輸入序列長度不等,一般來說我們會對一個 batch 內的句子進行 PAD,通常值為 0。但在前面我們也講過,PAD 為 0 會引起很多問題,影響最後的結果,因此,Mask 矩陣為解決 PAD 問題而產生。

舉個例子:

case 1: I like cats.

case 2: He does not like cats.

假設默認的 seq_len 是5,一般會對 case 1 做 pad 處理,變成

[1, 1, 1, 0, 1]

在上述例子數字編碼後,開始做 embedding,而 pad 也會有 embedding 向量,但 pad 本身沒有實際意義,參與訓練可能還是有害的。

因此,有必要維護一個 mask tensor 來記錄哪些是真實的 value,上述例子的兩個 mask 如下:

1 1 1 0 0

1 1 1 1 1

後續再梯度傳播中,mask 起到了過濾的作用,在 pytorch 中,有參數可以設置:

nn.Embedding(vocab_size, embed_dim,padding_idx=0)

sequence mask - 防止未來信息洩露

在語言模型中,常常需要從上一個詞預測下一個詞,sequence mask 是為了使得 decoder 不能看見未來的信息。也就是對於一個序列,在 time_step 為 t 的時刻,我們的解碼輸出應該只能依賴於 t 時刻之前的輸出,而不能依賴 t 之後的輸出。因此我們需要想一個辦法,把 t 之後的信息給隱藏起來。

那麼具體怎麼做呢?也很簡單:產生一個上三角矩陣,上三角的值全為 1,下三角的值全為 0,對角線也是 0。把這個矩陣作用在每一個序列上,就可以達到我們的目的啦。

一個常見的 trick 就是生成一個 mask 對角矩陣,如[1]:

def sequence_mask(seq):

batch_size, seq_len = seq.size()

mask = 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

哈佛大學的文章The Annotated Transformer有一張效果圖:

白話 NLP,3 個因果告訴你 Mask 矩陣因何而產生?

值得注意的是,本來 mask 只需要二維的矩陣即可,但是考慮到我們的輸入序列都是批量的,所以我們要把原本二維的矩陣擴張成 3 維的張量。上面的代碼可以看出,我們已經進行了處理。

如果覺得文章對您有幫助,歡迎點贊,轉發,關注。


分享到:


相關文章: