深入浅出理解Transformer和BERT:从零开始的NLP革命
本文深入浅出地介绍了Transformer和BERT这两个NLP领域的革命性模型。首先分析了Transformer出现前RNN模型的局限性,然后详细解析了Transformer的核心自注意力机制、多头注意力、位置编码等关键技术。文章重点阐述了BERT的架构特点,包括双向编码、预训练任务(MLM和NSP)以及微调流程。通过对比传统模型和Transformer的差异,以及BERT的创新之处,帮助读者理
深入浅出理解Transformer和BERT:从零开始的NLP革命
前言
如果你关注自然语言处理(NLP)领域,一定听说过Transformer和BERT这两个名字。它们不仅仅是两个模型,更代表了NLP发展史上的两次重大突破。本文将从零开始,用最通俗的语言带你理解这两个改变了AI世界的模型。
第一章:在Transformer之前,NLP的世界是什么样的?
1.1 传统的序列模型
在Transformer出现之前,处理文本序列主要依靠**RNN(循环神经网络)**及其变体LSTM、GRU。
RNN的工作方式:
输入: "我 爱 自然 语言 处理"
处理过程:
t1: 处理"我" → 输出h1
t2: 基于h1处理"爱" → 输出h2
t3: 基于h2处理"自然" → 输出h3
t4: 基于h3处理"语言" → 输出h4
t5: 基于h4处理"处理" → 输出h5
RNN的问题:
- 串行计算: 必须等前一个词处理完才能处理下一个,无法并行
- 长距离依赖: 句子太长时,前面的信息会逐渐"遗忘"
- 训练慢: 因为串行特性,训练效率低
1.2 注意力机制的萌芽
2014年,Bahdanau等人提出了注意力机制(Attention),让模型在处理每个词时,可以"关注"输入序列的不同部分。这为Transformer的诞生埋下了伏笔。
第二章:Transformer横空出世
2.1 核心思想:“Attention is All You Need”
2017年,Google团队发表了论文《Attention is All You Need》,提出了Transformer模型。核心思想是:完全抛弃RNN,只用注意力机制处理序列。
2.2 自注意力机制(Self-Attention)详解
什么是自注意力?
想象你在读这句话:“银行的利率上涨了”。当你读到"利率"时,你会自动关联到前面的"银行"。这就是注意力。
数学原理:
对于输入序列,每个词表示为向量。自注意力通过三个矩阵Q(Query)、K(Key)、V(Value)来计算:
1. 对每个词生成三个向量:
- Query(查询): 我要找什么?
- Key(键): 我是什么?
- Value(值): 我的具体内容是什么?
2. 计算注意力分数:
Score = Q · K^T / √d_k
3. 用Softmax归一化:
Attention_weights = Softmax(Score)
4. 加权求和:
Output = Attention_weights · V
直观例子:
句子: "The animal didn't cross the street because it was too tired"
当处理"it"时:
- Query(it) 与 Key(animal) 的相似度: 0.8
- Query(it) 与 Key(street) 的相似度: 0.2
- Query(it) 与 Key(tired) 的相似度: 0.6
结论:模型判断"it"更可能指代"animal"
2.3 多头注意力(Multi-Head Attention)
单个注意力机制只能捕捉一种关系。多头注意力相当于让多个专家同时工作:
Head 1: 关注语法关系(主谓宾)
Head 2: 关注语义关系(近义词)
Head 3: 关注位置关系(相邻词)
...
Head 8: 关注其他模式
实现方式:
# 伪代码
MultiHead(Q, K, V) = Concat(head1, head2, ..., head_h) · W_O
其中每个head:
head_i = Attention(Q·W_Q^i, K·W_K^i, V·W_V^i)
2.4 位置编码(Positional Encoding)
由于自注意力没有顺序概念,需要额外添加位置信息:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
其中:
- pos: 词的位置(0, 1, 2, ...)
- i: 维度索引
2.5 完整的Transformer架构
输入文本
↓
[Embedding + Positional Encoding]
↓
┌─────────────────────────────────┐
│ Encoder (6层) │
│ ┌─────────────────────┐ │
│ │ Multi-Head Attention │ │
│ │ ↓ │ │
│ │ Add & Norm │ │
│ │ ↓ │ │
│ │ Feed Forward │ │
│ │ ↓ │ │
│ │ Add & Norm │ │
│ └─────────────────────┘ │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Decoder (6层) │
│ ┌─────────────────────┐ │
│ │ Masked Multi-Head │ │
│ │ Attention │ │
│ │ ↓ │ │
│ │ Cross Attention │ │
│ │ (with Encoder) │ │
│ │ ↓ │ │
│ │ Feed Forward │ │
│ └─────────────────────┘ │
└─────────────────────────────────┘
↓
输出文本
关键组件解释:
- 残差连接(Add): 缓解深层网络的梯度消失问题
- 层归一化(Norm): 加速训练,稳定模型
- 前馈网络(Feed Forward): 两层全连接,增加非线性表达能力
2.6 Transformer的优势
| 特性 | RNN/LSTM | Transformer |
|---|---|---|
| 并行化 | 串行,慢 | 完全并行,快 |
| 长距离依赖 | 困难 | 直接建模 |
| 计算复杂度 | O(n) | O(n²) |
| 可解释性 | 弱 | 强(可视化注意力) |
第三章:BERT - 预训练语言模型的里程碑
3.1 BERT的诞生背景
2018年,Google发布BERT(Bidirectional Encoder Representations from Transformers),在11项NLP任务上刷新记录。
核心创新:
- 双向编码: 同时看到上下文(RNN只能看前文)
- 预训练+微调: 先在大规模语料上预训练,再针对具体任务微调
3.2 BERT的架构
BERT只使用了Transformer的Encoder部分:
[CLS] token1 token2 ... tokenN [SEP]
↓ ↓ ↓ ↓ ↓
┌────────────────────────────────┐
│ Token Embeddings │
│ + Segment Embeddings │
│ + Position Embeddings │
└───────────��────────────────────┘
↓ ↓ ↓ ↓ ↓
┌────────────────────────────────┐
│ Transformer Encoder × 12 │
│ (BERT-Base) │
└────────────────────────────────┘
↓ ↓ ↓ ↓ ↓
C T1 T2 ... TN E
特殊Token:
[CLS]: 句子的聚合表示,用于分类任务[SEP]: 分隔符,区分不同句子[MASK]: 遮盖标记,用于预训练
3.3 预训练任务
任务1:Masked Language Model (MLM)
思路: 随机遮盖15%的词,让模型预测
原始: "我喜欢吃苹果"
输入: "我[MASK]欢吃[MASK]果"
目标: 预测[MASK]位置是"喜"和"苹"
遮盖策略:
- 80%: 替换为[MASK]
- 10%: 替换为随机词
- 10%: 保持不变
任务2:Next Sentence Prediction (NSP)
思路: 判断两个句子是否连续
输入A: "我今天去了公园"
输入B: "那里的风景很美"
标签: IsNext (正样本)
输入A: "我今天去了公园"
输入B: "量子力学很复杂"
标签: NotNext (负样本)
3.4 BERT的变体
| 模型 | 参数量 | 层数 | 隐藏层大小 | 注意力头数 |
|---|---|---|---|---|
| BERT-Base | 110M | 12 | 768 | 12 |
| BERT-Large | 340M | 24 | 1024 | 16 |
3.5 微调(Fine-tuning)流程
# 伪代码示例
# 1. 加载预训练的BERT
model = BertModel.from_pretrained('bert-base-uncased')
# 2. 添加任务特定的输出层
classifier = nn.Linear(768, num_classes)
# 3. 在特定任务上微调
for batch in task_data:
outputs = model(batch)
logits = classifier(outputs[0][:, 0, :]) # 使用[CLS]的输出
loss = criterion(logits, labels)
loss.backward()
optimizer.step()
3.6 BERT的应用场景
1. 文本分类
输入: [CLS] 这部电影太棒了 [SEP]
输出: 情感=正面 (概率: 0.95)
2. 命名实体识别(NER)
输入: [CLS] 马云在杭州创办了阿里巴巴 [SEP]
输出:
- 马云: PERSON
- 杭州: LOCATION
- 阿里巴巴: ORGANIZATION
3. 问答系统
问题: 谁发明了电灯?
段落: 托马斯·爱迪生在1879年发明了实用的白炽灯...
输出: 托马斯·爱迪生
4. 文本相似度
句子1: "猫在桌子上"
句子2: "桌上有只猫"
相似度: 0.89
第四章:深入对比
4.1 Transformer vs BERT
| 维度 | Transformer | BERT |
|---|---|---|
| 架构 | Encoder-Decoder | 仅Encoder |
| 训练方式 | 有监督(翻译) | 自监督(MLM+NSP) |
| 适用任务 | 序列到序列(翻译) | 理解类任务(分类、NER) |
| 上下文 | Decoder是单向的 | 完全双向 |
4.2 BERT的局限性
- 只能理解,不能生成: 因为只用了Encoder
- 输入长度限制: 最大512个token
- 计算开销大: 自注意力的O(n²)复杂度
- NSP任务争议: 后续研究发现NSP作用有限
4.3 后续改进模型
BERT家族演进:
BERT (2018)
↓
RoBERTa (2019): 移除NSP,更大批次,更多数据
↓
ALBERT (2019): 参数共享,减少参数量
↓
ELECTRA (2020): 判别式预训练,效率更高
↓
DeBERTa (2021): 解耦注意力,相对位置编码
第五章:实战示例
5.1 Transformer注意力计算示例
import numpy as np
def scaled_dot_product_attention(Q, K, V):
"""
Q: Query矩阵 [seq_len, d_k]
K: Key矩阵 [seq_len, d_k]
V: Value矩阵 [seq_len, d_v]
"""
d_k = Q.shape[-1]
# 计算注意力分数
scores = np.matmul(Q, K.T) / np.sqrt(d_k)
# Softmax归一化
attention_weights = np.exp(scores) / np.exp(scores).sum(axis=-1, keepdims=True)
# 加权求和
output = np.matmul(attention_weights, V)
return output, attention_weights
# 示例
seq_len, d_k = 4, 8
Q = np.random.randn(seq_len, d_k)
K = np.random.randn(seq_len, d_k)
V = np.random.randn(seq_len, d_k)
output, weights = scaled_dot_product_attention(Q, K, V)
print("注意力权重矩阵:\n", weights)
5.2 使用PyTorch实现Multi-Head Attention
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
# 定义Q、K、V的线性变换
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 split_heads(self, x, batch_size):
"""分割成多头"""
x = x.view(batch_size, -1, self.num_heads, self.d_k)
return x.permute(0, 2, 1, 3)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# 线性变换
Q = self.W_q(Q)
K = self.W_k(K)
V = self.W_v(V)
# 分割多头
Q = self.split_heads(Q, batch_size)
K = self.split_heads(K, batch_size)
V = self.split_heads(V, batch_size)
# 计算注意力
scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32))
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attention = torch.softmax(scores, dim=-1)
context = torch.matmul(attention, V)
# 合并多头
context = context.permute(0, 2, 1, 3).contiguous()
context = context.view(batch_size, -1, self.d_model)
# 最终线性变换
output = self.W_o(context)
return output, attention
# 使用示例
d_model = 512
num_heads = 8
seq_len = 10
batch_size = 2
mha = MultiHeadAttention(d_model, num_heads)
x = torch.randn(batch_size, seq_len, d_model)
output, attention = mha(x, x, x)
print(f"输出形状: {output.shape}") # [2, 10, 512]
print(f"注意力形状: {attention.shape}") # [2, 8, 10, 10]
5.3 使用Hugging Face加载BERT
from transformers import BertTokenizer, BertModel
import torch
# 1. 加载预训练的tokenizer和模型
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
# 2. 准备输入
text = "Transformer and BERT are revolutionary models."
inputs = tokenizer(text, return_tensors='pt')
# 3. 前向传播
with torch.no_grad():
outputs = model(**inputs)
# 4. 获取输出
last_hidden_states = outputs.last_hidden_state # [1, seq_len, 768]
pooler_output = outputs.pooler_output # [1, 768] - [CLS]的表示
print(f"最后一层隐藏状态形状: {last_hidden_states.shape}")
print(f"池化输出形状: {pooler_output.shape}")
# 5. 文本分类微调示例
from transformers import BertForSequenceClassification
num_labels = 2 # 二分类
classifier = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=num_labels)
# 模拟训练
inputs['labels'] = torch.tensor([1]) # 标签
outputs = classifier(**inputs)
loss = outputs.loss
logits = outputs.logits
print(f"损失: {loss.item()}")
print(f"预测logits: {logits}")
第六章:可视化理解
6.1 注意力可视化
假设我们有句子:“The cat sat on the mat”
注意力矩阵(简化版):
The cat sat on the mat
The 0.1 0.2 0.1 0.1 0.2 0.3
cat 0.1 0.5 0.2 0.1 0.0 0.1
sat 0.1 0.3 0.3 0.2 0.1 0.0
on 0.0 0.1 0.2 0.2 0.2 0.3
the 0.1 0.1 0.1 0.1 0.3 0.3
mat 0.2 0.1 0.0 0.2 0.2 0.3
解读:
- “cat"最关注自己(0.5)和"The”(0.1)
- “sat"关注"cat”(0.3)和自己(0.3)
- “mat"关注"the”(0.2)和自己(0.3)
6.2 BERT的词向量演化
传统Word2Vec: "bank"只有一个向量
bank → [0.2, 0.5, -0.3, ...]
BERT: "bank"的向量取决于上下文
"river bank" → [0.1, 0.3, -0.8, ...] (河岸)
"bank account" → [0.5, -0.2, 0.6, ...] (银行)
第七章:工程实践建议
7.1 选择合适的模型
任务场景决策树:
需要生成文本?
├─ 是 → 使用GPT系列或Transformer Decoder
└─ 否 → 需要理解文本?
├─ 是 → 使用BERT系列
└─ 否 → 需要翻译/序列转换?
└─ 是 → 使用完整Transformer
7.2 训练技巧
- 学习率调度
# Warmup + Linear Decay
def get_lr(step, d_model, warmup_steps):
arg1 = step ** -0.5
arg2 = step * (warmup_steps ** -1.5)
return d_model ** -0.5 * min(arg1, arg2)
- 梯度累积 (显存不足时)
accumulation_steps = 4
for i, batch in enumerate(dataloader):
loss = model(batch) / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
- 混合精度训练
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for batch in dataloader:
with autocast():
loss = model(batch)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
7.3 部署优化
-
模型压缩
- 知识蒸馏: BERT → DistilBERT (参数减少40%,速度提升60%)
- 量化: FP32 → INT8
- 剪枝: 移除不重要的权重
-
推理加速
- ONNX Runtime
- TensorRT
- 动态batch
第八章:前沿发展
8.1 从BERT到ChatGPT的演进
2017: Transformer
2018: BERT (双向理解)
2018: GPT (单向生成)
2019: GPT-2 (���大规模)
2020: GPT-3 (少样本学习)
2022: ChatGPT (RLHF对齐)
2023: GPT-4 (多模态)
8.2 最新趋势
- 更长的上下文: 从512 → 8k → 100k tokens
- 多模态融合: 文本+图像+音���
- 高效Transformer: Flash Attention, Linear Attention
- 参数高效微调: LoRA, Adapter, Prompt Tuning
8.3 架构创新
Flash Attention (2022):
传统注意力: O(N²) 显存占用
Flash Attention:
- 分块计算
- 重计算代替存储
- 显存占用降低至O(N)
Sparse Attention:
并非所有token都需要关注所有token
- Local Attention: 只关注邻近的
- Strided Attention: 按步长关注
- Random Attention: 随机采样
第九章:常见问题解答
Q1: 为什么Transformer比RNN快?
A: Transformer可以并行计算所有位置,而RNN必须串行。就像多人同时做题 vs 一人依次做题。
Q2: BERT为什么不能生成文本?
A: BERT是双向的,生成时会"作弊"(看到未来的词)。就像考试时偷看答案,学不到真正的生成能力。
Q3: 注意力机制的计算复杂度为什么是O(n²)?
A: 每个token(n个)都要与其他所有token(n个)计算相似度,总共n×n次计算。
Q4: [MASK]标记会影响微调吗?
A: 不会。微调时不使用[MASK],直接输入原始文本。预训练学到的知识已经编码在参数中。
Q5: BERT-Large一定比BERT-Base好吗?
A: 不一定。小数据集上可能过拟合。资源有限时Base也足够好。
第十章:实践项目推荐
初级项目
- 使用BERT进行电影评论情感分类
- 基于Transformer的英文摘要生成
- 可视化BERT的注意力权重
中级项目
- 从头实现一个Mini-Transformer
- 使用BERT做命名实体识别(NER)
- 多语言BERT模型训练
高级项目
- 预训练自己的领域BERT模型
- 实现Flash Attention优化
- 构建基于Transformer的对话系统
总结
Transformer和BERT代表了NLP的范式转变:
Transformer的贡献:
- 完全基于注意力机制
- 并行化训练
- 成为现代LLM的基础架构
BERT的贡献:
- 预训练+微调范式
- 双向上下文理解
- 刷新了几乎所有NLP任务的SOTA
核心洞察:
- 注意力机制 > 循环结构
- 预训练 > 从头训练
- 大模型 + 大数据 = 强能力
无论你是研究者、工程师还是学生,理解这两个模型都是深入AI领域的必经之路。希望这篇文章能帮助你建立坚实的理论基础,在NLP的世界里走得更远。
参考资料
- Vaswani et al. (2017). “Attention is All You Need”
- Devlin et al. (2018). “BERT: Pre-training of Deep Bidirectional Transformers”
- The Illustrated Transformer - Jay Alammar
- Hugging Face Transformers Documentation
- Stanford CS224N: Natural Language Processing with Deep Learning
作者寄语: AI的世界日新月异,但底层原理是相通的。掌握了Transformer和BERT,你就拥有了理解现代LLM的钥匙。保持学习,持续探索!
更多推荐
所有评论(0)