大模型原理之Transformer 原理详解
什么是 Transformer?
Transformer 是由 Google 在 2017 年发表的论文《Attention is All You Need》中提出的深度学习模型架构。它彻底改变了自然语言处理(NLP)领域,成为了现代大语言模型(如 GPT、BERT、T5 等)的基础架构。
核心特点
- 完全基于注意力机制:不需要循环(RNN)或卷积(CNN)结构
- 并行计算能力强:可以同时处理所有位置的输入
- 长距离依赖建模优秀:能够直接捕捉序列中任意两个位置之间的关系
- 训练效率高:比 RNN 训练速度快得多
为什么需要 Transformer?
传统模型的局限
在 Transformer 出现之前,主流的序列模型主要使用 RNN(循环神经网络)和 LSTM:
RNN 的问题:
- ❌ 无法并行计算,训练速度慢
- ❌ 难以处理长距离依赖关系
- ❌ 梯度消失/爆炸问题
LSTM 的改进:
- ✅ 一定程度上解决了长距离依赖问题
- ❌ 但仍然是串行处理,无法并行化
- ❌ 计算复杂度仍然较高
Transformer 的优势
Transformer 通过自注意力机制(Self-Attention)解决了这些问题:
- ✅ 并行化:所有位置可以同时计算
- ✅ 长距离依赖:直接计算任意两个位置的关系
- ✅ 效率高:训练和推理速度大幅提升
Transformer 整体架构
Transformer 由两部分组成:编码器(Encoder) 和 解码器(Decoder)。
架构概览
架构特点
- 编码器:理解输入序列的含义
- 解码器:基于编码器的信息生成输出序列
- 堆叠结构:编码器和解码器都可以堆叠多层(通常 6-12 层)
核心组件详解
1. 输入嵌入(Input Embedding)
作用:将输入的词汇(token)转换为数值向量。
处理过程:
# 伪代码示例
vocab_size = 50000 # 词汇表大小
embedding_dim = 512 # 嵌入维度
# 每个词汇被映射为一个 512 维的向量
embedding_matrix = [
[0.1, 0.3, -0.2, ..., 0.5], # "我" 的向量
[0.2, -0.1, 0.4, ..., 0.3], # "爱" 的向量
...
]
示例:
输入文本:"我爱学习AI"
分词结果:["我", "爱", "学习", "AI"]
每个词 → 512维向量
"我" → [0.1, 0.3, -0.2, ..., 0.5]
"爱" → [0.2, -0.1, 0.4, ..., 0.3]
"学习" → [-0.1, 0.5, 0.2, ..., -0.3]
"AI" → [0.4, -0.2, 0.3, ..., 0.1]
2. 位置编码(Positional Encoding)
为什么需要位置编码?
Transformer 没有循环结构,无法自然地理解词汇的顺序。位置编码告诉模型每个词在序列中的位置。
编码方式:
Transformer 使用正弦和余弦函数来编码位置:
# 位置编码公式
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
# 其中:
# pos: 位置(0, 1, 2, ...)
# i: 维度索引
# d_model: 模型维度(通常是 512)
可视化理解:
关键点:
- 位置编码与输入嵌入相加(不是拼接)
- 每个位置都有唯一的编码
- 模型可以学习位置之间的相对距离
3. 多头自注意力机制(Multi-Head Self-Attention)
这是 Transformer 的核心创新!
3.1 什么是注意力?
注意力机制让模型能够"关注"输入序列中不同位置的信息。
形象理解:
想象你在阅读一句话"我喜欢吃苹果",当看到"吃"这个词时,你的注意力会集中在"苹果"上,而不是"我"或"喜欢"。
3.2 注意力机制的计算过程
步骤 1:生成 Q、K、V 矩阵
# 伪代码
输入序列 X: [batch_size, seq_len, d_model] # 例如:[1, 4, 512]
# 通过线性变换生成 Q、K、V
Q = X @ W_q # Query(查询)
K = X @ W_k # Key(键)
V = X @ W_v # Value(值)
# Q, K, V 的形状都是 [batch_size, seq_len, d_model]
步骤 2:计算注意力分数
# 计算注意力分数
scores = Q @ K.T / sqrt(d_k) # 缩放点积注意力
# 其中 d_k 是 K 的维度,sqrt(d_k) 用于缩放,防止分数过大
步骤 3:应用 Softmax
# 对每一行应用 Softmax,使分数变成概率分布
attention_weights = softmax(scores, dim=-1)
# 每行的和 = 1
步骤 4:加权求和
# 用注意力权重对 V 进行加权求和
output = attention_weights @ V
3.3 完整的注意力计算流程

