【學習筆記】Transformer (2)

Attention Is All You Need

摘要

針對序列轉錄模型,提出一個新的簡單網絡結構Transformer,基於純注意力機制構造的encoder-decoder,不用卷積和RNN,並行度更高訓練更快。

導言

  1. 當前(2017)主流的序列轉錄模型:RNN,LSTM,GRU
  2. RNN缺點:從左往右一步步計算,無法並行;如果序列很長,前面的信息容易丟失;但若構造很大的$h_t$則內存開銷大。最近工作改善性能,但問題仍然存在
  3. 介紹 attention在RNN上的應用
  4. 提出自己的架構

相關工作

  1. 用CNN替換RNN:長序列難以建模;只能看到局部像素,距離遠的像素要通過多次卷積操作才能融合,而Transfomer每次可以看到所有像素
  2. 卷積可以有多個輸出通道,不同的通道識別不同模式;所以提出多頭注意力機制

模型架構

用了encoder-decoder結構,自迴歸(前面的輸出也作爲後面的輸入),用了級聯的自注意力和point-wise的MLP(還有殘差連接)。

1. Encoder and Decoder Stacks

Encoder

關鍵公式:$LayerNorm(x+Sublayer(x))$

$N=6$個同樣的層級聯。每層有兩個子層:多頭注意力 & MLP。輸入與輸出殘差連接(保證大小一樣$d_{model}=512$),再經過一個LayerNorm(batch norm是對每列特徵做norm,layer norm是對每個樣本單獨做norm)

Decoder

同樣$N=6$,有三個子層,多了一個帶掩碼的多頭注意力層(掩碼作用是隻能看到前面的輸入,不能看到後面的)

2. Attention & Details

attention輸入是key-value對和query,輸出就是value的加權和 ,權重由query到key的相似度函數來算。不同的相似度函數導致不一樣的attention版本。

Scaled Dot-Product Attention

用內積來算query和key的相似度,內積越大相似度越高,內積爲0說明向量正交,無相似度。

用矩陣乘法來並行簡化計算:$Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V$

如果有mask就把當前t時刻和後面的qk內積都換成非常大的負數,這樣進入softmax後會變成0。

💡 爲什麼要乘$\frac{1}{\sqrt{d_k}}$?

$d_k$越大,向量越長,點積出來的值相對差距變大,偏大的值softmax後更靠近1,而其他的值靠近0,會產生梯度消失的問題,梯度小訓練時會跑不動。

$\frac{1}{\sqrt{d_k}}$讓點積的方差控制在1,softmax更平滑。

Multi-Head Attention

通過線性層把Q、K、V投影到低維再做h次attention(做多次是爲了匹配不同模式所需要的相似函數),把每個attention輸出拼接到一起再投影回原來的高維空間,得到最終的輸出。本文工作h=8。

$MultiHead(Q,K,V)=Concat(head_1,...,head_h)W^O$

where $head_i=Attention(QW^{Q}_{i},KW^{K}_{i},VW^{V}_{i})$

同一個東西既作爲K,也作爲V,Q,叫self-attention(自注意力)。

簡潔版的代碼實現:

import torch
import torch.nn as nn

class MultiHeadAttention(nn.Module):
	def __init__(self, d_model, num_heads):
		super(MultiHeadAttention, self).__init__()
		assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
		self.num_heads = num_heads
		self.head_dim = d_model // num_heads
		self.d_model = d_model
	
		self.W_q = nn.Linear(d_model, d_model)
		self.W_k = nn.Linear(d_model, d_model)
		self.W_v = nn.Linear(d_model, d_model)

		self.fc_out = nn.Linear(d_model, d_model)

	def split_heads(self, x, batch_size):
		x = x.view(batch_size, -1, self.num_heads, self.head_dim)
		return x.permute(0, 2, 1, 3)

	def forward(self, query, key, value, mask):
		batch_size = query.shape[0]

		# Linear projections
		Q = self.W_q(query)
		K = self.W_k(key)
		V = self.W_v(value)

		# Split into multiple heads
		Q = self.split_heads(Q, batch_size)
		K = self.split_heads(K, batch_size)
		V = self.split_heads(V, batch_size)

		# Scaled Dot-Product Attention
		QK = torch.matmul(Q, K.permute(0, 1, 3, 2)) / torch.sqrt(torch.tensor(self.head_dim, dtype=torch.float32))
		if mask is not None:
			QK += (mask * -1e9)

		attention_weights = torch.nn.functional.softmax(QK, dim=-1)
		output = torch.matmul(attention_weights, V)

		# Concatenate heads
		output = output.permute(0, 2, 1, 3).contiguous().view(batch_size, -1, self.d_model)

		# Final linear layer
		output = self.fc_out(output)

		return output, attention_weights

Position-wise Feed-Forward Networks

即一個全連接前饋型MLP,一個詞即爲一個point(一個position),MLP對每個詞單獨作用一次,所以叫position-wise。有兩個線性層和中間的一個ReLU激活層。輸入輸出是512維,中間層2048維。

$FFN(x)=max(0,xW_1+b_1)W_2+b_2$

Embeddings and Softmax

embedding層把詞(token)轉爲詞向量,權重要乘$\sqrt{d_{model}}$,因爲學embedding的時候會把每個向量的$L_2$norm學的比較小,d越大,學到的權重值越小。乘完後在和positional encoding相加時scale差不多。

Positional Encoding

attention沒有時序信息,用512長的向量來表示一個數字,用數字來標記位置,和嵌入層相加即把時序信息加入數據。

💡 Transformer和RNN的共同點:都用一個MLP來做語義空間的轉換;

不同點:傳遞序列信息方式不同。RNN把上一個時刻的信息輸出傳入下一個時刻作爲輸入,而Transformer通過一個attention層,拿到全局attention信息。

3. why self-attention:

計算複雜度在n和d規模相近時差不多;attention和CNN的並行度更高;attention信息傳遞最高效。

實驗

(見論文)(不是博主在偷懶)

結論

  • 第一個純注意力的序列轉錄模型
  • 把模型應用到其他領域:圖像、音頻和視頻
  • 使生成不那麼序列化(making generation less sequential)

 

論文就到這裏結束啦!

補充一下:

  • attention主要作用是從整個序列中抓取信息聚合起來,但後面的MLP和殘差連接缺一不可。
  • attention用了更廣泛的歸納偏置,能處理更general的信息。因此雖然它並未做任何空間上的假設,也可以媲美CNN。
  • 代價:因爲attention的假設更一般,所以它對數據抓取信息的能力變差,需要更多數據、更大模型來訓練達到和CNN同樣的效果。

最後,放一個知乎鏈接:Transformer常見問題與回答總結,大家可以對照着知識點考察自己對Transformer的掌握程度~

歡迎評論區留言討論哦!

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