日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【NLP】图解Transformer(完整版)

發布時間:2025/3/8 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【NLP】图解Transformer(完整版) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

譯者:張賢,哈爾濱工程大學,Datawhale原創作者

本文約16000字,是NLP專欄第一篇,建議收藏閱讀

審稿人:Jepson,Datawhale成員,畢業于中國科學院,目前在騰訊從事推薦算法工作。


結構總覽

前言

本文翻譯自http://jalammar.github.io/illustrated-transformer,是筆者看過的把 Transformer 講解得最好的文章。這篇文章從輸入開始,一步一步演示了數據在 Transformer 中的流動過程。由于看過一些中文翻譯的文章,感覺不夠好,所以我自己翻譯了一個版本,在一些難以直譯的地方,我加入了一些原文沒有的文字說明,來更好地解釋概念。另外,我添加了一些簡單的代碼,實現了一個基本的 Self Attention 以及 multi-head attention 的矩陣運算。

Transformer 依賴于 Self Attention 的知識。Attention 是一種在深度學習中廣泛使用的方法,Attention的思想提升了機器翻譯的效果。如果你還沒學習 Attention,請查看這篇 Attention 的精彩講解:https://zhuanlan.zhihu.com/p/265182368。

2017 年,Google 提出了 Transformer 模型,用 Self Attention 的結構,取代了以往 NLP 任務中的 RNN 網絡結構,在 WMT 2014 Englishto-German 和 WMT 2014 English-to-French兩個機器翻譯任務上都取得了當時 SOTA 的效果。

這個模型的其中一個優點,就是使得模型訓練過程能夠并行計算。在 RNN 中,每一個 time step 的計算都依賴于上一個 time step 的輸出,這就使得所有的 time step 必須串行化,無法并行計算,如下圖所示。

而在 Transformer 中,所有 time step 的數據,都是經過 Self Attention 計算,使得整個運算過程可以并行化計算。

這篇文章的目的是從上到下,一步一步拆解 Transformer 的各種概念,希望有助于初學者更加容易地理解 Transformer 到底是什么。

Transformer 使用了 Seq2Seq任務中常用的結構——包括兩個部分:Encoder 和 Decoder。一般的結構圖,都是像下面這樣。

如果你看到上圖不知所措,不要擔心,下面我們來一步步拆解 Transformer。

一、從整體宏觀來理解 Transformer

首先,我們將整個模型視為黑盒。在機器翻譯任務中,接收一種語言的句子作為輸入,然后將其翻譯成其他語言輸出。

中間部分的 Transformer 可以拆分為 2 部分:左邊是編碼部分(encoding component),右邊是解碼部分(decoding component)。

其中編碼部分是多層的編碼器(Encoder)組成(Transformer 的論文中使用了 6 層編碼器,這里的層數 6 并不是固定的,你也可以根據實驗效果來修改層數)。同理,解碼部分也是由多層的解碼器(Decoder)組成(論文里也使用了 6 層的解碼器)。

每一個編碼器 在結構上都是一樣的,但它們的權重參數是不同的。每一個編碼器里面,可以分為 2 層
  • Self-Attention Layer

  • Feed Forward Neural Network(前饋神經網絡,縮寫為 FFNN)

輸入編碼器的文本數據,首先會經過一個 Self Attention 層,這個層處理一個詞的時候,不僅會使用這個詞本身的信息,也會使用句子中其他詞的信息(你可以類比為:當我們翻譯一個詞的時候,不僅會只關注當前的詞,也會關注這個詞的上下文的其他詞的信息)。本文后面將會詳細介紹 Self Attention 的內部結構。

接下來,Self Attention 層的輸出會經過前饋神經網絡。

同理,解碼器也具有這兩層,但是這兩層中間還插入了一個 Encoder-Decoder Attention 層,這個層能幫助解碼器聚焦于輸入句子的相關部分(類似于 seq2seq 模型 中的 Attention)。

二、從細節來理解 Transformer

上面,我們從宏觀理解了 Transformer 的主要部分。下面,我們來看輸入的張量數據,在 Transformer 中運算最終得到輸出的過程。

2.1 Transformer 的輸入

和通常的 NLP 任務一樣,我們首先會使用詞嵌入算法(embedding algorithm),將每個詞轉換為一個詞向量。實際中向量一般是 256 或者 512 維。為了簡化起見,這里將每個詞的轉換為一個 4 維的詞向量

那么整個輸入的句子是一個向量列表,其中有 3 個詞向量。在實際中,每個句子的長度不一樣,我們會取一個適當的值,作為向量列表的長度。如果一個句子達不到這個長度,那么就填充全為 0 的詞向量;如果句子超出這個長度,則做截斷。句子長度是一個超參數,通常是訓練集中的句子的最大長度,你可以嘗試不同長度的效果。

