自然語言處理(NLP):17 Transformer模型解讀

專注於文本分類、關鍵詞抽取、文本摘要、FAQ問答系統、對話系統語義理解NLU、知識圖譜等。結合工業界具體案例和學術界最新研究成果實現NLP技術場景落地。更多學習內容關注“NLP技術交流羣”學習。


NLP 領域的模型研究已經被 transformer 模型佔領了,學習Transformer 理解Attention,Self-Attention 機制應用實現以及原理,理解BERT 模型必備內容。
作者:走在前方

本次主要分享的內容

  • 前沿知識介紹
    • 認識Transformer
    • 編碼器實現方案
  • 宏觀角度看Transformer
    • 介紹Transformer構成以及組建構建
  • Transformer 編碼器
    • Self-Attention 機制、矩陣運算
    • Multi-headed Attention 機制原理和優點
    • postional Encoding 作用
    • Residuals 殘差計算方式
  • Transformer 解碼器
    • 最終的線性層
    • softmax 層
  • 模型訓練知識點
    • 詞彙表示
    • 損失函數
  • Transformer 代碼源碼分析
    • Transformer模型實現以及原理
    • 介紹Transformer 中self-attention 機制在情感分析中應用

前沿

認識Transformer

2017 年中,有兩篇類似論文,分別是 FaceBook 的《Convolutional Sequence to Sequence Learning》和 Google 的《Attention is All You Need》,它們 Seq2Seq 上的創新,本質上來說,都是拋棄了 RNN 結構來做 Seq2Seq 任務。

近年來,NLP 領域的模型研究已經被 transformer 模型佔領了。Transformer是在"Attention is All You Need"中提出的,其中的TF應用是Tensor2Tensor的子模塊。哈佛的NLP團隊專門製作了對應的PyTorch的指南說明。Transformer 模型的火爆有很多原因,例如:

  • 模型簡單易懂,encoder 和 decoder 模塊高度相似且通用
  • (encoder)容易並行,模型訓練速度快
  • 在 NMT 等領域都取得了 state-of-the-art 的效果

論文地址:Attention Is All You Need

論文代碼解讀:The Annotated Transformer

編碼器實現方案

深度學習做 NLP 的方法,基本上都是先將句子分詞,然後每個詞轉化爲對應的詞向量序列。這樣一來,每個句子都對應的是一個矩陣 X=(x1,x2,…,xt)其中xi都代表着第i個詞的詞向量(行向量),維度爲d維,故X維度是n×d。這樣的話,問題就變成了編碼這些序列了。

  • 第一個思路是 RNN(或者LSTM、GRU) 層

圖片

圖片

RNN結構本身比較簡單,適合序列建模,特點如下:

  • 無法並行,因此速度較慢

  • 無法很好地學習到全局的結構信息

  • 第二個思路是 CNN 層,CNN 的方案窗口式遍歷

圖片

圖片

在FaceBook的論文中,純粹使用卷積也完成了Seq2Seq的學習,是卷積的一個精緻且極致的使用案例。特點

  • CNN方便並行
  • 容易捕捉到一些全局的結構信息

目前的工作或競賽模型中,我都已經儘量用CNN來代替已有的RNN模型了

  • 第二個思路是 Google 公司提出:純 Attention!單靠注意力就可以!

圖片

其中 A,B 是另外一個序列(矩陣)。如果都取 A=B=X,那麼就稱爲 Self Attention,它的意思是直接將x(t) 與原來的每個詞進行比較,最後算出y(t)。

從三個方面分析爲什麼Attention出世了

  • RNN要逐步遞歸才能獲得全局信息,因此一般要雙向RNN才比較好
  • CNN事實上只能獲取局部信息,是通過層疊來增大感受野
  • Attention的思路最爲粗暴,它一步到位獲取了全局信息

A High-Level Look

  • Transformer 模型輸入一個序列,生成另一個序列。

圖片

  • Transformer 兩個部分組成,encoders 和 decoders。

圖片

  • 編碼組件(encoder)是六層編碼器首位相連堆砌而成,解碼組件(decoder)也是六層解碼器堆成的。

圖片

  • 編碼器是完全結構相同的,但不共享參數

圖片

