您的位置:首頁 >聚焦 >

熱文:原創 | Attention is all you need 論文解析(附代碼)

2022-12-17 22:31:01    來源:程序員客棧

作者:楊金珊審校:陳之炎本文約4300字,建議閱讀8分鐘“Attention is all you need”一文在注意力機制的使用方面取得了很大的進步,對Transformer模型做出了重大改進。

目前NLP任務中的最著名模型(例如GPT-2或BERT),均由幾十個Transformer或它們的變體組成。

背景


(資料圖片僅供參考)

減少順序算力是擴展神經網絡GPU、ByteNet和ConvS2S的基本目標,它們使用卷積神經網絡作為基本構建塊,并行計算所有輸入和輸出位置的隱含表示。在這些模型中,將來自兩個任意輸入或輸出位置的信號關聯起來,所需的操作數量隨著位置距離的增加而增加,對于ConvS2S來說,二者是線性增長的;對于ByteNet來說,二者是對數增長的。這使得學習遙遠位置之間的依賴關系變得更加困難。在Transformer中,將操作數量減少到一個恒定數值,這是以降低有效分辨率為代價的,因為需要對注意力權重位置做平均,多頭注意力(Multi-Head Attention)抵消了這一影響。

為什么需要transformer

在序列到序列的問題中,例如神經機器翻譯,最初的建議是基于在編碼器-解碼器架構中使用循環神經網絡(RNN)。這一架構在處理長序列時受到了很大的限制,當新元素被合并到序列中時,它們保留來自第一個元素的信息的能力就喪失了。在編碼器中,每一步中的隱含狀態都與輸入句子中的某個單詞相關聯,通常是最鄰近的那個單詞。因此,如果解碼器只訪問解碼器的最后一個隱含狀態,它將丟失序列的第一個元素相關的信息。針對這一局限性,提出了注意力機制的概念。

與通常使用RNN時關注編碼器的最后狀態不同,在解碼器的每一步中我們都關注編碼器的所有狀態,從而能夠訪問有關輸入序列中所有元素的信息。這就是注意力所做的,它從整個序列中提取信息,即過去所有編碼器狀態的加權和,解碼器為輸出的每個元素賦予輸入的某個元素更大的權重或重要性。從每一步中正確的輸入元素中學習,以預測下一個輸出元素。

但是這種方法仍然有一個重要的限制,每個序列必須一次處理一個元素。編碼器和解碼器都必須等到t-1步驟完成后才能處理第t-1步驟。因此,在處理龐大的語料庫時,計算效率非常低。

什么是Transformer

Transformer是一種避免遞歸的模型架構,它完全依賴于注意力機制來繪制輸入和輸出之間的全局依賴關系。Transformer允許顯著的并行化……Transformer是第一個完全依靠自注意力來計算輸入和輸出的表示,而不使用序列對齊的RNN或卷積的傳導模型。

圖1Transformer架構

從圖1可以觀察到,左邊是一個編碼器模型,右邊是一個解碼器模型。兩者都包含一個重復N次的“一個注意力和一個前饋網絡”的核心塊。但為此,首先需要深入探討一個核心概念:自注意力機制。

Self-Attention基本操作

Self-attention是一個序列到序列的操作:一個向量序列進去,一個向量序列出來。我們稱它們為輸入向量, ,…,和相應的輸出向量, ,…,。這些向量的維數都是k。要產生輸出向量,Self-attention操作只需對所有輸入向量取加權平均值,最簡單的選擇是點積。在我們的模型的Self-attention機制中,我們需要引入三個元素:查詢、值和鍵(Queries, Values and Keys)。