3.4 多头注意力(Multi-Head Attention)
为什么需要多头?
单一注意力只能关注一种关系,多头注意力可以让模型同时关注多种不同的关系。
计算过程:
# 假设有 8 个头(heads)
num_heads = 8
d_model = 512
d_k = d_model // num_heads # 64
# 对每个头分别计算
for head in range(num_heads):
Q_head = Q[:, :, head*d_k:(head+1)*d_k]
K_head = K[:, :, head*d_k:(head+1)*d_k]
V_head = V[:, :, head*d_k:(head+1)*d_k]
# 计算该头的注意力
attention_head = Attention(Q_head, K_head, V_head)
# 拼接所有头的结果
multi_head_output = Concat([attention_head_1, ..., attention_head_8])
# 通过线性层输出
output = multi_head_output @ W_o
可视化理解:
4. 前馈神经网络(Feed Forward Network)
作用:对每个位置的特征进行非线性变换。
结构:
FFN(x) = max(0, x @ W1 + b1) @ W2 + b2
# 通常的结构:
# 第一层:线性变换 + ReLU 激活
# 第二层:线性变换(无激活函数)
参数设置:
- 第一层维度通常是
4 * d_model(例如 512 → 2048) - 第二层维度是
d_model(例如 2048 → 512)
可视化:

5. 残差连接和层归一化
残差连接(Residual Connection):
output = LayerNorm(x + Sublayer(x))
作用:
- 帮助梯度流动,防止梯度消失
- 允许模型学习残差(差值),更容易训练
层归一化(Layer Normalization):
- 对每个样本的特征进行归一化
- 稳定训练过程
可视化:
6. 编码器完整结构
7. 解码器结构
解码器比编码器多了一个**交叉注意力(Cross-Attention)**层。
三个子层:
- 掩码多头自注意力:关注已生成的部分
- 交叉注意力:关注编码器的输出
- 前馈神经网络:特征变换
掩码(Mask)的作用:
- 防止模型在生成时"看到"未来的词汇
- 保证自回归生成的正确性
解码器结构:
完整执行流程
现在让我们详细追踪一个提示词从输入到输出的完整过程。
场景示例
假设我们有一个翻译任务:
- 输入(英文):“I love learning AI”
- 输出(中文):“我喜欢学习AI”
阶段 1:输入处理
详细步骤:
-
文本输入:
"I love learning AI" -
分词处理:将文本分割成 tokens
["I", "love", "learning", "AI"] -
词汇表映射:每个词映射到唯一ID
[101, 234, 567, 890] -
嵌入查找:从嵌入矩阵中查找对应的向量
I: [0.1, 0.3, -0.2, ..., 0.5] (512维) love: [0.2, -0.1, 0.4, ..., 0.3] (512维) learning: [-0.1, 0.5, 0.2, ..., -0.3] (512维) AI: [0.4, -0.2, 0.3, ..., 0.1] (512维) -
位置编码:为每个位置添加位置信息
位置 0: [sin(0/10000^0), cos(0/10000^0), ...] 位置 1: [sin(1/10000^0), cos(1/10000^0), ...] 位置 2: [sin(2/10000^0), cos(2/10000^0), ...] 位置 3: [sin(3/10000^0), cos(3/10000^0), ...] -
相加:嵌入向量 + 位置编码 = 最终输入
最终输入形状: [4, 512] # 4个词,每个512维
阶段 2:编码器处理

