天天看點

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

本文的學習路線:

📍 Attention

從生物學的角度引入到計算機視角,介紹了什麼是

Attention

▶️介紹

Encoder-Decoder架構

(目前大部分Attention Model都是依附于該架構實作)▶️ 介紹了Attention模型中的基礎概念

查詢、鍵和值

▶️ 通過講解

Nadaraya-Watson核回歸模型

來了解常見的注意力彙聚模型 ▶️ 介紹了Attention Model中兩個常用的

注意力評分函數

▶️ 從基礎的Attention引入到

Self-Attention

Multihead-Attention

📍 Transformer

從宏觀的角度了解

Transformer 架構

▶️ 然後分别研究Transformer的

輸入

Encoder-Decoder

輸出

▶️ Transformer的

訓練過程

▶️ Transformer 的

代碼實作

🚩 參考

主要參考了李沐老師和李宏毅老師的機器學習課程,Jay Alammar的部落格以及其他資料。

文章目錄

  • 注意力
    • 注意力概述(Attention)
      • Encoder-Decoder
      • 查詢、鍵和值
      • 注意力彙聚: Nadaraya-Watson 核回歸
    • 注意力評分函數
      • 加性注意力
      • 縮放點積注意力
    • 自注意力(Self-Attention)
      • 自注意力的定義和計算
      • 自注意力的應用
      • Self-Attention 🆚 CNN 🆚 RNN
    • 多頭自注意力 (Multihead Attention)
  • Transformer
    • Transformer的整體結構
    • Transformer的輸入
      • 單詞Embedding
      • 位置Encoding
    • Transformer的Encoder-Decoder
      • Encoder block
      • Decoder block
    • Transformer的輸出
    • Transformer的訓練過程和損失函數
      • 訓練過程
      • 損失函數
    • Transformer的代碼實作
      • 基于位置的前饋神經網絡
      • 殘差連接配接和層規範化
      • 編碼器
      • 解碼器
      • 訓練
  • pytorch中的注意力機制類
    • **torch.nn.MultiheadAttention**
  • 參考

注意力

注意力概述(Attention)

📒注意力提示

首先,我們從生物學的角度了解注意力。

人一般基于

非自主性提示

自主性提示

有選擇地引導注意力的焦點。

  • 非自主性提示 (偏向于感官的輸入)

    想象一下,假如我們面前有五個物品: 一份報紙、一篇研究論文、一杯咖啡、一本筆記本和一本書。所有紙制品都是黑白印刷的,但咖啡杯是紅色的。咖啡杯在這種視覺環境中是突出和顯眼的, 不由自主地引起人們的注意。

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • 自主性提示(偏向于意識的控制)

    喝咖啡後,我們會變得興奮并想讀書, 是以轉過頭,重新聚焦眼睛,然後看看書。此時選擇書是受到了認知和意識的控制。

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Encoder-Decoder

目前大部分attention模型都是依附于Encoder-Decoder架構進行實作。 是以我們先來講解下Encoder-Decoder架構。

Encoder-Decoder架構顧名思義也就是編碼-解碼架構,在NLP中Encoder-Decoder架構主要被用來處理序列-序列問題。也就是輸入一個序列,生成一個序列的問題。這兩個序列可以分别是任意長度。

具體到NLP中的任務比如:

文本摘要,輸入一篇文章(序列資料),生成文章的摘要(序列資料)

文本翻譯,輸入一句或一篇英文(序列資料),生成翻譯後的中文(序列資料)

問答系統,輸入一個question(序列資料),生成一個answer(序列資料)

基于Encoder-Decoder架構具體使用什麼模型實作,由大家自己決定~用的較多的應該就是

seq2seq模型

Transformer

了。

📒Encoder-Decoder中的輸入和輸出

上面我們通過生物學的角度了解了注意力,其中輸入是五個物品的視覺信号,輸出是我們決定将眼睛注視在哪個物品上。

下面來看看如何在計算機中表示這種輸入和輸出。

  • 輸入

    1)輸入是一個向量

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
    2)輸入是一組向量
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
❔ 如何把現實中的語言、聲音編碼成一組向量呢?

1)把

自然語言

編碼成一組向量。

如下圖,代表的詞彙編碼方式有兩種:

左邊表示的是

獨熱編碼(One-hot Encoding)

右側表示的是

單詞嵌入(Word Embedding)

單詞嵌入比獨熱編碼更能反映詞義相近單詞之間的關系。

每個單詞可以編碼成一個向量,一個句子就是一組向量。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

2) 把

語音

編碼成一組向量

取一個25ms的視窗,這個視窗中采400個樣本點,組成一個向量。

每次将視窗向後滑動10ms取樣,組成一組向量。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

3) 把

編碼成一組向量

比如下圖的社會關系圖中,每個人的個人資訊都可以編碼成一個向量。很多人的資訊就構成了一組向量。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒輸出

  • 每一個向量對應一個輸出

    我們重點關注這種情況

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • 整個序列隻輸出一個标簽
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • 模型自己決定輸出序列的長度
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒Encoder-Decoder中的結構原理

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

上圖就是Encoder-Decoder架構在NLP領域中抽象後的最簡單的結構圖。

  • Encoder

    Encoder:編碼器,對于輸入的序列<x1,x2,x3…xn>進行編碼,使其轉化為一個語義編碼C,這個C中就儲存了序列<x1,x2,x3…xn>的資訊。

❔Encoder 是怎麼編碼的呢?

編碼方式有很多種,在文本處理領域主要有

RNN/LSTM/GRU/BiRNN/BiLSTM/BiGRU

,可以依照自己的喜好來選擇編碼方式

🌰我們以RNN為例來具體說明一下:

以上圖為例,輸入<x1,x2,x3,x4>,通過RNN生成隐藏層的狀态值<h1,h2,h3,h4>,如何确定語義編碼C呢?最簡單的辦法直接用最後時刻輸出的ht作為C的狀态值,這裡也就是可以用h4直接作為語義編碼C的值,也可以将所有時刻的隐藏層的值進行彙總,然後生成語義編碼C的值,這裡就是C=q(h1,h2,h3,h4),q是非線性激活函數。

得到了語義編碼C之後,接下來就是要在Decoder中對語義編碼C進行解碼了。

  • Decoder

    Decoder:解碼器,根據輸入的語義編碼C,然後将其解碼成序列資料,解碼方式也可以采用

    RNN/LSTM/GRU/BiRNN/BiLSTM/BiGRU

    Decoder和Encoder的編碼解碼方式可以任意組合。

❔Decoder 是怎麼解碼的呢?

基于

seq2seq

模型有兩種解碼方式:

1️⃣ 解碼方法1:《Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation》

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

該方法指出,因為語義編碼

C

包含了整個輸入序列的資訊,是以在解碼的每一步都引入

C

。文中Ecoder-Decoder均是使用RNN,在計算每一時刻的輸出yt時,都應該輸入語義編碼

C

,即 h ⟨ t ⟩ = f ( h ⟨ t − 1 ⟩ , y t − 1 , c ) \mathbf{h}_{\langle t\rangle}=f\left(\mathbf{h}_{\langle t-1\rangle}, y_{t-1}, \mathbf{c}\right) h⟨t⟩​=f(h⟨t−1⟩​,yt−1​,c)

類似的,下一個符号的條件分布是:

P ( y t ∣ y t − 1 , y t − 2 , … , y 1 , c ) = g ( h ⟨ t ⟩ , y t − 1 , c ) P\left(y_t \mid y_{t-1}, y_{t-2}, \ldots, y_1, \mathbf{c}\right)=g\left(\mathbf{h}_{\langle t\rangle}, y_{t-1}, \mathbf{c}\right) P(yt​∣yt−1​,yt−2​,…,y1​,c)=g(h⟨t⟩​,yt−1​,c)

其中 h t h_t ht​為目前t時刻的隐藏層的值, y t − 1 y_{t-1} yt−1​為上一時刻的預測輸出,作為t時刻的輸入,每一時刻的語義編碼C是相同地。

2️⃣ 解碼方法2:《Sequence to Sequence Learning with Neural Networks》

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

這個編碼方式相對簡單,隻在Decoder的初始輸入引入語義編碼C,将語義編碼C作為隐藏層狀态值 h 0 h_0 h0​的初始值,

P ( y t ∣ y t − 1 , y t − 2 , … , y 1 , c ) = g ( h ⟨ t ⟩ , y t − 1 ) P\left(y_t \mid y_{t-1}, y_{t-2}, \ldots, y_1, \mathbf{c}\right)=g\left(\mathbf{h}_{\langle t\rangle}, y_{t-1}\mathbf{}\right) P(yt​∣yt−1​,yt−2​,…,y1​,c)=g(h⟨t⟩​,yt−1​)

如上圖,該模型讀取一個輸入句子“ABC”,并産生“WXYZ”作為輸出句子。模型在輸出句尾标記後停止進行預測。注意,LSTM讀取反向輸入句子,因為這樣做會在資料中引入許多短期依賴關系

❔上述基于

seq2seq

模型的兩種解碼方式有什麼缺點?

如果按照方法1解碼:在生成目标句子的單詞時,不論生成哪個單詞,是y1,y2也好,還是y3也好,他們使用的語義編碼C都是一樣的。而語義編碼C是由輸入序列X的每個單詞經過Encoder 編碼産生的,這意味着不論是生成哪個單詞,y1,y2還是y3,其實輸入序列X中任意單詞對生成某個目标單詞yi來說影響力都是相同的。(其實如果Encoder是RNN的話,理論上越是後輸入的單詞影響越大,并非等權的,估計這也是為何Google提出Sequence to Sequence模型時發現把輸入句子逆序輸入做翻譯效果會更好的小Trick的原因)

如果按照方法2解碼:整個序列的資訊壓縮在了一個語義編碼C中,用一個語義編碼C來記錄整個序列的資訊,序列較短還行,如果序列是長序列,比如是一篇上萬字的文章,我們要生成摘要,那麼隻是用一個語義編碼C來表示整個序列的資訊肯定會損失很多資訊,而且序列一長,就可能出現梯度消失問題,這樣将所有資訊壓縮在一個C裡面顯然就不合理。

既然基于

seq2seq

模型有兩種解碼方式都不太好(兩種解碼方式都隻采用了一個語義編碼C),而基于

attention

模型的編碼方式中采用了多個

C

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

上圖就是引入了Attention 機制的Encoder-Decoder架構。咱們一眼就能看出上圖不再隻有一個單一的語義編碼C,而是有多個C1,C2,C3這樣的編碼。當我們在預測Y1時,可能Y1的注意力是放在C1上,那咱們就用C1作為語義編碼,當預測Y2時,Y2的注意力集中在C2上,那咱們就用C2作為語義編碼,以此類推,就模拟了人類的注意力機制。

🌰以機器翻譯例子"Tom chase Jerry" - "湯姆追逐傑瑞"來說明注意力機制:

當我們在翻譯"傑瑞"的時候,為了展現出輸入序列中英文單詞對于翻譯目前中文單詞不同的影響程度,比如給出類似下面一個機率分布值:

(Tom,0.3)(Chase,0.2)(Jerry,0.5)

每個英文單詞的機率代表了翻譯目前單詞“傑瑞”時,注意力配置設定模型配置設定給不同英文單詞的注意力大小。這對于正确翻譯目智語單詞肯定是有幫助的,因為引入了新的資訊。同理,目标句子中的每個單詞都應該學會其對應的源語句子中單詞的注意力配置設定機率資訊。這意味着在生成每個單詞Yi的時候,原先都是相同的中間語義表示C會替換成根據目前生成單詞而不斷變化的Ci。了解AM模型的關鍵就是這裡,即由固定的中間語義表示C換成了根據目前輸出單詞來調整成加入注意力模型的變化的Ci。

每個Ci 對應這不同源語句子單詞的注意力配置設定機率,比如對于上面的英漢翻譯來說,對應的資訊可能如下:

C 湯姆  = g ( 0.6 ∗ f 2 (  "Tom"),  0.2 ∗ f 2 (  Chase),  0.2 ∗ f 2  ("Jerry"))  C 追逐  = g ( 0.2 ∗ f 2 (  "Tom"),  0.7 ∗ f 2 (  Chase),  0.1 ∗ f 2 (  "Jerry")  ) C 傑瑞  = g ( 0.3 ∗ f 2 (  "Tom"),  0.2 ∗ f 2  (Chase),  0.5 ∗ f 2 (  "Jerry"))  \begin{aligned} & \mathrm{C}_{\text {湯姆 }}=\mathrm{g}(0.6 * \mathrm{f} 2(\text { "Tom"), } 0.2 * \mathrm{f} 2(\text { Chase), } 0.2 * \mathrm{f} 2 \text { ("Jerry")) } \\ & \mathrm{C}_{\text {追逐 }}=\mathrm{g}(0.2 * \mathrm{f} 2(\text { "Tom"), } 0.7 * \mathrm{f} 2(\text { Chase), } 0.1 * \mathrm{f} 2(\text { "Jerry") }) \\ & \mathrm{C}_{\text {傑瑞 }}=\mathrm{g}(0.3 * \mathrm{f} 2(\text { "Tom"), } 0.2 * \mathrm{f} 2 \text { (Chase), } 0.5 * \mathrm{f} 2(\text { "Jerry")) } \end{aligned} ​C湯姆 ​=g(0.6∗f2( "Tom"), 0.2∗f2( Chase), 0.2∗f2 ("Jerry")) C追逐 ​=g(0.2∗f2( "Tom"), 0.7∗f2( Chase), 0.1∗f2( "Jerry") )C傑瑞 ​=g(0.3∗f2( "Tom"), 0.2∗f2 (Chase), 0.5∗f2( "Jerry")) ​

f2(“Tom”),f2(“Chase”),f2(“Jerry”)就是對應的隐藏層的值h(“Tom”),h(“Chase”),h(“Jerry”)。g函數就是權重求和。αi表示權值分布。是以Ci的公式就可以寫成:

C i = ∑ j = 1 n α i j h j C_i=\sum_{j=1}^n \alpha_{i j} h_j Ci​=j=1∑n​αij​hj​

怎麼知道attention模型所需要的輸入句子單詞注意力配置設定機率分布值 a i j a_{ij} aij​呢? 我們可以通過下文介紹的

注意力評分函數

求得

查詢、鍵和值

下面來看看如何通過自主性的與非自主性的注意力提示, 用神經網絡來設計注意力機制的架構。

首先,考慮一個相對簡單的狀況, 即隻使用非自主性提示。 要想将選擇偏向于感官輸入, 則可以簡單地使用參數化的全連接配接層, 甚至是非參數化的最大彙聚層或平均彙聚層。

在注意力機制的背景下,自主性提示被稱為

查詢(query)

。 給定任何查詢,注意力機制通過

注意力彙聚(attention pooling)

将選擇引導至

感官輸入(sensory inputs,例如中間特征表示)

。 在注意力機制中,這些感官輸入被稱為

值(value)

。 更通俗的解釋,每個值都與一個

鍵(key)

配對, 這可以想象為感官輸入的非自主提示。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上圖: 注意力機制通過注意力彙聚(注意力的配置設定方法)将查詢(自主性提示)和鍵(非自主性提示)結合在一起,實作對值(感官輸入)的選擇傾向。

注意力彙聚: Nadaraya-Watson 核回歸

上圖中的

注意力彙聚

是怎麼實作的呢?

接下來,我們通過講解Nadaraya-Watson核回歸模型來了解常見的

注意力彙聚模型

平均彙聚、非參數注意力彙聚、帶參數注意力彙聚

)。

Nadaraya-Watson核回歸模型 是一個簡單但完整的例子,可以用于示範具有注意力機制的機器學習。

簡單起見, 考慮下面這個回歸問題: 給定的成對的"輸入-輸出"資料集 { ( x 1 , y 1 ) , … , ( x n , y n ) } \left\{\left(x_1, y_1\right), \ldots,\left(x_n, y_n\right)\right\} {(x1​,y1​),…,(xn​,yn​)}, 如何學習 f f f來預測任意新輸入 x x x的輸出 y ^ = f ( x ) \hat{y}=f(x) y^​=f(x) ?

