免费高清特黄a大片,九一h片在线免费看,a免费国产一级特黄aa大,国产精品国产主播在线观看,成人精品一区久久久久,一级特黄aa大片,俄罗斯无遮挡一级毛片

分享

哈工大SCIR lab帶你從頭開始了解Transformer

 黃爸爸好 2019-08-28

閱讀大概需要48分鐘

跟隨小博主,每天進步一丟丟

作者:Peter Bloem

原文:TRANSFORMERS FROM SCRATCH

鏈接:http://www./blog/transformers

代碼:https://github.com/pbloem/former

譯者:哈工大SCIR 徐嘯,顧宇軒

來自:哈工大SCIR

編者按自2017年提出以來,Transformer在眾多自然語言處理問題中取得了非常好的效果。它不但訓(xùn)練速度更快,而且更適合建模長距離依賴關(guān)系,因此大有取代循環(huán)或卷積神經(jīng)網(wǎng)絡(luò),一統(tǒng)自然語言處理的深度模型江湖之勢。我們(賽爾公眾號)曾于去年底翻譯了哈佛大學(xué)Alexander Rush教授撰寫的《Transformer注解及PyTorch實現(xiàn)》一文,并獲得了廣泛關(guān)注。近期,來自荷蘭阿姆斯特丹大學(xué)的Peter Bloem博士發(fā)表博文,從零基礎(chǔ)開始,深入淺出的介紹了Transformer模型,并配以PyTorch的代碼實現(xiàn)。我非常喜歡其中對Self-attention(Transformer的核心組件)工作基本原理進行解釋的例子。此外,該文還介紹了最新的Transformer-XL、Sparse Transformer等模型,以及基于Transformer的BERT和GPT-2等預(yù)訓(xùn)練模型。我們將其翻譯為中文,希望能幫助各位對Transformer感興趣,并想了解其最新進展的讀者。

——哈工大SCIR公眾號主編 車萬翔教授


Transformer 是一種非常令人興奮的機器學(xué)習(xí)架構(gòu)。目前已經(jīng)有許多優(yōu)秀的Transformer教程(例如[1,2]),但在最近幾年中,Transformer 大多變得更簡單,讓我們能更直接地解釋最新的Transformer架構(gòu)是如何工作的。這篇文章試圖在不受一些歷史包袱的影響下直接解釋最新Transformer是如何工作的,以及其為什么有效。