編碼器(Encoder)接收的輸入都是一個向量列表,輸出也是大小同樣的向量列表,然后接著輸入下一個編碼器。

第一個編碼器的輸入是詞向量,而后面的編碼器的輸入是上一個編碼器的輸出

下面,我們來看這個向量列表在編碼器里面是如何流動的。

這里我們可以注意到 Transformer 的一個重要特性:每個位置的詞向量經過編碼器都有自己單獨的路徑。具體來說,在 Self Attention 層中,這些路徑之間是有依賴關系的;而在 Feed Forward (前饋神經網絡)層中,這些路徑之間是沒有依賴關系的。因此這些詞向量在經過 Feed Forward 層中可以并行計算(這句話會造成困擾,我認為在 Self Attention 層中,也能并行計算,沒有必要單獨說 Feed Forward 層也可以并行計算)。

下面我們用一個更短的句子,來說明數據在編碼器的編碼過程。

2.2 Encoder(編碼器)

上面我們提到,一個編碼器接收的輸入是一個向量列表,它會把向量列表輸入到 Self Attention 層,然后經過 feed-forward neural network (前饋神經網絡)層,最后得到輸出,傳入下一個編碼器。

每個位置的詞都經過 Self Attention 層,得到的每個輸出向量都單獨經過前饋神經網絡層,每個向量經過的前饋神經網絡都是一樣的

三、 Self-Attention 整體理解

別被“Self-Attention”這么高大上的詞給唬住了,乍一聽好像每個人都應該對這個詞熟悉一樣。但我在讀論文《Attention is All You Need》 之前就沒有聽過這個詞。下面來分析 Self-Attention 的具體機制。

假設我們想要翻譯的句子是:

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

這個句子中的 it 是一個指代詞,那么 it 指的是什么呢?它是指animal還是street?這個問題對人來說,是很簡單的,但是對算法來說并不是那么容易。

當模型在處理(翻譯)it 的時候,Self Attention機制能夠讓模型把it和animal關聯起來。

同理,當模型處理句子中的每個詞時,Self Attention機制使得模型不僅能夠關注這個位置的詞,而且能夠關注句子中其他位置的詞,作為輔助線索,進而可以更好地編碼當前位置的詞。

如果你熟悉 RNN,回憶一下:RNN 在處理一個詞時,會考慮前面傳過來的hidden state,而hidden state就包含了前面的詞的信息。而 Transformer 使用Self Attention機制,會把其他單詞的理解融入處理當前的單詞。

當我們在第五層編碼器中(編碼部分中的最后一層編碼器)編碼“it”時,有一部分注意力集中在“The animal”上,并且把這兩個詞的信息融合到了"it"這個單詞中。

你可以查看 【Tensor2Tensor notebook】。在這個 notebook 里,你可以加載 Transformer 模型,并通過交互式的可視化,來理解 Self Attention。

四、Self-Attention 的細節

4.1 計算Query 向量,Key 向量,Value 向量

下面我們先看下如何使用向量來計算 Self Attention,然后再看下如何使用矩陣來實現 Self Attention。(矩陣運算的方式,使得 Self Attention 的計算能夠并行化,這也是 Self Attention 最終的實現方式)。

計算 Self Attention 的第 1 步是:對輸入編碼器的每個詞向量,都創建 3 個向量,分別是:Query 向量,Key 向量,Value 向量。這 3 個向量是詞向量分別和 3 個矩陣相乘得到的,而這個矩陣是我們要學習的參數。

注意,這 3 個新得到的向量一般比原來的詞向量的長度更小。假設這 3 個向量的長度是?,而原始的詞向量或者最終輸出的向量的長度是 512(這 3 個向量的長度,和最終輸出的向量長度,是有倍數關系的)。關于 Multi-head Attention,后面會給出實際代碼。這里為了簡化,假設只有一個 head 的 Self-Attention。

上圖中,有兩個詞向量:Thinking 的詞向量 x1 和 Machines 的詞向量 x2。以 x1 為例,X1 乘以 WQ 得到 q1,q1 就是 X1 對應的 Query 向量。同理,X1 乘以 WK 得到 k1,k1 是 X1 對應的 Key 向量;X1 乘以 WV 得到 v1,v1 是 X1 對應的 Value 向量。

Query 向量,Key 向量,Value 向量是什么含義呢?

其實它們就是 3 個向量,給它們加上一個名稱,可以讓我們更好地理解 Self-Attention 的計算過程和邏輯含義。繼續往下讀,你會知道 attention 是如何計算出來的,Query 向量,Key 向量,Value 向量又分別扮演了什么角色。