每一個 encoder = 一個自注意力(self-attention)層 和 Feed Forward Neural Network。

  • encoder 的輸入首先會經過一個 self-attention 層。self-attention 的作用是讓每個單詞可以看到自己和其他單詞的關係,並且將自己轉換成一個與所有單詞相關的,focus 在自己身上的詞向量。
  • self-attention 之後的輸出會再經過一層 feed-forward 神經網絡。每個位置的輸出被同樣的 feed-forward network 處理。
  • 解碼器結構

decoder 也有同樣的 self-attention 和 feed-forward 結構,但是在這兩層之間還有一層 encoder-decoder** attention 層,幫助 decoder 關注到某一些特別需要關注的 encoder 位置。**

  • self-attention
  • feed-forward

*encoder-decoder attetion

圖片

Tensors的變化

NLP應用的常見例子,先將輸入單詞使用embedding algorithm轉成向量。

圖片

Each word is embedded into a vector of size 512. We’ll represent those vectors with these simple boxes.

詞的向量化在最底層的編碼器的輸入時,這樣每個編碼器的都會接收到一個list(每個元素都是512維的詞向量),只不過其他編碼器的輸入是前個編碼器的輸出。list的尺寸是可以設置的超參,通常是訓練集的最長句子的長度。

在對輸入序列做詞的向量化之後,它們流經編碼器的如下兩個子層。

圖片

這裏能看到Transformer的一個關鍵特性,每個位置的詞僅僅流過它自己的編碼器路徑。在self-attention層中,這些路徑兩兩之間是相互依賴的。前向網絡層則沒有這些依賴性,但這些路徑在流經前向網絡時可以並行執行。

Transformer的編碼器

下面我們來詳細解讀一下編碼器的工作。

圖片

Self-Attention 機制

我們考慮用 Transformer 模型翻譯下面這一句話:

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

當我們翻譯到 it 的時候,我們知道 it 指代的是 animal 而不是 street。所以,如果有辦法可以讓 it 對應位置的 embedding 適當包含 animal 的信息,就會非常有用。self-attention 的出現就是爲了完成這一任務。

如下圖所示,self attention 會讓單詞 it 和 某些單詞發生比較強的聯繫,得到比較搞的 attention 分數。

圖片

當編碼"it"時(編碼器的最後層輸出),部分attention集中於"the animal",並將其表示合併進入到“it”的編碼中。

上述可視化

Self-attention 的細節

第一步 QKV 向量表示,爲了實現 self-attention,每個輸入的位置需要產生三個向量,分別是 Query 向量,Key 向量和 Value 向量,生成方法爲分別乘以三個矩陣,這些矩陣在訓練過程中需要學習。

注意到這些新向量的維度比輸入詞向量的維度要小(512–>64),並不是必須要小的,是爲了讓多頭attention的計算更穩定。

圖片

Multiplying x1 by the WQ weight matrix produces q1, the “query” vector associated with that word. We end up creating a “query”, a “key”, and a “value” projection of each word in the input sentence.

第二步是計算分數。當我們在用 self-attention encode 某個位置上的某個單詞的時候,我們希望知道這個單詞對應的句子上其他單詞的分數。其他單詞所得到的分數表示了當我們 encode 當前單詞的時候,應該放多少的關注度在其餘的每個單詞上。又或者說,其他單詞和我當前的單詞有多大的相關性或者相似性。

在 transformer 模型中,這個分數是由 query vector 和 key vector 做點積(dot product)所得的結果。所以說,當我們在對第一個單詞做 self-attention 處理的時候,第一個單詞的分數是 q_1 和 k_1 的點積,第二個分數是 q_1 和 k_2 的分數。

圖片

第三步和第四步是將這些分數除以 8。8 這個數字是 64 的開方,也就是 key vector 的維度的開方。據說這麼做可以穩定模型的 gradient。然後我們將這些分數傳入 softmax 層產生一些符合概率分佈的 probability scores。

圖片

這些分數就表示了在處理當前單詞的時候我們應該分配多少的關注度給其他單詞。

第五步是將每個 value vector 乘以它們各自的 attention score。第六步是把這些 weighted value vectors 相加,成爲當前單詞的 vector 表示

圖片

得到了 self-attention 生成的詞向量之後,我們就可以將它們傳入 feed-forward network 了。