本文假設(shè)讀者對于神經(jīng)網(wǎng)絡(luò)和反向傳播有基本的了解。如果你想要了解,這篇講座(https:///g2lziWxf_9Q)可以提供神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)知識,同時這篇(https:///VZwrbIBNzzA)將解釋如何把這些原理應(yīng)用于現(xiàn)代深度學(xué)習(xí)系統(tǒng)。

編者注:關(guān)于神經(jīng)網(wǎng)絡(luò)和反向傳播的基礎(chǔ)知識,推薦閱讀賽爾譯文《神經(jīng)網(wǎng)絡(luò)與深度學(xué)習(xí)》連載。

讀者需要了解Pytorch的工作原理(https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html)來理解編程示例,但也可以放心地跳過這些示例。

Self-attention

任何Transformer架構(gòu)的基本操作就是self-attention。

我們將在后面解釋“self-attention”這個名稱的來源,現(xiàn)在不需要糾結(jié)于此。

Self-attention是一個序列到序列的操作:一組向量輸入,一組向量輸出。讓我們用表示輸入向量,對應(yīng)的輸出向量。所有的向量都有相同的維度k。

為了產(chǎn)生輸出向量,self-attention操作僅對 所有輸入向量進行加權(quán)平均 

其中 j 是對整個序列的索引,并且其權(quán)重總和為1。權(quán)重不是正常的神經(jīng)網(wǎng)絡(luò)中的參數(shù),而是從推導(dǎo)出的函數(shù)。這個函數(shù)最簡單的選項是點積:

注意,是與當前輸出向量位置相同的輸入向量。對于下一個輸出向量,我們使用一系列全新的點積操作,以及不同的加權(quán)和。

點積給出的值在負無窮和正無窮之間,因此我們應(yīng)用softmax將值映射到 [0,1]并確保它們在整個序列中總和為1

這就是self-attention的基本操作。 

基于self-attention的可視化表示。注意未標示出對權(quán)重的softmax操作。

完整的Transformer需要一些其他的成分,我們將在之后討論,但這是基本的操作。更重要的是,這是整個架構(gòu)中唯一在向量之間傳播信息的操作。Transformer中的其余每項操作均應(yīng)用于輸入序列中的各個向量,而不會在向量之間進行交互。

理解為什么self-attention有效

盡管self-attention很簡單,但它為何有效并非顯而易見。讓我們先看看電影推薦任務(wù)的標準方法來建立一些直覺。

假設(shè)你經(jīng)營一家電影租賃公司并且有一些電影和一些用戶。你希望向你的用戶推薦他們可能喜歡的電影。

解決這個問題的一種方法是為電影建立人工特征,例如電影中有多少浪漫成分,以及多少動作成分,然后為用戶設(shè)計相應(yīng)的特征:他們喜歡浪漫電影的程度以及他們喜歡動作電影的程度。這樣兩個特征向量之間的點積將提供電影屬性與用戶喜好的匹配程度的分數(shù)。 

如果特征的符號與用戶和電影相匹配 — 電影是浪漫的并且用戶喜歡浪漫或者電影是不浪漫的并且用戶討厭浪漫 — 那么該特征得到的點積是一個正值。如果符號不匹配 — 電影是浪漫的并且用戶討厭浪漫,反之亦然 — 相應(yīng)的值是負的。

此外,特征的 數(shù)值大小 表明該特征應(yīng)該對總分有多大貢獻:電影可能有點浪漫但是不夠明顯,或者用戶可能是不喜歡浪漫但又有點矛盾。

當然,收集這些特征是不切實際的。對數(shù)百萬部電影的數(shù)據(jù)庫進行標注是非常昂貴的,并且用喜好程度來標注每一個用戶幾乎是不可能的。

相反,我們讓電影特征和用戶特征成為模型的 參數(shù) 。然后,我們向用戶詢問少量他們喜歡的電影,并優(yōu)化用戶特征和電影特征,使它們的點積與已知喜歡的產(chǎn)品相匹配。

即使我們沒有告訴模型各個特征的意義,但實際上,經(jīng)過訓(xùn)練后,這些特征實際上反映了有關(guān)電影內(nèi)容的有意義的信息。

前兩個從基本矩陣分解模型中學(xué)到的特征。該模型無法訪問有關(guān)電影內(nèi)容的任何信息,只有用戶喜歡的內(nèi)容。注意,電影從淺顯的到深奧的水平排列,從主流到古怪垂直排列。詳見[4]。

有關(guān)推薦系統(tǒng)的更多詳細信息,請參閱本講座(https:///L2mJ4o7F434)。目前,這足以解釋點積如何幫助我們表示對象及其關(guān)系。

這是self-attention工作的基本原理。假設(shè)我們面對的是一系列單詞。為了應(yīng)用self-attention,我們簡單地給詞表中的每個單詞 t 分配一個嵌入向量(我們需要學(xué)習(xí)的值)。這是所謂的序列模型中的嵌入層,它將單詞序列從

得到向量序列

如果我們將該序列輸入self-attention層,輸出則為另外一列向量

,其中第一個序列中所有嵌入向量的加權(quán)和,由它們的點積(歸一化)與加權(quán)。

由于我們正在 學(xué)習(xí) 中的值應(yīng)該是什么,兩個詞有多“相關(guān)”是完全由任務(wù)決定的。在大多數(shù)情況下,定冠詞(the)與句子中其他詞的解釋不太相關(guān);因此,我們可能會最終得到一個包含所有其他單詞的低的或負的點積值的嵌入。另一方面,要解釋這句話中的步行(walks)意義,找出 誰 在走路是非常有幫助的。這可能是由名詞表達的,所以對于像貓(cat)這樣的名詞和像步行(walks)這樣的動詞,我們可能會學(xué)習(xí)嵌入,使它們具有高的正的點積值。

這是self-attention背后的基本直覺。點積表示輸入序列中兩個向量的由學(xué)習(xí)任務(wù)定義的“相關(guān)”程度,并且輸出向量是整個輸入序列的加權(quán)和,其權(quán)重由這些點積確定。

在我們繼續(xù)之前,以下對于序列到序列的操作不常見的特性值得注意:

  • 模型中(目前)沒有參數(shù)。基本的self-attention實際上完全取決于產(chǎn)生輸入序列的任何機制。上游機制,如嵌入層,通過學(xué)習(xí)特定點積的表示來驅(qū)動self-attention(盡管我們稍后會添加一些參數(shù))。

  • Self-attention將其輸入視為一個 集合 ,而不是序列。如果我們置換輸入序列,輸出序列將完全相同,除了置換之外(即self-attention是 置換等變 的)。當我們構(gòu)建完整的Transformer時,我們會稍微減弱這一性質(zhì),但是self-attention本身實際上 忽略 了輸入的順序性質(zhì)。

Pytorch實現(xiàn): 基本的self-attention

正如費曼所說,我不能創(chuàng)造我不理解的東西。隨著我們的進展,我們將構(gòu)建一個簡單的Transformer。因此首先需要在Pytorch中實現(xiàn)這個基本的self-attention操作。

我們應(yīng)該做的第一件事就是弄清楚如何在矩陣乘法中表達self-attention。簡單地循環(huán)所有向量以計算權(quán)重和輸出過于緩慢。

我們將維數(shù)為 kt 個向量的輸入表示為 t * k 的矩陣X。包括一個minibatch維度b,得到一個大小為 (b, t, k) 的輸入張量。

所有原始點積的集合形成一個矩陣,我們可以通過簡單地將矩陣X乘上它自己的轉(zhuǎn)置來得到:

import torch
import torch.nn.functional as F

# assume we have some tensor x with size (b, t, k)
x = ...

raw_weights = torch.bmm(x, x.transpose(12))
# - torch.bmm is a batched matrix multiplication. It 
#   applies matrix multiplication over batches of 
#   matrices.

然后,為了將原始權(quán)重轉(zhuǎn)換為總和為 的正值,我們使用按行操作的softmax:

weights = F.softmax(raw_weights, dim=2)

最后,為了計算輸出序列,我們只需將權(quán)重矩陣乘以X。這產(chǎn)生一批大小為(b, t, e)的輸出矩陣Y,其行是對矩陣X的行的加權(quán)和。

y = torch.bmm(weights, x)

以上就是通過兩個矩陣乘法和一個softmax實現(xiàn)的self-attention。

額外技巧

最新的Transformer中實際使用的self-attention依賴的三個額外技巧。

1) 查詢、鍵和值

每個輸入向量在self-attention中以三種不同的方式使用:

  • 與其余每個向量進行比較以確定其自身輸出的權(quán)重

  • 將其與其余每個向量進行比較,以確定第 j 個向量輸出的權(quán)重

  • 在確定了權(quán)重之后被用作加權(quán)和的一部分來計算每個輸出向量

這些角色通常稱為查詢(Query)、鍵(Key)值(Value)(我們將在后面解釋這些名稱的來源)。在我們到目前為止看到的基本self-attention中,每個輸入向量必須扮演所有三個角色。通過對原始輸入向量應(yīng)用線性變換,我們能更輕松地為每個角色推導(dǎo)出新的向量。換句話說,我們添加三個 k * k 的權(quán)重矩陣并計算每個的三個線性變換,用于self-attention的三個不同部分:

這為self-attention層提供了一些可控參數(shù),并允許它修改傳入的向量以適應(yīng)它們必須扮演的三個角色。 

查詢、鍵和值與self-attention的流程圖

2) 縮放點積