编码器第 1 层的详细过程:
步骤 1:多头自注意力
# 输入: [batch=1, seq_len=4, d_model=512]
# 1. 生成 Q, K, V
Q = input @ W_q # [1, 4, 512]
K = input @ W_k # [1, 4, 512]
V = input @ W_v # [1, 4, 512]
# 2. 分割成 8 个头
Q = reshape(Q, [1, 4, 8, 64]) # 8个头,每个头64维
K = reshape(K, [1, 4, 8, 64])
V = reshape(V, [1, 4, 8, 64])
# 3. 对每个头计算注意力
for head in range(8):
scores = Q[:, :, head] @ K[:, :, head].T / sqrt(64)
# scores: [1, 4, 4] - 每个词对其他词的注意力分数
attention_weights = softmax(scores, dim=-1)
# 例如,对于 "I" 这个词:
# attention_weights[0] = [0.7, 0.2, 0.08, 0.02]
# 表示 "I" 对 "I" 自己关注度最高,其次是 "love"
head_output = attention_weights @ V[:, :, head]
# 4. 拼接所有头
multi_head_output = concat([head_0, ..., head_7]) # [1, 4, 512]
# 5. 输出投影
attention_output = multi_head_output @ W_o # [1, 4, 512]
注意力权重示例(实际值会更复杂):
I love learning AI
I [0.70 0.20 0.08 0.02]
love [0.15 0.65 0.15 0.05]
learning [0.10 0.15 0.60 0.15]
AI [0.05 0.10 0.15 0.70]
步骤 2:残差连接和层归一化
# 残差连接
residual_output = input + attention_output # [1, 4, 512]
# 层归一化
normalized_output = LayerNorm(residual_output) # [1, 4, 512]
步骤 3:前馈神经网络
# 第一层:扩展到 2048 维
ffn_intermediate = normalized_output @ W1 + b1 # [1, 4, 2048]
ffn_intermediate = ReLU(ffn_intermediate) # 激活函数
# 第二层:压缩回 512 维
ffn_output = ffn_intermediate @ W2 + b2 # [1, 4, 512]
步骤 4:再次残差连接和层归一化
# 残差连接
residual_output = normalized_output + ffn_output # [1, 4, 512]
# 层归一化
layer_output = LayerNorm(residual_output) # [1, 4, 512]
重复 6 次:上述过程会在编码器中重复 6 次(假设有 6 层编码器)。
编码器输出:
- 形状:
[1, 4, 512] - 每个位置的向量都包含了整个输入序列的信息
- 这些向量会被传递给解码器
阶段 3:解码器处理(生成阶段)
解码器是自回归的,即逐个生成输出词。
3.1 初始状态
3.2 第一个词的生成
步骤 1:掩码多头自注意力
# 输入: [<START>] # 只有开始标记
# 生成 Q, K, V
Q = decoder_input @ W_q
K = decoder_input @ W_k
V = decoder_input @ W_v
# 掩码:防止看到未来的词(虽然现在只有一个词,但这是为了训练时的正确性)
mask = [[True]] # 只允许看到当前位置及之前的位置
# 计算注意力(应用掩码)
scores = Q @ K.T / sqrt(d_k)
scores = masked_fill(scores, mask, -inf) # 掩码位置设为负无穷
attention_weights = softmax(scores)
masked_attention_output = attention_weights @ V
步骤 2:交叉注意力
# Query 来自解码器,Key 和 Value 来自编码器输出
Q = masked_attention_output @ W_q # 来自解码器
K = encoder_output @ W_k # 来自编码器 [1, 4, 512]
V = encoder_output @ W_v # 来自编码器 [1, 4, 512]
# 计算交叉注意力
scores = Q @ K.T / sqrt(d_k)
attention_weights = softmax(scores)
# 此时解码器会"关注"编码器中的哪些词?
# 例如:"我" 可能对 "I" 和 "love" 的注意力较高
cross_attention_output = attention_weights @ V
步骤 3:前馈神经网络
# 与前文相同的前馈网络处理
ffn_output = FeedForward(cross_attention_output)
步骤 4:输出层
# 线性层将 512 维映射到词汇表大小(例如 50000)
logits = decoder_output @ W_output # [1, 1, 50000]
# Softmax 得到概率分布
probabilities = softmax(logits) # [1, 1, 50000]
# 例如输出:
# "我": 0.35
# "喜欢": 0.15
# "AI": 0.05
# ... 其他词的概率
步骤 5:选择第一个词
# 方法1:贪心搜索(选择概率最高的词)
first_token = argmax(probabilities) # "我"
# 方法2:采样(根据概率分布随机采样)
first_token = sample(probabilities) # 可能是 "我"
3.3 第二个词的生成
关键点:
- 掩码确保在生成"喜欢"时,只能看到"“和"我”
- 交叉注意力让"喜欢"能够关注到编码器中的相关信息(如 “love”)
3.4 继续生成
重复上述过程,直到生成结束标记 <END>:
<START> → 我 → 喜欢 → 学习 → AI → <END>
完整流程可视化
关键技术细节
1. 缩放点积注意力公式
完整的注意力计算公式:
Attention(Q, K, V) = softmax(Q @ K^T / sqrt(d_k)) @ V
为什么除以 sqrt(d_k)?
- 防止点积值过大导致 Softmax 梯度消失
- 保持梯度的稳定性
2. 位置编码的数学原理
位置编码使用不同频率的正弦和余弦函数:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
优势:
- 可以表示相对位置关系
- 对于训练时未见过的位置,也能外推
3. 掩码机制详解
填充掩码(Padding Mask)
# 当序列长度不一致时,使用填充
# 例如:[I, love, learning, AI, <PAD>, <PAD>]
# 掩码:[True, True, True, True, False, False]
mask = (input_ids != PAD_TOKEN_ID)
attention_mask = mask.unsqueeze(1).unsqueeze(2)
因果掩码(Causal Mask)
# 下三角矩阵,防止看到未来信息
# [True, False, False, False]
# [True, True, False, False]
# [True, True, True, False]
# [True, True, True, True]
causal_mask = torch.tril(torch.ones(seq_len, seq_len))
4. 训练 vs 推理的区别
| 特性 | 训练阶段 | 推理阶段 |
|---|---|---|
| 输入方式 | 并行:一次性输入完整目标序列 | 串行:逐个生成词 |
| 掩码 | 使用因果掩码 | 使用因果掩码 |
| 效率 | 高效(并行计算) | 相对较慢(串行生成) |
训练时的并行化:
# 训练时,解码器一次性看到完整目标序列
target = ["<START>", "我", "喜欢", "学习", "AI"]
# 通过掩码确保每个位置只能看到之前的位置
推理时的自回归:
# 推理时,逐个生成
step1: "<START>" → "我"
step2: "<START> 我" → "喜欢"
step3: "<START> 我 喜欢" → "学习"
...
实际应用示例
示例 1:文本翻译
输入:"Hello, how are you?"
输出:"你好,你好吗?"
执行步骤:
- 编码器理解英文句子的含义
- 解码器基于编码器的信息,逐个生成中文词
- 交叉注意力确保生成的中文词与对应的英文词对齐
示例 2:文本摘要
输入:一篇长文章
输出:摘要
执行步骤:
- 编码器处理整篇文章,提取关键信息
- 解码器生成简洁的摘要
- 注意力权重可以可视化,显示摘要中每个词关注原文的哪些部分
示例 3:问答系统
输入:问题 + 上下文
输出:答案
执行步骤:
- 编码器同时处理问题和上下文
- 解码器生成答案
- 注意力机制帮助模型找到相关的上下文信息
代码实现示例(简化版)
以下是一个简化的 Transformer 实现,帮助理解核心概念:
import torch
import torch.nn as nn
import math
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
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, mask=None):
batch_size = Q.size(0)
# 1. 线性变换
Q = self.W_q(Q)
K = self.W_k(K)
V = self.W_v(V)
# 2. 分割成多头
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)
# 3. 计算注意力
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attention_weights = torch.softmax(scores, dim=-1)
attention_output = torch.matmul(attention_weights, V)
# 4. 拼接多头
attention_output = attention_output.transpose(1, 2).contiguous()
attention_output = attention_output.view(batch_size, -1, self.d_model)
# 5. 输出投影
output = self.W_o(attention_output)
return output, attention_weights
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
div_term = torch.exp(torch.arange(0, d_model, 2).float() *
-(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
return x + self.pe[:, :x.size(1)]
class TransformerEncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attention = MultiHeadAttention(d_model, num_heads)
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(),
nn.Linear(d_ff, d_model)
)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
# 多头自注意力 + 残差连接 + 层归一化
attn_output, _ = self.self_attention(x, x, x, mask)
x = self.norm1(x + self.dropout(attn_output))
# 前馈网络 + 残差连接 + 层归一化
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_output))
return x
class TransformerDecoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attention = MultiHeadAttention(d_model, num_heads)
self.cross_attention = MultiHeadAttention(d_model, num_heads)
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(),
nn.Linear(d_ff, d_model)
)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, encoder_output, src_mask=None, tgt_mask=None):
# 掩码自注意力
attn_output, _ = self.self_attention(x, x, x, tgt_mask)
x = self.norm1(x + self.dropout(attn_output))
# 交叉注意力
cross_attn_output, _ = self.cross_attention(x, encoder_output, encoder_output, src_mask)
x = self.norm2(x + self.dropout(cross_attn_output))
# 前馈网络
ff_output = self.feed_forward(x)
x = self.norm3(x + self.dropout(ff_output))
return x
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, num_heads=8,
num_encoder_layers=6, num_decoder_layers=6, d_ff=2048, max_len=5000, dropout=0.1):
super().__init__()
# 嵌入层
self.src_embedding = nn.Embedding(src_vocab_size, d_model)
self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_len)
# 编码器
self.encoder_layers = nn.ModuleList([
TransformerEncoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_encoder_layers)
])
# 解码器
self.decoder_layers = nn.ModuleList([
TransformerDecoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_decoder_layers)
])
# 输出层
self.output_projection = nn.Linear(d_model, tgt_vocab_size)
self.dropout = nn.Dropout(dropout)
def forward(self, src, tgt, src_mask=None, tgt_mask=None):
# 编码器
src_emb = self.pos_encoding(self.src_embedding(src) * math.sqrt(self.d_model))
src_emb = self.dropout(src_emb)
encoder_output = src_emb
for layer in self.encoder_layers:
encoder_output = layer(encoder_output, src_mask)
# 解码器
tgt_emb = self.pos_encoding(self.tgt_embedding(tgt) * math.sqrt(self.d_model))
tgt_emb = self.dropout(tgt_emb)
decoder_output = tgt_emb
for layer in self.decoder_layers:
decoder_output = layer(decoder_output, encoder_output, src_mask, tgt_mask)
# 输出投影
output = self.output_projection(decoder_output)
return output
总结与思考
Transformer 的核心优势
- 并行化能力:训练时可以并行处理所有位置
- 长距离依赖:直接建模任意两个位置的关系
- 可解释性:注意力权重可以可视化,理解模型的关注点
- 通用性强:适用于多种 NLP 任务
关键创新点
- 自注意力机制:让模型自己决定关注哪些信息
- 位置编码:用数学方法编码位置信息,无需循环结构
- 多头注意力:同时关注多种不同的关系
- 残差连接:帮助深层网络训练
更多推荐

所有评论(0)