自注意力機制詳解

1.1 注意力
先來看注意力機制,它的思想來自于網(wǎng)絡搜索:有一個q(query),相當于你在搜索框里打的東西;有一個v(value),相當于網(wǎng)絡上各種各樣的內(nèi)容;有一個k(key),它代表value所對應的關鍵詞,和value是一一對應的關系。
注意力權重就高),反之同理。
(eg:q是“發(fā)燒了怎么辦”,有個v是“xx退燒藥”,其對應的k是“發(fā)燒、退燒…”,將q和k一對比,發(fā)現(xiàn)相似度高,所以搜索結果中會把“xx退燒藥”放在前面。)
上面的過程很符合直覺,但是有個關鍵的技術性細節(jié)沒有講到:到底如何計算q和k的相似度呢?主要有兩個方法:
簡單粗暴的方法:把q和k丟進某個神經(jīng)網(wǎng)絡,讓網(wǎng)絡給你算一個相似度出來;
數(shù)學方法:對q和k做點積。接下來展開講一下這個方法:
我們知道,無論什么樣的數(shù)據(jù),到了神經(jīng)網(wǎng)絡里面無非就是向量(矩陣)的形式。所以這里,我們可以將q,k,v都視為向量:

對q和兩個k分別做點積(這里就隱含了一個要求,q和k的長度必須相等),得到兩個注意力權重:

然后,用得到的注意力權重對v求加權:

可以發(fā)現(xiàn),結果是一個向量,其長度=v的長度。
當然,在實操中q,k,v的個數(shù)不可能就一兩個,因此接下來要做一下維度的擴展。首先,把k,v都增加到m個:

自然而然地,k,v就可以各自合并成矩陣K,V:

(d_k表示key的長度,d_v表示value的長度)
接下來還是重復之前的步驟,先將q和K相乘(和剛才一樣,K要先轉(zhuǎn)置):

我們得到了一個長度為m的向量a_1,其中每一個元素分別代表q對每一個(k,v)對的注意力權重。
接下來將向量a_1與V相乘:

得到的結果和剛才一樣,還是一個長度為d_v的向量。
現(xiàn)在,把q增加到n個,那么最終的結果就會變成一個n\times d_v的矩陣。其中每一行是以Q中對應行作為q,對(k,v)做注意力計算得來的。
1.2 自注意力
以上,就是所謂的“注意力機制”。而自注意力機制呢,其實就是在注意力機制的基礎上,加入了一個前提條件:q,k,v都是一個東西。
我們用一個NLP的例子來理解自注意力機制:
輸入是一個句子長度(在這里是4)\times詞典長度的矩陣,它代表“state of the art”這個句子:

我們將它復制三份,每一份分別對應著Q,K,V。然后和剛才一樣,對Q和K做矩陣乘法:

得到一個4\times4的矩陣A,其中的元素,比如(3,2)代表的是第三個詞“the”與第二個詞“of”的相似度。
然后將A和V相乘:

結果是一個和輸入同樣形狀的矩陣。但是,在輸入中,每一行的意義很簡單,就是代表了句子中的每一個詞——而在這里,每一行的意義是:以句子中原來的那個詞為前提,考慮這個詞和句子中其他詞的相關性,根據(jù)相關性計算出的所有詞的信息的加權。
比如說,第一行是以“state”為前提,那么“state”肯定和自己相關性最大,其次也許是”art“,然后是”of“,”the“,那么第一行中的信息最主要就來自于state,還包含了一定”art“的信息,”of“,”the“的信息可能就沒多少了。
(各個矩陣現(xiàn)實意義的理解非常重要,之后的mask操作會用到這塊的知識。)
(做任何東西都不能只關注技術細節(jié),更要關注現(xiàn)實意義。經(jīng)濟學是這樣,深度學習也是如此。)
1.3 多頭自注意力
自注意力的思想還是非常符合直覺的:從人類的角度來看,對于一個包含很多詞的句子,我們在考慮每一個詞時,不是光考慮這個詞本身,更會考慮這個詞和其他詞的關系。
至于多頭自注意力呢…呃,說實話,就沒有這么優(yōu)美了,并不是很好解釋,所以我們直接進入技術細節(jié)。
(”多頭“和”自“其實并不一定要掛鉤,它們其實是兩個不相干的概念,但這里為了方便就放到一起講了)
首先,我們要決定”頭“的數(shù)量,比如說8。
”頭“是什么意思呢?你可以理解為,每有一個頭,就做一次自注意力。
聰明的你肯定一下就會發(fā)現(xiàn)不對:我們剛才談到的自注意力機制中,是沒有任何可學習參數(shù)的。換言之,在輸入序列給定的情況下,你無論做多少次自注意力,結果都只有一個。
因此在多頭自注意力中,并不是直接把輸入序列拿來做自注意力,而是對輸入序列做3n次線性投影(n為頭的數(shù)量),得到n組的(q,k,v)之后,再對各組做自注意力。
”線性投影“,看起來就不像人話,在實現(xiàn)中其實就是經(jīng)過某個不帶bias的線性層后得到的東西:
?linear = nn.Linear(INPUT_SIZE, OUTPUT_SIZE, bias=False)
?x = torch.ones([10, INPUT_SIZE])
?y = linear(x)
?# y就是x進行一次線性投影后的結果,其形狀是(10, OUTPUT_SIZE)
既然是線性層,那就有可學習參數(shù);有可學習參數(shù),得到的各組(q,k,v)就不一樣,進而做自注意力的結果就不一樣。
1.4 帶掩碼的自注意力
Transformer做預測的流程如下(假設是一個機器翻譯的任務):
把待翻譯的句子喂給encoder,得到某個輸出結果x';
將x'與<SOS>喂給decoder,得到翻譯結果的第一個詞y_1;
將x'與y_1喂給decoder,得到翻譯結果的第二個詞y_2;
…重復以上步驟,直到decoder輸出<EOS>為止。
我們可以發(fā)現(xiàn),這個流程和基于RNN的Seq2Seq差不多,都是串行的,也即每一次預測必須以上一次預測的結果為根據(jù),只能一個詞一個詞地輸出結果,沒辦法一次性輸出整個結果句子。
但Transformer的一個特點,它訓練的過程是并行的,也即我們會一次性把整個(真實的)翻譯結果都輸入decoder,然后讓它一次性輸出整個(預測的)翻譯結果。
我們還是拿剛才自注意里面的圖來講解:
(decoder的輸入按理應該有這個token,這里為了方便就不加了)

行代表的是注意力的”根據(jù)“,列代表的是注意力的”對象“。比如說,元素(1,2)就是”state“對”of“的注意力——等等,這里是不是有問題?
我們的模型最終是用來做預測的,不是光訓練著玩的,而預測是根據(jù)前一個詞預測后一個詞,比如說,我們知道了第一個詞是”state“,我們是在知道且僅知道它的情況下,去預測下一個詞”of“。但是上面矩陣里面的元素(1,2)是”state“對”of“的注意力——這下就變成:我們在知道”of“的情況下去預測”of“,訓練的意義就沒了。
所以我們需要對這個矩陣做一個”掩碼“操作,使得模型在預測某個詞的時候,只能看到它之前的詞。具體來說,就是把下面陰影部分的地方給”刪掉“:

具體是如何刪掉呢?就是把這些地方的值給替換成負無窮大(或者某一個很大的負數(shù)),這樣在對這個矩陣做softmax操作后,這些地方的值就會變成0。(softmax這一環(huán)剛才沒有提到,因為它只能算一個技術細節(jié),對理解自注意力機制本身并不是很關鍵)