根據下面的非線性函數生成一個人工資料集, 其中加入的噪聲項為 ϵ \epsilon ϵ ,其中 ϵ \epsilon ϵ 服從均值為 0 和标準差為 0.5 0.5 0.5 的正态分布。:

y i = 2 sin ⁡ ( x i ) + x i 0.8 + ϵ , y_i=2 \sin \left(x_i\right)+x_i^{0.8}+\epsilon, yi​=2sin(xi​)+xi0.8​+ϵ,

📒平均彙聚

平均彙聚相當于對所有輸入樣本配置設定相同程度的注意力

我們先使用最簡單的估計器來解決回歸問題: 基于平均彙聚來計算所有訓練樣本輸出值的平均值:

f ( x ) = 1 n ∑ i = 1 n y i f(x)=\frac{1}{n} \sum_{i=1}^n y_i f(x)=n1​i=1∑n​yi​

如下圖所示,這個估計器确實不夠聰明: 真實函數

f

(“Truth”)和預測函數(“Pred”)相差很大。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒非參數注意力彙聚

非參數注意力彙聚相當于對靠近x的樣本配置設定更大程度的注意力。

顯然,平均彙聚忽略了輸入 x i x_i xi​。 于是提出了一個更好的想法: 根據輸入的位置對輸出 y i y_i yi​ 進行權重:

f ( x ) = ∑ i = 1 n K ( x − x i ) ∑ j = 1 n K ( x − x j ) y i , f(x)=\sum_{i=1}^n \frac{K\left(x-x_i\right)}{\sum_{j=1}^n K\left(x-x_j\right)} y_i, f(x)=i=1∑n​∑j=1n​K(x−xj​)K(x−xi​)​yi​,

其中 K K K是核 (kernel) 。公式所描述的估計器被稱為 Nadaraya-Watson核回歸 (Nadaraya-Watson kernel regression)。

這裡我們不會深入讨論核函數的細節, 但受此啟發, 我們可以從注意力機制架構的角度重寫, 成為一個更加通用的注意力彙聚 (attention pooling) 公式:

f ( x ) = ∑ i = 1 n α ( x , x i ) y i , f(x)=\sum_{i=1}^n \alpha\left(x, x_i\right) y_i, f(x)=i=1∑n​α(x,xi​)yi​,

其中 x x x是查詢, ( x i , y i ) \left(x_i, y_i\right) (xi​,yi​)是鍵值對。注意力彙聚是 y i y_i yi​的權重平均。将查詢 x x x和鍵 x i x_i xi​之間的關系模組化為注意力權重 (attention weight) α ( x , x i ) \alpha\left(x, x_i\right) α(x,xi​), 這個權重将被配置設定給每一個對應值 y i y_i yi​ 。對于任何查詢, 模型在所有鍵值對注意力權重都是一個有效的機率分布:它們是非負的, 并且總和為 1 。

為了更好地了解注意力彙聚, 我們考慮一個高斯核 (Gaussian kernel), 其定義為:

K ( u ) = 1 2 π exp ⁡ ( − u 2 2 ) K(u)=\frac{1}{\sqrt{2 \pi}} \exp \left(-\frac{u^2}{2}\right) K(u)=2π

​1​exp(−2u2​)

将高斯核代入可以得到:

f ( x ) = ∑ i = 1 n α ( x , x i ) y i = ∑ i = 1 n exp ⁡ ( − 1 2 ( x − x i ) 2 ) ∑ j = 1 n exp ⁡ ( − 1 2 ( x − x j ) 2 ) y i = ∑ i = 1 n softmax ⁡ ( − 1 2 ( x − x i ) 2 ) y i . \begin{aligned} f(x) & =\sum_{i=1}^n \alpha\left(x, x_i\right) y_i \\ & =\sum_{i=1}^n \frac{\exp \left(-\frac{1}{2}\left(x-x_i\right)^2\right)}{\sum_{j=1}^n \exp \left(-\frac{1}{2}\left(x-x_j\right)^2\right)} y_i \\ & =\sum_{i=1}^n \operatorname{softmax}\left(-\frac{1}{2}\left(x-x_i\right)^2\right) y_i . \end{aligned} f(x)​=i=1∑n​α(x,xi​)yi​=i=1∑n​∑j=1n​exp(−21​(x−xj​)2)exp(−21​(x−xi​)2)​yi​=i=1∑n​softmax(−21​(x−xi​)2)yi​.​

在上式中, 如果一個鍵 x i x_i xi​越是接近給定的查詢 x x x, 那麼配置設定給這個鍵對應值 y i y_i yi​的注意力權重就會越大, 也就“獲得了 更多的注意力"。值得注意的是, Nadaraya-Watson核回歸是一個非參數模型。是以,上式是非參數的注意力彙聚 (nonparametric attention pooling)模型。

接下來, 我們将基于這個非參數的注意力彙聚模型來繪制預測結果。你會發現新的模型預測 線是平滑的, 并且比平均彙聚的預測更接近真實。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

現在,我們來觀察注意力的權重。 這裡測試資料的輸入相當于查詢,而訓練資料的輸入相當于鍵。 因為兩個輸入都是經過排序的,是以由觀察可知“查詢-鍵”對越接近, 注意力彙聚的注意力權重就越高。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒帶參數注意力彙聚

帶參數注意力彙聚相當于在非參數的注意力機制上添加一個可學習的參數

非參數的Nadaraya-Watson核回歸具有一緻性(consistency)的優點: 如果有足夠的資料,此模型會收斂到最優結果。 盡管如此,我們還是可以輕松地将可學習的參數內建到注意力彙聚中。

例如,在下面的查詢 x x x和鍵 x i x_i xi​之間的距離乘以

可學習參數

w w w:

f ( x ) = ∑ i = 1 n α ( x , x i ) y i = ∑ i = 1 n exp ⁡ ( − 1 2 ( ( x − x i ) w ) 2 ) ∑ j = 1 n exp ⁡ ( − 1 2 ( ( x − x j ) w ) 2 ) y i = ∑ i = 1 n softmax ⁡ ( − 1 2 ( ( x − x i ) w ) 2 ) y i \begin{aligned}f(x) & =\sum_{i=1}^n \alpha\left(x, x_i\right) y_i \\& =\sum_{i=1}^n \frac{\exp \left(-\frac{1}{2}\left(\left(x-x_i\right) w\right)^2\right)}{\sum_{j=1}^n \exp \left(-\frac{1}{2}\left(\left(x-x_j\right) w\right)^2\right)} y_i \\& =\sum_{i=1}^n \operatorname{softmax}\left(-\frac{1}{2}\left(\left(x-x_i\right) w\right)^2\right) y_i\end{aligned} f(x)​=i=1∑n​α(x,xi​)yi​=i=1∑n​∑j=1n​exp(−21​((x−xj​)w)2)exp(−21​((x−xi​)w)2)​yi​=i=1∑n​softmax(−21​((x−xi​)w)2)yi​​

我們将通過訓練這個模型來學習注意力彙聚的參數。訓練完帶參數的注意力彙聚模型後可以發現: 在嘗試拟合帶噪聲的訓練資料時, 預測結果繪制的線不如之前非參數模型的平滑。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

為什麼新的模型更不平滑了呢? 下面看一下輸出結果的繪制圖: 與非參數的注意力彙聚模型相比, 帶參數的模型加入可學習的參數後, 曲線在注意力權重較大的區域變得更不平滑。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

❓ 為什麼要在機器學習中引入注意力機制呢?

在全連接配接層,FC隻能考慮相鄰的幾個資料,但是無法考慮到整個序列。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

注意力機制(self-attention)可以考慮到整個序列的資訊。是以,輸出的向量帶有全局的上下文資訊。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

例如,下圖中的 b 1 b^1 b1 是綜合考慮了整個輸入序列( a 1 , a 2 , a 3 , a 4 a^1,a^2,a^3,a^4 a1,a2,a3,a4)得到的。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

但是對于 a 1 , a 2 , a 3 , a 4 a^1,a^2,a^3,a^4 a1,a2,a3,a4的關注程度是不一樣的(也可以了解為相關性 α \alpha α),我們後續再細講如何配置設定注意力。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

注意力評分函數

接下來,我們講解如何通過注意力評分函數來配置設定注意力。

我們使用高斯核來對查詢(query)和鍵(key)之間的關系模組化。 我們可以将高斯核指數部分視為注意力評分函數(attention scoring function), 簡稱

評分函數(scoring function)

, 然後把這個函數的輸出結果輸入到

softmax函數

中進行運算。 通過上述步驟,我們将得到與鍵對應的值的機率分布(即注意力權重)。 最後,注意力彙聚的輸出就是基于這些

注意力權重的值的權重和

下圖說明了如何将注意力彙聚的輸出計算成為值的權重和, 其中a表示注意力評分函數。 由于注意力權重是機率分布, 是以權重和其本質上是權重平均值。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

用數學語言描述, 假設有一個查詢 q ∈ R q \mathbf{q} \in \mathbb{R}^q q∈Rq 和 m m m個"鍵一值"對 ( k 1 , v 1 ) , … , ( k m , v m ) \left(\mathbf{k}_1, \mathbf{v}_1\right), \ldots,\left(\mathbf{k}_m, \mathbf{v}_m\right) (k1​,v1​),…,(km​,vm​), 其中 k i ∈ R k , v i ∈ R v \mathbf{k}_i \in \mathbb{R}^k, \mathbf{v}_i \in \mathbb{R}^v ki​∈Rk,vi​∈Rv。注意 力彙聚函數 f f f就被表示成值的權重和:

f ( q , ( k 1 , v 1 ) , … , ( k m , v m ) ) = ∑ i = 1 m α ( q , k i ) v i ∈ R v , f\left(\mathbf{q},\left(\mathbf{k}_1, \mathbf{v}_1\right), \ldots,\left(\mathbf{k}_m, \mathbf{v}m\right)\right)=\sum_{i=1}^m \alpha\left(\mathbf{q}, \mathbf{k}_i\right) \mathbf{v}_i \in \mathbb{R}^v, f(q,(k1​,v1​),…,(km​,vm))=i=1∑m​α(q,ki​)vi​∈Rv,

其中查詢 q \mathbf{q} q和鍵 k i \mathbf{k}_i ki​的注意力權重(标量)是通過注意力評分函數 a a a将兩個向量映射成标量, 再經過softmax運算得到的:

α ( q , k i ) = softmax ⁡ ( a ( q , k i ) ) = exp ⁡ ( a ( q , k i ) ) ∑ j = 1 m exp ⁡ ( a ( q , k j ) ) ∈ R . \alpha\left(\mathbf{q}, \mathbf{k}_i\right)=\operatorname{softmax}\left(a\left(\mathbf{q}, \mathbf{k}_i\right)\right)=\frac{\exp \left(a\left(\mathbf{q}, \mathbf{k}i\right)\right)}{\sum{j=1}^m \exp \left(a\left(\mathbf{q}, \mathbf{k}_j\right)\right)} \in \mathbb{R} . α(q,ki​)=softmax(a(q,ki​))=∑j=1mexp(a(q,kj​))exp(a(q,ki))​∈R.

正如我們所看到的,選擇不同的注意力評分函數a會導緻不同的注意力彙聚操作。 在本節中,我們将介紹兩個流行的

評分函數(加性注意力、縮放點積注意力)

,稍後将用他們來實作更複雜的

注意力機制

📒掩蔽softmax操作

掩蔽softmax操作, 是為實作下文的評分函數做鋪墊。

正如上面提到的,softmax操作用于輸出一個機率分布作為注意力權重。 在某些情況下,并非所有的值都應該被納入到注意力彙聚中。 例如,為了高效處理小批量資料集, 某些文本序列被填充了沒有意義的特殊詞元。 為了僅将有意義的詞元作為值來擷取注意力彙聚, 我們可以指定一個有效序列長度(即詞元的個數), 以便在計算softmax時過濾掉超出指定範圍的位置。 通過這種方式,我們可以在下面的

masked_softmax

函數中 實作這樣的掩蔽softmax操作(masked softmax operation), 其中任何超出有效長度的位置都被掩蔽并置為0。

#@save
def masked_softmax(X, valid_lens):
    """通過在最後一個軸上掩蔽元素來執行softmax操作"""
    # X:3D張量,valid_lens:1D或2D張量
    if valid_lens is None:
        return nn.functional.softmax(X, dim=-1)
    else:
        shape = X.shape
        if valid_lens.dim() == 1:
            valid_lens = torch.repeat_interleave(valid_lens, shape[1])
        else:
            valid_lens = valid_lens.reshape(-1)
        # 最後一軸上被掩蔽的元素使用一個非常大的負值替換,進而其softmax輸出為0
        X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens,
                              value=-1e6)
        return nn.functional.softmax(X.reshape(shape), dim=-1)
           

為了示範此函數是如何工作的, 考慮由兩個2×4矩陣表示的樣本, 這兩個樣本的有效長度分别為2和3。 經過掩蔽softmax操作,超出有效長度的值都被掩蔽為0。

tensor([[[0.5423, 0.4577, 0.0000, 0.0000],
         [0.6133, 0.3867, 0.0000, 0.0000]],

        [[0.3324, 0.2348, 0.4329, 0.0000],
         [0.2444, 0.3943, 0.3613, 0.0000]]])
           

同樣,我們也可以使用二維張量,為矩陣樣本中的每一行指定有效長度。

tensor([[[1.0000, 0.0000, 0.0000, 0.0000],
         [0.4142, 0.3582, 0.2275, 0.0000]],

        [[0.5565, 0.4435, 0.0000, 0.0000],
         [0.3305, 0.2070, 0.2827, 0.1798]]])
           

加性注意力

首先,我們先來了解下第一種注意力評分函數:加性注意力

📒加性注意力(Addtive) 的數學定義

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

一般來說, 當查詢和鍵是不同長度的矢量時, 我們可以使用加性注意力作為評分函數。給定查詢 q ∈ R q \mathbf{q} \in \mathbb{R}^q q∈Rq和 鍵 k ∈ R k \mathbf{k} \in \mathbb{R}^k k∈Rk, 加性注意力 (additive attention) 的評分函數為 :

a ( q , k ) = w v ⊤ tanh ⁡ ( W q q + W k k ) ∈ R a(\mathbf{q}, \mathbf{k})=\mathbf{w}_v^{\top} \tanh \left(\mathbf{W}_q \mathbf{q}+\mathbf{W}_k \mathbf{k}\right) \in \mathbb{R} a(q,k)=wv⊤​tanh(Wq​q+Wk​k)∈R

其中可學習的參數是 W q ∈ R h × q 、 W k ∈ R h × k \mathbf{W}_q \in \mathbb{R}^{h \times q} 、 \mathbf{W}_k \in \mathbb{R}^{h \times k} Wq​∈Rh×q、Wk​∈Rh×k和 w v ∈ R h \mathbf{w}_v \in \mathbb{R}^h wv​∈Rh 。将查詢和鍵連結起來後輸入到一個多 層感覺機 (MLP) 中, 感覺機包含一個隐藏層, 其隐藏單元數是一個超參數 h h h。通過使用 tanh ⁡ \tanh tanh作為激活函數, 并且禁用偏置項。

📒加性注意力(Addtive) 的實作

