B站視頻講解php
Transformer是谷歌大腦在2017年末發表的論文attention is all you need中所提出的seq2seq模型。如今已經取得了大範圍的應用和擴展,而BERT就是從Transformer中衍生出來的預訓練語言模型html
這篇文章分爲如下幾個部分python
Transformer和LSTM的最大區別,就是LSTM的訓練是迭代的、串行的,必需要等當前字處理完,才能夠處理下一個字。而Transformer的訓練時並行的,即全部字是同時訓練的,這樣就大大增長了計算效率。Transformer使用了位置嵌入(Positional Encoding)來理解語言的順序,使用自注意力機制(Self Attention Mechanism)和全鏈接層進行計算,這些後面會講到git
Transformer模型主要分爲兩大部分,分別是Encoder和Decoder。Encoder負責把輸入(語言序列)隱射成隱藏層(下圖中第2步用九宮格表明的部分),而後解碼器再把隱藏層映射爲天然語言序列。例以下圖機器翻譯的例子(Decoder輸出的時候,是經過N層Decoder Layer才輸出一個token,並非經過一層Decoder Layer就輸出一個token)github
本篇文章大部份內容在於解釋Encoder部分,即把天然語言序列映射爲隱藏層的數學表達的過程。理解了Encoder的結構,再理解Decoder就很簡單了markdown
上圖爲Transformer Encoder Block結構圖,注意:下面的內容標題編號分別對應着圖中1,2,3,4個方框的序號網絡
因爲Transformer模型沒有循環神經網絡的迭代操做, 因此咱們必須提供每一個字的位置信息給Transformer,這樣它才能識別出語言中的順序關係app
如今定義一個位置嵌入的概念,也就是Positional Encoding,位置嵌入的維度爲[max_sequence_length, embedding_dimension]
, 位置嵌入的維度與詞向量的維度是相同的,都是embedding_dimension
。max_sequence_length
屬於超參數,指的是限定每一個句子最長由多少個詞構成ide
注意,咱們通常以字爲單位訓練Transformer模型。首先初始化字編碼的大小爲[vocab_size, embedding_dimension]
,vocab_size
爲字庫中全部字的數量,embedding_dimension
爲字向量的維度,對應到PyTorch中,其實就是nn.Embedding(vocab_size, embedding_dimension)
svg
論文中使用了sin和cos函數的線性變換來提供給模型位置信息:
上式中 指的是一句話中某個字的位置,取值範圍是 , 指的是字向量的維度序號,取值範圍是 , 指的是embedding_dimension的值
上面有 和 一組公式,也就是對應着embedding_dimension維度的一組奇數和偶數的序號的維度,例如0,1一組,2,3一組,分別用上面的 和 函數作處理,從而產生不一樣的週期性變化,而位置嵌入在embedding_dimension維度上隨着維度序號增大,週期變化會愈來愈慢,最終產生一種包含位置信息的紋理,就像論文原文中第六頁講的,位置嵌入函數的週期從 到 變化,而每個位置在embedding_dimension維度上都會獲得不一樣週期的 和 函數的取值組合,從而產生獨一的紋理位置信息,最終使得模型學到位置之間的依賴關係和天然語言的時序特性
若是不理解這裏爲什麼這麼設計,能夠看這篇文章Transformer中的Positional Encoding
下面畫一下位置嵌入,縱向觀察,可見隨着embedding_dimension序號增大,位置嵌入函數的週期變化愈來愈平緩
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
def get_positional_encoding(max_seq_len, embed_dim):
# 初始化一個positional encoding
# embed_dim: 字嵌入的維度
# max_seq_len: 最大的序列長度
positional_encoding = np.array([
[pos / np.power(10000, 2 * i / embed_dim) for i in range(embed_dim)]
if pos != 0 else np.zeros(embed_dim) for pos in range(max_seq_len)])
positional_encoding[1:, 0::2] = np.sin(positional_encoding[1:, 0::2]) # dim 2i 偶數
positional_encoding[1:, 1::2] = np.cos(positional_encoding[1:, 1::2]) # dim 2i+1 奇數
return positional_encoding
positional_encoding = get_positional_encoding(max_seq_len=100, embed_dim=16)
plt.figure(figsize=(10,10))
sns.heatmap(positional_encoding)
plt.title("Sinusoidal Function")
plt.xlabel("hidden dimension")
plt.ylabel("sequence length")
複製代碼
plt.figure(figsize=(8, 5))
plt.plot(positional_encoding[1:, 1], label="dimension 1")
plt.plot(positional_encoding[1:, 2], label="dimension 2")
plt.plot(positional_encoding[1:, 3], label="dimension 3")
plt.legend()
plt.xlabel("Sequence length")
plt.ylabel("Period of Positional Encoding")
複製代碼
對於輸入的句子 ,經過WordEmbedding獲得該句子中每一個字的字向量,同時經過Positional Encoding獲得全部字的位置向量,將其相加(維度相同,能夠直接相加),獲得該字真正的向量表示。第 個字的向量記做
接着咱們定義三個矩陣 ,使用這三個矩陣分別對全部的字向量進行三次線性變換,因而全部的字向量又衍生出三個新的向量 。咱們將全部的 向量拼成一個大矩陣,記做查詢矩陣 ,將全部的 向量拼成一個大矩陣,記做鍵矩陣 ,將全部的 向量拼成一個大矩陣,記做值矩陣 (見下圖)
爲了得到第一個字的注意力權重,咱們須要用第一個字的查詢向量 乘以鍵矩陣K(見下圖)
[0, 4, 2]
[1, 0, 2] x [1, 4, 3] = [2, 4, 4]
[1, 0, 1]
複製代碼
以後還須要將獲得的值通過softmax,使得它們的和爲1(見下圖)
softmax([2, 4, 4]) = [0.0, 0.5, 0.5]
複製代碼
有了權重以後,將權重其分別乘以對應字的值向量 (見下圖)
0.0 * [1, 2, 3] = [0.0, 0.0, 0.0]
0.5 * [2, 8, 0] = [1.0, 4.0, 0.0]
0.5 * [2, 6, 3] = [1.0, 3.0, 1.5]
複製代碼
最後將這些權重化後的值向量求和,獲得第一個字的輸出(見下圖)
[0.0, 0.0, 0.0]
+ [1.0, 4.0, 0.0]
+ [1.0, 3.0, 1.5]
-----------------
= [2.0, 7.0, 1.5]
複製代碼
對其它的輸入向量也執行相同的操做,便可獲得經過self-attention後的全部輸出
上面介紹的方法須要一個循環遍歷全部的字 ,咱們能夠把上面的向量計算變成矩陣的形式,從而一次計算出全部時刻的輸出
第一步就不是計算某個時刻的 了,而是一次計算全部時刻的 和 。計算過程以下圖所示,這裏的輸入是一個矩陣 ,矩陣第 行爲第 個詞的向量表示
接下來將 和 相乘,而後除以 (這是論文中提到的一個trick),通過softmax之後再乘以 獲得輸出
這篇論文還提出了Multi-Head Attention的概念。其實很簡單,前面定義的一組 可讓一個詞attend to相關的詞,咱們能夠定義多組 ,讓它們分別關注不一樣的上下文。計算 的過程仍是同樣,只不過線性變換的矩陣從一組 變成了多組 , ,…以下圖所示
對於輸入矩陣 ,每一組 、 和 均可以獲得一個輸出矩陣 。以下圖所示
上面Self Attention的計算過程當中,咱們一般使用mini-batch來計算,也就是一次計算多句話,即
的維度是[batch_size, sequence_length]
,sequence_length是句長,而一個mini-batch是由多個不等長的句子組成的,咱們須要按照這個mini-batch中最大的句長對剩餘的句子進行補齊,通常用0進行填充,這個過程叫作padding
但這時在進行softmax就會產生問題。回顧softmax函數 , 是1,是有值的,這樣的話softmax中被padding的部分就參與了運算,至關於讓無效的部分參與了運算,這可能會產生很大的隱患。所以須要作一個mask操做,讓這些無效的區域不參與運算,通常是給無效區域加一個很大的負數偏置,即
咱們在上一步獲得了通過self-attention加權以後輸出,也就是 ,而後把他們加起來作殘差鏈接
Layer Normalization的做用是把神經網絡中隱藏層歸一爲標準正態分佈,也就是 獨立同分布,以起到加快訓練速度,加速收斂的做用
上式以矩陣的列(column)爲單位求均值;
上式以矩陣的列(column)爲單位求方差
而後用每一列的每個元素減去這列的均值,再除以這列的標準差,從而獲得歸一化後的數值,加 是爲了防止分母爲0
下圖展現了更多細節:輸入 經self-attention層以後變成 ,而後和輸入 進行殘差鏈接,通過LayerNorm後輸出給全鏈接層。全鏈接層也有一個殘差鏈接和一個LayerNorm,最後再輸出給下一個Encoder(每一個Encoder Block中的FeedForward層權重都是共享的)
通過上面3個步驟,咱們已經基本瞭解了Encoder的主要構成部分,下面咱們用公式把一個Encoder block的計算過程整理一下:
1). 字向量與位置編碼
2). 自注意力機制
3). self-attention殘差鏈接與Layer Normalization
4). 下面進行Encoder block結構圖中的第4部分,也就是FeedForward,其實就是兩層線性映射並用激活函數激活,好比說
5). FeedForward殘差鏈接與Layer Normalization
其中
咱們先從HighLevel的角度觀察一下Decoder結構,從下到上依次是:
和Encoder同樣,上面三個部分的每個部分,都有一個殘差鏈接,後接一個 Layer Normalization。Decoder的中間部件並不複雜,大部分在前面Encoder裏咱們已經介紹過了,可是Decoder因爲其特殊的功能,所以在訓練時會涉及到一些細節
具體來講,傳統Seq2Seq中Decoder使用的是RNN模型,所以在訓練過程當中輸入 時刻的詞,模型不管如何也看不到將來時刻的詞,由於循環神經網絡是時間驅動的,只有當 時刻運算結束了,才能看到 時刻的詞。而Transformer Decoder拋棄了RNN,改成Self-Attention,由此就產生了一個問題,在訓練過程當中,整個ground truth都暴露在Decoder中,這顯然是不對的,咱們須要對Decoder的輸入進行一些處理,該處理被稱爲Mask
舉個例子,Decoder的ground truth爲"<start> I am fine",咱們將這個句子輸入到Decoder中,通過WordEmbedding和Positional Encoding以後,將獲得的矩陣作三次線性變換( )。而後進行self-attention操做,首先經過 獲得Scaled Scores,接下來很是關鍵,咱們要對Scaled Scores進行Mask,舉個例子,當咱們輸入"I"時,模型目前僅知道包括"I"在內以前全部字的信息,即"<start>"和"I"的信息,不該該讓其知道"I"以後詞的信息。道理很簡單,咱們作預測的時候是按照順序一個字一個字的預測,怎麼能這個字都沒預測完,就已經知道後面字的信息了呢?Mask很是簡單,首先生成一個下三角全0,上三角全爲負無窮的矩陣,而後將其與Scaled Scores相加便可
以後再作softmax,就能將-inf變爲0,獲得的這個矩陣即爲每一個字之間的權重
Multi-Head Self-Attention無非就是並行的對上述步驟多作幾回,前面Encoder也介紹了,這裏就很少贅述了
其實這一部分的計算流程和前面Masked Self-Attention很類似,結構也一摸同樣,惟一不一樣的是這裏的 爲Encoder的輸出, 爲Decoder中Masked Self-Attention的輸出
到此爲止,Transformer中95%的內容已經介紹完了,咱們用一張圖展現其完整結構。不得不說,Transformer設計的十分巧奪天工
下面有幾個問題,是我從網上找的,感受看完以後能對Transformer有一個更深的理解
原論文中說到進行Multi-head Attention的緣由是將模型分爲多個頭,造成多個子空間,可讓模型去關注不一樣方面的信息,最後再將各個方面的信息綜合起來。其實直觀上也能夠想到,若是本身設計這樣的一個模型,必然也不會只作一次attention,屢次attention綜合的結果至少可以起到加強模型的做用,也能夠類比CNN中同時使用多個卷積核的做用,直觀上講,多頭的注意力有助於網絡捕捉到更豐富的特徵/信息
這裏用代替這個詞略顯不穩當,seq2seq雖已老,但始終仍是有其用武之地,seq2seq最大的問題在於將Encoder端的全部信息壓縮到一個固定長度的向量中,並將其做爲Decoder端首個隱藏狀態的輸入,來預測Decoder端第一個單詞(token)的隱藏狀態。在輸入序列比較長的時候,這樣作顯然會損失Encoder端的不少信息,並且這樣一股腦的把該固定向量送入Decoder端,Decoder端不可以關注到其想要關注的信息。Transformer不但對seq2seq模型這兩點缺點有了實質性的改進(多頭交互式attention模塊),並且還引入了self-attention模塊,讓源序列和目標序列首先「自關聯」起來,這樣的話,源序列和目標序列自身的embedding表示所蘊含的信息更加豐富,並且後續的FFN層也加強了模型的表達能力,而且Transformer並行計算的能力遠遠超過了seq2seq系列模型