4.2 計算 Attention Score(注意力分數)

第 2 步,是計算 Attention Score(注意力分數)。假設我們現在計算第一個詞 Thinking 的 Attention Score(注意力分數),需要根據 Thinking 這個詞,對句子中的其他每個詞都計算一個分數。這些分數決定了我們在編碼Thinking這個詞時,需要對句子中其他位置的每個詞放置多少的注意力。

這些分數,是通過計算 "Thinking" 對應的 Query 向量和其他位置的每個詞的 Key 向量的點積,而得到的。如果我們計算句子中第一個位置單詞的 Attention Score(注意力分數),那么第一個分數就是 q1 和 k1 的內積,第二個分數就是 q1 和 k2 的點積。

第 3 步就是把每個分數除以?( 是 Key 向量的長度)。你也可以除以其他數,除以一個數是為了在反向傳播時,求取梯度更加穩定。

第 4 步,接著把這些分數經過一個 Softmax 層,Softmax可以將分數歸一化,這樣使得分數都是正數并且加起來等于 1。

這些分數決定了在編碼當前位置(這里的例子是第一個位置)的詞時,對所有位置的詞分別有多少的注意力。很明顯,在上圖的例子中,當前位置(這里的例子是第一個位置)的詞會有最高的分數,但有時,關注到其他位置上相關的詞也很有用。

第 5 步,得到每個位置的分數后,將每個分數分別與每個 Value 向量相乘。這種做法背后的直覺理解就是:對于分數高的位置,相乘后的值就越大,我們把更多的注意力放到了它們身上;對于分數低的位置,相乘后的值就越小,這些位置的詞可能是相關性不大的,這樣我們就忽略了這些位置的詞。

第 6 步是把上一步得到的向量相加,就得到了 Self Attention 層在這個位置(這里的例子是第一個位置)的輸出。

上面這張圖,包含了 Self Attention 的全過程,最終得到的當前位置(這里的例子是第一個位置)的向量會輸入到前饋神經網絡。但這樣每次只能計算一個位置的輸出向量,在實際的代碼實現中,Self Attention 的計算過程是使用矩陣來實現的,這樣可以加速計算,一次就得到所有位置的輸出向量。下面讓我們來看,如何使用矩陣來計算所有位置的輸出向量。

五、使用矩陣計算 Self-Attention

第一步是計算 Query,Key,Value 的矩陣。首先,我們把所有詞向量放到一個矩陣 X 中,然后分別和 3 個權重矩陣,, 相乘,得到 Q,K,V 矩陣。

矩陣 X 中的每一行,表示句子中的每一個詞的詞向量,長度是 512。Q,K,V 矩陣中的每一行表示 Query 向量,Key 向量,Value 向量,向量長度是 64。

接著,由于我們使用了矩陣來計算,我們可以把上面的第 2 步到第 6 步壓縮為一步,直接得到 Self Attention 的輸出。

六、多頭注意力機制(multi-head attention)