#@save
class AdditiveAttention(nn.Module):
    """加性注意力"""
    def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs):
        super(AdditiveAttention, self).__init__(**kwargs)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=False)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=False)
        self.w_v = nn.Linear(num_hiddens, 1, bias=False)
        self.dropout = nn.Dropout(dropout)

    def forward(self, queries, keys, values, valid_lens):
        queries, keys = self.W_q(queries), self.W_k(keys)
        # 在次元擴充後,
        # queries的形狀:(batch_size,查詢的個數,1,num_hidden)
        # key的形狀:(batch_size,1,“鍵-值”對的個數,num_hiddens)
        # 使用廣播方式進行求和
        features = queries.unsqueeze(2) + keys.unsqueeze(1)
        features = torch.tanh(features)
        # self.w_v僅有一個輸出,是以從形狀中移除最後那個次元。
        # scores的形狀:(batch_size,查詢的個數,“鍵-值”對的個數)
        scores = self.w_v(features).squeeze(-1)
        self.attention_weights = masked_softmax(scores, valid_lens)
        # values的形狀:(batch_size,“鍵-值”對的個數,值的次元)
        return torch.bmm(self.dropout(self.attention_weights), values)
           

我們用一個小例子來示範上面的

AdditiveAttention

類, 其中查詢、鍵和值的形狀為(批量大小,步數或詞元序列長度,特征大小), 實際輸出為(2,1,20)、(2,10,2)和(2,10,4)。 注意力彙聚輸出的形狀為(批量大小,查詢的步數,值的次元)。

queries, keys = torch.normal(0, 1, (2, 1, 20)), torch.ones((2, 10, 2))
# values的小批量,兩個值矩陣是相同的
values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat(
    2, 1, 1)
valid_lens = torch.tensor([2, 6])

attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8,
                              dropout=0.1)
attention.eval()
attention(queries, keys, values, valid_lens)
           
tensor([[[ 2.0000,  3.0000,  4.0000,  5.0000]],

        [[10.0000, 11.0000, 12.0000, 13.0000]]], grad_fn=<BmmBackward0>)
           

盡管加性注意力包含了可學習的參數,但由于本例子中每個鍵都是相同的, 是以注意力權重是均勻的,由指定的有效長度決定。

d2l.show_heatmaps(attention.attention_weights.reshape((1, 1, 2, 10)),
                  xlabel='Keys', ylabel='Queries')
           
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

縮放點積注意力

然後我們再來看看第二種注意力評分函數:縮放點積注意力。(在self-attention中使用的就是縮放點積注意力)

📒縮放點積注意力(Dot-product) 的數學定義

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

使用點積可以得到計算效率更高的評分函數, 但是點積操作要求查詢和鍵具有相同的長度 d d d 假設查詢和鍵的所有元素 都是獨立的随機變量, 并且都滿足零均值和機關方差, 那麼兩個向量的點積的均值為 0 , 方差為 d d d 。為確定無論向量長 度如何, 點積的方差在不考慮向量長度的情況下仍然是 1 , 我們将點積除以 d \sqrt{d} d

​, 則縮放點積注意力 (scaled dotproduct attention)評分函數為:

a ( q , k ) = q ⊤ k / d a(\mathbf{q}, \mathbf{k})=\mathbf{q}^{\top} \mathbf{k} / \sqrt{d} a(q,k)=q⊤k/d

在實踐中, 我們通常從小批量的角度來考慮提高效率, 例如基于 n n n個查詢和 m m m個鍵一值對計算注意力, 其中查詢和鍵的

softmax ⁡ ( Q K ⊤ d ) V ∈ R n × v . \operatorname{softmax}\left(\frac{\mathbf{Q } \mathbf{K}^{\top}}{\sqrt{d}}\right) \mathbf{V} \in \mathbb{R}^{n \times v} . softmax(d

​QK⊤​)V∈Rn×v.

📒圖解縮放點積的實作過程

首先我們以單個元素的計算為例 (下述的圖省略了除以根号d的操作)

1)計算

注意力得分(Attention score)

如下圖,以計算 a 1 , 2 a_{1,2} a1,2​ a 1 , 3 a_{1,3} a1,3​ a 1 , 4 a_{1,4} a1,4​ 為例。

先将 a 1 a^1 a1和 W q W^q Wq 相乘,得到 q 1 q^1 q1。

然後分别将 a 2 a^2 a2 a 3 a^3 a3 a 4 a^4 a4 和 W k W^k Wk 相乘,得到 k 2 k^2 k2 k 3 k^3 k3 k 4 k^4 k4

接着,分别将 q 1 q^1 q1和 k 2 k^2 k2 k 3 k^3 k3 k 4 k^4 k4進行點積運算得到 a 1 , 2 a_{1,2} a1,2​ a 1 , 3 a_{1,3} a1,3​ a 1 , 4 a_{1,4} a1,4​

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

2)通過Soft-max進行歸一化

通過下式将注意力得分進行

soft-max

操作。

α 1 , i ′ = exp ⁡ ( α 1 , i ) / ∑ j exp ⁡ ( α 1 , j ) \alpha_{1, i}^{\prime}=\exp \left(\alpha_{1, i}\right) / \sum_j \exp \left(\alpha_{1, j}\right) α1,i′​=exp(α1,i​)/j∑​exp(α1,j​)

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

3) 權重求和

首先,分别将 a 1 a^1 a1 a 2 a^2 a2 a 3 a^3 a3 a 4 a^4 a4 和 W v W^v Wv 相乘,得到 v 1 v^1 v1 v 2 v^2 v2 v 3 v^3 v3 v 4 v^4 v4

然後 按照下式求得 b 1 b^1 b1

b 1 = ∑ i α 1 , i ′ v i b^{\mathbf{1}}=\sum_i \alpha_{1, i}^{\prime} \boldsymbol{v}^i b1=i∑​α1,i′​vi

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

同理,通過下式計算得到 b 2 b^2 b2

b 2 = ∑ i α 2 , i ′ v i \boldsymbol{b}^2=\sum_i \alpha_{2, i}^{\prime} \boldsymbol{v}^{\boldsymbol{i}} b2=i∑​α2,i′​vi

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

依次類推,我們可以計算得到 b 1 b^1 b1, b 2 b^2 b2 , b 3 b^3 b3, b 4 b^4 b4 。并且計算過程可以

并行

執行。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
然後,我們嘗試将上述的計算過程表述成矩陣運算。

a) 首先,我們來觀察下 q i q^i qi k i k^i ki v i v^i vi 的計算過程。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

b) 然後,再來觀察下注意力得分 a 1 , i a_{1,i} a1,i​的計算過程

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

a 2 , i a_{2,i} a2,i​ 同理

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

c) 然後,我們可以得到 A ′ A' A′ 的矩陣表達形式。

A = K T Q A ′ = s o f t m a x ( A ) A=K^{T}Q \\ A'=softmax(A) A=KTQA′=softmax(A)

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

d) 最終得到輸出張量 O O O

O = V A ′ O=VA' O=VA′

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

e)總結

  • 當輸入的資料是 I I I, 将 I I I 分别和可學習的參數 W q W^q Wq, W k W^k Wk, W v W^v Wv 相乘,得到 Q , K , V Q,K,V Q,K,V
  • A = K T Q A=K^{T}Q A=KTQ 然後對A進行softmax操作,得到

    注意力矩陣(Attention Matrix)

    A ′ A' A′
  • 最終得到輸出張量 O = V A ′ O=VA' O=VA′
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒縮放點積注意力的代碼實作

在下面的縮放點積注意力的實作中, 我們使用了暫退法進行模型正則化。

#@save
class DotProductAttention(nn.Module):
    """縮放點積注意力"""
    def __init__(self, dropout, **kwargs):
        super(DotProductAttention, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)

    # queries的形狀:(batch_size,查詢的個數,d)
    # keys的形狀:(batch_size,“鍵-值”對的個數,d)
    # values的形狀:(batch_size,“鍵-值”對的個數,值的次元)
    # valid_lens的形狀:(batch_size,)或者(batch_size,查詢的個數)
    def forward(self, queries, keys, values, valid_lens=None):
        d = queries.shape[-1]
        # 設定transpose_b=True為了交換keys的最後兩個次元
        scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d)
        self.attention_weights = masked_softmax(scores, valid_lens)
        return torch.bmm(self.dropout(self.attention_weights), values)
           

為了示範上述的

DotProductAttention

類, 我們使用與先前加性注意力例子中相同的鍵、值和有效長度。 對于點積操作,我們令查詢的特征次元與鍵的特征次元大小相同。

queries = torch.normal(0, 1, (2, 1, 2))
attention = DotProductAttention(dropout=0.5)
attention.eval()
attention(queries, keys, values, valid_lens)
           
tensor([[[ 2.0000,  3.0000,  4.0000,  5.0000]],
        [[10.0000, 11.0000, 12.0000, 13.0000]]])
           

與加性注意力示範相同,由于鍵包含的是相同的元素, 而這些元素無法通過任何查詢進行區分,是以獲得了均勻的注意力權重。

d2l.show_heatmaps(attention.attention_weights.reshape((1, 1, 2, 10)),
                  xlabel='Keys', ylabel='Queries')
           
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

自注意力(Self-Attention)

自注意力的定義和計算

📒Attention和Self-Attention 的差別

首先我們先來看看Attention的優缺點。
  • Attention 的優點:

    1.速度快。Attention機制不再依賴于RNN,解決了RNN不能并行計算的問題。這裡需要說明一下,基于Attention機制的seq2seq模型,因為是有監督的訓練,是以咱們在訓練的時候,在decoder階段并不是說預測出了一個詞,然後再把這個詞作為下一個輸入,因為有監督訓練,咱們已經有了target的資料,是以是可以并行輸入的,可以并行計算decoder的每一個輸出,但是再做預測的時候,是沒有target資料地,這個時候就需要基于上一個時間節點的預測值來當做下一個時間節點decoder的輸入。是以節省的是訓練的時間。

    2.效果好。效果好主要就是因為注意力機制,能夠擷取到局部的重要資訊,能夠抓住重點。

  • Attention 的缺點

    1.隻能在Decoder階段實作并行運算,Encoder部分依舊采用的是RNN,LSTM這些按照順序編碼的模型,Encoder部分還是無法實作并行運算,不夠完美。

    2.就是因為Encoder部分目前仍舊依賴于RNN,是以對于中長距離之間,兩個詞互相之間的關系沒有辦法很好的擷取。

Self-Attention 是在Attention的基礎上,針對上述的兩個缺點進行改進得到的。
  • Self-Attention和Attention的差別

    在一般任務的Encoder-Decoder架構中,輸入

    Source

    和輸出

    Target

    内容是不一樣的。比如對于英-中機器翻譯來說,Source是英文句子,Target是對應的翻譯出的中文句子,

    Attention機制發生在Target的元素和Source中的所有元素之間

    Self Attention,指的是Source内部元素之間或者Target内部元素之間發生的Attention機制

    ,也可以了解為Target=Source這種特殊情況下的注意力計算機制
下面我們講解一個中英翻譯的例子來了解self-attention

例如,句子

”The animal didn't cross the street because it was too tired”

中的

it

代指的是什麼?即尋找這句話中和

it

最相關的單詞。

通過self-attention我們發現

it

在這句話中與之最相關的是

The animal

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

可以這麼通俗的了解:

尋找英文句子對應的中文翻譯,這個是Attention (隻能在解碼階段使用,而不能夠在編碼階段使用)

尋找英文句子中單詞與單詞之間的關系,這個是Self-Attention (在編碼階段就可以使用,可以尋找到輸入句子間的内部關聯)

  • Self-Attention的好處

    1)如此引入Self Attention後會更容易捕獲句子中長距離的互相依賴的特征,因為如果是RNN或者LSTM,需要依次序序列計算,對于遠距離的互相依賴的特征,要經過若幹時間步步驟的資訊累積才能将兩者聯系起來,而距離越遠,有效捕獲的可能性越小。但是Self Attention在計算過程中會直接将句子中任意兩個單詞的聯系通過一個計算步驟直接聯系起來,是以遠距離依賴特征之間的距離被極大縮短,有利于有效地利用這些特征。

    2)Self Attention對于增加計算的并行性也有直接幫助作用

Self Attention正好彌補了attention機制的兩個缺點,是以Self Attention逐漸被廣泛使用。

分步自注意力機制的計算過程:

Transformer 論文中采用的

縮放點積注意力

。作者的理由是:點積注意力計算起來更快更簡單。

1)計算query、key、value向量

假如輸入序列是"Thinking Machines",x1,x2就是對應地"Thinking"和"Machines"添加過位置編碼之後的詞向量,然後詞向量通過三個權值矩陣 W Q , W K , W V W^Q,W^K,W^V WQ,WK,WV

轉變成為計算Attention值所需的Query,Keys,Values向量。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

2)計算注意力得分。

假設我們現在在計算輸入中第一個單詞Thinking的自注意力。我們需要使用自注意力給輸入句子中的每個單詞打分,這個分數決定當我們編碼某個位置的單詞的時候,應該對其他位置上的單詞給予多少關注度。

這個得分是query和key的點乘積得出來的。

例如,我們要算第一個位置的注意力得分的時候就要将第一個單詞的query和其他的key依次相乘,在這裡就是 q 1 k 1 q_1 k_1 q1​k1​和 q 1 k 2 q_1 k_2 q1​k2​

3) 将計算獲得的注意力分數除以8。

為什麼選8?是因為key向量的次元 d k d_k dk​是64,取其平方根,這樣讓梯度計算的時候更穩定。預設是這麼設定的,當然也可以用其他值。

4)softmax歸一化

然後通過softmax計算,将每個單詞之間的得分向量轉換成[0,1]之間的機率分布,同時更加凸顯單詞之間的關系。

5)将每個value向量乘以注意力分數。

這是為了留下我們想要關注的單詞的value,并把其他不相關的單詞丢掉。

6)将上一步的結果相加,輸出本位置的注意力結果。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒将上述過程用矩陣計算:

計算Query, Key, Value矩陣。直接把輸入的向量打包成一個矩陣 X X X, 再把它乘以訓練好的 W Q W^Q WQ、 W K W^K WK、 W V W^V WV。

實際使用中,每一個樣本,也就是每一條序列資料都是以矩陣的形式輸入地,故可以看到下圖中,X矩陣是由"Tinking"和"Machines"詞向量組成的矩陣,然後跟過變換得到Q,K,V。假設詞向量是512維,X矩陣的次元是(2,512)。 W Q , W K , W V W^Q,W^K,W^V WQ,WK,WV均是(512,64)維,得到的Query,Keys,Values就都是(2,64)維。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
如上圖,X矩陣的每一行都代表輸入句子中的一個單詞。我們看一下次元的差異:原文中嵌入矩陣的長度為 512 , q 、 k 、 v 矩陣的長度為 64 ;在這裡我們分别用 4 個格子表示和3個格子表示。

因為我們現在用矩陣處理,是以可以直接将之前的第二步到第六步壓縮到一個公式中一步到位獲得最終的注意力結果Z

首先計算注意力機制得分 ,score是一個2x2的矩陣。

 score  = Q ⋅ K T \text { score }=\mathrm{Q} \cdot \mathrm{K}^{\mathrm{T}}  score =Q⋅KT

然後進行歸一化

 score  =  score  / d k \text { score }=\text { score } / \sqrt{\mathrm{d}_{\mathrm{k}}}  score = score /dk​

接着,經過softmax後,score轉換成一個值分布在[0,1]之間的 α α α機率分布矩陣, α α α大小為(2,2) 。

α = s o f t m a x ( score ) \alpha = softmax(\text{score}) α=softmax(score)

最後,根據每個單詞之間的機率分布,然後乘上對應的Values值,α與V進行點積

Z = α ⋅ V Z=\alpha \cdot V Z=α⋅V

V的為次元是(2,64),(2,2)x(2,64)最後得到的Z是(2,64)維的矩陣

整體的計算圖如下:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

自注意力的應用

🔨自然語言處理

Self-Attention最廣泛的應用是在自然語言處理(Natural Langue Processing ,NLP)

  • Transformer《Attention Is All You Need》
  • BERT 《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》

🔨語音處理

對語音信号進行采樣,得到一組向量。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如果輸入的序列長度為L, 則Attention Matrix如下圖所示:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • Truncated Self-Attention 《Transformer-Transducer: End-to-End Speech Recognition with Self-Attention》

    以往的attention是注意在整個sequence上面,而truncated self-attention隻考慮部分sequence,因為語音的sequence非常長。具體多長是一個需要調整的參數,也就是說隻注意一個視窗内的元素,至于這個視窗是多大是一個待調整的參數。

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

