经验驱动。这是一次 bert-base-chinese 的文本分类微调,留下了两个真实 checkpoint。最有意思的地方在于:第 2 个 epoch 的准确率比第 1 个高(0.9054 → 0.9114),但验证损失也更高(0.2533 → 0.2625)。准确率和损失朝相反方向走,这说明什么?本文所有数字来自 trainer_state.json 的原始记录。

一、实验设置

项目值
基座bert-base-chinese(21128 词表 / 12 层 / 768 隐层)
任务电商评论文本分类(BertForSequenceClassification)
计划 epoch3(max_steps: 8442)
实际停止第 2 个 epoch,step 5628
train batch size16
验证 / 保存 / 日志频率每 500 / 500 / 50 步
早停EarlyStoppingCallback(early_stopping_patience=2)
保存策略load_best_model_at_end,最终保留 checkpoint-5628

先注意一个细节:计划跑 3 个 epoch(8442 步),实际停在 5628 步,正好是第 2 个 epoch 结束。说明早停机制介入了——虽然配置里写的是 patience=2,但训练确实没有跑满。

二、核心异常:准确率升了,损失也升了

BERT 情感分类:准确率与损失的背离

两个验证点的完整指标:

epochstepeval_accuracyeval_f1eval_loss
128140.90540.90500.2533
256280.91140.90850.2625

基于 argmax 的两个指标(accuracy、F1)都在涨,但损失在涨。

这说明什么

交叉熵损失不只关心”选对没选对”,它关心概率分布的形状。所以准确率涨 + 损失涨,通常意味着:

模型把更多样本分对了,但对分错的那些样本变得更加自信了。

具体讲:epoch 1 时分错的样本,模型可能给 0.45 vs 0.55 这种接近五五开的概率,损失不大;到 epoch 2,它把一部分原本分对的样本进一步推向了正确类的极端(损失降低),但对分错的样本也敢给出 0.9 vs 0.1 了(损失暴涨)。净效果是 argmax 正确的个数略增,但总损失上升。

这是过拟合早期的一种典型形态——比”准确率下降”出现得更早。如果只看准确率,你会觉得”还在涨,继续训”;看了损失才知道,模型已经开始在训练集上过度自信了。

三、训练过程里的其他信号

训练损失早就平台化了

从 500 步之后,train loss 基本在 0.19 ~ 0.30 之间震荡,没有任何持续下降趋势:

1
2
3
4
5
6
7
8
9
10
step 2650  loss 0.2172
step 2700 loss 0.2451
step 2750 loss 0.2519
step 2800 loss 0.2660 ← epoch 1 结束
...
step 5400 loss 0.2037
step 5450 loss 0.2113
step 5500 loss 0.2289
step 5550 loss 0.2084
step 5600 loss 0.2247 ← epoch 2 结束

第 2 个 epoch 的训练损失并没有比第 1 个更低——模型已经从训练数据里学不到新东西了。这和验证损失的上升是同一件事的两面。

梯度范数有多次尖峰

训练全程 grad_norm 大部分在 1~10 之间,但出现了几次明显尖峰:

stepgrad_norm
90054.23
330023.19
405018.59
495020.86
510021.31

单点尖峰(而不是持续增大)通常是个别困难样本或噪声样本造成的,不是训练不稳定。但如果尖峰频繁出现且幅度持续增长,就要检查数据里是不是有脏样本,或者考虑加梯度裁剪。

四、一个容易忽略的部署坑:config.json 没记标签

翻 checkpoint-5628/config.json 时会发现:

1
2
3
4
5
6
7
{
"architectures": ["BertForSequenceClassification"],
"model_type": "bert",
"hidden_size": 768,
"num_hidden_layers": 12,
"vocab_size": 21128
}

没有 num_labels,没有 id2label,没有 label2id。

这意味着:模型文件本身不告诉你 index 0 是负面还是正面。真要部署,你必须自己去训练脚本或数据里确认标签顺序,否则很容易把正负类搞反——准确率看着 0.91,实际全反了。

这是我在这次实验里踩到的实打实的坑。保存模型时一定要显式写入标签映射:

1
2
3
model.config.id2label = {0: "负面", 1: "正面"}
model.config.label2id = {"负面": 0, "正面": 1}
model.save_pretrained(output_dir)

五、这次实验的四条结论

  1. 准确率和损失要一起看。单看准确率会漏掉”过拟合早期”这个信号。理想情况是两个都降;准确率涨但损失涨,就该警惕了。
  2. 早停值得配。这次 3 个 epoch 的计划实际只跑了 2 个,省下的不只是时间,更是避免了第 3 个 epoch 的进一步过拟合。
  3. train loss 平台化比 eval loss 上升更早出现。训练损失不再下降的时候,其实就已经可以准备收了。
  4. checkpoint 的 config.json 要检查标签映射。这是部署阶段最容易翻的车,而且翻得无声无息。

六、和这个系列其他篇的关系

三篇合起来是一条完整的线:怎么训(方式)→ 训多久(轮数/早停)→ 怎么判断训坏了(指标背离)。


关于本文数据:来自 Week11/week11_output_model/bertbase/bert_emotion_classifier/checkpoint-2814 与 checkpoint-5628 下真实的 trainer_state.json(共 114 条训练日志 + 2 条验证记录),以及 checkpoint-5628/config.json。训练脚本使用 🤗 transformers Trainer。