Softmax函數(shù)對非常大的輸入值會很敏感。這會導(dǎo)致梯度消失,并減慢學(xué)習(xí)速度,甚至使其完全停止。由于點積的平均值隨著嵌入向量維度 k 的增長而增長,所以將點積的值減小一點有助于防止softmax函數(shù)的輸入變得過大:

為什么是?假設(shè)有一個值全為 的 維向量,它的歐幾里德長度是。因此,我們除以隨著維度增加而使向量均值增長的倍率。

3) Multi-head attention

最后,我們必須考慮到一個詞對不同的鄰居有不同的意思??紤]以下示例。

我們看到單詞gave和句子中的不同部分有不同的關(guān)系。mary表示誰正在做給予這件事,而roses表示正在被給予的東西,susan則表示接受者是誰。

在一個簡單的self-attention操作中,所有這些信息只被求和到一起。如果susan給了Mary玫瑰花,輸出的向量會是相同的,即使含義已經(jīng)改變了。

我們可以通過結(jié)合幾種有不同矩陣的self-attention機制(用 r 來作為索引)來使其具有更強的辨別能力。這些不同的self-attention機制被稱為attention heads。

對于輸入,每個attention head產(chǎn)生不同的輸出向量。我們將它們連接起來,并通過線性變換將它們的維度變回 k。

Pytorch實現(xiàn): 完整的self-attention

現(xiàn)在讓我們實現(xiàn)一個詳盡的self-attention模塊。我們將它打包成Pytorch模塊,以便以后重用。

import torch
from torch import nn
import torch.nn.functional as F

class SelfAttention(nn.Module):
  def __init__(self, k, heads=8):
    super().__init__()
    self.k, self.heads = k, heads

將三個attention heads組合成一個矩陣(和queries相乘)

我們將h個attention heads視為三個矩陣的h個獨立的集合,但實際上將所有heads組合成三個 k * hk 矩陣更有效,這樣我們就可以通過一個乘法計算所有查詢、鍵和值。

    # These compute the queries, keys and values for all 
    # heads (as a single concatenated vector)
    self.tokeys    = nn.Linear(k, k * heads, bias=False)
    self.toqueries = nn.Linear(k, k * heads, bias=False)
    self.tovalues  = nn.Linear(k, k * heads, bias=False)

    # This unifies the outputs of the different heads into 
    # a single k-vector
    self.unifyheads = nn.Linear(heads * k, k)

我們現(xiàn)在可以實現(xiàn)self-attention的計算(模塊的前向功能)。首先,我們計算查詢、鍵和值:

  def forward(self, x):
    b, t, k = x.size()
    h = self.heads

    queries = self.toqueries(x).view(b, t, h, k)
    keys    = self.tokeys(x)   .view(b, t, h, k)
    values  = self.tovalues(x) .view(b, t, h, k)

每個線性模塊的輸出具有大小 (b,t,h * k),我們簡單地重塑為 (b,t,h,k) 給每個head自己的維度。

接下來,我們需要計算點積。這與每個head的操作相同,因此我們將head折疊到batch的維度中。這確保我們可以像以前一樣使用torch.bmm(),并且鍵、查詢和值的整個集合將被視為稍微大一些的batch。

由于head和batch的維度不是彼此相鄰,我們需要在重塑之前進行轉(zhuǎn)置。(這很昂貴,但似乎是不可避免的。)

    # - fold heads into the batch dimension
    keys = keys.transpose(12).contiguous().view(b * h, t, k)
    queries = queries.transpose(12).contiguous().view(b * h, t, k)
    values = values.transpose(12).contiguous().view(b * h, t, k)

之前點積可以在單個矩陣乘法中計算,但現(xiàn)在在查詢和鍵之間進行。