🔨圖像處理

一張圖檔也可以看做是一組向量

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • Self-Attention GAN 《Self-Attention Generative Adversarial Networks》
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • Detection Transformer (DETR) 《End-to-End Object Detection with Transformers》
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

🔨更多

  • 《Long Range Arena: A Benchmark for Efficient Transformers》比較了不同注意力模型的性能。
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • 《Efficient Transformers: A Survey》 對多種Transformer的模型進行了綜述。
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Self-Attention 🆚 CNN 🆚 RNN

下面我們來看一下Self-Attention 和 卷積神經網絡(CNN)、循環神經網絡(RNN)之間的差別和聯系。

Self-attention 🆚 CNN

CNN隻能注意到

recptive filed

中的内容,可以把CNN看做一個簡化的Self-attention。

也可以把Self-attention看做是具有

learnable receptive field 的CNN

.

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

《On the Relationship between Self-Attention and Convolutional Layers》 介紹了Self-Attention和CNN之間的關系

《An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale》 表明:在少量資料的情況下,CNN表現的更好。但是在大量的資料下,Self-Attention的表現更出色。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Self-attention 🆚 RNN

1)對于RNN而言,在序列開始的的元素,随着序列的增加,很難影響到靠後的元素;而對于Self-Attention而言,考慮了全局的注意力資訊,序列中靠前的元素依然可能對靠後的元素産生較大的影響。

2)對于RNN而言,目前時間點元素的輸出都依賴于前一個時間點元素的輸出。是順序操作的,無法并行;而對于Self-Attention而言,是可以并行操作的,提升了計算效率。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

《Transformers are RNNs:Fast Autoregressive Transformers with Linear Attention》 這篇文章進一步介紹了Transformer和RNN之間的關系

Self-Attention 🆚 CNN 🆚 RNN

接下來比較下面幾個架構,目标都是将由n個詞元組成的序列映射到另一個長度相等的序列,其中的每個輸入詞元或輸出詞元都由d維向量表示。具體來說,将比較的是卷積神經網絡、循環神經網絡和自注意力這幾個架構的計算複雜性、順序操作和最大路徑長度。請注意,順序操作會妨礙并行計算,而任意的序列位置組合之間的路徑越短,則能更輕松地學習序列中的遠距離依賴關系

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

考慮一個卷積核大小為 k k k 的卷積層。在後面的章節将提供關于使用卷積神經網絡處理序列的更多詳細資訊。目前隻需要 知道的是, 由于序列長度是 n n n, 輸入和輸出的通道數量都是 d d d, 是以卷積層的計算複雜度為 O ( k n d 2 ) \mathcal{O}\left(k n d^2\right) O(knd2) 。如上圖所示, 卷積神經網絡是分層的, 是以為有 O ( 1 ) \mathcal{O}(1) O(1) 個順序操作, 最大路徑長度為 O ( n / k ) \mathcal{O}(n / k) O(n/k) 。例如, x 1 \mathbf{x}_1 x1​ 和 x 5 \mathbf{x}_5 x5​ 處于上圖圖中卷積核大 小為 3 的雙層卷積神經網絡的感受野内。

當更新循環神經網絡的隐狀态時, d × d d \times d d×d 權重矩陣和 d d d 維隐狀态的乘法計算複雜度為 O ( d 2 ) \mathcal{O}\left(d^2\right) O(d2) 。由于序列長度為 n n n, 是以循 環神經網絡層的計算複雜度為 O ( n d 2 ) \mathcal{O}\left(n d^2\right) O(nd2) 。根據上圖 有 O ( n ) \mathcal{O}(n) O(n) 個順序操作無法并行化, 最大路徑長度也是 O ( n ) \mathcal{O}(n) O(n) 。

在自注意力中, 查詢、鍵和值都是 n × d n \times d n×d 矩陣。考慮 縮放的"點一積“注意力, 其中 n × d n \times d n×d 矩陣乘以 d × n d \times n d×n 矩 陣。之後輸出的 n × n n \times n n×n 矩陣乘以 n × d n \times d n×d 矩陣。是以, 自注意力具有 O ( n 2 d ) \mathcal{O}\left(n^2 d\right) O(n2d) 計算複雜性。正如在 圖10.6.1中所講, 每個 詞元都通過自注意力直接連接配接到任何其他詞元。是以, 有 O ( 1 ) \mathcal{O}(1) O(1) 個順序操作可以并行計算, 最大路徑長度也是 O ( 1 ) \mathcal{O}(1) O(1) 。

總而言之,卷積神經網絡和自注意力都擁有并行計算的優勢, 而且自注意力的最大路徑長度最短。 但是因為其計算複雜度是關于序列長度的二次方,是以在很長的序列中計算會非常慢。

多頭自注意力 (Multihead Attention)

在實踐中,當給定相同的查詢、鍵和值的集合時, 我們希望模型可以基于相同的注意力機制學習到不同的行為, 然後将不同的行為作為知識組合起來, 捕獲序列内各種範圍的依賴關系 (例如,短距離依賴和長距離依賴關系)。 是以,允許注意力機制組合使用查詢、鍵和值的不同子空間表示(representation subspaces)可能是有益的。

為此,與其隻使用單獨一個注意力彙聚, 我們可以用獨立學習得到的h組不同的 線性投影(linear projections)來變換查詢、鍵和值。 然後,這h組變換後的查詢、鍵和值将并行地送到注意力彙聚中。 最後,将這h個注意力彙聚的輸出拼接在一起, 并且通過另一個可以學習的線性投影進行變換, 以産生最終輸出。 這種設計被稱為多頭注意力(multihead attention)。 對于h個注意力彙聚輸出,每一個注意力彙聚都被稱作一個頭(head)。 下圖 展示了使用全連接配接層來實作可學習的線性變換的多頭注意力。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒 多頭自注意力機制的數學定義

在實作多頭注意力之前, 讓我們用數學語言将這個模型形式化地描述出來。給定查詢 q ∈ R d q \mathbf{q} \in \mathbb{R}^{d_q} q∈Rdq​ 、鍵 k ∈ R d k \mathbf{k} \in \mathbb{R}^{d_k} k∈Rdk​和 值 v ∈ R d v \mathbf{v} \in \mathbb{R}^{d_v} v∈Rdv​,每個注意力頭 h i ( i = 1 , … , h ) \mathbf{h}_i(i=1, \ldots, h) hi​(i=1,…,h)的計算方法為:

h i = f ( W i ( q ) q , W i ( k ) k , W i ( v ) v ) ∈ R p v \mathbf{h}_i=f\left(\mathbf{W}_i^{(q)} \mathbf{q}, \mathbf{W}_i^{(k)} \mathbf{k}, \mathbf{W}_i^{(v)} \mathbf{v}\right) \in \mathbb{R}^{p_v} hi​=f(Wi(q)​q,Wi(k)​k,Wi(v)​v)∈Rpv​

其中, 可學習的參數包括 W i ( q ) ∈ R p q × d q 、 W i ( k ) ∈ R p k × d k  和  W i ( v ) ∈ R p v × d v \mathbf{W}_i^{(q)} \in \mathbb{R}^{p_q \times d_q} 、 \mathbf{W}_i^{(k)} \in \mathbb{R}^{p_k \times d_k \text { 和 }} \mathbf{W}_i^{(v)} \in \mathbb{R}^{p_v \times d_v} Wi(q)​∈Rpq​×dq​、Wi(k)​∈Rpk​×dk​ 和 Wi(v)​∈Rpv​×dv​, 以及代表注意力彙聚的函數 f f f 。 f f f可以加性注意力和縮放點積注意力。多頭注意力的輸出需要經過另一個線性轉換, 它對應着 h h h個頭連結後的結 果, 是以其可學習參數是 W o ∈ R p o × h p v \mathbf{W}_o \in \mathbb{R}^{p_o \times h p_v} Wo​∈Rpo​×hpv​ :

W o [ h 1 ⋮ h h ] ∈ R p o . \mathbf{W}_o\left[\begin{array}{c} \mathbf{h}_1 \\ \vdots \\ \mathbf{h}_h \end{array}\right] \in \mathbb{R}^{p_o} . Wo​

​h1​⋮hh​​

​∈Rpo​.

基于這種設計, 每個頭都可能會關注輸入的不同部分, 可以表示比簡單權重平均值更複雜的函數。

📒 圖解多頭自注意力機制的實作過程

如下圖,以兩頭的自注意力機制為例。其中

head-1

head-2

的可學習權重

W

不同。

設 a i a^i ai和 a j a^j aj 是輸入序列中的兩個元素。對于

head-1

而言, b i , 1 b^{i,1} bi,1的計算過程如下。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

對于

head-2

而言, b i , 2 b^{i,2} bi,2的計算過程如下。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

然後将矩陣 W O W^O WO和矩陣 [ b i , 0 , b i , 1 ] [b^{i,0},b^{i,1}] [bi,0,bi,1]相乘,得到 b i b^i bi

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒 多頭注意力機制的好處:

  1. 它擴充了模型專注于不同位置的能力。

    在上面例子裡隻計算一個自注意力的的例子中,編碼“Thinking”的時候,雖然最後 Z 1 Z_1 Z1​或多或少包含了其他位置單詞的資訊,但是它實際編碼中還是被“Thinking”單詞本身所支配。

    如果我們翻譯一個句子,比如

    “The animal didn’t cross the street because it was too tired”

    ,我們會想知道

    “it”

    指的是哪個詞,這時模型的“多頭”注意力機制會起到作用。
  2. 它給了注意層多個“表示子空間”。

    就是在多頭注意力中同時用多個不同的 W Q W^Q WQ、 W K W^K WK、 W V W^V WV權重矩陣(Transformer使用8個頭部,是以我們最終會得到8個計算結果),每個權重都是随機初始化的。經過訓練每個 W Q W^Q WQ、 W K W^K WK、 W V W^V WV都能将輸入的矩陣投影到不同的表示子空間。

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
    Transformer中的一個多頭注意力(有8個head)的計算,就相當于用自注意力做8次不同的計算,并得到8個不同的結果 Z Z Z。
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
    但是這會存在一點問題,多頭注意力出來的結果會進入一個前饋神經網絡,這個前饋神經網絡可不能一下接收8個注意力矩陣,它的輸入需要是單個矩陣(矩陣中每個行向量對應一個單詞),是以我們需要一種方法把這8個壓縮成一個矩陣。怎麼做呢?我們将這些矩陣連接配接起來,然後将乘以一個附加的權重矩陣 W O W^O WO
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
    以上就是多頭自注意力的全部内容。讓我們把多頭注意力上述内容 放到一張圖裡看一下子:
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

現在我們已經看過什麼是多頭注意力了,讓我們回顧一下之前的一個例子,再看一下編碼“it”的時候每個頭的關注點都在哪裡:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

編碼it,用兩個head的時候:其中一個更關注the animal,另一個更關注tired。此時該模型對it的編碼。除了it本身的表達之外,同時也包含了the animal和tired的相關資訊

如果我們把所有的頭的注意力都可視化一下,就是下圖這樣,但是看起來事情好像突然又複雜了。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒 多頭注意力機制的代碼實作

在實作過程中, 我們選擇縮放點積注意力作為每一個注意力頭。為了避免計算代價和參數代價的大幅增長, 我們設定 p q = p k = p v = p o / h p_q=p_k=p_v=p_o / h pq​=pk​=pv​=po​/h 值得注意的是, 如果我們将查詢、鍵和值的線性變換的輸出數量設定為

p q h = p k h = p v h = p o p_q h=p_k h=p_v h=p_o pq​h=pk​h=pv​h=po​ ,則可以并行計算 h h h個頭。在下面的實作中, p o p_o po​是通過參數

num_hiddens

指定的。