class SelfAttention(nn.Module):def __init__(self, embed_size, heads):super(SelfAttention,self).__init__()self.embed_size=embed_sizeself.heads=headsself.head_dim=embed_size//headsassert(self.head_dim*heads==embed_size),"Embed size needs to be div by heads"self.values=nn.Linear(self.head_dim, self.head_dim, bias=False)self.keys=nn.Linear(self.head_dim, self.head_dim, bias=False)self.queries=nn.Linear(self.head_dim, self.head_dim, bias=False)self.fc_out=nn.Linear(heads*self.head_dim, embed_size)def forward(self,values,keys,query,mask):N=query.shape[0]value_len,key_len,query_len=values.shape[1],keys.shape[1],query.shape[1]#split embedding into self.heads piecesvalues=values.reshape(N,value_len,self.heads,self.head_dim)keys=keys.reshape(N,key_len,self.heads,self.head_dim)queries=query.reshape(N,query_len,self.heads,self.head_dim)values=self.values(values)keys=self.keys(keys)queries=self.queries(queries)energy=torch.einsum("nqhd,nkhd->nhqk",[queries,keys])#queries shape: (N,query_len, heads, heads_dim)#keys shape: (N,key_len, heads, heads_dim)#energy shape: (N,heads,query_len,key_len)if mask is not None:energy=energy.masked_fill(mask==0,float("-1e20"))#close it ,0attention=torch.softmax(energy/(self.embed_size**(1/2)),dim=3)#softmaxout=torch.einsum("nhql,nlhd->nqhd",[attention,values]).reshape(N,query_len,self.heads*self.head_dim)#attention shape: (N,heads, query_len,key_len)#values shape: (N,value_len,heads,head_dim)#key_len=value_len=l#after einsum(N,query_len,heads,head_dim) then flatten last two dimout=self.fc_out(out)return out

Queries,Values和Keys

在自注意力機制中,通常輸入向量以三種不同的方式使用:查詢、鍵和值。在每個角色中,它將與其他向量進行比較,以獲得自己的輸出(Query),獲得第j個輸出(Key),并在權重建立后計算每個輸出向量(Value)。為了得到這些,我們需要三個維數為k * k的權重矩陣,并為每個計算三個線性變換:

圖2 查詢、值和鍵(Queries, Values and Keys)三元素

通常稱這三個矩陣為K、Q和V,這三個可學習權值層應用于相同的編碼輸入。因此,由于這三個矩陣都來自相同的輸入,可以應用輸入向量本身的注意力機制,即“Self-attention”。

TheScaledDot-ProductAttention(帶縮放的點積注意力)

輸入由維的“查詢”和“鍵”以及維的“值”值組成。我們用所有“鍵”計算“查詢”的點積,每個“鍵”除以的平方根,應用一個softmax函數來獲得值的權重。

使用Q, K和V矩陣來計算注意力分數。分數衡量的是對輸入序列的其他位置或單詞的關注程度。也就是說,查詢向量與要評分的單詞的鍵向量的點積。對于位置1,我們計算和的點積,然后是、2, 、等等,…

接下來應用“縮放”因子來獲得更穩定的梯度。softmax函數在大的值下無法正常工作,會導致梯度消失和減慢學習速度[1]。在“softmax”之后,我們乘以“值”矩陣,保留想要關注的單詞的值,并最小化或刪除無關單詞的值(它在V矩陣中的值應該非常小)。

這些操作的公式為:

Multi-head Attention(多頭注意力)

在前面的描述中,注意力分數一次集中在整個句子上,即使兩個句子包含相同的單詞,但順序不同,也將產生相同的結果。相反,如果想關注單詞的不同部分,”self-attention”的辨別能力則比較大,通過組合幾個自注意力頭,將單詞向量分成固定數量(h,頭的數量)的塊,然后使用Q, K和V子矩陣將自注意力應用到相應的塊。

圖3 多頭注意力機制

由于下一層(前饋層)只需要一個矩陣,每個單詞的一個向量,所以“在計算每個頭部的點積之后,需要連接輸出矩陣,并將它們乘以一個附加的權重矩陣Wo”[2]。最后輸出的矩陣從所有的注意力頭部獲取信息。

PositionalEncoding(位置編碼)

前文已經簡單地提到,由于網絡和self-attention機制是排列不變的,句子中單詞的順序是該模型中需要解決的問題。如果我們打亂輸入句子中的單詞,會得到相同的解。需要創建單詞在句子中位置的表示,并將其添加到單詞嵌入(embedding)中。

