前言
BERT流程回顾
整个流程:
- 数据准备
- 预训练的BERT选择与下载
- 优化目标+损失函数选择
用CLS 还是最后一层的输出?
都用一下,结果差异区别很大
BERT那么多层,我该调那几层?
BERT各层都学到了什么?
理解这个有助于你做决策:
- 底层(1-4层):主要学习语法、词性、短语结构等基础语言特征。接近传统的Word Embedding。
- 中层(5-8层):开始捕获语义、句法关系,比如主谓宾结构、指代关系等。
- 高层(9-12层):学习高级语义、上下文信息,更贴近具体预训练任务(如Next Sentence Prediction)。
实践建议总结
| 场景 | 推荐策略 | 理由 |
|---|---|---|
| 数据量少(<1k) | 只微调最后2-4层 + 分类头 | 防止过拟合,训练稳定,速度快。 |
| 数据量中等(1k-10k) | 分层学习率(底层小,顶层大) | 最推荐的普适策略,在性能和稳定性间取得最佳平衡。 |
| 数据量大(>10k) | 微调所有层(或分层学习率) | 数据充足,模型有能力学习所有参数的调整。 |
| 领域差异大 | 微调所有层 | 需要调整基础特征来适应新领域。 |
| 计算资源紧张 | 只微调顶层 | 大大减少可训练参数量,加快训练速度。 |
给你的直接建议:
- 首先尝试默认策略:用一个统一的学习率(如2e-5)微调所有层。这通常能得到一个不错的基线。
- 如果效果不佳或过拟合:尝试分层学习率,这是性价比最高的优化手段。
- 如果数据非常少:果断冻结底层,只训练顶层和分类头。
记住,在NLP中,实验是唯一的真理。你可以先用一种策略跑一个基线,然后根据训练损失、验证集精度和过拟合情况来调整你的策略。
pytorch代码实现:微调最后三层(9,10,11)+第一层(0)+word embedding层
def __init__(self, config):
super(Model, self).__init__()
self.bert = BertModel.from_pretrained(config.bert_path)
for name, param in self.bert.named_parameters() :
# param.requires_grad = False
if "encoder.layer.11" in name or "encoder.layer.10" in name or "encoder.layer.9" in name or "encoder.layer.1" in name or "embeddings." in name:
# if "encoder.layer.8" in name or "encoder.layer.9" in name or "encoder.layer.11" in name or "encoder.layer.10" in name or "embeddings." in name :
param.requires_grad = True
# print(name)
else :
param.requires_grad = False
下篇损失函数