#@save
class MultiHeadAttention(nn.Module):
    """多頭注意力"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 num_heads, dropout, bias=False, **kwargs):
        super(MultiHeadAttention, self).__init__(**kwargs)
        self.num_heads = num_heads
        self.attention = d2l.DotProductAttention(dropout)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)
        self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)
        self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)

    def forward(self, queries, keys, values, valid_lens):
        # queries,keys,values的形狀:
        # (batch_size,查詢或者“鍵-值”對的個數,num_hiddens)
        # valid_lens 的形狀:
        # (batch_size,)或(batch_size,查詢的個數)
        # 經過變換後,輸出的queries,keys,values 的形狀:
        # (batch_size*num_heads,查詢或者“鍵-值”對的個數,
        # num_hiddens/num_heads)
        queries = transpose_qkv(self.W_q(queries), self.num_heads)
        keys = transpose_qkv(self.W_k(keys), self.num_heads)
        values = transpose_qkv(self.W_v(values), self.num_heads)

        if valid_lens is not None:
            # 在軸0,将第一項(标量或者矢量)複制num_heads次,
            # 然後如此複制第二項,然後諸如此類。
            valid_lens = torch.repeat_interleave(
                valid_lens, repeats=self.num_heads, dim=0)

        # output的形狀:(batch_size*num_heads,查詢的個數,
        # num_hiddens/num_heads)
        output = self.attention(queries, keys, values, valid_lens)

        # output_concat的形狀:(batch_size,查詢的個數,num_hiddens)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)
           

為了能夠使多個頭并行計算, 上面的

MultiHeadAttention

類将使用下面定義的兩個轉置函數。 具體來說,

transpose_output

函數反轉了

transpose_qkv

函數的操作。

#@save
def transpose_qkv(X, num_heads):
    """為了多注意力頭的并行計算而變換形狀"""
    # 輸入X的形狀:(batch_size,查詢或者“鍵-值”對的個數,num_hiddens)
    # 輸出X的形狀:(batch_size,查詢或者“鍵-值”對的個數,num_heads,
    # num_hiddens/num_heads)
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)

    # 輸出X的形狀:(batch_size,num_heads,查詢或者“鍵-值”對的個數,
    # num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)

    # 最終輸出的形狀:(batch_size*num_heads,查詢或者“鍵-值”對的個數,
    # num_hiddens/num_heads)
    return X.reshape(-1, X.shape[2], X.shape[3])

#@save
def transpose_output(X, num_heads):
    """逆轉transpose_qkv函數的操作"""
    X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])
    X = X.permute(0, 2, 1, 3)
    return X.reshape(X.shape[0], X.shape[1], -1)
           

下面我們使用鍵和值相同的小例子來測試我們編寫的

MultiHeadAttention

類。 多頭注意力輸出的形狀是(

batch_size

num_queries

num_hiddens

)。

num_hiddens, num_heads = 100, 5
attention = MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,
                               num_hiddens, num_heads, 0.5)
attention.eval()
           
MultiHeadAttention(
  (attention): DotProductAttention(
    (dropout): Dropout(p=0.5, inplace=False)
  )
  (W_q): Linear(in_features=100, out_features=100, bias=False)
  (W_k): Linear(in_features=100, out_features=100, bias=False)
  (W_v): Linear(in_features=100, out_features=100, bias=False)
  (W_o): Linear(in_features=100, out_features=100, bias=False)
)
           
batch_size, num_queries = 2, 4
num_kvpairs, valid_lens =  6, torch.tensor([3, 2])
X = torch.ones((batch_size, num_queries, num_hiddens))
Y = torch.ones((batch_size, num_kvpairs, num_hiddens))
attention(X, Y, Y, valid_lens).shape
# torch.Size([2, 4, 100])
           

Transformer

論文《Attention is all you need》 是Transformer模型的開山之作

Transformer 的整體結構主要由三部分組成:Input 、Encoder-Decoder 、Output

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Transformer的整體結構

首先,我們從上至下一點點的來看一看Transformer的結構

讓我們首先将模型看作一個單獨的黑盒。在機器翻譯應用程式中,它将擷取一種語言的句子,并輸出另一種語言的翻譯。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

打開這個黑盒,我們首先可以看到一個編碼器(encoder)子產品和一個解碼器(decoder)子產品,以及二者之間存在某種關聯。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

再往裡看一下,編碼器子產品是n個encoder元件堆在一起,同樣解碼器子產品也是n個decoder元件堆在一起 (n是一個可調參數)。

n個編碼器元件的結構是相同的,但是它們之間的

權重是不共享的

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • 編碼器

    每個編碼器元件都可以分為2個子層。編碼器的輸入首先會進入一個自注意力層(Self-Attention),自注意力層的輸出會傳遞給一個前饋神經網絡(Feed Forward Neural Network),每個編碼器元件都是在相同的位置使用結構相同的前饋神經網絡。

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • 解碼器

    解碼器元件也含有前面編碼器中提到的兩個層,差別在于這兩個層之間還夾了一個注意力層,多出來的這個自注意力層的作用是讓解碼器能夠注意到輸入句子中相關的部分(和seq2seq中的attention一樣的作用)。

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
到這裡,我們已經對Transformer有了一個大緻的認識。接下來,我們将逐個仔細講解構成Transformer的三大部分: Input 、Encoder-Decoder 、Output

Transformer的輸入

首先,我們來看一下Transformer的第一個部分:輸入
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Transformer 中單詞的輸入表示 x由

Input Embeddings(單詞向量)

Poditional Encoding(位置編碼)

相加得到。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

為了讓模型能知道單詞的順序,我們添加了位置編碼,位置編碼是遵循某些特定模式的。

位置編碼向量和單詞embedding的次元是一樣的,比如下邊都是四個格子:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
❔ 為什麼是将positional encoding與詞向量相加,而不是拼接呢?

拼接相加都可以,隻是本身詞向量的次元512維就已經蠻大了,再拼接一個512維的位置向量,變成1024維,這樣訓練起來會相對慢一些,影響效率。兩者的效果是差不多地,既然效果差不多當然是選擇學習習難度較小的相加了。

單詞Embedding

單詞的 Embedding 有很多種方式可以擷取,例如可以采用

Word2Vec、Glove, one-hot

等算法預訓練得到,也可以在 Transformer 中訓練得到。

  • ont-hot 編碼
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • Word2Vec編碼
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

位置Encoding

Transformer 中除了單詞的 Embedding,還需要使用位置Embedding 表示單詞出現在句子中的位置。因為 Transformer 不采用 RNN 的結構,而是使用全局資訊,不能利用單詞的順序資訊,而這部分資訊對于 NLP 來說非常重要。是以 Transformer 中使用位置 Embedding 儲存單詞在序列中的相對或絕對位置。

在處理詞元序列時,循環神經網絡是逐個的重複地處理詞元的, 而自注意力則因為并行計算而放棄了順序操作。

為了使用序列的順序資訊,通過在輸入表示中添加

位置編碼(positional encoding)

來注入絕對的或相對的位置資訊。

位置編碼可以通過學習得到也可以直接固定得到。

Transformer中采用的是基于正弦函數和餘弦函數的固定位置編碼;BERT中的位置編碼是通過訓練得到的。

📒基于正弦函數和餘弦函數的固定位置編碼

《Attention Is All You Need》論文中Transformer使用的是正餘弦位置編碼。位置編碼通過使用不同頻率的正弦、餘弦函數生成,然後和對應的位置的詞向量相加,位置向量次元必須和詞向量的次元一緻。

假設輸入表示 X ∈ R n × d \mathbf{X} \in \mathbb{R}^{n \times d} X∈Rn×d包含一個序列中n個詞元的 d 維嵌入表示。位置編碼使用相同形狀的位置嵌入矩陣 P ∈ R n × d \mathbf{P} \in \mathbb{R}^{n \times d} P∈Rn×d 輸 出 X + P \mathbf{X}+\mathbf{P} X+P , 第 i i i行、第 2 j 2j 2j列和 2 j + 1 2j+1 2j+1列上的元素為:

p i , 2 j = sin ⁡ ( i 1000 0 2 j / d ) p i , 2 j + 1 = cos ⁡ ( i 1000 0 2 j / d ) . \begin{aligned} p_{i, 2 j} & =\sin \left(\frac{i}{10000^{2 j / d}}\right) \\ p_{i, 2 j+1} & =\cos \left(\frac{i}{10000^{2 j / d}}\right) . \end{aligned} pi,2j​pi,2j+1​​=sin(100002j/di​)=cos(100002j/di​).​

其中 i i i表示單詞在句子中的絕對位置, i = 0 , 1 , 2 … i=0,1,2… i=0,1,2… 例如:

Jerry

"Tom chase Jerry"

中的 i = 2 i=2 i=2; d m o d e l d_{model} dmodel​表示詞向量的次元,在這裡 d m o d e l = 512 d_{model}=512 dmodel​=512; 2 j 2j 2j和 2 j + 1 2j+1 2j+1表示奇偶性, j j j表示詞向量中的第幾維,例如這裡 d m o d e l = 512 d_{model}=512 dmodel​=512,故 j = 0 , 1 , 2 … 255 j=0,1,2…255 j=0,1,2…255。

乍一看, 這種基于三角函數的設計看起來很奇怪。在解釋這個設計之前, 讓我們先在下面的PositionalEncoding類 中實作它。
#@save
class PositionalEncoding(nn.Module):
    """位置編碼"""
    def __init__(self, num_hiddens, dropout, max_len=1000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)
        # 建立一個足夠長的P
        self.P = torch.zeros((1, max_len, num_hiddens))
        X = torch.arange(max_len, dtype=torch.float32).reshape(
            -1, 1) / torch.pow(10000, torch.arange(
            0, num_hiddens, 2, dtype=torch.float32) / num_hiddens)
        self.P[:, :, 0::2] = torch.sin(X)
        self.P[:, :, 1::2] = torch.cos(X)

    def forward(self, X):
        X = X + self.P[:, :X.shape[1], :].to(X.device)
        return self.dropout(X)
           

在位置嵌入矩陣P中, 行代表詞元在序列中的位置,列代表位置編碼的不同次元。 從下面的例子中可以看到位置嵌入矩陣的第6列和第7列的頻率高于第8列和第9列。 第6列和第7列之間的偏移量(第8列和第9列相同)是由于正弦函數和餘弦函數的交替。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒絕對位置資訊

為了明白沿着編碼次元單調降低的頻率與絕對位置資訊的關系, 讓我們列印出0,1,…,7的二進制表示形式。 正如所看到的,每個數字、每兩個數字和每四個數字上的比特值 在第一個最低位、第二個最低位和第三個最低位上分别交替。

for i in range(8):
    print(f'{i}的二進制是:{i:>03b}')
           
0的二進制是:000
1的二進制是:001
2的二進制是:010
3的二進制是:011
4的二進制是:100
5的二進制是:101
6的二進制是:110
7的二進制是:111
           

在二進制表示中,較高比特位的交替頻率低于較低比特位, 與下面的熱圖所示相似,隻是位置編碼通過使用三角函數在編碼次元上降低頻率。 由于輸出是浮點數,是以此類連續表示比二進制表示法更節省空間。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒相對位置資訊

除了捕獲絕對位置資訊之外, 上述的位置編碼還允許模型學習得到輸入序列中相對位置資訊。這是因為對于任何确定的位置偏移 δ \delta δ, 位置 i + δ i+\delta i+δ處 的位置編碼可以線性投影位置 i i i處的位置編碼來表示。

這種投影的數學解釋是, 令 ω j = 1 / 1000 0 2 j / d \omega_j=1 / 10000^{2 j / d} ωj​=1/100002j/d, 對于任何确定的位置偏移 δ \delta δ, 中的任何一對 ( p i , 2 j , p i , 2 j + 1 ) \left(p_{i, 2 j}, p_{i, 2 j+1}\right) (pi,2j​,pi,2j+1​) 都可以 線性投影到 ( p i + δ , 2 j , p i + δ , 2 j + 1 ) \left(p_{i+\delta, 2 j}, p_{i+\delta, 2 j+1}\right) (pi+δ,2j​,pi+δ,2j+1​) :

[ cos ⁡ ( δ ω j ) sin ⁡ ( δ ω j ) − sin ⁡ ( δ ω j ) cos ⁡ ( δ ω j ) ] [ p i , 2 j p i , 2 j + 1 ] = [ cos ⁡ ( δ ω j ) sin ⁡ ( i ω j ) + sin ⁡ ( δ ω j ) cos ⁡ ( i ω j ) − sin ⁡ ( δ ω j ) sin ⁡ ( i ω j ) + cos ⁡ ( δ ω j ) cos ⁡ ( i ω j ) ] = [ sin ⁡ ( ( i + δ ) ω j ) cos ⁡ ( ( i + δ ) ω j ) ] = [ p i + δ , 2 j p i + δ , 2 j + 1 ] , \begin{aligned} & {\left[\begin{array}{cc} \cos \left(\delta \omega_j\right) & \sin \left(\delta \omega_j\right) \\ -\sin \left(\delta \omega_j\right) & \cos \left(\delta \omega_j\right) \end{array}\right]\left[\begin{array}{c} p_{i, 2 j} \\ p_{i, 2 j+1} \end{array}\right] } \\ = & {\left[\begin{array}{c} \cos \left(\delta \omega_j\right) \sin \left(i \omega_j\right)+\sin \left(\delta \omega_j\right) \cos \left(i \omega_j\right) \\ -\sin \left(\delta \omega_j\right) \sin \left(i \omega_j\right)+\cos \left(\delta \omega_j\right) \cos \left(i \omega_j\right) \end{array}\right] } \\ = & {\left[\begin{array}{c} \sin \left((i+\delta) \omega_j\right) \\ \cos \left((i+\delta) \omega_j\right) \end{array}\right] } \\ = & {\left[\begin{array}{c} p_{i+\delta, 2 j} \\ p_{i+\delta, 2 j+1} \end{array}\right], } \end{aligned} ===​[cos(δωj​)−sin(δωj​)​sin(δωj​)cos(δωj​)​][pi,2j​pi,2j+1​​][cos(δωj​)sin(iωj​)+sin(δωj​)cos(iωj​)−sin(δωj​)sin(iωj​)+cos(δωj​)cos(iωj​)​][sin((i+δ)ωj​)cos((i+δ)ωj​)​][pi+δ,2j​pi+δ,2j+1​​],​

2 × 2 2 \times 2 2×2投影矩陣不依賴于任何位置的索引 i i i

📒Transformer中采用的位置編碼

一直說位置向量遵循某個模式,這個模式到底是什麼?

在下面的圖中,每一行對應一個位置編碼。是以第一行就是我們輸入序列中第一個單詞的位置編碼,之後我們要把它加到詞嵌入向量上。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上圖表示的是一個句子有20個詞(縱軸),詞嵌入向量的長度為512(橫軸)。可以看到圖像從中間一分為二,因為左半部分是由正弦函數生成的。右半部分由餘弦函數生成。然後将它們二者拼接起來,形成了每個位置的位置編碼。

可以在get_timing_signal_1d()中看到生成位置編碼的代碼。這不是位置編碼的唯一方法。但是使用正餘弦編碼有諸多好處,具體可以看這裡:Transformer 結構詳解:位置編碼

但是需要注意注意一點,上圖的可視化是官方Tensor2Tensor庫中的實作方法,将sin和cos拼接起來。但是和論文原文寫的不一樣,論文原文的3.5節寫了位置編碼的公式,采用的是基于正弦函數和餘弦函數的固定位置編碼。論文中公式的寫法可以看這個代碼:transformer_positional_encoding_graph

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Transformer的Encoder-Decoder

接下來,我們來看一下Transformer的第二個部分:

Encoder-Decoder

主要由

Encoder block

Decoder Block

構成。
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

上圖是論文中 Transformer 的内部結構圖,左側為 Encoder block,右側為 Decoder block。紅色圈中的部分為

Multi-Head Attention

,是由多個 Self-Attention組成的,可以看到 Encoder block 包含一個 Multi-Head Attention,而 Decoder block 包含兩個 Multi-Head Attention (其中有一個用到

Masked

)。Multi-Head Attention 上方還包括一個

Add & Norm 層

Add

表示

殘差連接配接 (Residual Connection)

用于防止網絡退化,

Norm

表示

Layer Normalization

,用于對每一層的激活值進行歸一化。

Encoder block

Encoder block

中包括N個

Encoder

, 每個Encoder的結構相同,但是權重不共享。

每個Encoder主要由

Multi-Head Attention、Add&Norm、Feed Forward

三種結構。接下來,我們将依次講解這三種結構。

📒 Multi-Head Attention

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Self-attention

隻使用了一組 W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV 來進行變換得到Query,Keys,Values。而

Multi-Head Attention

使用多組 W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV 得到多組

Query,Keys,Values

,然後每組分别計算得到一個Z矩 陣,最後将得到的多個Z矩陣進行拼接。Transformer裡面是使用了N組不同的 W Q , W K , W V \mathrm{W}^{\mathrm{Q}}, \mathrm{W}^{\mathrm{K}}, \mathrm{W}^{\mathrm{V}} WQ,WK,WV 。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒 Add&Normalize

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

每個編碼器中的每個子層(自注意力層、前饋神經網絡)都有一個殘差連接配接(Add),之後是做了一個層歸一化(layer-normalization)。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

将過程中的向量相加和layer-norm可視化如下所示:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • Add

    Add,就是在Z的基礎上加了一個

    殘差塊X

    ,加入殘差塊X的目的是為了防止在深度神經網絡訓練中發生退化問題。

    退化的意思就是深度神經網絡通過增加網絡的層數,Loss逐漸減小,然後趨于穩定達到飽和,然後再繼續增加網絡層數,Loss反而增大。

❔ 為什麼深度神經網絡會發生退化?

舉個例子:假如某個神經網絡的最優網絡層數是18層,但是我們在設計的時候并不知道到底多少層是最優解,本着層數越深越好的理念,我們設計了32層,那麼32層神經網絡中有14層其實是多餘地,我們要想達到18層神經網絡的最優效果,必須保證這多出來的14層網絡必須進行

恒等映射

,恒等映射的意思就是說,輸入什麼,輸出就是什麼,可以了解成F(x)=x這樣的函數,因為隻有進行了這樣的恒等映射咱們才能保證這多出來的14層神經網絡不會影響我們最優的效果。

❔ 殘差塊又是什麼?
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

上圖就是構造的一個殘差塊,可以看到X是這一層殘差塊的輸入,也稱作F(X)為殘差,X為輸入值,F(X)是經過第一層線性變化并激活後的輸出,該圖表示在殘差網絡中,第二層進行線性變化之後激活之前,F(X)加入了這一層輸入值X,然後再進行激活後輸出。在第二層輸出值激活前加入X,這條路徑稱作

shortcut連接配接

❔ 為什麼添加了殘差塊能防止神經網絡退化問題呢?

添加了殘差塊後,輸出就變成了h(X)=F(X)+X。如果要讓h(X)=X,那麼是不是相當于隻需要讓F(X)=0就可以了。神經網絡通過訓練變成0是比變成X容易很多地,因為一般初始化神經網絡的參數的時候就是設定的[0,1]之間的随機數,是以經過網絡變換後很容易接近于0。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

🌰 舉例,如上圖:假設該網絡隻經過線性變換,沒有bias也沒有激活函數。我們發現因為随機初始化權重一般偏向于0,那麼經過該網絡的輸出值為[0.6 0.6],很明顯會更接近與[0 0],而不是[2 1],相比與學習h(x)=x,模型要更快到學習F(x)=0。

并且ReLU能夠将負數激活為0,過濾了負數的線性變化,也能夠更快的使得F(x)=0。這樣當網絡自己決定哪些網絡層為備援層時,使用ResNet的網絡很大程度上解決了學習恒等映射的問題,用學習殘差F(x)=0更新該備援層的參數來代替學習h(x)=x更新備援層的參數。

這樣當網絡自行決定了哪些層為備援層後,通過學習殘差F(x)=0來讓該層網絡恒等映射上一層的輸入,使得有了這些備援層的網絡效果與沒有這些備援層的網絡效果相同,這樣很大程度上解決了網絡的退化問題。

Transformer中加上的X也就是Multi-Head Attention的輸入,X矩陣。
  • Normalize
❔ 為什麼要進行Normalize呢?

在神經網絡進行訓練之前,都需要對于輸入資料進行Normalize歸一化,目的有二:1,能夠加快訓練的速度。2.提高訓練的穩定性。

❔ 什麼是Layer Normalization,什麼是Batch Normalization

《Layer Normalization》 首次提出了層規範化。

《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》 首次提出了批量規範化

Batch Normalization 的處理對象是對一批樣本, Layer Normalization 的處理對象是單個樣本。Batch Normalization 是對這批樣本的同一次元特征做歸一化, Layer Normalization 是對這單個樣本的所有次元特征做歸一化。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
❔ 為什麼使用Layer Normalization(LN)而不使用Batch Normalization(BN)呢?
  1. 論文《PowerNorm: Rethinking Batch Normalization in Transformers》指出,Transformer中BN表現不太好的原因可能在于CV和NLP資料特性的不同,對于NLP資料,前向和反向傳播中,batch統計量及其梯度都不太穩定。
  2. 論文《Understanding and Improving Layer Normalization》主要研究LN的優點,除了一般意義上認為可以穩定前向輸入分布,加快收斂快,還有沒有啥原因。最後的結論有:

    a)相比于穩定前向輸入分布,反向傳播時mean和variance計算引入的梯度更有用,可以穩定反向傳播的梯度

    b) 去掉 gain和bias這兩個參數可以在很多資料集上有提升,可能是因為這兩個參數會帶來過拟合,因為這兩個參數是在訓練集上學出來的

  3. 論文《Leveraging Batch Normalization for Vision Transformers》研究對于CV data,能不能使用BN。主要提出了如下觀點:

    1)LN特别适合處理變長資料,因為是對channel次元做操作(這裡指NLP中的hidden次元),和句子長度和batch大小無關

    2)BN比LN在inference的時候快,因為不需要計算mean和variance,直接用running mean和running variance就行

    3)直接把VIT中的LN替換成BN,容易訓練不收斂,原因是FFN沒有被Normalized,是以還要在FFN block裡面的兩層之間插一個BN層。(可以加速20% VIT的訓練)

上述解釋參考了回答1和回答2

❔ Layer Normalization 的位置可以改變嗎?

論文《On Layer Normalization in the Transformer Architecture》研究了為什麼學習率預熱階段在訓練Transformer中很重要,并表明層歸一化的位置很重要。原始的Transformer,LN位于殘差塊之外(如下圖a),在初始化時輸出層附近參數的期望梯度很大。當使用大的學習率時,這會導緻訓練的不穩定;在殘差塊中使用LN的Transformer(如下圖b),可以在沒有預熱階段的情況下進行訓練,并且收斂得更快。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒 Feed-Forward Networks

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

全連接配接層公式如下:

FFN ⁡ ( x ) = max ⁡ ( 0 , x W 1 + b 1 ) W 2 + b 2 \operatorname{FFN}(\mathrm{x})=\max \left(0, \mathrm{xW}_1+\mathrm{b}_1\right) \mathrm{W}_2+\mathrm{b}_2 FFN(x)=max(0,xW1​+b1​)W2​+b2​

這裡的全連接配接層是一個兩層的神經網絡,先線性變換,然後ReLU非線性,再線性變換。

這裡的x就是我們Multi-Head Attention的輸出Z,還是引用上面的例子,那麼Z是(2,512)維的矩陣,假設W1是(512,1024),其中W2與W1次元相反(1024,612),那麼按照上面的公式:FFN(Z)=(2,512)x(512,1024)x(1024,512)=(2,512),我們發現次元沒有發生變化,這兩層網絡就是為了将輸入的Z映射到更加高維的空間中(2,64)x(64,1024)=(2,1024),然後通過非線性函數ReLU進行篩選,篩選完後再變回原來的次元。

然後經過Add&Normalize,輸入下一個encoder中,經過6個encoder後輸入到decoder中,至此Transformer的Encoder部分就全部介紹完了。

Decoder block

解碼器有兩種類型,我們先來了解下自回歸的Decoder(AT)和非自回歸的解碼器(NAT)

Decoder-Autoregressive(AT) 🆚 Decoder-Non-autoregressive

如下圖,Decoder每次選擇softmax後機率最大的元素作為輸出

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

每次預測時,需要将前一時刻的預測輸出作為輸入。第一次預測時,輸入的是

START

起始符。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

但是,如上圖所示會永遠不知道在什麼時候停止。

是以在詞彙表中加入了

END

作為終止符

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • Decoder-Autoregressive(AT)

    自回歸的解碼器(AT), 當預測輸出

    END

    時,AT就會停止工作。
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
  • Decoder-Non-autoregressive

    二對于非自回歸的解碼器(NAT)而言,如何決定輸出的長度呢?有兩種方法:a) 額外采用一個預測器來預測輸出的長度。b) 輸出一個很長的序列,但忽略

    END

    後的輸出。

    NAT的優點:可以并行計算,且生成更穩定(如TTS)

    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
但是NAT一般沒有AT的效果好,是以在Transformer中我們用的是AT模型。

📒 Decoder Block 的工作流程

現在我們已經介紹了編碼器的大部分概念,(因為encoder的decoder元件差不多)我們基本上也知道了解碼器的元件是如何工作的。那讓我們直接看看二者是如何協同工作的。

編碼器首先處理輸入序列,将最後一個編碼器元件的輸出轉換為一組注意向量K和V。每個解碼器元件将在“encoder-decoder attention”層中使用編碼器傳過來的K和V,這有助于解碼器将注意力集中在輸入序列中的适當位置:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上圖:完成編碼階段後,我們開始進行解碼階段。在解碼階段每一輪計算都隻預測 輸出(而不是直接輸出一整個句子),在本例中是輸出一個翻譯之後的英語單詞。

輸出步驟會一直重複,直到遇到句子結束符 表明transformer的解碼器已完成輸出。

每一步的輸出都會在下一個時間步輸入到底部解碼器,解碼器會像編碼器一樣運算并輸出結果(每次往外蹦一個詞)。

跟編碼器一樣,在解碼器中我們也為其添加位置編碼,以訓示每個單詞的位置。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

📒Transformer Decoder的輸入

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Decoder的輸入分為兩類:

一種是訓練時的輸入,一種是預測時的輸入。

訓練時的輸入就是已經對準備好對應的target資料

。例如翻譯任務,Encoder輸入"Tom chase Jerry",Decoder輸入"湯姆追逐傑瑞"。

如下圖,在訓練時,把Groud Truth作為輸入,這種政策也叫作

Teacher Forcing

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

預測時的輸入,一開始輸入的是起始符,然後每次輸入是上一時刻Transformer的輸出

。例如,輸入"“,輸出"湯姆”,輸入"湯姆",輸出"湯姆追逐",輸入"湯姆追逐",輸出"湯姆追逐傑瑞",輸入"湯姆追逐傑瑞",輸出"湯姆追逐傑瑞"結束。

📒Masked Multi-Head Attention

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

與Encoder的Multi-Head Attention計算原理一樣,隻是多加了一個

mask

掩碼,它對某些值進行掩蓋,使其在參數更新時不産生效果。Transformer 模型裡面涉及兩種 mask,分别是

padding mask

sequence mask

❔ 什麼是 padding mask 呢?
  • padding mask

    因為每個批次輸入序列長度是不一樣的也就是說,我們要對輸入序列進行對齊 。具體來說,就是給在較短的序列後面填充 0。但是如果輸入的序列太長,則是截取左邊的内容,把多餘的直接舍棄。因為這些填充的位置,其實是沒什麼意義的,是以我們的attention機制不應該把注意力放在這些位置上,是以我們需要進行一些處理。

    具體的做法是,把這些位置的值加上一個非常大的負數(負無窮),這樣的話,經過 softmax,這些位置的機率就會接近0!

❔ 什麼是 sequence mask 呢?
  • sequence mask

    sequence mask 是為了使得 decoder 不能看見未來的資訊。對于一個序列,在 time_step 為 t 的時刻,我們的解碼輸出應該隻能依賴于 t 時刻之前的輸出,而不能依賴 t 之後的輸出。是以我們需要想一個辦法,把 t 之後的資訊給隐藏起來。這在訓練的時候有效,因為訓練的時候每次我們是将target資料完整輸入進decoder中地,預測時不需要,預測的時候我們隻能得到前一時刻預測出的輸出。

❔ 怎麼實作sequence mask呢?

産生一個上三角矩陣,上三角的值全為0。把這個矩陣作用在每一個序列上,就可以達到我們的目的。

🌰下面我們來舉一個例子來看看mask-self attention 的工作流程。

第一步:是 Decoder 的輸入矩陣和 Mask 矩陣輸入矩陣包含 “

<Begin> I have a cat

” (0, 1, 2, 3, 4) 五個單詞的表示向量,Mask 是一個 5×5 的矩陣。在 Mask 可以發現單詞 0 隻能使用單詞 0 的資訊,而單詞 1 可以使用單詞 0, 1 的資訊,即隻能使用之前的資訊。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

第二步:接下來的操作和之前的 Self-Attention 一樣,通過輸入矩陣 X \mathbf{X} X 計算得到 Q K , V \mathbf{Q} \mathbf{K}, \mathbf{V} QK,V 矩陣。然後 計算 Q \mathbf{Q} Q 和 K T K^T KT 的乘積 Q K T Q K^T QKT 。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

第三步:在得到 Q K T QK^T QKT之後需要進行 Softmax,計算 attention score,我們在 Softmax 之前需要使用Mask矩陣遮擋住每一個單詞之後的資訊,遮擋操作如下:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

得到 Mask Q K T QK^T QKT 之後在 Mask Q K T QK^T QKT上進行 Softmax,每一行的和都為 1。但是單詞 0 在單詞 1, 2, 3, 4 上的 attention score 都為 0。

第四步:使用 Mask Q K T QK^T QKT與矩陣 V相乘,得到輸出 Z,則單詞 1 的輸出向量 Z 1 Z_1 Z1​是隻包含單詞 1 資訊的。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

第五步:通過上述步驟就可以得到一個 Mask Self-Attention 的輸出矩陣 Z i Z_i Zi​,然後和 Encoder 類似,通過 Multi-Head Attention 拼接多個輸出 Z i Z_i Zi​然後計算得到第一個 Multi-Head Attention 的輸出Z,Z與輸入X次元一樣。

在Encoder中的Multi-Head Attention也是需要進行mask地,隻不過Encoder中隻需要padding mask即可,而Decoder中需要padding mask和sequence mask。OK除了這點mask不一樣以外,其他的部分均與Encoder一樣啦~

📒基于Encoder-Decoder 的Multi-Head Attention

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Encoder中的Multi-Head Attention是基于Self-Attention地。Decoder中的第二個Multi-Head Attention就隻是基于Attention,它的輸入

Query

來自于Masked Multi-Head Attention的輸出,

Keys

Values

來自于Encoder中最後一層的輸出。是以也叫做

Cross Attention

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
❔ Cross Attention 中,

Keys

Values

一定要來自于Encoder中最後一層 的輸出嗎?

《Rethinking and Improving Natural Language Generation with Layer-Wise Multi-View Decoding》提出了不同的交叉注意力方式

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Transformer的輸出

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
❔ Decoder輸出的是一個浮點型向量,如何把它變成一個詞?這就是最後一個線性層和softmax要做的事情。

線性層就是一個簡單的

全連接配接神經網絡(Linear)

,它将解碼器生成的向量映射到logits向量中。

假設我們的模型詞彙表是10000個英語單詞,它們是從訓練資料集中學習的。那logits向量維數也是10000,每一維對應一個單詞的分數。

然後,

softmax層

将這些分數轉化為機率(全部為正值,加起來等于1.0),選擇其中機率最大的位置的詞彙作為目前時間步的輸出。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Transformer的訓練過程和損失函數

訓練過程

現在我們已經了解了Transformer的整個前向傳播的過程,那我們繼續看一下訓練過程。

在訓練期間,未經訓練的模型會進行相同的前向傳播過程。由于我們是在有标記的訓練資料集上訓練它,是以我們可以将其輸出與實際的輸出進行比較。

為了便于了解,我們假設預處理階段得到的詞彙表隻包含六個單詞(“a”, “am”, “i”, “thanks”, “student”, “”)。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上表:這個詞彙表是在預處理階段就建立的,在訓練之前就已經得到了。

一旦我們定義好了詞彙表,我們就可以使用長度相同的向量(獨熱碼,one-hot 向量)來表示詞彙表中的每個單詞。例如,我們可以用以下向量表示單詞“am”:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

接下來讓我們讨論一下模型的損失函數,損失函數是我們在訓練階段優化模型的名額,通過損失函數,可以幫助我們獲得一個準确的、我們想要的模型。

損失函數

假設現在是訓練階段的第一步,我們用一個簡單的例子(一個句子就一個詞)來訓練模型:把 “merci” 翻譯成 “thanks”。

這意味着,我們希望輸出是表示“謝謝”的機率分布。但由于這個模型還沒有經過訓練,是以目前還不太可能實作。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上表:由于模型的參數都是随機初始化的,未經訓練的模型為每個單詞生成任意的機率分布。

我們可以将其與實際輸出進行比較,然後使用反向傳播調整模型的權重,使輸出更接近我們所需要的值。

如何比較兩種機率分布?在這個例子中我們隻是将二者相減。實際應用中的損失函數請檢視交叉熵損失和Kullback–Leibler散度。

上述隻是最最簡單的一個例子。現在我們來使用一個短句子(一個詞的句子更新到三三個詞的句子了),比如輸入 “je suis étudiant” 預期的翻譯結果為: “i am a student”。

是以我們希望模型不是一次輸出一個詞的機率分布了,能不能連續輸出機率分布,最好滿足下邊要求:

每個機率分布向量長度都和詞彙表長度一樣。我們的例子中詞彙表長度是6,實際操作中一般是30000或50000。

在我們的例子中第一個機率分布應該在與單詞“i”相關的位置上具有最高的機率

第二種機率分布在與單詞“am”相關的單元處具有最高的機率

以此類推,直到最後輸出分布訓示“

<eos>

”符号。除了單詞本身之外,單詞表中也應該包含諸如“

<eos>

”的資訊,這樣softmax之後指向“

<eos>

”位置,标志解碼器輸出結束。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

在足夠大的訓練集上訓練足夠時間之後,我們期望産生的機率分布如下所示:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

這個是我們訓練之後最終得到的結果。當然這個機率并不能表明某個詞是否是訓練集之中的詞彙。

在這裡你可以看到softmax的一個特性,就是即使其他單詞并不是本時間步的輸出,

也會有一丁點的機率存在,這一特性有助于幫助模型進行訓練。

🔍搜尋序列

模型一次産生一個輸出,在這麼多候選中我們如何獲得我們想要的輸出呢?接下來,我們介紹三種搜尋算法。

序列搜尋政策包括貪心搜尋、窮舉搜尋和束搜尋。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上圖,紅色箭頭訓示的是貪心算法,綠色箭頭訓示的是束搜尋,搜尋所有的路徑是窮舉搜尋。

  • 貪心搜尋(greedy decoding)

    :在每個時間步,貪心搜尋選擇具有最高條件機率的詞元
    詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
    預測輸出序列“A”“B”“C”和“”。 這個輸出序列的條件機率是 0.5 × 0.4 × 0.4 × 0.6 = 0.048 0.5 \times 0.4 \times 0.4 \times 0.6=0.048 0.5×0.4×0.4×0.6=0.048。

那麼貪心搜尋存在的問題是什麼呢? 現實中, 最優序列(optimal sequence)應該是最大化

∏ t ′ = 1 T ′ P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) \prod_{t^{\prime}=1}^{T^{\prime}} P\left(y_{t^{\prime}} \mid y_1, \ldots, y_{t^{\prime}-1}, \mathbf{c}\right) ∏t′=1T′​P(yt′​∣y1​,…,yt′−1​,c) 值的輸出序列, 這是基于輸入序列生成輸出序列的條件嘅率。然而, 貪心搜尋無法保證 得到最優序列。

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上圖,在時間步2,應該選擇具有第二高條件機率的詞元“C”(而非最高條件機率的詞元)

  • 窮舉搜尋(exhaustive search)

    : 窮舉地列舉所有可能的輸出序列及其條件機率, 然後計算輸出條件機率最高的一個。

    雖然我們可以使用窮舉搜尋來獲得最優序列, 但是計算量很龐大。

  • 束搜尋(beam search)

    :束搜尋 (beam search) 是貪心搜尋的一個改進版本。它有一個超參數,名為束寬 (beam size) k k k 。在時間步 1 , 我們 先擇具有最高條件機率的 k k k 個詞元。這 k k k 個詞元将分别是 k k k 個候選輸出序列的第一個詞元。在随後的每個時間步, 基于上 一時間步的 k k k 個候選輸出序列, 我們将繼續從 k ∣ Y ∣ k|\mathcal{Y}| k∣Y∣ 個可能的選擇中 挑出具有最高條件機率的 k k k 個候選輸出序列。
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

如上圖,示範了束搜尋過程(束寬:2,輸出序列的最大長度:3)。候選輸出序列是 A 、 C 、 A B 、 C E 、 A B D A 、 C 、 A B 、 C E 、 A B D A、C、AB、CE、ABD 和 C E D C E D CED

假設輸出的詞表隻包含五個元素: Y = { A , B , C , D , E } \mathcal{Y}=\{A, B, C, D, E\} Y={A,B,C,D,E}, 其中有一個是“”。設定 束寬為 2 , 輸出序列的最大長度為 3 。在時間步 1 , 假設具有最高條件機率 P ( y 1 ∣ c ) P\left(y_1 \mid \mathbf{c}\right) P(y1​∣c) 的詞元是 A A A 和 C C C 。在時間步 2 , 我們 計算所有 y 2 ∈ Y y_2 \in \mathcal{Y} y2​∈Y 為:

P ( A , y 2 ∣ c ) = P ( A ∣ c ) P ( y 2 ∣ A , c ) , P ( C , y 2 ∣ c ) = P ( C ∣ c ) P ( y 2 ∣ C , c ) , \begin{aligned} & P\left(A, y_2 \mid \mathbf{c}\right)=P(A \mid \mathbf{c}) P\left(y_2 \mid A, \mathbf{c}\right), \\ & P\left(C, y_2 \mid \mathbf{c}\right)=P(C \mid \mathbf{c}) P\left(y_2 \mid C, \mathbf{c}\right), \end{aligned} ​P(A,y2​∣c)=P(A∣c)P(y2​∣A,c),P(C,y2​∣c)=P(C∣c)P(y2​∣C,c),​

從這十個值中選擇最大的兩個, 比如 P ( A , B ∣ c ) P(A, B \mid \mathbf{c}) P(A,B∣c) 和 P ( C , E ∣ c ) P(C, E \mid \mathbf{c}) P(C,E∣c) 。然後在時間步 3 , 我們計算所有 y 3 ∈ Y y_3 \in \mathcal{Y} y3​∈Y 為:

P ( A , B , y 3 ∣ c ) = P ( A , B ∣ c ) P ( y 3 ∣ A , B , c ) , P ( C , E , y 3 ∣ c ) = P ( C , E ∣ c ) P ( y 3 ∣ C , E , c ) , \begin{aligned} & P\left(A, B, y_3 \mid \mathbf{c}\right)=P(A, B \mid \mathbf{c}) P\left(y_3 \mid A, B, \mathbf{c}\right), \\ & P\left(C, E, y_3 \mid \mathbf{c}\right)=P(C, E \mid \mathbf{c}) P\left(y_3 \mid C, E, \mathbf{c}\right), \end{aligned} ​P(A,B,y3​∣c)=P(A,B∣c)P(y3​∣A,B,c),P(C,E,y3​∣c)=P(C,E∣c)P(y3​∣C,E,c),​

從這十個值中選擇最大的兩個, 即 P ( A , B , D ∣ c ) P(A, B, D \mid \mathbf{c}) P(A,B,D∣c) 和 P ( C , E , D ∣ c ) P(C, E, D \mid \mathbf{c}) P(C,E,D∣c), 我們會得到六個候選輸出序列:(1) A A A; (2) C C C; (3) A , B A, B A,B; (4) C , E C, E C,E; (5) A , B , D A, B, D A,B,D; (6) C , E , D C, E, D C,E,D 。

最後, 基于這六個序列(例如, 丢棄包括“”和之後的部分),我們獲得最終候選輸出序列集合。然後我們選擇其 中條件機率乘積最高的序列作為輸出序列:

1 L α log ⁡ P ( y 1 , … , y L ∣ c ) = 1 L α ∑ t ′ = 1 L log ⁡ P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) , \frac{1}{L^\alpha} \log P\left(y_1, \ldots, y_L \mid \mathbf{c}\right)=\frac{1}{L^\alpha} \sum_{t^{\prime}=1}^L \log P\left(y_{t^{\prime}} \mid y_1, \ldots, y_{t^{\prime}-1}, \mathbf{c}\right), Lα1​logP(y1​,…,yL​∣c)=Lα1​t′=1∑L​logP(yt′​∣y1​,…,yt′−1​,c),

其中 L L L 是最終候選序列的長度, α \alpha α 通常設定為 0.75 0.75 0.75 。因為一個較長的序列在 (9.8.4) 的求和中會有更多的對數項, 是以分 母中的 L α L^\alpha Lα 用于懲罰長序列。

束搜尋的計算量為 O ( k ∣ Y ∣ T ′ ) \mathcal{O}\left(k|\mathcal{Y}| T^{\prime}\right) O(k∣Y∣T′), 這個結果介于貪心搜尋和窮舉搜尋之間。實際上, 貪心搜尋可以看作一種束寬為 1 的特 殊類型的束搜尋。通過靈活地選擇束寬, 束搜尋可以在正确率和計算代價之間進行權衡。

那麼該選取哪種序列搜尋政策呢? 如果精度最重要,則顯然是窮舉搜尋。 如果計算成本最重要,則顯然是貪心搜尋。 而束搜尋的實際應用則介于這兩個極端之間。

Transformer的代碼實作

首先,我們先回顧下Transformer的整體架構

Transformer作為編碼器-解碼器架構的一個執行個體,其整體架構圖如下:

詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

Transformer是由編碼器和解碼器組成的。基于Bahdanau注意力實作的序列到序列的學習相比,Transformer的編碼器和解碼器是基于自注意力的子產品疊加而成的,源(輸入)序列和目标(輸出)序列的嵌入(embedding)表示将加上位置編碼(positional encoding),再分别輸入到編碼器和解碼器中。

從宏觀角度來看, Transformer的編碼器是由多個相同的層疊加而成的, 每個 層都有兩個子層(子層表示為 sublayer)。第一個子層是多頭自注意力 (multi-head self-attention)彙聚; 第二個子層 是基于位置的前饋網絡 (positionwise feed-forward network)。具體來說, 在計算編碼器的自注意力時, 查詢、鍵和值 都來自前一個編碼器層的輸出。受殘差網絡的啟發, 每個子層都采用了殘差連接配接 (residual connection) 。在 Transformer中, 對于序列中任何位置的任何輸入 x ∈ R d \mathbf{x} \in \mathbb{R}^d x∈Rd, 都要求滿足sublayer ( x ) ∈ R d (\mathbf{x}) \in \mathbb{R}^d (x)∈Rd, 以便殘差連接配接滿足 x + sublayer ⁡ ( x ) ∈ R d \mathbf{x}+\operatorname{sublayer}(\mathbf{x}) \in \mathbb{R}^d x+sublayer(x)∈Rd 。在殘差連接配接的加法計算之後,緊接着應用層規範化 (layer normalization)。 是以, 輸入序列對應的每個位置, Transformer編碼器都将輸出一個 d d d 維表示向量。

Transformer解碼器也是由多個相同的層疊加而成的,并且層中使用了殘差連接配接和層規範化。除了編碼器中描述的兩個子層之外,解碼器還在這兩個子層之間插入了第三個子層,稱為編碼器-解碼器注意力(encoder-decoder attention)層。在編碼器-解碼器注意力中,查詢來自前一個解碼器層的輸出,而鍵和值來自整個編碼器的輸出。在解碼器自注意力中,查詢、鍵和值都來自上一個解碼器層的輸出。但是,解碼器中的每個位置隻能考慮該位置之前的所有位置。這種掩蔽(masked)注意力保留了自回歸(auto-regressive)屬性,確定預測僅依賴于已生成的輸出詞元。

在此之前已經描述并實作了基于縮放點積多頭注意力 和位置編碼 。接下來将實作Transformer模型的剩餘部分。

首先,先導入需要的包

import math
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l
           

基于位置的前饋神經網絡

基于位置的前饋網絡對序列中的所有位置的表示進行變換時使用的是同一個多層感覺機(MLP),這就是稱前饋網絡是基于位置的(positionwise)的原因。在下面的實作中,輸入

X

的形狀(批量大小,時間步數或序列長度,隐單元數或特征次元)将被一個兩層的感覺機轉換成形狀為(批量大小,時間步數,

ffn_num_outputs

)的輸出張量。

#@save
class PositionWiseFFN(nn.Module):
    """基于位置的前饋網絡"""
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                 **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
        self.relu = nn.ReLU()
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)

    def forward(self, X):
        return self.dense2(self.relu(self.dense1(X)))
           

下面的例子顯示,改變張量的最裡層次元的尺寸,會改變成基于位置的前饋網絡的輸出尺寸。因為用同一個多層感覺機對所有位置上的輸入進行變換,是以當所有這些位置的輸入相同時,它們的輸出也是相同的。

ffn = PositionWiseFFN(4, 4, 8)
ffn.eval()
ffn(torch.ones((2, 3, 4)))[0]
           

殘差連接配接和層規範化

在一個小批量的樣本内基于批量規範化對資料進行重新中心化和重新縮放的調整。層規範化和批量規範化的目标相同,但層規範化是基于特征次元進行規範化。盡管批量規範化在計算機視覺中被廣泛應用,但在自然語言處理任務中(輸入通常是變長序列)批量規範化通常不如層規範化的效果好。

以下代碼對比不同次元的層規範化和批量規範化的效果。

ln = nn.LayerNorm(2)
bn = nn.BatchNorm1d(2)
X = torch.tensor([[1, 2], [2, 3]], dtype=torch.float32)
# 在訓練模式下計算X的均值和方差
print('layer norm:', ln(X), '\nbatch norm:', bn(X))
           
layer norm: tensor([[-1.0000,  1.0000],
        [-1.0000,  1.0000]], grad_fn=<NativeLayerNormBackward0>)
batch norm: tensor([[-1.0000, -1.0000],
        [ 1.0000,  1.0000]], grad_fn=<NativeBatchNormBackward0>)
           

現在可以使用殘差連接配接和層規範化來實作

AddNorm

類。暫退法也被作為正則化方法使用。

#@save
class AddNorm(nn.Module):
    """殘差連接配接後進行層規範化"""
    def __init__(self, normalized_shape, dropout, **kwargs):
        super(AddNorm, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)
        self.ln = nn.LayerNorm(normalized_shape)

    def forward(self, X, Y):
        return self.ln(self.dropout(Y) + X)
           

殘差連接配接要求兩個輸入的形狀相同,以便加法操作後輸出張量的形狀相同。

add_norm = AddNorm([3, 4], 0.5)
add_norm.eval()
add_norm(torch.ones((2, 3, 4)), torch.ones((2, 3, 4))).shape
# torch.Size([2, 3, 4])
           

編碼器

有了組成Transformer編碼器的基礎元件,現在可以先實作編碼器中的一個層。下面的

EncoderBlock

類包含兩個子層:多頭自注意力和基于位置的前饋網絡,這兩個子層都使用了殘差連接配接和緊随的層規範化。

#@save
class EncoderBlock(nn.Module):
    """Transformer編碼器塊"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, use_bias=False, **kwargs):
        super(EncoderBlock, self).__init__(**kwargs)
        self.attention = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout,
            use_bias)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(
            ffn_num_input, ffn_num_hiddens, num_hiddens)
        self.addnorm2 = AddNorm(norm_shape, dropout)

    def forward(self, X, valid_lens):
        Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))
        return self.addnorm2(Y, self.ffn(Y))
           