為此,我們在編碼器和解碼器棧底部的輸入嵌入中添加了“位置編碼”。位置編碼與嵌入具有相同的維數,因此兩者可以求和,位置編碼有多種選擇。

應用一個函數將句子中的位置映射為實值向量之后,網絡將學習如何使用這些信息。另一種方法是使用位置嵌入,類似于單詞嵌入,用向量對每個已知位置進行編碼?!八枰柧氀h中所有被接受的位置的句子,但位置編碼允許模型外推到比訓練中遇到的序列長度更長的序列”,[1]。

TransformerBlock(Transformer代碼塊)

class TransformerBlock(nn.Module):def __init__(self, embed_size,heads,dropout,forward_expansion):super(TransformerBlock,self).__init__()self.attention=SelfAttention(embed_size,heads)self.norm1=nn.LayerNorm(embed_size)self.norm2=nn.LayerNorm(embed_size)self.feed_forward=nn.Sequential(nn.Linear(embed_size, forward_expansion*embed_size),nn.ReLU(),nn.Linear(forward_expansion*embed_size,embed_size))self.dropout=nn.Dropout(dropout)def forward(self,values,keys,query,mask):attention=self.attention(values,keys,query,mask)x=self.dropout(self.norm1(attention+query))forward=self.feed_forward(x)out=self.dropout(self.norm2(forward+x))return out

Theencoder(編碼器)

位置編碼:將位置編碼添加到輸入嵌入(將輸入單詞被轉換為嵌入向量)。

N=6個相同的層,包含兩個子層:一個多頭自注意力機制,和一個全連接的前饋網絡(兩個線性轉換與一個ReLU激活)。它按位置應用于輸入,這意味著相同的神經網絡會應用于屬于句子序列的每一個“標記”向量。

每個子層(注意和FC網絡)周圍都有一個殘余連接,將該層的輸出與其輸入相加,然后進行歸一化。

在每個殘余連接之前,應用正則化:“對每個子層的輸出應用dropout,然后將其添加到子層輸入并正則化。

圖4 編碼器結構

class Encoder(nn.Module):def __init__(self,src_vocab_size,embed_size,num_layers,heads,device,forward_expansion,dropout,max_length):super(Encoder,self).__init__()self.embed_size=embed_sizeself.device=deviceself.word_embedding=nn.Embedding(src_vocab_size,embed_size)self.position_embedding=nn.Embedding(max_length,embed_size)self.layers=nn.ModuleList([TransformerBlock(embed_size,heads,dropout=dropout,forward_expansion=forward_expansion) for _ in range(num_layers)])self.dropout=nn.Dropout(dropout)def forward(self,x,mask):N,seq_length=x.shapepositions=torch.arange(0,seq_length).expand(N,seq_length).to(self.device)out=self.dropout(self.word_embedding(x)+self.position_embedding(positions))for layer in self.layers:out=layer(out,out,out,mask)   #key,query,value all the samereturn out

DecoderBlock(解碼器代碼塊)

位置編碼:編碼器的編碼相類似。

N=6個相同的層,包含3個子層。第一,屏蔽多頭注意力或屏蔽因果注意力,以防止位置注意到后續位置。禁用點積注意力模塊的軟最大層,對應的值設置為?∞。第二個組件或“編碼器-解碼器注意力”對解碼器的輸出執行多頭注意力,“鍵”和“值”向量來自編碼器的輸出,但“查詢”來自前面的解碼器層,使得解碼器中的每個位置都能覆蓋輸入序列中的所有位置。,最后是完連接的網絡。

每個子層周圍的殘差連接和層歸一化,類似于編碼器。

然后重復在編碼器中執行的相同殘差dropout。

class DecoderBlock(nn.Module):def __init__(self, embed_size,heads,dropout,forward_expansion,device):super(DecoderBlock,self).__init__()self.attention=SelfAttention(embed_size,heads)self.norm=nn.LayerNorm(embed_size)self.transformer_block=TransformerBlock(embed_size, heads, dropout, forward_expansion)self.dropout=nn.Dropout(dropout)def forward(self,x,value,key,src_mask,trg_mask):#source mask and target maskattention=self.attention(x,x,x,trg_mask)#trg_mask is the mask mult-headed attention the first one in decoder blockquery=self.dropout(self.norm(attention+x))out=self.transformer_block(value,key,query,src_mask)return out

