日期:2026-08-16
我们团队做垂直领域大模型微调,数据是从业务里攒出来的几十万条标注样本。第一版训出来的模型在验证集上分数很好看,准确率和 F1 都漂亮,我们高高兴兴拿去上线。结果业务方用了一周就回来找,说模型对那批长尾的难判样本几乎全错,反而把简单的都答对了。那次被怼得挺尴尬,我们之前汇报还拿验证集九成多当亮点。
我们把验证集按难度分了层一看,简单样本准确率九成八,最难的那层只有六成出头。问题出在训练数据分布上,简单样本占了绝大多数,模型把精力都花在刷简单题上,难例被淹没了。这之后我们定了个规矩,任何微调模型上线前,验证集都要按难度分层看,不能再被一个均值糊弄。
这个模型用在票据要素抽取和合同款项判断这类任务上,输入长、格式杂,简单样本是标准模板,难例是那些手写模糊、字段缺失、条款嵌套的。我们要做的,是在训练阶段就把难例找出来重点练,而不是平均用力。
具体落地分几步:先给训练集每一份样本打难度分,再把高分难例过采样、低分简单样本适当降权,然后训练时按由易到难的课程顺序喂数据,最后把模型在难例上的表现单独拉出来评估,误判的再回流重新标注进下一轮。整个流程做成了一个可复用的训练流水线,新业务接入时套上就能跑,不用每次重新搭。
难例怎么自动识别是第一个难点。我们没有现成的难度标签,就用模型自己当裁判:先用基线模型跑一遍训练集,预测置信度低、或者和标注不一致的样本,大概率就是难例。这个办法简单但有效,省去了人工标难度。我们也试过用人工挑难例,成本太高,几万条样本人工过一遍不现实,所以自动打分是必须的。
课程式训练的节奏不好把控。由易到难如果切得太生硬,模型容易在简单阶段学偏;切得太慢又失去课程的意义。我们试了几种调度曲线,最后用了一个温和的线性过渡,前三分之一纯简单样本打底,中间混着加难例比例,后段难例为主,效果比一次性混合喂好。这个曲线我们拿三个不同业务的数据集都验证过,结论一致,说明不是偶然。
难例过采样有个坑,就是过拟合。我们把难例的过采样倍数压在三点五倍以内,同时加了一点数据增强,避免模型死记硬背那几十条难例。回流再训练时只回流人工确认过的误判,不让模型自己标的数据直接进训练,防止噪声放大。有次偷懒跳过人工确认,结果难例层反而掉了一点点,之后老老实实加回人工把关。
这个办法上线后我们建了一套难例监控,每周把模型在难例层的表现拉成趋势图,一旦某类难例的准确率掉下来就触发复盘,而不是等业务方投诉。我们还把难例样本单独存了一份黄金集,每次模型迭代都先在这份集上跑一遍回归,确保新版本不会把长尾短板又漏掉。数据版本也跟着管起来,哪一轮训练用了哪批难例、效果如何,全链可追溯,后续排查问题方便很多。
用难例挖掘加课程式训练之后,最难那层样本的准确率从六成二拉到了八成四,整体验证集只涨了一点,但业务真正在乎的长尾误判率降了快四成。训练收敛的轮次反而少了,因为难例被集中练,不用刷那么多遍简单样本。我们在三个业务数据集上做了同样的处理,难例层平均提升约二十个点,说明这个办法不是碰运气。
案例片段(已脱敏): 难度打分与采样的一段配置(yaml 示意): difficulty: method: baseline_confidence threshold_low: 0.9 threshold_high: 0.6 curriculum: schedule: linear oversample_hard: 3.0 max_epoch: 8 一轮训练日志:难例层准确率 62% 到 84%,验证集 macro-F1 从 0.91 到 0.93,训练轮次由 12 轮降到 8 轮,长尾误判工单下降 38%。同方案在另外两个数据集上难例层分别提升 19 和 22 个点。
微调这事我现在的习惯是,先把验证集按难度分层再看指标,别被一个均值糊弄了。均值九成不代表难例也九成,业务踩坑的全在长尾那层。我们那次被业务方怼回来,根子就在只看均值。
课程式训练省算力是真的,但节奏别搞太复杂。我们一开始想上各种花式调度曲线,调参调得头疼,最后回归到一个简单的线性过渡,效果不差还好看懂。工程上能简单就别上复杂,这条在好几个地方都验证过。
难例回流一定要人工把关。我们有一次偷懒让模型自己标的误判直接进下一轮,结果把几条标错的噪声也带进去了,难例层反而掉了一点点,后来老老实实加人工确认才稳。自动化的前提是人工先把住这道关,这点和数据质量本身是绑定的。