正如從代碼中所看到的,Transformer編碼器中的任何層都不會改變其輸入的形狀。

X = torch.ones((2, 100, 24))
valid_lens = torch.tensor([3, 2])
encoder_blk = EncoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5)
encoder_blk.eval()
encoder_blk(X, valid_lens).shape
# torch.Size([2, 100, 24])
           

下面實作的Transformer編碼器的代碼中,堆疊了

num_layers

EncoderBlock

類的執行個體。由于這裡使用的是值範圍在−1和1之間的固定位置編碼,是以通過學習得到的輸入的嵌入表示的值需要先乘以嵌入次元的平方根進行重新縮放,然後再與位置編碼相加。

#@save
class TransformerEncoder(d2l.Encoder):
    """Transformer編碼器"""
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, use_bias=False, **kwargs):
        super(TransformerEncoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                EncoderBlock(key_size, query_size, value_size, num_hiddens,
                             norm_shape, ffn_num_input, ffn_num_hiddens,
                             num_heads, dropout, use_bias))

    def forward(self, X, valid_lens, *args):
        # 因為位置編碼值在-1和1之間,
        # 是以嵌入值乘以嵌入次元的平方根進行縮放,
        # 然後再與位置編碼相加。
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self.attention_weights = [None] * len(self.blks)
        for i, blk in enumerate(self.blks):
            X = blk(X, valid_lens)
            self.attention_weights[
                i] = blk.attention.attention.attention_weights
        return X
           

