1153 字
6 分钟
BERT 微调实践:情感分析

微调的本质#

BERT 在预训练阶段学到了通用语言表示。微调是在预训练权重 θBERT\theta_{\text{BERT}} 的基础上,用少量标注数据同时更新编码器和任务特定层 θtask\theta_{\text{task}}:

θ∗=arg⁡min⁡θBERT,θtask1N∑i=1NL(f(xi;θBERT,θtask),yi)\theta^* = \arg\min_{\theta_{\text{BERT}}, \theta_{\text{task}}} \frac{1}{N}\sum_{i=1}^{N} \mathcal{L}\big(f(\mathbf{x}_i; \theta_{\text{BERT}}, \theta_{\text{task}}), y_i\big)

与从零训练相比,微调需要的数据和算力都少得多:

方面从零训练BERT 微调
数据需求百万级标注样本几千到几万条
训练时间数天到数周单卡数小时
泛化能力小数据上容易过拟合预训练提供通用语言知识

任务与数据#

以 IMDb 影评情感分析为例,任务是把影评分类为正面或负面。数据集包含 25,000 条训练样本和 25,000 条测试样本,类别均衡。

Tokenization#

使用 BERT 的 WordPiece 分词器:

text = "This movie is fantastic!"
tokens = ["[CLS]", "this", "movie", "is", "fantastic", "##!", "[SEP]"]
input_ids = [101, 2023, 3185, 2003, 10392, 999, 102]

##! 表示它是前一个 token 的延续;[CLS] 和 [SEP] 是 BERT 的特殊标记。

截断、填充与掩码#

BERT 的最大输入长度为 512。长影评需要截断,短影评用 [PAD](ID 0)填充到统一长度。常见截断策略是保留开头和结尾,因为影评的关键情感信息常出现在这两处。

同时生成 attention mask:真实 token 为 1,填充 token 为 0。self-attention 计算时把填充位置的权重置为 −∞-\infty,softmax 后接近 0,避免它们影响真实 token 的表示。

单句分类任务中所有 token 的 segment ID 都为 0;句子对任务中句子 A 为 0、句子 B 为 1。

模型结构#

在预训练 BERT 之上加一个线性分类头:

class BertForSentimentAnalysis(nn.Module):
def __init__(self, pretrained_bert):
super().__init__()
self.bert = pretrained_bert
self.classifier = nn.Linear(768, 2)
def forward(self, input_ids, attention_mask):
outputs = self.bert(input_ids, attention_mask=attention_mask)
cls_output = outputs.last_hidden_state[:, 0, :]
return self.classifier(cls_output)

关键是取 [CLS] 的最后一层隐藏状态作为句子表示。[CLS] 通过 self-attention 聚合整个序列,预训练中的 NSP 又要求它编码句对关系,因此适合做句子级分类。对所有 token 取平均也可行,ALBERT 就采用了平均池化,但 [CLS] 是 BERT 的原始设计。

训练策略#

损失函数#

二分类交叉熵:

L=−1B∑i=1B[yilog⁡y^i+(1−yi)log⁡(1−y^i)]\mathcal{L} = -\frac{1}{B}\sum_{i=1}^{B} \left[y_i \log \hat{y}_i + (1-y_i)\log(1-\hat{y}_i)\right]

其中 y^i\hat{y}_i 是模型预测为正类的概率。

优化器与分层学习率#

通常使用 AdamW,并给不同部分设置不同学习率:

  • BERT 编码器:2×10−52\times10^{-5} 到 5×10−55\times10^{-5},预训练权重只需小幅调整;
  • 分类头:约 1×10−41\times10^{-4},它是随机初始化的,需要更快收敛。

学习率调度一般采用线性预热加线性衰减:前 10% 的步数从 0 线性增加到目标值,随后逐渐衰减到 0。预热可以避免训练初期梯度不稳定。

def get_linear_schedule(optimizer, num_warmup_steps, num_training_steps):
def lr_lambda(step):
if step < num_warmup_steps:
return float(step) / float(max(1, num_warmup_steps))
return max(0.0,
float(num_training_steps - step) /
float(max(1, num_training_steps - num_warmup_steps)))
return LambdaLR(optimizer, lr_lambda)

超参数#

超参数常用取值说明
批次大小16 或 32取决于显存
BERT 学习率2×10−52\times10^{-5}–5×10−55\times10^{-5}过大会破坏预训练知识
训练轮数2–4过多容易过拟合
最大序列长度128 或 256影评任务通常够用,训练更快
预热比例总步数的 10%稳定训练初期

评估与常见问题#

类别均衡时用准确率;类别不平衡时用宏平均 F1 或 AUC,指标定义见 NLP 评价指标。

问题症状对策
过拟合训练准确率远高于验证准确率降低学习率、增加 dropout、减少轮数
欠拟合训练准确率本身很低提高学习率、增加轮数或模型容量
灾难性遗忘微调后通用语言能力下降降低学习率、混合预训练数据
长文本截断超过 512 token 的信息丢失分段编码后聚合,或使用长上下文模型

与 LLM 的 SFT 区分#

BERT 微调是任务特定微调:一个任务一个分类头,输入是单句或句对,输出是类别或跨度。大语言模型的 SFT 通常指指令微调:输入是“指令 + 上下文”,输出是目标文本,用自回归交叉熵训练,一个模型可以适配多种任务。

两者共享“预训练 + 有监督微调”的思路,但架构、输出形式和任务组织方式不同。LLM SFT 的具体流程见 LLM 有监督微调。

相关阅读#

BERT 微调实践:情感分析
https://biscuit0613.github.io/posts/nlp/06-09-bert-finetune/
作者
Biscuit
发布于
2026-10-20
许可协议
CC BY-NC-SA 4.0
大模型预训练:整体框架与模型架构
大模型训练系统:显存、并行与数据