共计 2318 个字符,预计需要花费 6 分钟才能阅读完成。
背景介绍
BERT(Bidirectional Encoder Representations from Transformers)是自然语言处理(NLP)领域的里程碑式模型,通过预训练 - 微调范式在多项任务上取得突破性表现。对于初学者而言,BERT 的核心难点在于理解其双向上下文编码机制和预训练任务的协同作用。

常见理解障碍包括:
- 如何实现真正的双向编码(传统语言模型仅单向)
- Masked Language Model 如何捕捉上下文语义
- Next Sentence Prediction 对句子级任务的意义
- 预训练与下游任务的参数迁移方式
整体流程图解
BERT 预训练包含三大核心模块:
- 输入表示层:将原始文本转换为模型可处理的数值向量
- Transformer 编码器:通过多层自注意力机制提取特征
- 预训练任务头:执行 MLM 和 NSP 任务计算损失
graph TD
A[原始文本] --> B(Tokenization)
B --> C[Input Embeddings]
C --> D[Segment Embeddings]
D --> E[Position Embeddings]
E --> F{Embedding 相加}
F --> G[Transformer Encoder]
G --> H[MLM 任务头]
G --> I[NSP 任务头]
核心模块详解
输入表示(Tokenization)
采用 WordPiece 分词算法,处理流程:
- 文本标准化(统一大小写 /Unicode)
- 按空格初步分词
- 递归应用 WordPiece 合并子词
- 添加特殊标记:
- [CLS]分类标记
- [SEP]句子分隔标记
- [MASK]掩码标记
Transformer 编码结构
关键参数配置(以 BERT-base 为例):
- 12 层 Encoder
- 768 隐藏维度
- 12 个注意力头
- 110M 总参数
每层包含:
- 多头自注意力机制
- LayerNorm+ 残差连接
- 前馈神经网络
预训练任务实现
Masked Language Model
- 随机选择 15% 的 Token 进行替换:
- 80% 替换为[MASK]
- 10% 随机替换
- 10% 保持不变
- 通过 softmax 预测被掩盖的原始 Token
数学表达:
P(w_i|w_{1..i-1},w_{i+1..n}) = softmax(W*h_i + b)
Next Sentence Prediction
- 输入两个句子 A 和 B
- 50% 概率 B 是 A 的真实后续句
- 使用 [CLS] 标记的向量进行二分类
PyTorch 实现示例
import torch
from transformers import BertTokenizer, BertModel
# 初始化 Tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 样本输入
text = "[CLS] The quick brown fox jumps over the lazy dog [SEP]"
# Tokenization 流程
tokens = tokenizer.tokenize(text)
input_ids = tokenizer.convert_tokens_to_ids(tokens)
segment_ids = [0] * len(tokens) # 单句场景
attention_mask = [1] * len(tokens)
# 转换为模型输入
inputs = {'input_ids': torch.tensor([input_ids]),
'token_type_ids': torch.tensor([segment_ids]),
'attention_mask': torch.tensor([attention_mask])
}
# 加载预训练模型
model = BertModel.from_pretrained('bert-base-uncased')
# 前向传播
with torch.no_grad():
outputs = model(**inputs)
last_hidden_states = outputs.last_hidden_state # 最终隐藏层
pooler_output = outputs.pooler_output # [CLS]向量
性能优化策略
-
混合精度训练:使用 AMP(Automatic Mixed Precision)
from torch.cuda.amp import autocast with autocast(): outputs = model(**inputs) -
梯度累积:解决显存不足问题
for i, batch in enumerate(dataloader): loss = model(**batch).loss loss = loss / accumulation_steps loss.backward() if (i+1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad() -
动态填充:按 batch 内最大长度 padding
避坑指南
- OOM 错误:
- 症状:CUDA out of memory
-
方案:减小 batch_size 或使用梯度累积
-
NaN 损失:
- 症状:训练初期出现 NaN
-
方案:检查学习率是否过高
-
性能瓶颈:
- 症状:GPU 利用率低
-
方案:增大 dataloader 的 num_workers
-
微调失效:
- 症状:下游任务指标不提升
-
方案:检查是否冻结了不应冻结的层
-
文本截断:
- 症状:长文本预测效果差
- 方案:调整 max_position_embedding 参数
延伸思考
- 如何设计领域自适应的预训练任务?
- 对比 BERT 与 RoBERTa 在训练策略上的本质区别
- 知识蒸馏如何应用于 BERT 模型压缩?
通过本文的流程图解和代码实践,读者应能掌握 BERT 预训练的核心机制,并在实际项目中避免常见陷阱。建议下一步尝试在具体 NLP 任务(如文本分类、NER)上进行微调实验。
正文完
发表至: 自然语言处理
近一天内
