【学习笔记】Transformer (2)
Attention Is All You Need
摘要
针对序列转录模型,提出一个新的简单网络结构Transformer,基于纯注意力机制构造的encoder-decoder,不用卷积和RNN,并行度更高训练更快。
导言
- 当前(2017)主流的序列转录模型:RNN,LSTM,GRU
- RNN缺点:从左往右一步步计算,无法并行;如果序列很长,前面的信息容易丢失;但若构造很大的$h_t$则内存开销大。最近工作改善性能,但问题仍然存在
- 介绍 attention在RNN上的应用
- 提出自己的架构
相关工作
- 用CNN替换RNN:长序列难以建模;只能看到局部像素,距离远的像素要通过多次卷积操作才能融合,而Transfomer每次可以看到所有像素
- 卷积可以有多个输出通道,不同的通道识别不同模式;所以提出多头注意力机制
模型架构
用了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的掌握程度~
欢迎评论区留言讨论哦!