Transformer 的論文通過增加多頭注意力機制(一組注意力稱為一個 attention head),進一步完善了 Self Attention 層。這種機制從如下兩個方面增強了 attention 層的能力:

  • 它擴展了模型關注不同位置的能力。在上面的例子中,第一個位置的輸出 z1 包含了句子中其他每個位置的很小一部分信息,但 z1 可能主要是由第一個位置的信息決定的。當我們翻譯句子:The animal didn’t cross the street because it was too tired時,我們想讓機器知道其中的it指代的是什么。這時,多頭注意力機制會有幫助。

  • 多頭注意力機制賦予 attention 層多個“子表示空間”。下面我們會看到,多頭注意力機制會有多組?,, 的權重矩陣(在 Transformer 的論文中,使用了 8 組注意力(attention heads)。因此,接下來我也是用 8 組注意力頭 (attention heads))。每一組注意力的?,,的權重矩陣都是隨機初始化的。經過訓練之后,每一組注意力可以看作是把輸入的向量映射到一個”子表示空間“。


  • 在多頭注意力機制中,我們為每組注意力維護單獨的 WQ, WK, WV 權重矩陣。將輸入 X 和每組注意力的WQ, WK, WV 相乘,得到 8 組 Q, K, V 矩陣。

    接著,我們把每組 K, Q, V 計算得到每組的 Z 矩陣,就得到 8 個 Z 矩陣。

    接下來就有點麻煩了,因為前饋神經網絡層接收的是 1 個矩陣(其中每行的向量表示一個詞),而不是 8 個矩陣。所以我們需要一種方法,把 8 個矩陣整合為一個矩陣。

    怎么才能做到呢?我們把矩陣拼接起來,然后和另一個權重矩陣? 相乘。

  • 把 8 個矩陣 {Z0,Z1...,Z7} 拼接起來

  • 把拼接后的矩陣和 WO 權重矩陣相乘

  • 得到最終的矩陣 Z,這個矩陣包含了所有 attention heads(注意力頭) 的信息。這個矩陣會輸入到 FFNN (Feed Forward Neural Network)層。

  • 這就是多頭注意力的全部內容。我知道,在上面的講解中,出現了相當多的矩陣。下面我把所有的內容都放到一張圖中,這樣你可以總攬全局,在這張圖中看到所有的內容。

    既然我們已經談到了多頭注意力,現在讓我們重新回顧之前的翻譯例子,看下當我們編碼單詞it時,不同的 attention heads (注意力頭)關注的是什么部分。

    當我們編碼單詞"it"時,其中一個 attention head (注意力頭)最關注的是"the animal",另外一個 attention head 關注的是"tired"。因此在某種意義上,"it"在模型中的表示,融合了"animal"和"word"的部分表達。

    然而,當我們把所有 attention heads(注意力頭) 都在圖上畫出來時,多頭注意力又變得難以解釋了。

    七、代碼實現矩陣計算 Attention

    下面我們是用代碼來演示,如何使用矩陣計算 attention。首先使用 PyTorch 庫提供的函數實現,然后自己再實現。

    7.1 使用 PyTorch 庫的實現

    PyTorch 提供了 MultiheadAttention 來實現 attention 的計算。

    torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)

    參數說明如下:

    • embed_dim:最終輸出的 K、Q、V 矩陣的維度,這個維度需要和詞向量的維度一樣

    • num_heads:設置多頭注意力的數量。如果設置為 1,那么只使用一組注意力。如果設置為其他數值,那么 num_heads 的值需要能夠被 embed_dim 整除

    • dropout:這個 dropout 加在 attention score 后面

    現在來解釋一下,為什么 ?num_heads 的值需要能夠被 embed_dim 整除。這是為了把詞的隱向量長度平分到每一組,這樣多組注意力也能夠放到一個矩陣里,從而并行計算多頭注意力。

    例如,我們前面說到,8 組注意力可以得到 8 組 Z 矩陣,然后把這些矩陣拼接起來,得到最終的輸出。如果最終輸出的每個詞的向量維度是 512,那么每組注意力的向量維度應該是?。

    如果不能夠整除,那么這些向量的長度就無法平均分配。

    下面的會有代碼示例,如何使用矩陣實現多組注意力的并行計算。

    定義 MultiheadAttention 的對象后,調用時傳入的參數如下。

    forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None)
    • query:對應于 Key 矩陣,形狀是 (L,N,E) 。其中 L 是輸出序列長度,N 是 batch size,E 是詞向量的維度

    • key:對應于 Key 矩陣,形狀是 (S,N,E) 。其中 S 是輸入序列長度,N 是 batch size,E 是詞向量的維度

    • value:對應于 Value 矩陣,形狀是 (S,N,E) 。其中 S 是輸入序列長度,N 是 batch size,E 是詞向量的維度

    • key_padding_mask:如果提供了這個參數,那么計算 attention score 時,忽略 Key 矩陣中某些 padding 元素,不參與計算 attention。形狀是 (N,S)。其中 N 是 batch size,S 是輸入序列長度。

      • 如果 key_padding_mask 是 ByteTensor,那么非 0 元素對應的位置會被忽略

      • 如果 key_padding_mask 是 BoolTensor,那么 ?True 對應的位置會被忽略

    • attn_mask:計算輸出時,忽略某些位置。形狀可以是 2D ?(L,S),或者 3D (N?numheads,L,S)。其中 L 是輸出序列長度,S 是輸入序列長度,N 是 batch size。

      • 如果 attn_mask 是 ByteTensor,那么非 0 元素對應的位置會被忽略

      • 如果 attn_mask 是 BoolTensor,那么 ?True 對應的位置會被忽略

    需要注意的是:在前面的講解中,我們的 K、Q、V 矩陣的序列長度都是一樣的。但是在實際中,K、V 矩陣的序列長度是一樣的,而 Q 矩陣的序列長度可以不一樣。

    這種情況發生在:在解碼器部分的Encoder-Decoder Attention層中,Q 矩陣是來自解碼器下層,而 K、V 矩陣則是來自編碼器的輸出。


    在完成了編碼(encoding)階段之后,我們開始解碼(decoding)階段。解碼(decoding )階段的每一個時間步都輸出一個翻譯后的單詞(這里的例子是英語翻譯)。

    輸出是:

    • attn_output:形狀是 (L,N,E)

    • attn_output_weights:形狀是 (N,L,S)

    代碼示例如下:

    ## nn.MultiheadAttention 輸入第0維為length # batch_size 為 64,有 12 個詞,每個詞的 Query 向量是 300 維 query = torch.rand(12,64,300) # batch_size 為 64,有 10 個詞,每個詞的 Key 向量是 300 維 key = torch.rand(10,64,300) # batch_size 為 64,有 10 個詞,每個詞的 Value 向量是 300 維 value= torch.rand(10,64,300)embed_dim = 299 num_heads = 1 # 輸出是 (attn_output, attn_output_weights) multihead_attn = nn.MultiheadAttention(embed_dim, num_heads) attn_output = multihead_attn(query, key, value)[0] # output: torch.Size([12, 64, 300]) # batch_size 為 64,有 12 個詞,每個詞的向量是 300 維 print(attn_output.shape)

    7.2 手動實現計算 Attention

    在 PyTorch 提供的 MultiheadAttention ?中,第 1 維是句子長度,第 2 維是 batch size。這里我們的代碼實現中,第 1 維是 batch size,第 2 維是句子長度。代碼里也包括:如何用矩陣實現多組注意力的并行計算。代碼中已經有詳細注釋和說明。

    class MultiheadAttention(nn.Module):# n_heads:多頭注意力的數量# hid_dim:每個詞輸出的向量維度def __init__(self, hid_dim, n_heads, dropout):super(MultiheadAttention, self).__init__()self.hid_dim = hid_dimself.n_heads = n_heads# 強制 hid_dim 必須整除 hassert hid_dim % n_heads == 0# 定義 W_q 矩陣self.w_q = nn.Linear(hid_dim, hid_dim)# 定義 W_k 矩陣self.w_k = nn.Linear(hid_dim, hid_dim)# 定義 W_v 矩陣self.w_v = nn.Linear(hid_dim, hid_dim)self.fc = nn.Linear(hid_dim, hid_dim)self.do = nn.Dropout(dropout)# 縮放self.scale = torch.sqrt(torch.FloatTensor([hid_dim // n_heads]))def forward(self, query, key, value, mask=None):# K: [64,10,300], batch_size 為 64,有 12 個詞,每個詞的 Query 向量是 300 維# V: [64,10,300], batch_size 為 64,有 10 個詞,每個詞的 Query 向量是 300 維# Q: [64,12,300], batch_size 為 64,有 10 個詞,每個詞的 Query 向量是 300 維bsz = query.shape[0]Q = self.w_q(query)K = self.w_k(key)V = self.w_v(value)# 這里把 K Q V 矩陣拆分為多組注意力,變成了一個 4 維的矩陣# 最后一維就是是用 self.hid_dim // self.n_heads 來得到的,表示每組注意力的向量長度, 每個 head 的向量長度是:300/6=50# 64 表示 batch size,6 表示有 6組注意力,10 表示有 10 詞,50 表示每組注意力的詞的向量長度# K: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉置得到 -> [64,6,10,50]# V: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉置得到 -> [64,6,10,50]# Q: [64,12,300] 拆分多組注意力 -> [64,12,6,50] 轉置得到 -> [64,6,12,50]# 轉置是為了把注意力的數量 6 放到前面,把 10 和 50 放到后面,方便下面計算Q = Q.view(bsz, -1, self.n_heads, self.hid_dim //self.n_heads).permute(0, 2, 1, 3)K = K.view(bsz, -1, self.n_heads, self.hid_dim //self.n_heads).permute(0, 2, 1, 3)V = V.view(bsz, -1, self.n_heads, self.hid_dim //self.n_heads).permute(0, 2, 1, 3)# 第 1 步:Q 乘以 K的轉置,除以scale# [64,6,12,50] * [64,6,50,10] = [64,6,12,10]# attention:[64,6,12,10]attention = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale# 把 mask 不為空,那么就把 mask 為 0 的位置的 attention 分數設置為 -1e10if mask is not None:attention = attention.masked_fill(mask == 0, -1e10)# 第 2 步:計算上一步結果的 softmax,再經過 dropout,得到 attention。# 注意,這里是對最后一維做 softmax,也就是在輸入序列的維度做 softmax# attention: [64,6,12,10]attention = self.do(torch.softmax(attention, dim=-1))# 第三步,attention結果與V相乘,得到多頭注意力的結果# [64,6,12,10] * [64,6,10,50] = [64,6,12,50]# x: [64,6,12,50]x = torch.matmul(attention, V)# 因為 query 有 12 個詞,所以把 12 放到前面,把 5 和 60 放到后面,方便下面拼接多組的結果# x: [64,6,12,50] 轉置-> [64,12,6,50]x = x.permute(0, 2, 1, 3).contiguous()# 這里的矩陣轉換就是:把多組注意力的結果拼接起來# 最終結果就是 [64,12,300]# x: [64,12,6,50] -> [64,12,300]x = x.view(bsz, -1, self.n_heads * (self.hid_dim // self.n_heads))x = self.fc(x)return x# batch_size 為 64,有 12 個詞,每個詞的 Query 向量是 300 維 query = torch.rand(64, 12, 300) # batch_size 為 64,有 12 個詞,每個詞的 Key 向量是 300 維 key = torch.rand(64, 10, 300) # batch_size 為 64,有 10 個詞,每個詞的 Value 向量是 300 維 value = torch.rand(64, 10, 300) attention = MultiheadAttention(hid_dim=300, n_heads=6, dropout=0.1) output = attention(query, key, value) ## output: torch.Size([64, 12, 300]) print(output.shape)

    7.3 關鍵代碼

    其中用矩陣實現多頭注意力的關鍵代碼如下所示, K、Q、V 矩陣拆分為多組注意力,變成了一個 4 維的矩陣。

    # 這里把 K Q V 矩陣拆分為多組注意力,變成了一個 4 維的矩陣# 最后一維就是是用 self.hid_dim // self.n_heads 來得到的,表示每組注意力的向量長度, 每個 head 的向量長度是:300/6=50# 64 表示 batch size,6 表示有 6組注意力,10 表示有 10 個詞,50 表示每組注意力的詞的向量長度# K: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉置得到 -> [64,6,10,50]# V: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉置得到 -> [64,6,10,50]# Q: [64,12,300] 拆分多組注意力 -> [64,12,6,50] 轉置得到 -> [64,6,12,50]# 轉置是為了把注意力的數量 6 放到前面,把 10 和 50 放到后面,方便下面計算Q = Q.view(bsz, -1, self.n_heads, self.hid_dim //self.n_heads).permute(0, 2, 1, 3)K = K.view(bsz, -1, self.n_heads, self.hid_dim //self.n_heads).permute(0, 2, 1, 3)V = V.view(bsz, -1, self.n_heads, self.hid_dim //self.n_heads).permute(0, 2, 1, 3) 經過 attention 計算得到 x 的形狀是 `[64,12,6,50]`,64 表示 batch size,6 表示有 6組注意力,10 表示有 10 個詞,50 表示每組注意力的詞的向量長度。把這個矩陣轉換為 `[64,12,300]`的矩陣,就是相當于把多組注意力的結果拼接起來。 e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e ee這里的矩陣轉換就是:把多組注意力的結果拼接起來,最終結果就是 [64,12,300],x: [64,12,6,50] -> [64,12,300] x = x.view(bsz, -1, self.n_heads * (self.hid_dim // self.n_heads))

    八、使用位置編碼來表示序列的順序

    到目前為止,我們闡述的模型中缺失了一個東西,那就是表示序列中單詞順序的方法。

    為了解決這個問題,Transformer 模型對每個輸入的向量都添加了一個向量。這些向量遵循模型學習到的特定模式,有助于確定每個單詞的位置,或者句子中不同單詞之間的距離。這種做法背后的直覺是:將這些表示位置的向量添加到詞向量中,得到了新的向量,這些新向量映射到 Q/K/V,然后計算點積得到 attention 時,可以提供有意義的信息。

    為了讓模型了解單詞的順序,我們添加了帶有位置編碼的向量--這些向量的值遵循特定的模式。

    如果我們假設詞向量的維度是 4,那么帶有位置編碼的向量可能如下所示:

    上圖為帶有位置編碼的向量長度為 4 的例子。

    那么帶有位置編碼的向量到底遵循什么模式?

    在下圖中,每一行表示一個帶有位置編碼的向量。所以,第一行對應于序列中第一個單詞的位置編碼向量。每一行都包含 512 個值,每個值的范圍在 -1 和 1 之間。我對這些向量進行了涂色可視化,你可以從中看到向量遵循的模式。

    這是一個真實的例子,包含了 20 個詞,每個詞向量的維度是 512。你可以看到,它看起來像從中間一分為二。這是因為左半部分的值是由 sine 函數產生的,而右半部分的值是由 cosine 函數產生的,然后將他們拼接起來,得到每個位置編碼向量。

    你可以在get_timing_signal_1d()上查看生成位置編碼的代碼。這種方法來自于Tranformer2Transformer 的實現。

    而論文中的方法和上面圖中的稍有不同,它不是直接拼接兩個向量,而是將兩個向量交織在一起。如下圖所示。

    此為生成位置編碼的公式,在 Transformer 論文的 3.5 節中有詳細說明。

    這不是唯一一種生成位置編碼的方法。但這種方法的優點是:可以擴展到未知的序列長度。例如:當我們的模型需要翻譯一個句子,而這個句子的長度大于訓練集中所有句子的長度,這時,這種位置編碼的方法也可以生成一樣長的位置編碼向量。

    九、殘差連接

    在我們繼續講解之前,編碼器結構中有一個需要注意的細節是:編碼器的每個子層(Self Attention 層和 FFNN)都有一個殘差連接和層標準化(layer-normalization)。

    將 Self-Attention 層的層標準化(layer-normalization)和向量都進行可視化,如下所示:

    在解碼器的子層里面也有層標準化(layer-normalization)。假設一個 Transformer 是由 2 層編碼器和兩層解碼器組成的,如下圖所示。

    十、Decoder(解碼器)

    現在我們已經介紹了解碼器中的大部分概念,我們也基本知道了解碼器的原理。現在讓我們來看下, 編碼器和解碼器是如何協同工作的。

    上面說了,編碼器一般有多層,第一個編碼器的輸入是一個序列,最后一個編碼器輸出是一組注意力向量 K 和 V。這些注意力向量將會輸入到每個解碼器的Encoder-Decoder Attention層,這有助于解碼器把注意力集中中輸入序列的合適位置。

    在完成了編碼(encoding)階段之后,我們開始解碼(decoding)階段。解碼(decoding )階段的每一個時間步都輸出一個翻譯后的單詞(這里的例子是英語翻譯)。

    接下來會重復這個過程,直到輸出一個結束符,Transformer 就完成了所有的輸出。每一步的輸出都會在下一個時間步輸入到下面的第一個解碼器。Decoder 就像 Encoder 那樣,從下往上一層一層地輸出結果。正對如編碼器的輸入所做的處理,我們把解碼器的輸入向量,也加上位置編碼向量,來指示每個詞的位置。

    解碼器中的 Self Attention 層,和編碼器中的 Self Attention 層不太一樣:在解碼器里,Self Attention 層只允許關注到輸出序列中早于當前位置之前的單詞。具體做法是:在 Self Attention 分數經過 Softmax 層之前,屏蔽當前位置之后的那些位置。

    Encoder-Decoder Attention層的原理和多頭注意力(multiheaded Self Attention)機制類似,不同之處是:Encoder-Decoder Attention層是使用前一層的輸出來構造 Query 矩陣,而 Key 矩陣和 Value 矩陣來自于解碼器最終的輸出。

    十一、 最后的線性層和 Softmax 層

    Decoder 最終的輸出是一個向量,其中每個元素是浮點數。我們怎么把這個向量轉換為單詞呢?這是由 Softmax 層后面的線性層來完成的。

    線性層就是一個普通的全連接神經網絡,可以把解碼器輸出的向量,映射到一個更長的向量,這個向量稱為 logits 向量。

    現在假設我們的模型有 10000 個英語單詞(模型的輸出詞匯表),這些單詞是從訓練集中學到的。因此 logits 向量有 10000 個數字,每個數表示一個單詞的分數。我們就是這樣去理解線性層的輸出。

    然后,Softmax 層會把這些分數轉換為概率(把所有的分數轉換為正數,并且加起來等于 1)。然后選擇最高概率的那個數字對應的詞,就是這個時間步的輸出單詞。

    在上圖中,最下面的向量,就是編碼器的輸出,這個向量輸入到線性層和 Softmax 層,最終得到輸出的詞。

    十二、 Transformer 的訓練過程

    現在我們已經了解了 Transformer 的前向傳播過程,下面講講 Transformer 的訓練過程,這也是非常有用的知識。

    在訓練過程中,模型會經過上面講的所有前向傳播的步驟。但是,當我們在一個標注好的數據集上訓練這個模型的時候,我們可以對比模型的輸出和真實的標簽。

    為了可視化這個對比,讓我們假設輸出詞匯表只包含 6 個單詞(“a”, “am”, “i”, “thanks”, “student”, and “<eos>”(“<eos>”表示句子末尾))。

    我們模型的輸出詞匯表,是在訓練之前的數據預處理階段構造的。當我們確定了輸出詞匯表,我們可以用向量來表示詞匯表中的每個單詞。這個表示方法也稱為 ?one-hot encoding。例如,我們可以把單詞 “am” 用下面的向量來表示:

    介紹了訓練過程,我們接著討論模型的損失函數,這我們在訓練時需要優化的目標,通過優化這個目標來得到一個訓練好的、非常精確的模型。

    十三、 損失函數

    用一個簡單的例子來說明訓練過程,比如:把“merci”翻譯為“thanks”。

    這意味著我們希望模型最終輸出的概率分布,會指向單詞 ”thanks“(在“thanks”這個詞的概率最高)。但模型還沒訓練好,它輸出的概率分布可能和我們希望的概率分布相差甚遠。

    由于模型的參數都是隨機初始化的。模型在每個詞輸出的概率都是隨機的。我們可以把這個概率和正確的輸出概率做對比,然后使用反向傳播來調整模型的權重,使得輸出的概率分布更加接近震數輸出。

    那我們要怎么比較兩個概率分布呢?我們可以簡單地用一個概率分布減去另一個概率分布。關于更多細節,你可以查看交叉熵(cross-entropy)]和KL 散度(Kullback–Leibler divergence)的相關概念。

    但上面的例子是經過簡化的,因為我們的句子只有一個單詞。在實際中,我們使用的句子不只有一個單詞。例如--輸入是:“je suis étudiant” ,輸出是:“i am a student”。這意味著,我們的模型需要輸出多個概率分布,滿足如下條件:

    • 每個概率分布都是一個向量,長度是 vocab_size(我們的例子中,向量長度是 6,但實際中更可能是 30000 或者 50000)

    • 第一個概率分布中,最高概率對應的單詞是 “i”

    • 第二個概率分布中,最高概率對應的單詞是 “am”

    • 以此類推,直到第 5 個概率分布中,最高概率對應的單詞是 “<eos>”,表示沒有下一個單詞了

    我們用例子中的句子訓練模型,希望產生圖中所示的概率分布

    我們的模型在一個足夠大的數據集上,經過足夠長時間的訓練后,希望輸出的概率分布如下圖所示:

    希望經過訓練,模型會輸出我們希望的正確翻譯。當然,如果你要翻譯的句子是訓練集中的一部分,那輸出的結果并不能說明什么。我們希望的是模型在沒見過的句子上也能夠準確翻譯。需要注意的是:概率分布向量中,每個位置都會有一點概率,即使這個位置不是輸出對應的單詞--這是 Softmax 中一個很有用的特性,有助于幫助訓練過程。

    現在,由于模型每個時間步只產生一個輸出,我們可以認為:模型是從概率分布中選擇概率最大的詞,并且丟棄其他詞。這種方法叫做貪婪解碼(greedy decoding)。另一種方法是每個時間步保留兩個最高概率的輸出詞,然后在下一個時間步,重復執行這個過程:假設第一個位置概率最高的兩個輸出的詞是”I“和”a“,這兩個詞都保留,然后根據第一個詞計算第二個位置的詞的概率分布,再取出 2 個概率最高的詞,對于第二個位置和第三個位置,我們也重復這個過程。這種方法稱為集束搜索(beam search),在我們的例子中,beam_size 的值是 2(含義是:在所有時間步,我們保留兩個最高概率),top_beams 的值也是 2(表示我們最終會返回兩個翻譯的結果)。beam_size ?和 top_beams 都是你可以在實驗中嘗試的超參數。

    更進一步理解

    我希望上面講的內容,可以幫助你理解 Transformer 中的主要概念。如果你想更深一步地理解,我建議你可以參考下面這些:

    • 閱讀 Transformer 的論文:
      《Attention Is All You Need》
      鏈接地址:https://arxiv.org/abs/1706.03762

    • 閱讀Transformer 的博客文章:
      《Transformer: A Novel Neural Network Architecture for Language Understanding》
      鏈接地址:https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html

    • 閱讀《Tensor2Tensor announcement》
      鏈接地址:https://ai.googleblog.com/2017/06/accelerating-deep-learning-research.html

    • 觀看視頻 【?ukasz Kaiser’s talk】來理解模型和其中的細節
      鏈接地址:https://www.youtube.com/watch?v=rBCqOTEfxvg

    • 運行這份代碼:【Jupyter Notebook provided as part of the Tensor2Tensor repo】
      鏈接地址:https://colab.research.google.com/github/tensorflow/tensor2tensor/blob/master/tensor2tensor/notebooks/hello_t2t.ipynb。

    • 查看這個項目:【Tensor2Tensor repo】
      鏈接地址:https://github.com/tensorflow/tensor2tensor

    往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統計學習方法》的代碼復現專輯 AI基礎下載機器學習的數學基礎專輯

    獲取一折本站知識星球優惠券,復制鏈接直接打開:

    https://t.zsxq.com/y7uvZF6

    本站qq群704220115。

    加入微信群請掃碼:

    總結

    以上是生活随笔為你收集整理的【NLP】图解Transformer(完整版)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。