共计 1982 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点
SQuADv2.0 相较于 v1.1 版本主要增加了不可回答问题(unanswerable questions)的挑战。这要求模型不仅要找到答案的位置,还要判断问题是否可回答。此外,SQuADv2.0 中的段落通常较长,这对 BERT 模型的 512 个 token 长度限制提出了挑战。

技术选型
对于初学者来说,BERT-base 是一个更好的起点:
- BERT-base 有 12 层,110M 参数,在大多数消费级 GPU(如 RTX 3090)上可以运行
- BERT-large 有 24 层,340M 参数,需要更大的显存(通常需要 A100 级别的 GPU)
- 在推理速度上,BERT-base 比 BERT-large 快约 2 - 3 倍
核心实现
数据加载与预处理
使用 HuggingFace 的 DatasetDict 可以方便地加载 SQuADv2.0 数据集:
from datasets import load_dataset
squad_dataset = load_dataset("squad_v2")
处理答案位置
对于每个可回答问题,我们需要找到答案在 tokenized 文本中的 start 和 end 位置:
- 首先对问题和上下文进行 tokenize
- 然后找到原始文本中答案的字符位置
- 将这些字符位置映射到 tokenized 后的位置
不可回答问题处理
对于不可回答问题:
- 将 answer_start 和 answer_end 都设为 0
- 在训练时,模型需要学习输出 [CLS] 位置的 start 和 end
完整代码示例
动态 padding 实现
使用 DataCollatorWithPadding 可以自动处理不同长度的样本:
from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
学习率 warmup 策略
训练开始时逐渐增大学习率,有助于稳定训练:
from transformers import AdamW, get_linear_schedule_with_warmup
optimizer = AdamW(model.parameters(), lr=5e-5)
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=500,
num_training_steps=len(train_dataloader)*epochs
)
梯度累积
当 GPU 显存不足时,可以通过梯度累积来等效增大 batch size:
for step, batch in enumerate(train_dataloader):
outputs = model(**batch)
loss = outputs.loss
loss = loss / gradient_accumulation_steps
loss.backward()
if step % gradient_accumulation_steps == 0:
optimizer.step()
scheduler.step()
optimizer.zero_grad()
性能考量
显存占用对比
在 RTX 3090 上(24GB 显存):
- BERT-base, batch size=8: 约 10GB
- BERT-base, batch size=16: 约 18GB
- BERT-large, batch size=2: 约 15GB
FP16 混合精度
使用 FP16 可以显著减少显存占用并提高训练速度:
from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()
with autocast():
outputs = model(**batch)
loss = outputs.loss
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
避坑指南
处理 tokenizer 长度限制
对于超过 512 token 的段落:
- 将长段落切分为多个 512 token 的块
- 对每个块单独处理
- 选择置信度最高的答案
答案跨越多段文本
当答案被切分到两个块中时:
- 优先选择完全包含在单个块中的答案
- 或者合并相邻块的预测结果
验证集指标波动
如果验证集指标波动较大:
- 检查学习率是否设置过高
- 尝试增大 batch size
- 增加 warmup steps
延伸思考
- 如何改进模型对否定问题(如 ” 文中没有提到什么 ”)的处理能力?
- 除了 BERT,还有哪些预训练模型可能更适合 SQuADv2.0 任务?
- 如何设计一个评估指标,同时考虑答案准确性和不可回答问题的判断能力?
总结
通过本文的实践,我们完成了从数据准备到模型训练的全流程。BERT 虽然在 SQuADv2.0 上表现良好,但在实际应用中仍有许多可以改进的空间。希望读者能在此基础上进一步探索,开发出更适合自己应用场景的问答系统。
正文完