在N個堆疊的解碼器的最后,線性層,一個全連接的網絡,將堆疊的輸出轉換為一個更大的向量,logits。

圖5 解碼器結構

Joining all the pieces: the Transformer(全部拼接起來構成Transformer)

定義并創建了編碼器、解碼器和linear-softmax最后一層等部件之后,便可以將這些部件連接起來,形成Transformer模型。

值得一提的是,創建了3個掩碼,包括:

編碼器掩碼:它是一個填充掩碼,從注意力計算中丟棄填充標記。

解碼器掩碼1:該掩碼是填充掩碼和前向掩碼的結合,它將幫助因果注意力丟棄“未來”的標記,我們取填充掩碼和前向掩碼之間的最大值。

解碼器掩碼2:為填充掩碼,應用于編碼器-解碼器注意力層。

class Transformer(nn.Module):def __init__(self,src_vocab_size,trg_vocab_size,src_pad_idx,trg_pad_idx,embed_size=256,num_layers=6,forward_expansion=4,heads=8,dropout=0,device="cuda",max_length=100):super(Transformer,self).__init__()self.encoder=Encoder(src_vocab_size,embed_size,num_layers,heads,device,forward_expansion,dropout,max_length)self.decoder=Decoder(trg_vocab_size,embed_size,num_layers,heads,forward_expansion,dropout,device,max_length)self.src_pad_idx=src_pad_idxself.trg_pad_idx=trg_pad_idxself.device=devicedef make_src_mask(self,src):src_mask=(src!= self.src_pad_idx).unsqueeze(1).unsqueeze(2)#(N,1,1,src_len)return src_mask.to(self.device)def make_trg_mask(self,trg):N,trg_len=trg.shapetrg_mask=torch.tril(torch.ones((trg_len,trg_len))).expand(N,1,trg_len,trg_len)return trg_mask.to(self.device)def forward(self,src,trg):src_mask=self.make_src_mask(src)trg_mask=self.make_trg_mask(trg)enc_src=self.encoder(src,src_mask)out=self.decoder(trg, enc_src,src_mask, trg_mask)return out

參考文獻:[1] Peter Bloem,“Transformers from scratch”blog post, 2019.[2] Jay Alammar,“The Ilustrated Transformer”blog post, 2018.編輯:王菁校對:林亦霖

數據派研究部介紹

數據派研究部成立于2017年初,以興趣為核心劃分多個組別,各組既遵循研究部整體的知識分享和實踐項目規劃,又各具特色:

算法模型組:積極組隊參加kaggle等比賽,原創手把手教系列文章;

調研分析組:通過專訪等方式調研大數據的應用,探索數據產品之美;

系統平臺組:追蹤大數據&人工智能系統平臺技術前沿,對話專家;

自然語言處理組:重于實踐,積極參加比賽及策劃各類文本分析項目;

制造業大數據組:秉工業強國之夢,產學研政結合,挖掘數據價值;

數據可視化組:將信息與藝術融合,探索數據之美,學用可視化講故事;

網絡爬蟲組:爬取網絡信息,配合其他各組開發創意項目。

點擊文末“閱讀原文”,報名數據派研究部志愿者,總有一組適合你~

轉載須知

如需轉載,請在開篇顯著位置注明作者和出處(轉自:數據派THUID:DatapiTHU),并在文章結尾放置數據派醒目二維碼。有原創標識文章,請發送【文章名稱-待授權公眾號名稱及ID】至聯系郵箱,申請白名單授權并按要求編輯。

未經許可的轉載以及改編者,我們將依法追究其法律責任。

點擊“閱讀原文”加入組織~

關鍵詞: 神經網絡 輸入向量 輸出向量

相關閱讀

欧美视频线路在线_欧美中文字幕在线中出观看_中年美女露比自慰交配a一级片免费播放_九九精品国中文字幕在线视频