第二章 Transformer与注意力机制 v1.0
第一部分 背景
一、RNN的困境
在Transformer出现之前,处理序列数据主要靠RNN和它的变体LSTM、GRU
RNN的思路很直观:像读书一样,一个词一个词地读,边读边记。听起来很合理,但有个致命问题:无法并行计算
RNN必须按顺序处理序列,t时刻的计算依赖t-1时刻的输出,这严重限制了训练效率
二、论文贡献
"Attention is All You Need"论文的主要贡献:
- 提出了Transformer架构,完全基于注意力机制,抛弃了循环和卷积
- 引入了多头注意力机制,允许模型同时关注不同位置的信息
- 使用位置编码来表示序列中元素的位置
- 在机器翻译任务上取得了当时的最优结果
第二部分 注意力机制
一、核心思想
注意力机制的核心思想是:让模型学会"看哪里"
举个例子,翻译这句话:
"The animal didn't cross the street because it was too tired"
"it"指的是什么?是"animal"还是"street"?
人类一眼就能看出"it"指的是"animal",因为"tired"通常形容动物而不是街道
注意力机制就是让机器也学会这种"一眼看出"的能力
二、自注意力计算
自注意力(Self-Attention)的计算分三步
第一步:生成Q、K、V
把每个词向量通过三个不同的线性变换,得到Query、Key、Value三个向量
- Q (Query):我在找什么?
- K (Key):我有什么?
- V (Value):我的具体内容是什么?
第二步:计算注意力分数
用Q和K做点积,得到注意力分数。分数越高,说明两个词之间的关系越强
核心公式:
其中
import torch
import torch.nn.functional as F
import math
def attention(Q, K, V):
"""
Q: (batch, seq_len, d_k) - 查询
K: (batch, seq_len, d_k) - 键
V: (batch, seq_len, d_v) - 值
"""
d_k = K.size(-1)
# 1. 计算Q和K的相似度
scores = torch.matmul(Q, K.transpose(-2, -1))
# 2. 缩放(防止点积太大)
scores = scores / math.sqrt(d_k)
# 3. Softmax归一化,得到注意力权重
weights = F.softmax(scores, dim=-1)
# 4. 用权重对V加权求和
output = torch.matmul(weights, V)
return output, weights
# 示例
batch_size, seq_len, d_model = 1, 4, 8
Q = torch.randn(batch_size, seq_len, d_model)
K = torch.randn(batch_size, seq_len, d_model)
V = torch.randn(batch_size, seq_len, d_model)
output, weights = attention(Q, K, V)
print("注意力权重:")
print(weights[0])第三步:加权求和
用注意力权重对V加权求和,得到最终的输出
三、多头注意力
一个注意力头只能关注一种模式。为了同时关注多种模式,Transformer使用了多头注意力
将Q、K、V分成多个头,分别计算注意力,然后拼接起来
import torch
import torch.nn as nn
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
# 线性变换层
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.W_o = nn.Linear(d_model, d_model)
def forward(self, Q, K, V):
batch_size = Q.size(0)
# 线性变换
Q = self.W_q(Q)
K = self.W_k(K)
V = self.W_v(V)
# 拆分成多个头
Q = Q.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = K.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = V.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# 计算注意力
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
weights = torch.softmax(scores, dim=-1)
output = torch.matmul(weights, V)
# 合并多个头
output = output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
# 最终线性变换
return self.W_o(output)第三部分 位置编码
RNN天然有位置信息,因为它是一个一个处理的。但Transformer是并行处理的,不知道词的顺序
解决方案:给每个位置加上一个"位置编码"
Transformer使用的是正弦余弦函数:
为什么用正弦余弦?因为这样可以让模型学到相对位置关系:位置4和位置5的差距,应该和位置8和位置9的差距差不多
第四部分 架构详解
一、编码器
每个编码器层包含:
- 多头自注意力层
- 前馈神经网络层
- 残差连接和层归一化
二、解码器
每个解码器层包含:
- 带掩码的自注意力层(不能看到后面的词)
- 编码器-解码器注意力层
- 前馈神经网络层
三、前馈神经网络
前馈神经网络由两个线性变换和一个ReLU激活函数组成:
第五部分 实验结果
论文在机器翻译任务上进行了实验
一、WMT 2014 英德翻译
- Transformer: 28.4 BLEU
- 之前最优: 26.4 BLEU
- 提升: +2.0 BLEU
二、WMT 2014 英法翻译
- Transformer: 41.8 BLEU
- 之前最优: 41.0 BLEU
- 提升: +0.8 BLEU
三、训练效率
Transformer在8个GPU上训练3.5天,而之前的模型需要几周时间
第六部分 影响
Transformer的影响远远超出了NLP领域
- BERT:预训练语言模型,刷爆了各项NLP任务
- GPT:生成式模型,后来发展成了ChatGPT
- Vision Transformer:把Transformer用到了图像领域
- DALL-E:文本生成图片