Thinking 最後通過 self.attention 後向量表示 :z1 (通過 attetion 考慮全文的信息

Self-Attention 中的矩陣運算

首先,我們要對每一個詞向量計算 Query, Key 和 Value 矩陣。我們把句子中的每個詞向量拼接到一起變成一個矩陣 X,然後乘以不同的矩陣做線性變換(WQ, WK, WV)。

X:[ n_vocab,embedding_size]

圖片

輸入矩陣X的每一行表示輸入句子的一個詞向量

然後我們就用矩陣乘法實現上面介紹過的 Self-Attention 機制了。

圖片

**Z: 每個單詞的 attention weight (n_vocab,embedding_size) **

Multi-headed attention

在論文當中,每個 embedding vector 並不止產生一個 key, value, query vectors,而是產生若干組這樣的 vectors,稱之爲"multi-headed" attention。這麼做有幾個好處:

  • k: key, q: query, v: value
  • 模型有更強的能力產生不同的 attention 機制,focus 在不同的單詞上。
  • attention layer 有多個不同的"representation space"。

圖片

每個 attention head 最終都產生了一個 matrix 表示這個句子中的所有詞向量。在 transformer 模型中,我們產生了八個 matrices。我們知道 self attention 之後就是一個 feed-forward network。那麼我們是否需要做 8 次 feed-forward network 運算呢?事實上是不用的。我們只需要將這 8 個 matrices 拼接到一起,然後做一次前向神經網絡的運算就可以了。

圖片

綜合起來,我們可以用下面一張圖表示 Self-Attention 模塊所做的事情。

圖片

Positional Encoding

爲解決詞序的利用問題,Transformer新增了一個向量對每個詞。

到目前爲止,我們的模型完全沒有考慮單詞的順序。即使我們將句子中單詞的順序完全打亂,對於 transformer 這個模型來說,並沒有什麼區別。爲了加入句子中單詞的順序信息,我們引入一個概念叫做 positional encoding。

(具體什麼數值不重要,更重要的是能夠代表不同的位置)

圖片

如果假設位置向量有4維,實際的位置向量將如下所示:

圖片

在下圖中,每一行表示一個位置的pos-emb,所以第一行是我們將要加到句子第一個詞向量上的vector。每個行有512值,每個值範圍在[-1,1]。

圖片

Residuals

另外一個細節是,encoder 中的每一層都包含了一個 residual connection 和 layer-normalization。如下圖所示。

圖片

下面這張圖是更詳細的 vector 表示。

圖片

decoder 也是同樣的架構。如果我們把 encoder 和 decoder 放到一起,他們就長這樣。

圖片

Transformer的解碼器

encoder 最後一層會輸出 attention vectors K 和 V。K 和 V 會被 decoder 用作解碼的原材料。

圖片

在解碼的過程中,解碼器每一步會輸出一個 token。一直循環往復,直到它輸出了一個特殊的 end of sequence token,表示解碼結束了。

圖片

decoder 的 self attention 機制與 encoder 稍有不同。在 decoder 當中,self attention 層只能看到之前已經解碼的文字。我們只需要把當前輸出位置之後的單詞全都 mask 掉(softmax 層之前全都設置成-inf)即可。

Encoder-Decoder Attention 層和普通的 multiheaded self-attention 一樣,除了它的** Queries 完全來自下面的 decoder 層,然後 Key 和 Value 來自 encoder 的輸出向量**。

最後的線性層和 softmax 層

解碼器最後輸出浮點向量,如何將它轉成詞?這是最後的線性層和 softmax 層的主要工作。

線性層是個簡單的全連接層,將解碼器的最後輸出映射到一個非常大的 logits 向量上。假設模型已知有 1 萬個單詞(輸出的詞表)從訓練集中學習得到。那麼,logits 向量就有 1 萬維,每個值表示是某個詞的可能傾向值。

softmax 層將這些分數轉換成概率值(都是正值,且加和爲 1),最高值對應的維上的詞就是這一步的輸出單詞。

圖片

logits 可以看成最後一層全連接的輸出*權重參數,假設神經網絡最後一層有 m 個結點,那 logits= wx+b, 其中 x 是 m 維

模型的訓練

詞彙表示

現在我們已經瞭解了一個訓練完畢的 Transformer 的前向過程,在訓練時,模型將經歷上述的前向過程,當我們在標記訓練集上訓練時,可以對比預測輸出與實際輸出。爲了可視化,假設輸出一共只有 6 個單詞(“a”, “am”, “i”, “thanks”, “student”, and “” (short for ‘end of sentence’)).

圖片

模型的詞表是在訓練之前的預處理中生成的

一旦定義了詞表,我們就能夠構造一個同維度的向量來表示每個單詞,比如 one-hot 編碼,下面舉例編碼“am”。

圖片

舉例採用 one-hot 編碼輸出詞表

下面讓我們討論下模型的 loss 損失,在訓練過程中用來優化的指標,指導學習得到一個非常準確的模型。

損失函數

我們用一個簡單的例子來示範訓練,比如翻譯**“merci”爲“thanks”**。那意味着輸出的概率分佈指向單詞“thanks”,但是由於模型未訓練是隨機初始化的,不太可能就是期望的輸出。

圖片

由於模型參數是隨機初始化的,未訓練的模型輸出隨機值。我們可以對比真實輸出,然後利用誤差後傳調整模型權重,使得輸出更接近與真實輸出。如何對比兩個概率分佈呢?簡單採用 cross-entropy 或者 Kullback-Leibler divergence 中的一種。鑑於這是個極其簡單的例子,更真實的情況是,使用一個句子作爲輸入。比如,輸入是“je suis étudiant”,期望輸出是“i am a student”。在這個例子下,我們期望模型輸出連續的概率分佈滿足如下條件:

  1. 每個概率分佈都與詞表同維度
  2. 第一個概率分佈對“i”具有最高的預測概率值。
  3. 第二個概率分佈對“am”具有最高的預測概率值。
  4. 一直到第五個輸出指向""標記。

圖片

對一個句子而言,訓練模型的目標概率分佈

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

圖片

訓練好之後,模型的輸出是我們期望的翻譯。當然,這並不意味着這一過程是來自訓練集。注意,每個位置都能有值,即便與輸出近乎無關,這也是 softmax 對訓練有幫助的地方。現在,**因爲模型每步只產生一組輸出,假設模型選擇最高概率,扔掉其他的部分,這是種產生預測結果的方法,叫做 greedy 解碼。**另外一種方法是 beam search,每一步僅保留最頭部高概率的兩個輸出,根據這倆輸出再預測下一步,再保留頭部高概率的兩個輸出,重複直到預測結束

訓練預測結果兩種方法:

  • greedy 方法 :選擇模型概率最高的數值
  • beam search 方法 :保留頭部高概率的兩個輸出結果

Transformer 代碼實現

Model Architecture

圖片

  • LayNorm 進行歸一化
class LayerNorm(nn.Module):
    "Construct a layernorm module (See citation for details)."
    def __init__(self, features, eps=1e-6):
        super(LayerNorm, self).__init__()
        self.a_2 = nn.Parameter(torch.ones(features))
        self.b_2 = nn.Parameter(torch.zeros(features))
        self.eps = eps
    def forward(self, x):
        mean = x.mean(-1, keepdim=True)
        std = x.std(-1, keepdim=True)
        return self.a_2 * (x - mean) / (std + self.eps) + self.b_2

Attention

計算Attention圖說明

QK^T

-> math.sqrt(d_k) 數值縮放(避免softmax 計算過程e的指數過大的問題,當前我們也可以換成別的)

-> score 的得分mask 填充

-> softmax 獲取query在所有key上的attn_score

-> V 權重乘以score

圖片

計算Attention公式

圖片

參數說明

  • Q 所有query組成的矩陣
  • K 所有key組成的矩陣
  • V 所有value組成的矩陣
def attention(query, key, value, mask=None, dropout=None):
    "Compute 'Scaled Dot Product Attention'"
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-2, -1)) \
             / math.sqrt(d_k)
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -1e9)
    p_attn = F.softmax(scores, dim = -1)
    if dropout is not None:
        p_attn = dropout(p_attn)
    return torch.matmul(p_attn, value), p_attn
  • transformer 中計算attn_sore 採用dot_product ,也可以採用其他的方法(博客中有介紹)
  • d_k 是key的維度
  • mask_fill 中的mask==0 的內容填充-1e9,目的是在計算softmax 概率的時候接近0
  • torch.matmul(p_attn, value) 矩陣相乘,表示加權求和,作爲最終query的表示

更多代碼請查看 博客

更多資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章