下面我們指定了超參數來建立一個兩層的Transformer編碼器。 Transformer編碼器輸出的形狀是(批量大小,時間步數目,

num_hiddens

)。

encoder = TransformerEncoder(
    200, 24, 24, 24, 24, [100, 24], 24, 48, 8, 2, 0.5)
encoder.eval()
encoder(torch.ones((2, 100), dtype=torch.long), valid_lens).shape
# torch.Size([2, 100, 24])
           

解碼器

Transformer解碼器也是由多個相同的層組成。在

DecoderBlock

類中實作的每個層包含了三個子層:解碼器自注意力、“編碼器-解碼器”注意力和基于位置的前饋網絡。這些子層也都被殘差連接配接和緊随的層規範化圍繞。

正如在本節前面所述,在掩蔽多頭解碼器自注意力層(第一個子層)中,查詢、鍵和值都來自上一個解碼器層的輸出。關于序列到序列模型(sequence-to-sequence model),在訓練階段,其輸出序列的所有位置(時間步)的詞元都是已知的;然而,在預測階段,其輸出序列的詞元是逐個生成的。是以,在任何解碼器時間步中,隻有生成的詞元才能用于解碼器的自注意力計算中。為了在解碼器中保留自回歸的屬性,其掩蔽自注意力設定了參數

dec_valid_lens

,以便任何查詢都隻會與解碼器中所有已經生成詞元的位置(即直到該查詢位置為止)進行注意力計算。

