Attention 機(jī)制由 Bengio 團(tuán)隊(duì)于 2014 年提出,并廣泛應(yīng)用在深度學(xué)習(xí)的各個(gè)領(lǐng)域。而 Google 提出的用于生成詞向量的 Bert 在 NLP 的 11 項(xiàng)任務(wù)中取得了效果的大幅提升,Bert 正是基于雙向 Transformer。
Transformer 是第一個(gè)完全依賴于 Self-Attention 來計(jì)算其輸入和輸出表示的模型,而不使用序列對(duì)齊的 RNN 或 CNN。更準(zhǔn)確的講,Transformer 由且僅由 self-Attention 和 Feed Forward Neural Network 組成。一個(gè)基于 Transformer 的可訓(xùn)練的神經(jīng)網(wǎng)絡(luò)可以通過堆疊 Transformer 的形式進(jìn)行搭建,作者的實(shí)驗(yàn)是通過搭建編碼器和解碼器各 6 層,總共 12 層的 Encoder-Decoder,并在機(jī)器翻譯中取得了 BLEU 值得新高。
Transformer 結(jié)構(gòu)
解釋一下上面這個(gè)結(jié)構(gòu)圖。Transformer 采用的也是經(jīng)典的 Encoder 和 Decoder 架構(gòu),由 Encoder 和 Decoder 組成。
Encoder 的結(jié)構(gòu)由 Multi-Head Self-Attention 和 position-wise feed-forward network 組成,Encoder 的輸入由 Input Embedding 和 Positional Embedding 求和組成。
Decoder 的結(jié)構(gòu)由 Masked Multi-Head Self-Attention,Multi-Head Self-Attention 和 position-wise feed-forward network 組成。Decoder 的初始輸入由 Output Embedding 和 Positional Embedding 求和得到。
上圖左半邊 Nx 框出來的部分是 Encoder 的一層,Transformer 中 Encoder 有 6 層。
上圖右半邊 Nx 框出來的部分是 Decoder 的一層,Transformer 中 Decoder 有 6 層。
Encoder
Encoder 由 6 個(gè)相同的層組成,每個(gè)層包含 2 個(gè)部分:
- Multi-Head Self-Attention
- Position-Wise Feed-Forward Network (全連接層)
兩個(gè)部分都有殘差連接 (redidual connection),然后接一個(gè) Layer Normalization。
Encoder 的輸入由 Input Embedding 和 Positional Embedding 求和組成。
如果你是剛開始學(xué) Transformer,你可能會(huì)問:
- Multi-Head Self-Attention 是什么?
- 殘差連接 (redidual connection) 是什么?
- Layer Normalization 是什么?
后面都會(huì)一一解答,請(qǐng)往后看。
Decoder
和 Encoder 相似,Decoder 也是由 6 個(gè)相同的層組成,每個(gè)層包含 3 個(gè)部分:
- Multi-Head Self-Attention
- Multi-Head Context-Attention
- Position-Wise Feed-Forward Network
上面三個(gè)部分都有殘差連接 (redidual connection),然后接一個(gè) Layer Normalization。
Decoder 多了個(gè) Multi-Head Context-Attention,如果理解了 Multi-Head Self-Attention,這個(gè)就很好理解了,后面會(huì)提到這兩個(gè) Attention。
Self-Attention 機(jī)制
Attention 常用的有兩種,一種是加性注意力(Additive Attention),另一組是點(diǎn)乘注意力(Dot-product Attention),論文采用的是點(diǎn)乘注意力,這種注意力機(jī)制相比加法注意力機(jī)制,更快,同時(shí)更省空間。
Self-Attention 是 Transformer 的核心內(nèi)容,然而作者并沒用詳細(xì)講解。
以下面這句話為例,作為我們翻譯的輸入語句,我們可以看下 Attention 如何對(duì)這句話進(jìn)行表示。
The animal didn’t cross the street because it was too tired
我們可以思考一個(gè)問題,“it” 指代什么?是 “street” 還是 “animal” ? 對(duì)人來說,很容易就能知道是 “animal”,但是對(duì)于算法來說,并沒有這么簡(jiǎn)單。
模型處理單詞 “it” 時(shí),Attention 允許將 “it” 和 “animal” 聯(lián)系起來。當(dāng)模型處理每個(gè)位置時(shí),Attention 對(duì)不同位置產(chǎn)生不同的注意力,使其來更好的編碼當(dāng)前位置的詞,如果你熟悉 RNN,就知道 RNN 如何根據(jù)之前的隱狀態(tài)信息來編碼當(dāng)前詞。
即:當(dāng)編碼 “it” 時(shí),部分 Attention 集中于 “the animal”,并將其表示合并到 “it” 的編碼中。
RNN 要逐步遞歸才能獲取全局信息,因此一般要雙向 RNN 才比較好,且下一時(shí)刻信息要依賴于前面時(shí)刻的信息。CNN 只能獲取局部信息,是通過疊層來增大感受野,Attention 思路最為粗暴,一步到位獲得了全局信息。
而 Transformer 使用 Self-Attention,簡(jiǎn)單的解釋:通過確定Q和K之間的相似程度來選擇V!
使用 Self-Attention 有幾個(gè)好處:
-
每一層的復(fù)雜度小:
- 如果輸入序列 n 小于表示維度 d 的話,Self-Attention 的每一層時(shí)間復(fù)雜度有優(yōu)勢(shì)。
- 當(dāng) n 比較大時(shí),作者也給出了解決方案,Self-Attention 中每個(gè)詞不是和所有詞計(jì)算 Attention,而是只與限制的 r 個(gè)詞進(jìn)行 Attention 計(jì)算。
- 并行 Multi-Head Attention 和 CNN 一樣不依賴前一時(shí)刻的計(jì)算,可以很好的并行,優(yōu)于 RNN。
- 長(zhǎng)距離依賴 優(yōu)于 Self-Attention 是每個(gè)詞和所有詞計(jì)算 Attention,所以不管他們中間有多長(zhǎng)距離,最大路徑長(zhǎng)度都只是 1,可以捕獲長(zhǎng)距離依賴關(guān)系。
上面講到 Decoder 中有兩種 Attention,一種是 Self-Attention,一種是 Context-Attention。
Context-Attention 也就是 Encoder 和 Decoder 之間的 Attention,也可以稱之為 Encoder-Decoder Attention。
無論是Self-Attention 還是 Context-Attention,它們?cè)谟?jì)算 Attention 分?jǐn)?shù)的時(shí)候,可以有很多選擇:
- additive attention
- local-base
- general
- dot-product
- scaled dot-product
那么我們的Transformer模型,采用的是哪種呢?答案是:scaled dot-product attention。
為什么要加這個(gè)縮放因子呢?論文里給出了解釋:如果 dk 很小,加性注意力和點(diǎn)乘注意力相差不大,但是如果 dk 很大,點(diǎn)乘得到的值很大,如果不做 scaling,結(jié)果就沒有加性注意力好,另外,點(diǎn)乘結(jié)果過大,使得經(jīng)過 softmax 之后的梯度很小,不利于反向傳播的進(jìn)行,所以我們通過對(duì)點(diǎn)乘的結(jié)果進(jìn)行scaling。
先簡(jiǎn)單說下 Q、K、V 是什么:
- Encoder 的 Self-Attention 中,Q、K、V 都來自同一個(gè)地方(相等),他們是上一層 Encoder 的輸出,對(duì)于第一層 Encoder,他們就是 Word Embedding 和 Positional Embedding 相加得到的輸入。
- Decoder 的 Self-Attention 中,Q、K、V都來自于同一個(gè)地方(相等),它們是上一層 Decoder 的輸出,對(duì)于第一層 Decoder,他們就是 Word Embedding 和 Positional Embedding 相加得到的輸入。但是對(duì)于 Decoder,我們不希望它能獲得下一個(gè) time step(將來的信息),因此我們需要進(jìn)行 Sequence masking。
- 在 Encoder-Decoder Attention 中,Q 來自于上一層 Decoder 的輸出,K 和 V 來自于 Encoder 的輸出,K 和 V 是一樣的。
Multi-Head Attention
論文提出,由于不同的 Attention 的權(quán)重側(cè)重點(diǎn)不一樣,所以將這個(gè)任務(wù)交給不同的 Attention 一起做,最后取綜合結(jié)果會(huì)更好,有點(diǎn)像 CNN 中的 Keynel。
文章表示,將 Q、K、V 通過一個(gè)線性映射后,分成 h 份,對(duì)沒分進(jìn)行 Scaled Dot-Product Attention 效果更好, 再把這幾個(gè)部分 Concat 起來,過一個(gè)線性層的效果更好,可以綜合不同位置的不同表征子空間的信息。
論文里面,。所以在scaled dot-product attention里面的
Residual connection 殘差連接
在了解殘差網(wǎng)絡(luò)之前,先思考下面的問題:
- 神經(jīng)網(wǎng)絡(luò)越深越好嗎?
下圖中顯示,傳統(tǒng)神經(jīng)網(wǎng)絡(luò)越深效果不一定好。而 Deep Residual Learning for Image Recognition 這篇論文認(rèn)為,理論上,可以訓(xùn)練一個(gè)淺層網(wǎng)絡(luò),然后再這個(gè)訓(xùn)練好的淺層網(wǎng)絡(luò)上堆幾層恒等映射層,即輸出等于輸入層,構(gòu)建一個(gè)深層網(wǎng)絡(luò)。淺層網(wǎng)絡(luò)和深層網(wǎng)絡(luò)得到的結(jié)果一模一樣,因?yàn)槎焉先サ膶邮呛愕茸儞Q的。
這樣就可以得出一個(gè)結(jié)論:理論上,在訓(xùn)練集上,深層網(wǎng)絡(luò)不會(huì)比淺層網(wǎng)絡(luò)差。但是為什么出現(xiàn)下面這種情況呢?隨著層數(shù)增加,訓(xùn)練集上效果反而變差,這被稱為退化問題。原因是隨著網(wǎng)絡(luò)越來越深,訓(xùn)練和優(yōu)化變得越來越難,過深的網(wǎng)絡(luò)會(huì)產(chǎn)生退化問題,效果反而不如相對(duì)較淺的網(wǎng)絡(luò)。而餐內(nèi)存網(wǎng)絡(luò)可以解決這個(gè)問題,殘差網(wǎng)絡(luò)月神,訓(xùn)練集上效果越好。
殘差網(wǎng)絡(luò)通過加入 shortcut connections,變得更加容易被優(yōu)化。包含一個(gè) shortcut connection 的幾層網(wǎng)絡(luò)被稱為一個(gè)殘差塊(residual block)。殘差塊分成兩部分直接映射部分和殘差部分。
殘差網(wǎng)絡(luò)由殘差塊組成,一個(gè)殘差塊可以表示為:
殘差網(wǎng)絡(luò)有什么好處呢?顯而易見:因?yàn)樵黾恿?x 項(xiàng),那么該網(wǎng)絡(luò)求 x 的偏導(dǎo)的時(shí)候,多了一項(xiàng)常數(shù) 1,所以反向傳播過程,梯度連乘,也不會(huì)造成梯度消失。
殘差網(wǎng)絡(luò)的實(shí)現(xiàn)非常簡(jiǎn)單:
def residual(sublayer_fn,x):
return sublayer_fn(x)+x
Layer normalization
Normalization 有很多種,但是它們都有一個(gè)共同的目的,那就是把輸入轉(zhuǎn)化成均值為 0 方差為 1 的數(shù)據(jù)。我們?cè)诎褦?shù)據(jù)送入激活函數(shù)之前進(jìn)行 Normalization(歸一化),因?yàn)槲覀儾幌M斎霐?shù)據(jù)落在激活函數(shù)的飽和區(qū)。
隨著訓(xùn)練的進(jìn)行,網(wǎng)絡(luò)中的參數(shù)也隨著梯度下降在不停更新。
- 一方面,當(dāng)?shù)讓泳W(wǎng)絡(luò)中參數(shù)發(fā)生微弱變化時(shí),由于每一層中的線性變換與非線性激活映射,這些微弱變化隨著網(wǎng)絡(luò)層數(shù)的加深而被放大(類似蝴蝶效應(yīng))。
- 另一方面,參數(shù)的變化導(dǎo)致每一層的輸入分布會(huì)發(fā)生改變,進(jìn)而上層的網(wǎng)絡(luò)需要不停地去適應(yīng)這些分布變化,使得我們的模型訓(xùn)練變得困難。上述這一現(xiàn)象叫做 Internal Covariate Shift。
BN 的作者給 Internal Covariate Shift 的定義為:在深層網(wǎng)絡(luò)訓(xùn)練過程中,由于網(wǎng)絡(luò)中參數(shù)變化而引起內(nèi)部節(jié)點(diǎn)數(shù)據(jù)分布發(fā)生變化的這一過程被稱作 Internal Covariate Shift。
BN 就是為了解決這一問題,一方面可以簡(jiǎn)化計(jì)算過程,一方面經(jīng)過規(guī)范化處理后讓數(shù)據(jù)盡可能保留原始表達(dá)能力。
BN 的主要思想是:在每一層的每一批數(shù)據(jù)上進(jìn)行歸一化。
說完 Batch Normalization,就該說說咱們今天的主角 Layer normalization。
那么什么是 Layer Normalization 呢?它也是歸一化數(shù)據(jù)的一種方式,不過 LN 是在每一個(gè)樣本上計(jì)算均值和方差,而不是 BN 那種在批方向計(jì)算均值和方差!
Mask
現(xiàn)在終于輪到講解 Mask 了! 大概就是對(duì)某些值進(jìn)行掩蓋,使其不產(chǎn)生效果。
Transformer 模型里面涉及兩種 Mask。分別是 Padding Mask 和 Sequence Mask。
其中,Padding Mask 在所有的 Scaled Dot-Product Attention 里面都需要用到,而 Sequence Mask 只有在 Decoder 的 Self-Attention 里面用到。
所以,我們之前 Scaled Dot-Product Attention 的 forward 方法里面的參數(shù) attn_mask 在不同的地方會(huì)有不同的含義。
Padding Mask
什么是 Padding Mask 呢?回想一下,我們的每個(gè)批次輸入序列長(zhǎng)度是不一樣的。我們要對(duì)輸入序列進(jìn)行對(duì)齊!就是給在較短的序列后面填充 0。因?yàn)檫@些填充的位置,其實(shí)是沒什么意義的,所以我們的 Attention 機(jī)制不應(yīng)該把注意力放在這些位置上,所以我們需要進(jìn)行一些處理。
具體的做法是,把這些位置的值加上一個(gè)非常大的負(fù)數(shù)(負(fù)無窮),這樣的話,經(jīng)過 Softmax,這些位置的概率就會(huì)接近 0 !
而我們的 Padding Mask 實(shí)際上是一個(gè)張量,每個(gè)值都是一個(gè) Boolen,值為 False 的地方就是我們要進(jìn)行處理的地方。
def padding_mask(seq_k, seq_q):
# seq_k 和 seq_q 的形狀都是 [B,L]
len_q = seq_q.size(1)
# `PAD` is 0
pad_mask = seq_k.eq(0)
# shape [B, L_q, L_k]
pad_mask = pad_mask.unsqueeze(1).expand(-1, len_q, -1)
return pad_mask
Sequence mask
文章前面也提到,Sequence Mask 是為了使得 Decoder 不能看見未來的信息。也就是對(duì)于一個(gè)序列,在 time_step 為 t 的時(shí)刻,我們的解碼輸出應(yīng)該只能依賴于 t 時(shí)刻之前的輸出,而不能依賴 t 之后的輸出。因此我們需要想一個(gè)辦法,把 t 之后的信息給隱藏起來。
那么具體怎么做呢?也很簡(jiǎn)單:產(chǎn)生一個(gè)上三角矩陣,上三角的值全為 1,下三角的權(quán)值為 0,對(duì)角線也是 0。把這個(gè)矩陣作用在每一個(gè)序列上,就可以達(dá)到我們的目的啦。
本來 Mask 只需要二維的矩陣即可,但是考慮到我們的輸入序列都是批量的,所以我們要把原本 2 維的矩陣擴(kuò)張成 3 維的張量。
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
回到本小結(jié)開始的問題,attn_mask 參數(shù)有幾種情況?分別是什么意思?
- 對(duì)于decoder的self-attention,里面使用到的scaled dot-product attention,同時(shí)需要padding mask 和 sequence mask 作為 attn_mask,具體實(shí)現(xiàn)就是兩個(gè) mask 相加作為attn_mask。
- 其他情況,attn_mask 一律等于 padding mask。
至此,Mask 相關(guān)的問題解決了。
Positional encoding
因?yàn)?Transformer 利用 Attention 的原因,少了對(duì)序列的順序約束,這樣就無法組成有意義的語句。為了解決這個(gè)問題,Transformer 對(duì)位置信息進(jìn)行編碼。
pos 指詞語在序列中的位置,偶數(shù)位置,使用正弦編碼,奇數(shù)位置,使用余弦編碼。
上述公式解釋:給定詞語的位置 pos,我們可以把它編碼成 d_model 維的向量!也就是說,位置編碼的每一個(gè)維度對(duì)應(yīng)正弦曲線,波長(zhǎng)構(gòu)成了從 到
的等比序列。
上面的位置編碼是絕對(duì)位置編碼。但是詞語的相對(duì)位置也非常重要。這就是論文為什么要使用三角函數(shù)的原因!
正弦函數(shù)能夠表達(dá)相對(duì)位置信息,主要數(shù)學(xué)依據(jù)是以下兩個(gè)公式:
上面的公式說明,對(duì)于詞匯之間的位置偏移 k, 可以表示成
和
組合的形式,相當(dāng)于有了可以表達(dá)相對(duì)位置的能力。
class PositionalEncoding(nn.Module):
"Implement the PE function."
def __init__(self, d_model, dropout, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
# Compute the positional encodings once in log space.
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) *
-(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
x = x + Variable(self.pe[:, :x.size(1)],
requires_grad=False)
return self.dropout(x)
我們對(duì) Position Encoding 打印查看其位置圖像:
plt.figure(figsize=(15, 5))
pe = PositionalEncoding(20, 0)
y = pe.forward(Variable(torch.zeros(1, 100, 20)))
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
plt.legend(["dim %d"%p for p in [4,5,6,7]])
我們還嘗試使用學(xué)習(xí) Position Embedding,發(fā)現(xiàn)這兩個(gè)版本的結(jié)果幾乎相同。我們選擇正弦,因?yàn)樗梢蕴幚砀L(zhǎng)序列的情況。
Position-wise Feed-Forward network
這是一個(gè)全連接網(wǎng)絡(luò),包含兩個(gè)線性變換和一個(gè)非線性函數(shù) (實(shí)際上就是 ReLU)。公式如下
這個(gè)線性變換在不同的位置都表現(xiàn)地一樣,并且在不同的層之間使用不同的參數(shù)。
這里實(shí)現(xiàn)上用到了兩個(gè)一維卷積。
總結(jié)
比起傳統(tǒng)的 RNN、CNN 模型,Transformer 的優(yōu)勢(shì)在于它在每個(gè)時(shí)刻 t 求得的隱藏向量 ht 都包含整個(gè)序列的信息 ( 其實(shí)是 Self-Attention 結(jié)構(gòu)的優(yōu)勢(shì),可建模出任意一個(gè)時(shí)刻 item 和所有時(shí)刻 item 的相關(guān)性 ) 。因此可將 Transformer 結(jié)構(gòu)用于用戶的短期興趣 Embedding 建模,然后再將該 Embedding 向量用于召回或者 Ranking 階段。
如果您覺得文章對(duì)您有幫助,歡迎關(guān)注個(gè)人公眾號(hào)學(xué)習(xí)交流。