共计 3113 个字符,预计需要花费 8 分钟才能阅读完成。
背景痛点
初学者在接触 BERT 时往往会遇到几个常见问题:
- 硬件资源不足:BERT 模型参数量大,对显存要求高,普通消费级显卡难以直接运行
- 微调效果不稳定:同一套代码在不同数据集上表现差异大,训练过程难以复现
- 模型选择困难:BERT 变体众多,不清楚何时该选用哪种预训练模型
- 超参数敏感:学习率、batch size 等参数对最终效果影响显著但缺乏调参指导
技术对比
主流的预训练语言模型各有特点:
- BERT (2018)
- 优势:首个双向 Transformer 模型,开源实现成熟
-
缺点:最大长度 512,计算资源消耗大
-
RoBERTa (2019)
- 改进:更长的训练步数、更大的 batch size
-
适用:需要更高精度的场景
-
ALBERT (2019)
- 创新:参数共享技术大幅减少参数量
- 适用:资源受限的移动端或嵌入式设备
核心实现
BERT 架构解析
BERT 的核心是 Transformer 编码器堆叠,其核心组件包括:
- 多头注意力机制:允许模型同时关注不同位置的语义信息
- 位置编码:通过正弦函数为输入添加位置信息
- 层归一化:稳定深层网络的训练过程

加载预训练模型
使用 Hugging Face Transformers 库可以轻松加载 BERT:
from transformers import BertModel, BertTokenizer
# 初始化 tokenizer 和 model
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
# 示例文本处理
inputs = tokenizer("Hello world!", return_tensors="pt")
outputs = model(**inputs)
完整微调示例
数据准备
from torch.utils.data import Dataset, DataLoader
class TextDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = str(self.texts[idx])
label = self.labels[idx]
encoding = self.tokenizer.encode_plus(
text,
add_special_tokens=True,
max_length=self.max_len,
padding='max_length',
truncation=True,
return_attention_mask=True,
return_tensors='pt'
)
return {'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}
训练循环
from transformers import BertForSequenceClassification, AdamW
from transformers import get_linear_schedule_with_warmup
# 初始化模型
model = BertForSequenceClassification.from_pretrained(
'bert-base-uncased',
num_labels=num_classes
)
# 优化器设置
optimizer = AdamW(model.parameters(), lr=2e-5, correct_bias=False)
# 学习率预热
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=100,
num_training_steps=len(train_loader) * epochs
)
# 训练循环
for epoch in range(epochs):
model.train()
for batch in train_loader:
optimizer.zero_grad()
# 前向传播
outputs = model(input_ids=batch['input_ids'],
attention_mask=batch['attention_mask'],
labels=batch['labels']
)
loss = outputs.loss
loss.backward()
optimizer.step()
scheduler.step()
性能优化技巧
梯度累积
当显存不足时,可以通过多次小 batch 的前向传播累积梯度,再统一更新参数:
accumulation_steps = 4
for i, batch in enumerate(train_loader):
# 前向传播和损失计算
loss = model(batch).loss
# 梯度归一化
loss = loss / accumulation_steps
# 反向传播
loss.backward()
# 累积足够 steps 后更新参数
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
混合精度训练
使用 AMP(Automatic Mixed Precision)可以显著减少显存占用并加速训练:
from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()
with autocast():
outputs = model(input_ids, attention_mask, labels)
loss = outputs.loss
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
避坑指南
类别不平衡处理
- 加权交叉熵:为少数类别分配更大的权重
- 过采样 / 欠采样:调整数据分布使其均衡
- Focal Loss:降低易分类样本的权重
层学习率衰减
BERT 的不同层应使用不同的学习率,底层使用较小学习率:
param_optimizer = list(model.named_parameters())
no_decay = ['bias', 'LayerNorm.weight']
optimizer_grouped_parameters = [
{
'params': [p for n, p in param_optimizer
if not any(nd in n for nd in no_decay)],
'weight_decay': 0.01
},
{
'params': [p for n, p in param_optimizer
if any(nd in n for nd in no_decay)],
'weight_decay': 0.0
}
]
延伸思考
- 如何通过知识蒸馏将 BERT 大模型压缩为轻量级模型?
- 在 Colab 等免费平台上运行 BERT 有哪些资源优化技巧?
- 不同领域的文本 (如医疗、法律) 是否需要特殊的预训练策略?
建议读者在 Colab 上尝试复现本文示例,并分享在不同数据集上的实验结果对比。通过实践可以更深入地理解 BERT 的微调过程和优化方法。
正文完