在此之前,我們需要將點積的縮放移動,而現(xiàn)在需要將鍵和查詢在相乘之前各自按進行縮放。這樣可以為較長的序列節(jié)省內(nèi)存。

    queries = queries / (k ** (1/4))
    keys    = keys / (k ** (1/4))

    # - get dot product of queries and keys, and scale
    dot = torch.bmm(queries, keys.transpose(12))
    # - dot has size (b*h, t, t) containing raw weights

    dot = F.softmax(dot, dim=2
    # - dot now contains row-wise normalized weights

我們將self-attention應(yīng)用于值,從而得到每個attention head的輸出。

    # apply the self attention to the values
    out = torch.bmm(dot, values).view(b, h, t, k)

為了統(tǒng)一attention head,我們再次進行轉(zhuǎn)置,使head的維度和嵌入的維度彼此相鄰,并重新形成維度為 kh 的拼接向量。之后,我們通過unifyheads層將它們投影回 維。

    # swap h, t back, unify heads
    out = out.transpose(12).contiguous().view(b, t, h * k)
    return self.unifyheads(out)

現(xiàn)在我們理解了multi-head和scaled dot-product self-attention。你可以在這里(https://github.com/pbloem/former/blob/b438731ceeaf6c468f8b961bb07c2adde3b54a9f/former/modules.py#L10)查看完整的實現(xiàn)。

構(gòu)建transformers

Transformer不只是一個self-attention層,它是一個架構(gòu)。目前Transformer的定義還不清楚,但在這里我們將使用以下定義:

任何用于處理連接單元集 (connected units) 的體系結(jié)構(gòu)——例如序列中的標記或圖像中的像素——單元之間的唯一交互是通過self-attention。

與其他機制 (例如卷積) 一樣,已經(jīng)出現(xiàn)了或多或少的標準方法,用于如何將self-attention層構(gòu)建到更大的網(wǎng)絡(luò)中。第一步是將self-attention包裝成一個我們可以重復(fù)使用的塊。

Transformer塊 (block)

關(guān)于如何構(gòu)建基本的Transformer存在一些變化,但大多數(shù)結(jié)構(gòu)大致如下:

也就是說,這一模塊依次應(yīng)用:self-attention層,層歸一化(layer normalization)層,前饋層(一個獨立地應(yīng)用于每個向量的 MLP 層),以及另一個歸一化層。在歸一化之前,在兩者之間添加殘差連接(Residual connections)。各種組件的順序不是一成不變的;重要的是將self-attention與本地前饋相結(jié)合,并添加歸一化和殘差連接。

歸一化和殘差連接是用于幫助深度神經(jīng)網(wǎng)絡(luò)訓(xùn)練更快,更準確的標準技巧。歸一化層僅作用于嵌入維度。

下面是Transformer在pytorch中的樣子

class TransformerBlock(nn.Module):
  def __init__(self, k, heads):
    super().__init__()

    self.attention = SelfAttention(k, heads=heads)

    self.norm1 = nn.LayerNorm(k)
    self.norm2 = nn.LayerNorm(k)

    self.ff = nn.Sequential(
      nn.Linear(k, 4 * k),
      nn.ReLU(),
      nn.Linear(4 * k, k))

  def forward(self, x):
    attended = self.attention(x)
    x = self.norm1(attended + x)

    fedforward = self.ff(x)
    return self.norm2(fedforward + x)

我們做了相對隨意的選擇,使前饋的隱藏層的隱藏層節(jié)點數(shù)量成為輸入和輸出節(jié)點數(shù)量的 倍。較小的值也可以起作用,并節(jié)省內(nèi)存,但它應(yīng)該大于輸入/輸出層。

分類Transformer

我們可以構(gòu)建的最簡單的Transformer是序列分類器。我們將使用 IMDb 情緒分類數(shù)據(jù)集:實例是電影評論,被標記為單詞序列,并且分類標簽是正面 (positive) 和負面 (negative) 的 (指示評論是關(guān)于電影的正面還是負面) 。

該架構(gòu)的核心只是一大堆的Transformer塊。我們需要做的就是弄清楚如何為輸入序列提供輸入,以及如何將最終輸出序列轉(zhuǎn)換為一個單一分類。

我們不會在這篇博客文章中講解處理數(shù)據(jù)的事情。按照代碼中的鏈接查看數(shù)據(jù)的加載和準備方式。整個實驗可以在這里找到:https://github.com/pbloem/former/blob/master/experiments/classify.py

輸出: 生成分類

從序列到序列層構(gòu)建序列分類器的最常用方法,是將全局平均池化應(yīng)用于最終的輸出序列,并將結(jié)果映射為 softmax 處理后的類別向量。

一個簡單的序列分類 Transformer 的概述。對輸出序列求平均以產(chǎn)生表示整個序列的單一向量。該向量被投影到一個向量,向量中的每一個元素對應(yīng)實際的每一個類別,并且使用 softmax 以生成概率。

輸入: 使用位置

我們已經(jīng)討論了嵌入層的原理。我們使用它來表示單詞。

但是,正如我們已經(jīng)提到的那樣,我們堆疊置換等變層,最終的全局平均池化是置換不變的,因此整個網(wǎng)絡(luò)也是置換不變的。更簡單地說:如果我們打亂句子中的單詞,無論我們學(xué)習(xí)到什么權(quán)重,我們都會得到完全相同的分類。顯然,我們希望我們最先進的語言模型至少對單詞順序有一些敏感性,因此需要修復(fù)這一問題。

解決方案很簡單:我們創(chuàng)建一個等長的第二個向量,它表示單詞在當前句子中的位置,并將其添加到單詞嵌入中。這里有兩種選擇。

位置嵌入 我們只是簡單地像創(chuàng)建詞嵌入一樣,創(chuàng)建了位置的嵌入。就像我們創(chuàng)建嵌入向量一樣,我們創(chuàng)建嵌入向量。無論我們期望得到的序列有多長。缺點是我們必須在訓(xùn)練期間知道每個序列的長度,否則相關(guān)的位置嵌入不會被訓(xùn)練。好處是它工作得很好,而且很容易實現(xiàn)。

位置編碼 位置編碼的工作方式與嵌入相同,只是我們不學(xué)習(xí)位置向量,我們只選擇一些函數(shù)來將位置映射到實值向量,并且讓網(wǎng)絡(luò)弄清楚如何解釋這些編碼。好處是,對于精心挑選的函數(shù),網(wǎng)絡(luò)應(yīng)該能夠處理比訓(xùn)練期間看到的更長的序列(神經(jīng)網(wǎng)絡(luò)可能在這些編碼上的表現(xiàn)不夠好,但至少我們可以檢查)。缺點是編碼函數(shù)的選擇是一個復(fù)雜的超參數(shù),它使實現(xiàn)變得復(fù)雜一點。

為簡單起見,我們將在實現(xiàn)中使用位置嵌入。

Pytorch

這是pytorch中的完整文本分類Transformer。

class Transformer(nn.Module):
    def __init__(self, k, heads, depth, seq_length, num_tokens, num_classes):
        super().__init__()

        self.num_tokens = num_tokens
        self.token_emb = nn.Embedding(k, num_tokens)
        self.pos_emb = nn.Embedding(k, seq_length)

        # The sequence of transformer blocks that does all the 
        # heavy lifting
        tblocks = []
        for i in range(depth):
            tblocks.append(TransformerBlock(k=k, heads=heads))
        self.tblocks = nn.Sequential(*tblocks)

        # Maps the final output sequence to class logits
        self.toprobs = nn.Linear(k, num_classes)

    def forward(self, x):
        '''
        :param x: A (b, t) tensor of integer values representing 
                  words (in some predetermined vocabulary).
        :return: A (b, c) tensor of log-probabilities over the 
                 classes (where c is the nr. of classes).
        '''

        # generate token embeddings
        tokens = self.token_emb(x)
        b, t, e = tokens.size()

        # generate position embeddings
        positions = torch.arange(t)
        positions = self.pos_emb(positions)[None, :, :].expand(b, t, e)

        x = tokens + positions
        x = self.tblocks(x)

        # Average-pool over the t dimension and project to class 
        # probabilities
        x = self.toprobs(x.mean(dim=1))
        return F.log_softmax(x, dim=1)

在深度為6,最大序列長度為 512 時,該Transformer的精度達到約85%,與RNN模型的結(jié)果相比更好,并且訓(xùn)練速度更快。為了看到Transformer真正的近人類的表現(xiàn),我們需要在更多數(shù)據(jù)上訓(xùn)練更深入的模式。更多關(guān)于如何在以后做到這一點。

文本生成Transformer

我們下一個將嘗試的技巧是自回歸 (autoregressive) 模型。我們將訓(xùn)練一個字符級 Transformer 來預(yù)測序列中的下一個字符。訓(xùn)練制度很簡單 (并且比 Transformer 的周期長得多(http://karpathy./2015/05/21/rnn-effectiveness/))。我們向序列到序列模型輸入一個序列,并且我們要求它預(yù)測序列中每個時間點的下一個字符。換句話說,目標輸出是向左移動一個字符的相同序列:

對于 RNN ,這是我們需要做的全部,因為它們無法在輸入序列中向前看:輸出 i 僅取決于輸入 。對Transformer,輸出取決于整個輸入序列,因此預(yù)測下一個字符變得很容易,只需從輸入中檢索它。

要使用self-attention作為自回歸模型,我們需要確保它不能在序列中向前看。我們通過在應(yīng)用softmax之前,將掩碼應(yīng)用于點積矩陣來實現(xiàn)此目的。該掩碼禁用矩陣對角線上方的所有元素。

使用mask的self-attention,確保元素只能處理序列中前面的輸入元素。請注意,乘法符號有點誤導(dǎo):我們實際上將屏蔽掉的元素(白色方塊)設(shè)置為負無窮。

由于我們希望在softmax之后這些元素為零,我們將它們設(shè)置為負無窮。下面是 pytorch 中的實現(xiàn):

dot = torch.bmm(queries, keys.transpose(12))

indices = torch.triu_indices(k, k, offset=0)
dot[:, indices[0], indices[1]] = float('-inf')

dot = F.softmax(dot, dim=2

在我們像這樣阻礙self-attention模塊之后,模型不再能夠在序列中向前看。

我們在標準 enwik8 數(shù)據(jù)集上訓(xùn)練 (取自 Hutter 獎(http://prize./)) ,其中包含個來自維基百科文本的字符 (包括標記) 。在訓(xùn)練期間,我們通過從數(shù)據(jù)中隨機抽取子序列來生成多個批次。

我們使用 12 個Transformer塊和嵌入維度為 256 的模型訓(xùn)練長度為 256 的序列。在RTX 2080Ti(大約 170K 個大小為 32 的批次)的大約 24 小時訓(xùn)練之后,我們讓模型從一個 256 個字符的種子開始生成:對于每個字符,我們?yōu)樗峁┣懊娴?nbsp;256 個字符,并查看它為下一個字符(最后一個輸出向量)預(yù)測的內(nèi)容。我們從temperature(https:///how-to-sample-from-language-models-682bceb97277)為 0.5 的樣本中進行采樣,然后移動到下一個字符。

輸出像下面這樣:

1228X Human & Rousseau. Because many of his stories were originally published in long-forgotten magazines and journals, there are a number of [[anthology|anthologies]] by different collators each containing a different selection. His original books have been considered an anthologie in the [[Middle Ages]]and were likely to be one of the most common in the [[Indian Ocean]] in the [[1st century]]. As a result of his death, the Bible was recognised as a counter-attack by the [[Gospel of Matthew]] (1177-1133), and the [[Saxony|Saxons]] of the [[Isle of Matthew]] (1100-1138), the third was a topic of the [[Saxony|Saxon]] throne, and the [[Roman Empire|Roman]] troops of [[Antiochia]] (1145-1148). The [[Roman Empire|Romans]] resigned in [[1148]] and [[1148]] began to collapse. The [[Saxony|Saxons]] of the [[Battle of Valasander]] reported the y

請注意,這里正確使用了Wikipedia鏈接標記語法,鏈接中的文本代表鏈接的合理主題。最重要的是,請注意有一個粗略的主題一致性;生成的文本在不同地方使用不同的相關(guān)術(shù)語保持圣經(jīng)和羅馬帝國的主題。雖然這遠遠不如GPT-2這樣的模型的性能,但是相比于RNN模型的優(yōu)勢已經(jīng)很明顯:更快的訓(xùn)練 (類似的RNN模型需要花費很多天訓(xùn)練) 和更好的長期一致性。

如果你很好奇,the Battle of Valasander 似乎是網(wǎng)絡(luò)的發(fā)明。

此時,該模型在驗證集上實現(xiàn)每字節(jié) 1.343 比特的壓縮,這與GPT-2模型 (下面描述) 實現(xiàn)的每字節(jié) 0.93 比特的現(xiàn)有技術(shù)相差不太遠。

設(shè)計注意事項

為了理解為什么Transformer以這種方式設(shè)置,這有助于理解其中的基本設(shè)計注意事項。Transformer的主要目的是克服之前最先進的RNN架構(gòu) (通常是LSTM或GRU) 的問題。展開后(https://colah./posts/2015-08-Understanding-LSTMs/),RNN看起來像這樣:

這里最大的弱點是循環(huán)連接。雖然這允許信息沿著序列傳播,但這也意味著我們無法在時間步驟 i 計算單元,直到我們在時間步長 i-1 計算單元。將此與 1D 卷積進行對比:

在該模型中,每個輸出向量可以與其他每個輸出向量并行計算。這使得卷積更快。然而,卷積的缺點在于它們在模擬遠程依賴方面受到嚴重限制。在一個卷積層中,只有相距比卷積核大小更小的單詞才能相互交互。為了更長的依賴性,我們需要堆疊許多卷積。

Transformer試圖吸收兩者的優(yōu)點。它們可以對輸入序列的整個范圍建立依賴關(guān)系,就像它們彼此相鄰的單詞一樣容易 (事實上,沒有位置向量,它們甚至無法區(qū)分) 。然而,這里沒有循環(huán)連接,因此可以以非常有效的前饋方式計算整個模型。

Transformer設(shè)計的其余部分主要基于一個考慮因素:深度。大多數(shù)選擇都來自于訓(xùn)練大量Transformer塊的愿望。注意,例如Transformer中只有兩個位置出現(xiàn)非線性:self-attention中的softmax和前饋層中的ReLU。模型的其余部分完全由線性變換組成,完美地保留了梯度。

我認為層歸一化也是非線性的,但這是一個實際上有助于保持梯度在向下傳播回網(wǎng)絡(luò)時,保持穩(wěn)定的非線性。

歷史包袱

如果你已經(jīng)讀過其他Transformer的介紹,你可能已經(jīng)注意到它們包含了我跳過的一些內(nèi)容。我認為這些都不是理解最新的Transformer的必要條件。然而,它們有助于理解一些術(shù)語和一些關(guān)于最新的Transformer的文章。

為什么稱作self-attention?

在首次提出self-attention之前,序列模型主要包括堆疊在一起的循環(huán)網(wǎng)絡(luò)或卷積。實驗表明有些時候通過添加注意力機制能夠提升模型性能:引入了一個中間機制而不是將前一層的輸出序列直接輸送到下一層。

一般機制如下。我們將輸入稱為值 (value) 。一些 (可訓(xùn)練的) 機制為每個值分配一個鍵 (key) 。然后,對于每個輸出,一些其他機制指定一個查詢 (query) 。

這些名稱源自鍵值存儲的數(shù)據(jù)結(jié)構(gòu)。在這種情況下,我們希望我們存儲中只有一個項目具有與查詢匹配的密鑰,該查詢在執(zhí)行查詢時返回。注意力是一個更寬松的版本:對于任一查詢而言,存儲中的每個鍵都在某種程度上與該查詢相關(guān)。我們返回所有鍵與該查詢匹配程度的結(jié)果并且進行加權(quán)求和。

self-attention 的重大突破是,注意力本身就是一個足夠強大的機制來完成所有的學(xué)習(xí)。正如作者所說, Attention is all you need(https:///abs/1706.03762)。鍵、查詢和值都是相同的向量(具有輕微的線性變換)。它們關(guān)注自己,堆疊這種self-attention提供了足夠的非線性和表示能力來學(xué)習(xí)非常復(fù)雜的函數(shù)。

最初的Transformer: 編碼器和解碼器

但作者并沒有免除當代序列建模的所有復(fù)雜性。過去,序列到序列模型的標準結(jié)構(gòu)是編碼器 - 解碼器架構(gòu),使用教師強制(teacher forcing)(https://blog./a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html)

編碼器獲取輸入序列并將其映射到表示整個序列的單個潛在向量。然后將該向量傳遞給解碼器,解碼器將其取出到所需的目標序列 (例如,另一種語言的同一句話) 。

教師強制指的是也允許解碼器訪問輸入句子的技術(shù),但是是以自回歸方式。也就是說,解碼器基于潛在向量和它已經(jīng)生成的單詞生成逐字輸出語句。這消除了潛在表示的一些壓力:解碼器可以使用逐字逐句采樣來處理句法和語法等低級結(jié)構(gòu),并使用潛在向量來捕獲更高級別的語義結(jié)構(gòu)。理想情況下,使用相同的潛在向量解碼兩次將給出兩個具有相同含義的不同句子。

在后來的Transformer中,如BERT和GPT-2,完全省去了編碼器/解碼器配置。發(fā)現(xiàn)一堆簡單的Transformer塊足以在許多基于序列的任務(wù)中實現(xiàn)現(xiàn)有技術(shù)水平。

這有時被稱為decoder-only Transformer (用于自回歸模型) 或encoder-only Transformer (用于沒有掩碼的模型) 。

最新的Transformers

這里有一些最新的Transformer及其最具特色的細節(jié)。

BERT

https:///abs/1810.04805

BERT是最早的模型之一,表明Transformer可以在各種基于語言的任務(wù)上達到人類水平的表現(xiàn):問答,情感分類或分類兩個句子是否自然地接續(xù)。

BERT由一組簡單的Transformer塊組成,我們上面描述的類型。該結(jié)構(gòu)是在大型通用域語料庫上預(yù)先訓(xùn)練的,該語料庫包括來自英語書籍的 800M 單詞 (現(xiàn)代作品,來自未發(fā)表的作者) 和來自英語維基百科文章的 2.5B 單詞 (沒有標記) 。

預(yù)訓(xùn)練是通過兩個任務(wù)完成的:

Masking 對輸入序列中的部分單詞進行以下任一操作:屏蔽掉、用隨機單詞替換或按原樣保存。然后要求該模型根據(jù)這些單詞預(yù)測原始單詞是什么。需要注意的是,模型不需要預(yù)測整個去噪的句子(即masking之前的原始輸入),只需要恢復(fù)已被masking修改的單詞。由于模型不知道將詢問哪些單詞,因此它會學(xué)習(xí)序列中每個單詞的表示。

Next sequence classification 對兩個約 256 個單詞的序列進行采樣,其中 (a) 直接在語料庫中相互接續(xù),或者 (b) 都取自隨機位置。然后,模型必須預(yù)測情況是 a 還是 b 。

BERT使用 WordPiece 標記化,它位于字級和字符級序列之間。它將像 walking 這樣的單詞分解為標記 walk 和 ##ing。這允許模型基于單詞結(jié)構(gòu)做出一些推斷:以 -ing 結(jié)尾的兩個動詞具有相似的語法功能,并且兩個以 walk- 開始的動詞具有相似的語義功能。

輸入前面帶有一個特殊的 <cls> 標記。對應(yīng)于該標記的輸出向量用作序列分類任務(wù)中的句子表示,如下一句子分類 (與我們在上面的分類模型中使用的所有向量的全局平均池化相對) 。

在預(yù)訓(xùn)練之后,在Transformer的主體之后放置一個單一任務(wù)特定層,其將通用表示映射到任務(wù)特定輸出。對于分類任務(wù),這只是將第一個輸出標記映射到類上的softmax概率。對于更復(fù)雜的任務(wù),最終的序列到序列層是專門為該任務(wù)設(shè)計的。

然后重新訓(xùn)練整個模型,以針對手頭的特定任務(wù)微調(diào)模型。

在消融實驗中,作者表明,與之前的模型相比,最大的改進來自BERT的雙向性質(zhì)。也就是說,像GPT這樣的先前模型使用了自回歸掩碼,只允許注意以前的標記。在BERT中,所有注意力都集中在整個序列上是提高性能的主要原因。

這就是為什么 BERT 中的 B 代表“雙向”的原因。

最大的BERT模型使用 24 個Transformer模塊,嵌入尺寸為 1024 和 16 個注意頭,產(chǎn)生 340M 參數(shù)。

GPT-2

https:///blog/better-language-models/

GPT-2是實際上第一個成為主流新聞的Transformer模型,此前OpenAI公司決定不發(fā)布完整模型。

因為 GPT-2 可以產(chǎn)生足夠可信的文本,單憑一己之力便可生成像2016年美國總統(tǒng)大選時所見到的那樣多的假新聞。

GPT-2的作者使用的第一個技巧是創(chuàng)建一個新的高質(zhì)量數(shù)據(jù)集。雖然BERT使用高質(zhì)量的數(shù)據(jù) (精心制作的書籍和精心編輯的維基百科文章) ,但這導(dǎo)致其在寫作風格上有一定程度的欠缺。為了在不犧牲質(zhì)量的情況下收集更多不同的數(shù)據(jù),作者使用社交媒體網(wǎng)站Reddit找到的一大堆具有一定最低社會支持度的文本 (在 Reddit 上稱作 karma ) 。

GPT-2基本上是一種語言生成模型,因此它像我們在上面的模型中所做的那樣使用了masked self-attention。它使用byte-pair編碼來對語言進行標記,這與WordPiece編碼一樣,將單詞分解為比單個字符略大但小于整個單詞的標記。

GPT-2的構(gòu)建與上面的文本生成模型非常相似,只是在各層順序之間有微小的差異,另外也增加了一些深層網(wǎng)絡(luò)訓(xùn)練的技巧。GPT-2最大的型號使用48個Transformer塊,其中序列長度為1024,嵌入尺寸為1600,總共有1.5B參數(shù)。

它們在許多任務(wù)中表現(xiàn)出最先進的性能。例如我們上面嘗試的維基百科壓縮任務(wù)中,它們每字節(jié)達到0.93位。

Transformer-XL

https:///abs/1901.02860

雖然Transformer代表了遠程依賴建模的巨大飛躍,但到目前為止我們看到的模型仍然基本上受到輸入大小的限制。由于點積矩陣的大小在序列長度上呈二次方式增長,因此當我們嘗試擴展輸入序列的長度時,這很快成為瓶頸。Transformer-XL是首批成功解決此問題的Transformer模型之一。

在訓(xùn)練期間,一長串文本 (比模型可以處理的更長) 被分解為更短的片段。每個片段按順序處理,在當前片段和前一片段中的標記上計算self-attention。僅在當前段上計算梯度,但是當段窗口在文本中移動時,信息仍會傳播。理論上,窗口在第n層時只會使用n層之前的信息。

RNN訓(xùn)練中的類似技巧稱為隨時間截斷的反向傳播。我們?yōu)槟P吞峁┝艘粋€很長的序列,但只反向傳播它的一部分。序列中第一個沒有計算梯度的部分仍然影響它們所在部分中隱藏狀態(tài)的值。

為了使這個方法有效,作者不得不放棄標準位置編碼/嵌入方案。由于位置編碼是絕對的,因此每個片段的編碼都會改變,從而導(dǎo)致整個序列的位置嵌入不一致。相反,他們使用相對編碼。對于每個輸出向量,使用不同的位置向量序列,其表示的不是絕對位置,而是到當前輸出的距離。

這需要將位置編碼移動到注意機制中。一個好處是產(chǎn)生的Transformer可能會更好地推廣到看不見長度的序列。

稀疏(Sparse) Transformer

https:///blog/sparse-transformer/

稀疏Transformer正面解決了使用二次方存儲空間的問題。它們不是計算密集的注意力矩陣 (它們以二次方式增長) ,而是僅為特定的輸入標記對計算self-attention,從而產(chǎn)生稀疏的注意力矩陣,只有個顯式元素。

這允許模型處理規(guī)模非常大的上下文,例如對于圖像的生成建模,在像素之間具有大的依賴性。這里需要權(quán)衡的問題是稀疏結(jié)構(gòu)是不可學(xué)習(xí)的,既然選擇了稀疏矩陣,我們將無法使用一些可能有用的輸入token之間的交互信息。然而,兩個不直接相關(guān)的單元仍然可以在Transformer的較高層中相互作用 (類似于卷積神經(jīng)網(wǎng)絡(luò)用更多卷積層構(gòu)建更大的接收場) 。

除了能訓(xùn)練具有處理長序列能力的Transformer的簡單好處之外,稀疏Transformer還允許以非常優(yōu)雅的方式設(shè)計歸納偏差。我們將一系列單位的集合 (例如:單詞,字符,圖像中的像素,圖中的節(jié)點) 作為輸入,并通過注意矩陣的稀疏性指定我們認為相關(guān)的單位。剩下要做的就是將Transformer建造得盡可能深,再看看訓(xùn)練效果如何。

更進一步

訓(xùn)練Transformer的一大瓶頸是self-attention的點積矩陣。對于序列長度t ,這是包含個元素的密集矩陣。在標準的 32 位精度下,當 t = 1000 時,一批 16 個這樣的矩陣占用大約 250Mb 的內(nèi)存。由于我們每次self-attention的操作至少需要四次 (在softmax之前和之后,加上它們的梯度) ,這使得我們在標準12Gb GPU中最多使用 12 個Transformer層。在實踐中,我們能用的更少,因為輸入和輸出也占用了大量內(nèi)存 (盡管點積占主導(dǎo)地位) 。

然而,文獻中報道的模型包含的序列長度超過 12000,具有 48 層Transformer(https:///blog/sparse-transformer/),使用密集點積矩陣。當然,這些模型在集群上進行訓(xùn)練,但仍然需要單個GPU來執(zhí)行單個前向/后向傳播。我們?nèi)绾螌⑦@些巨大的Transformer裝入 12Gb 的內(nèi)存中?有三個主要技巧:

半精度 在現(xiàn)代GPU和TPU上,張量計算可以在 16 位浮點張量上有效地完成。這并不像將張量的 dtype 設(shè)置為 torch.float16 那么簡單。對于網(wǎng)絡(luò)的某些部分,如損失,需要 32 位精度。但是現(xiàn)有的庫(https://github.com/NVIDIA/apex)可以相對輕松地處理大部分內(nèi)容。實際上,這會使你的有效內(nèi)存加倍。

梯度積累 對于大型模型,我們可能只能在單個實例上執(zhí)行前向/后向傳播。批量大小為 時不太能進行穩(wěn)定的學(xué)習(xí)。幸運的是,我們可以在更大的批次中為每個實例執(zhí)行單個前進/后退,并簡單地加和我們得到的梯度 (這是多變量鏈規(guī)則的結(jié)果(https:///VZwrbIBNzzA?t=1562)) 。當我們處理完該批次時,執(zhí)行單步的梯度下降,并將梯度歸零。在Pytorch中,這很容易:你覺得你的訓(xùn)練循環(huán)中的 optimizer.zero_grad() 調(diào)用似乎是多余的嗎?如果你不進行該調(diào)用,新的梯度會被簡單地添加到舊梯度中。

設(shè)置梯度檢查點 如果你的模型太大,即使單個前進/后退內(nèi)存也會溢出,你可以為了內(nèi)存效率權(quán)衡更多的計算。在梯度檢查點中,將模型分成幾個部分。對于每個部分,可以單獨向前/向后計算梯度,而不保留其余部分的中間值。Pytorch有特殊的設(shè)置梯度檢查點的功能(https://pytorch.org/docs/stable/checkpoint.html)。

有關(guān)如何執(zhí)行此操作的詳細信息,請參閱此博客帖子(https:///huggingface/training-larger-batches-practical-tips-on-1-gpu-multi-gpu-distributed-setups-ec88c3e51255)。

結(jié)論

幾十年來,Transformer可能是最簡單的機器學(xué)習(xí)架構(gòu)。如果還沒有關(guān)注它們的話,現(xiàn)在你有充分的理由了。

首先,當前的性能限制純粹是在硬件中。與卷積或LSTMs不同,當前對它們 能力的限制完全取決于我們可以在GPU內(nèi)存中放置的模型有多大以及我們可以在一段可靠的時間內(nèi)推送多少數(shù)據(jù)。我毫不懷疑,我們最終會達到更多層次和更多數(shù)據(jù)不再有用的程度,但目前似乎還有遠未達到。

其次,Transformer非常通用。到目前為止,Transformer在語言建模方面以及圖像和音樂分析方面取得了巨大成功,但其普遍性還有待探索?;镜腡ransformer是一種set-to-set的模型。只要你的數(shù)據(jù)是一組單位,就可以應(yīng)用Transformer。你可以通過添加位置嵌入或通過修改注意力矩陣的結(jié)構(gòu) (使其稀疏或掩蓋部分) 來添加任何有關(guān)數(shù)據(jù)的額外信息(例如局部結(jié)構(gòu))。

這在多模態(tài)學(xué)習(xí)中特別有用。我們可以輕松地將標題圖像組合成一組像素和字符,并設(shè)計一些巧妙的嵌入和稀疏結(jié)構(gòu),以幫助模型找出如何組合和對齊兩者。如果將我們領(lǐng)域的全部知識結(jié)合到相關(guān)結(jié)構(gòu)中,如多模態(tài)知識圖 (如[3]中所討論的) ,就可以使用簡單的Transformer塊在多模態(tài)單元之間傳播信息,并將使它們與稀疏結(jié)構(gòu)對齊,從而控制哪些單元直接相互作用。

到目前為止,Transformer仍然主要被視為語言模型。我希望在不久后,我們會看到它們在其他領(lǐng)域被更多的采用,不僅僅是為了提高性能,而是為了簡化現(xiàn)有模型,并允許從業(yè)者更直觀地控制模型的歸納偏差。

參考文獻

[1] The illustrated transformer. Jay Allamar.

[2] The annotated transformer. Alexander Rush.

[3] The knowledge graph as the default data model for learning on heterogeneous knowledge. Xander Wilcke, Peter Bloem, Victor de Boer.

[4] Matrix factorization techniques for recommender systems. Yehuda Koren et al.

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多