class DecoderBlock(nn.Module):
    """解碼器中第i個塊"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, i, **kwargs):
        super(DecoderBlock, self).__init__(**kwargs)
        self.i = i
        self.attention1 = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.attention2 = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm2 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,
                                   num_hiddens)
        self.addnorm3 = AddNorm(norm_shape, dropout)

    def forward(self, X, state):
        enc_outputs, enc_valid_lens = state[0], state[1]
        # 訓練階段,輸出序列的所有詞元都在同一時間處理,
        # 是以state[2][self.i]初始化為None。
        # 預測階段,輸出序列是通過詞元一個接着一個解碼的,
        # 是以state[2][self.i]包含着直到目前時間步第i個塊解碼的輸出表示
        if state[2][self.i] is None:
            key_values = X
        else:
            key_values = torch.cat((state[2][self.i], X), axis=1)
        state[2][self.i] = key_values
        if self.training:
            batch_size, num_steps, _ = X.shape
            # dec_valid_lens的開頭:(batch_size,num_steps),
            # 其中每一行是[1,2,...,num_steps]
            dec_valid_lens = torch.arange(
                1, num_steps + 1, device=X.device).repeat(batch_size, 1)
        else:
            dec_valid_lens = None

        # 自注意力
        X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
        Y = self.addnorm1(X, X2)
        # 編碼器-解碼器注意力。
        # enc_outputs的開頭:(batch_size,num_steps,num_hiddens)
        Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
        Z = self.addnorm2(Y, Y2)
        return self.addnorm3(Z, self.ffn(Z)), state
           

為了便于在“編碼器-解碼器”注意力中進行縮放點積計算和殘差連接配接中進行加法計算,編碼器和解碼器的特征次元都是

num_hiddens

decoder_blk = DecoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5, 0)
decoder_blk.eval()
X = torch.ones((2, 100, 24))
state = [encoder_blk(X, valid_lens), valid_lens, [None]]
decoder_blk(X, state)[0].shape
# torch.Size([2, 100, 24])
           

現在我們建構了由

num_layers

DecoderBlock

執行個體組成的完整的Transformer解碼器。最後,通過一個全連接配接層計算所有

vocab_size

個可能的輸出詞元的預測值。解碼器的自注意力權重和編碼器解碼器注意力權重都被存儲下來,友善日後可視化的需要。

class TransformerDecoder(d2l.AttentionDecoder):
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, **kwargs):
        super(TransformerDecoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.num_layers = num_layers
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                DecoderBlock(key_size, query_size, value_size, num_hiddens,
                             norm_shape, ffn_num_input, ffn_num_hiddens,
                             num_heads, dropout, i))
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, enc_valid_lens, *args):
        return [enc_outputs, enc_valid_lens, [None] * self.num_layers]

    def forward(self, X, state):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self._attention_weights = [[None] * len(self.blks) for _ in range (2)]
        for i, blk in enumerate(self.blks):
            X, state = blk(X, state)
            # 解碼器自注意力權重
            self._attention_weights[0][
                i] = blk.attention1.attention.attention_weights
            # “編碼器-解碼器”自注意力權重
            self._attention_weights[1][
                i] = blk.attention2.attention.attention_weights
        return self.dense(X), state

    @property
    def attention_weights(self):
        return self._attention_weights
           

訓練

依照Transformer架構來執行個體化編碼器-解碼器模型。在這裡,指定Transformer的編碼器和解碼器都是2層,都使用4頭注意力。

為了進行序列到序列的學習,下面在“英語-法語”機器翻譯資料集上訓練Transformer模型。

num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10
lr, num_epochs, device = 0.005, 200, d2l.try_gpu()
ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4
key_size, query_size, value_size = 32, 32, 32
norm_shape = [32]

train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)

encoder = TransformerEncoder(
    len(src_vocab), key_size, query_size, value_size, num_hiddens,
    norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
    num_layers, dropout)
decoder = TransformerDecoder(
    len(tgt_vocab), key_size, query_size, value_size, num_hiddens,
    norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
    num_layers, dropout)
net = d2l.EncoderDecoder(encoder, decoder)
d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
# loss 0.032, 5679.3 tokens/sec on cuda:0
           
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

訓練結束後,使用Transformer模型将一些英語句子翻譯成法語,并且計算它們的BLEU分數。

engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, dec_attention_weight_seq = d2l.predict_seq2seq(
        net, eng, src_vocab, tgt_vocab, num_steps, device, True)
    print(f'{eng} => {translation}, ',
          f'bleu {d2l.bleu(translation, fra, k=2):.3f}')
           
go . => va !,  bleu 1.000
i lost . => j'ai perdu .,  bleu 1.000
he's calm . => il est calme .,  bleu 1.000
i'm home . => je suis chez moi .,  bleu 1.000
           

當進行最後一個英語到法語的句子翻譯工作時,讓我們可視化Transformer的注意力權重。編碼器自注意力權重的形狀為(編碼器層數,注意力頭數,

num_steps

或查詢的數目,

num_steps

或“鍵-值”對的數目)。

enc_attention_weights = torch.cat(net.encoder.attention_weights, 0).reshape((num_layers, num_heads,
    -1, num_steps))
enc_attention_weights.shape
# torch.Size([2, 4, 10, 10])
           

在編碼器的自注意力中,查詢和鍵都來自相同的輸入序列。因為填充詞元是不攜帶資訊的,是以通過指定輸入序列的有效長度可以避免查詢與使用填充詞元的位置計算注意力。接下來,将逐行呈現兩層多頭注意力的權重。每個注意力頭都根據查詢、鍵和值的不同的表示子空間來表示不同的注意力。

d2l.show_heatmaps(
    enc_attention_weights.cpu(), xlabel='Key positions',
    ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)],
    figsize=(7, 3.5))
           
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

為了可視化解碼器的自注意力權重和“編碼器-解碼器”的注意力權重,我們需要完成更多的資料操作工作。例如用零填充被掩蔽住的注意力權重。值得注意的是,解碼器的自注意力權重和“編碼器-解碼器”的注意力權重都有相同的查詢:即以序列開始詞元(beginning-of-sequence,BOS)打頭,再與後續輸出的詞元共同組成序列。

dec_attention_weights_2d = [head[0].tolist()
                            for step in dec_attention_weight_seq
                            for attn in step for blk in attn for head in blk]
dec_attention_weights_filled = torch.tensor(
    pd.DataFrame(dec_attention_weights_2d).fillna(0.0).values)
dec_attention_weights = dec_attention_weights_filled.reshape((-1, 2, num_layers, num_heads, num_steps))
dec_self_attention_weights, dec_inter_attention_weights = \
    dec_attention_weights.permute(1, 2, 3, 0, 4)
dec_self_attention_weights.shape, dec_inter_attention_weights.shape
# (torch.Size([2, 4, 6, 10]), torch.Size([2, 4, 6, 10]))
           

由于解碼器自注意力的自回歸屬性,查詢不會對目前位置之後的“鍵-值”對進行注意力計算。

# Plusonetoincludethebeginning-of-sequencetoken
d2l.show_heatmaps(
    dec_self_attention_weights[:, :, :, :len(translation.split()) + 1],
    xlabel='Key positions', ylabel='Query positions',
    titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5))
           
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考

與編碼器的自注意力的情況類似,通過指定輸入序列的有效長度,輸出序列的查詢不會與輸入序列中填充位置的詞元進行注意力計算。

d2l.show_heatmaps(
    dec_inter_attention_weights, xlabel='Key positions',
    ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)],
    figsize=(7, 3.5))
           
詳解注意力機制和Transformer注意力Transformerpytorch中的注意力機制類參考
盡管Transformer架構是為了序列到序列的學習而提出的,但Transformer編碼器或Transformer解碼器通常被單獨用于不同的深度學習任務中。
  • 小結
    • Transformer是編碼器-解碼器架構的一個實踐,盡管在實際情況中編碼器或解碼器可以單獨使用。
    • 在Transformer中,多頭自注意力用于表示輸入序列和輸出序列,不過解碼器必須通過掩蔽機制來保留自回歸屬性。
    • Transformer中的殘差連接配接和層規範化是訓練非常深度模型的重要工具。
    • Transformer模型中基于位置的前饋網絡使用同一個多層感覺機,作用是對所有序列位置的表示進行轉換。

pytorch中的注意力機制類

torch.nn.MultiheadAttention

Class:
torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None, batch_first=False, device=None, dtype=None)
           
  • 功能:允許模型聯合關注來自論文《Attention is all you need》中描述的不同表示子空間的資訊。

Attention Is All You Need

  • Multi-Head Attention 的定義如下:

    MultiHead ⁡ ( Q , K , V ) = Concat ⁡ (  head  1 , … ,  head  h ) W O \operatorname{MultiHead}(Q, K, V)=\operatorname{Concat}\left(\text { head }_1, \ldots, \text { head }_h\right) W^O MultiHead(Q,K,V)=Concat( head 1​,…, head h​)WO

    其中 h e a d i = Attention ⁡ ( Q W i Q , K W i K , V W i V ) h e a d_i=\operatorname{Attention}\left(Q W_i^Q, K W_i^K, V W_i^V\right) headi​=Attention(QWiQ​,KWiK​,VWiV​)

    如果滿足以下所有條件,

    forward()

    将使用一個特殊的優化實作 :
    • 采用自注意力機制(也就是說,query、key和value是同一個張量。)
    • 要麼autograd被禁用(使用

      torch.Inference_mode

      torch.no_grad

      ),要麼沒有張量參數

      requires_grad

    • 禁止訓練(使用

      .eval()

      模式)
    • dropout

      是 0
    • add_bias_kv

      False

    • add_zero_attn

      False

    • batch_first

      True

      ,而且輸入是批處理的。
    • kdim

      vdim

      等于

      embed_dim

    • 最多傳遞

      key_padding_mask

      attn_mask

      中的一個
    • 如果傳遞了

      NestedTensor

      ,則既沒有傳遞

      key_padding_mask

      ,也沒有傳遞

      attn_mask

  • 參數
    • embed_dim

      :模型的總次元。
    • num_heads

      :并行注意力頭的數量。請注意,embed_dim将在num_heads中拆分(即每個head将具有次元

      embed_dim // num_heads

      )。
    • dropout

      :

      attn_output_weights

      上的dropout機率。預設值:0.0(無dropout)。
    • bias

      :如果指定,添加偏置到輸入/輸出投影層(input / output projection layers)。預設值:True。
    • add_bias_kv

      : 如果指定,則為dim=0處的鍵值序列添加偏差。預設值: False。
    • add_zero_attn

      :如果指定,則在dim=1的鍵值序列中添加一批新的0。預設值:False。
    • kdim

      :鍵的特征總數。預設值:無(使用

      kdim=embed_dim

      )。
    • dim

      :值的特征總數。預設值:無(使用vdim=embed_dim)。
    • batch_first

      : 如果為

      True

      ,則輸入和輸出張量以(

      batch, seq, feature

      )的形式提供。預設值 :

      False

      (

      seq, batch, feature

      )。對于,未批處理的輸入,這個參數将被忽略。
  • 例子
multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
attn_output, attn_output_weights = multihead_attn(query, key, value)
           

forward 函數

Function:
forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None, average_attn_weights=True)
           
  • 參數
    • query(Tensor)

      : 對于非批量化的輸入,查詢嵌入(Query embeddings)的大小為 ( L , E q ) (L,E_q) (L,Eq​); 對于批量化的輸入,當batch_first=False時,查詢嵌入的大小為 ( L , N , E q ) (L,N,E_q) (L,N,Eq​), 當batch_first=True時,查詢嵌入的大小為 ( N , L , E q ) (N,L,E_q) (N,L,Eq​)。 其中, L L L 為目标序列的長度, N N N是批量大小(batch size), E q E_q Eq​是查詢嵌入的次元

      embed_dim

      。 查詢會根據鍵值對進行比較以産生輸出。
    • key(Tensor)

      : 對于非批量化的輸入,鍵嵌入(key embeddings)的大小為 ( S , E k ) (S,E_k) (S,Ek​); 對于批量化的輸入,當batch_first=False時,鍵嵌入的大小為 ( S , N , E k ) (S,N,E_k) (S,N,Ek​), 當batch_first=True時,鍵嵌入的大小為 ( N , S , E k ) (N,S,E_k) (N,S,Ek​)。 其中, S S S為源序列的長度, N N N是批量大小(batch size), E k E_k Ek​是鍵嵌入的次元

      kdim

    • value(Tensor)

      : 對于非批量化的輸入,值嵌入(value embeddings)的大小為 ( S , E v ) (S,E_v) (S,Ev​); 對于批量化的輸入,當batch_first=False時,值嵌入的大小為 ( S , N , E v ) (S,N,E_v) (S,N,Ev​), 當batch_first=True時,值嵌入的大小為 ( N , S , E v ) (N,S,E_v) (N,S,Ev​)。 其中, S S S為源序列的長度, N N N是批量大小(batch size), E v E_v Ev​是值嵌入的次元

      vdim

    • key_padding_mask(Optional[Tensor])

      : 如果指定,形狀為 ( N , S ) (N,S) (N,S)的掩碼,表示key中的哪些元素需要忽略以供注意(即将其視為“填充”)。對于非批量查詢,形狀應該是一個或多個。支援二進制掩碼和位元組掩碼。對于二進制掩碼,True值表示将忽略相應的鍵值以進行注意。對于浮點掩碼,它将直接添加到相應的鍵值。
    • need_weights (bool)

      :如果指定,除了傳回attn_outputs外,還傳回attn_output_weights。預設值: True。
    • attn_mask(Optional[Tensor])

      : 如果指定,一個2D或3D掩碼,以防止注意某些位置。形狀必須為 ( L , S ) (L,S) (L,S) 或 ( N ⋅ num_heads , L , S ) (N\cdot\text{num\_heads}, L,S) (N⋅num_heads,L,S),其中 N N N為批量大小, L L L為目标序列長度, S S S為源序列長度。2D掩碼将在整個批進行中廣播,而3D掩碼允許批進行中的每個條目使用不同的掩碼。支援二進制掩碼、位元組掩碼和浮點掩碼。對于二進制掩碼,True值表示不允許參與相應的位置。對于位元組掩碼,非零值表示不允許進入對應的位置。對于浮動掩碼,掩碼值将被添加到注意力權重中。
    • average_attn_weights (bool)

      : 如果為true,表示傳回的attn_weights應該在head之間求平均值。否則,每個head的attn_weights是單獨提供的。請注意,該标志僅在need_weights=True時起作用。預設值:True(即頭的平均權重)
  • 傳回值類型
    • Tuple[Tensor,Optional[Tensor]]

  • 輸出:
    • attn_output

      : 當輸入未批處理時, 注意力輸出的形狀為(L,E),當batch_first=False時輸出形狀為(L,N,E) 。或當batch_first=True時,輸出形狀為(N,L,E),其中L是目标序列長度,N是批大小,E是嵌入次元embed_dim。
    • attn_output_weights

      : 僅在need_weights=True時傳回。如果average_attn_weights=True,則在輸入未分批或(N,L,S)時,傳回形狀為(L,S) 的頭的平均注意力權重,其中N是批量大小,L是目标序列長度,S是源序列長度。如果average_attn_weights=False,則在未批量輸入或 (num_heads,L,S)的情況下,傳回每個形狀為 ( num_heads, L , S ) (\text{num\_heads}, L,S) (num_heads,L,S)的頭部的注意力權重。

參考

💗感謝如下文章或者視訊對我的啟發和幫助~

本文代碼主要參考了李沐老師的《Dive into Deep Learning》書中的兩節内容

“現代循環神經網絡”

“注意力機制”

本文中的講解和圖檔部分源自李宏毅老師的機器學習課程

【機器學習2021】自注意力機制 (Self-attention) (上)】

【機器學習2021】自注意力機制 (Self-attention) (下)

【機器學習2021】Transformer (上)

【機器學習2021】Transformer (下)

本文中的圖解部分源自:Jay Alammar 的兩篇文章

《The Illustrated Transformer》

《Visualizing A Neural Machine Translation Model (Mechanics of Seq2seq Models With Attention)》

其他關于Transformer的講解,參考的有如下文章

Transformer模型詳解(圖解最完整版)

史上最小白之Transformer詳解

pytorch 文檔-torch.nn.MultiheadAttention

💭 若有不足和疏漏,歡迎交流和指正!

繼續閱讀