共计 2549 个字符,预计需要花费 7 分钟才能阅读完成。
背景介绍
BERT(Bidirectional Encoder Representations from Transformers)是 Google 在 2018 年提出的预训练语言模型,通过 Masked Language Model(MLM)和 Next Sentence Prediction(NSP)任务进行预训练。预训练的重要性在于模型可以学习通用的语言表示,从而在下游任务(如文本分类、问答系统等)中通过微调快速适配,显著提升性能。

数据准备
文本预处理方法
BERT 的输入数据需要经过一系列预处理步骤:
- 分词:使用 WordPiece 分词器,将文本分割成子词单元。例如,”unhappiness” 可能被分割为 ”un”, “##happiness”。
- 构造 Masked LM 样本:随机遮盖输入文本中 15% 的 token,其中 80% 替换为[MASK],10% 替换为随机 token,10% 保持不变。
- 构造 NSP 样本:50% 的样本为连续的句子对(正样本),50% 为随机组合的句子对(负样本)。
数据并行处理技巧
处理大规模文本数据时,可以采用以下方法加速:
- 使用多进程并行处理数据,例如 Python 的
multiprocessing模块。 - 将预处理后的数据保存为 TFRecord 或 HDF5 格式,便于快速加载。
示例代码
from transformers import BertTokenizer
import numpy as np
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
def preprocess(text):
# 分词
tokens = tokenizer.tokenize(text)
# 随机遮盖 15% 的 token
masked_tokens = []
labels = []
for token in tokens:
if np.random.rand() < 0.15:
rand = np.random.rand()
if rand < 0.8:
masked_tokens.append('[MASK]')
elif rand < 0.9:
masked_tokens.append(token)
else:
masked_tokens.append(tokenizer.vocab[random.choice(list(tokenizer.vocab.keys()))])
labels.append(tokenizer.convert_tokens_to_ids(token))
else:
masked_tokens.append(token)
labels.append(-100) # 忽略未遮盖的 token
return masked_tokens, labels
模型架构
超参数选择
BERT-base 模型的典型超参数如下:
- Transformer 层数:12
- 隐藏层维度:768
- 注意力头数:12
- 总参数量:约 110M
对于更大规模的模型(如 BERT-large),可以增加层数和隐藏层维度。
注意力机制实现细节
BERT 使用多头自注意力机制,计算过程如下:
- 将输入嵌入分为多个头,每个头独立计算注意力。
- 计算 Query、Key、Value 矩阵:
Q = torch.matmul(x, W_q) K = torch.matmul(x, W_k) V = torch.matmul(x, W_v) - 计算注意力分数并缩放:
attention_scores = torch.matmul(Q, K.transpose(-1, -2)) / sqrt(d_k) attention_probs = torch.softmax(attention_scores, dim=-1) - 输出加权和:
context = torch.matmul(attention_probs, V)
训练策略
学习率调度
BERT 使用线性预热(Linear Warmup)和学习率衰减策略:
- 在前 10% 的训练步数中,学习率从 0 线性增加到初始学习率(如 2e-5)。
- 之后线性衰减到 0。
批次大小设置
由于 BERT 模型较大,通常使用较大的批次(如 256 或 512)以提高训练效率。如果显存不足,可以使用梯度累积(Gradient Accumulation)。
混合精度训练
使用 FP16 混合精度训练可以显著减少显存占用并加速训练:
from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()
with autocast():
outputs = model(inputs)
loss = outputs.loss
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
性能优化
分布式训练实现
使用 PyTorch 的DistributedDataParallel(DDP)进行多 GPU 训练:
torch.distributed.init_process_group(backend='nccl')
model = torch.nn.parallel.DistributedDataParallel(model)
内存优化技巧
- 使用梯度检查点(Gradient Checkpointing):
from torch.utils.checkpoint import checkpoint def forward(self, x): return checkpoint(self._forward, x) - 优化注意力计算:使用稀疏注意力或内存高效的注意力实现。
避坑指南
常见训练失败原因分析
- 梯度爆炸:初始学习率过高或未进行梯度裁剪。
- 显存不足:批次大小过大或模型参数过多。
- 训练不稳定:未使用学习率预热或混合精度训练配置不当。
超参数调优建议
- 初始学习率:通常在 1e- 5 到 5e- 5 之间。
- 批次大小:根据显存调整,尽可能使用较大的批次。
- 预热步数:总训练步数的 10%。
微调建议
将预训练模型适配到下游任务时,通常需要:
- 根据任务类型添加输出层(如分类层)。
- 使用较小的学习率(如 5e-6)进行微调。
- 冻结部分底层参数,只训练顶层参数。
思考题
如何评估预训练模型的质量?
- 下游任务性能:在多个标准任务(如 GLUE 基准)上微调并评估。
- 探针任务:设计简单的任务(如词性标注)测试模型的语言理解能力。
- 内部评估:检查 MLM 和 NSP 任务的准确率。
正文完
