【学习笔记】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的掌握程度~

欢迎评论区留言讨论哦!

posted @ 2023-09-14 20:02  Aikoin  阅读(17)  评论(1编辑  收藏  举报