微调到底是怎么工作的
在你自己的数据上继续训练一个现成的模型。它会改变什么、改变不了什么,以及为什么数据集就是全部工作。
微调是训练,不是写 prompt。真实的梯度更新、真实的参数改变,最后得到一套新的权重。
它与预训练的唯一区别是规模和起点:从一个已经很强的模型出发,在一个小得多、也更聚焦的数据集上继续训练。
机制
和预训练一模一样,只是更短。给模型一个例子,让它预测,对比,调整。
重要的差别在于例子长什么样。预训练用的是原始文本,微调用的是你任务格式下的输入-输出对:
{"input": "Customer says the app crashes on launch",
"output": "{\"category\": \"BUG\", \"severity\": \"HIGH\", \"component\": \"startup\"}"}损失通常只算在输出那部分上。你不是在教模型生成你的输入,而只是教它在给定输入时产出正确的输出。
有两个超参数承担了大部分风险。学习率必须比预训练时用的低得多;太高,模型会在它原本会的一切上退化。轮次(epoch)——也就是把你的数据过了多少遍——应该小,通常一到三。遍数多了就是死记,而不是泛化。
数据集就是全部工作
其余都是机械操作。数据集质量决定结果,也几乎吞噬掉全部精力。
**一致性比数量更重要。**一百个互相一致的例子,胜过一千个互相矛盾的例子。标注不一致会让模型认为这个任务本身有歧义,于是它就会产出有歧义的输出。
**覆盖边界。**例子应该包含你会做错的情况,而不只是显而易见的那些——这和选 few-shot 例子是同一个原则,只是放到了更大的规模上。
**贴合生产分布。**精挑细选的例子比现实干净。如果真实输入里有错别字、截断和混合语言,你的训练数据也应该有。
**格式与推理时完全一致。**服务时用什么包装,训练里就必须出现什么包装。这里对不上,会带来莫名其妙的性能不佳。
**把拒答和边界情况放进去。**如果有些输入没有有效答案,就要展示出来。否则模型会学到「每个输入都有答案」。
实际的最低量:分类、格式化这类窄任务,几百个例子;只要涉及风格或判断,就得上千个。
灾难性遗忘
这是它最典型的失效。在一个窄任务上狠训,模型在这个任务上变好了,其他所有方面却变差了——包括你一直依赖的通用指令遵循能力。
原因通常有三:学习率没有压低,轮次太多,或者数据集相对改动幅度来说太窄了。
缓解办法:学习率压低、轮次减少,在任务数据里混入一些通用指令数据,以及使用只动到极少权重的参数高效方法。除了任务本身,也要在通用能力上评测——否则这个回归在生产环境之前都是隐形的。
它擅长什么
**格式与结构的可靠性。**当写 prompt 只能做到 95%、而你需要更高的时候。
**语气与风格。**用文字说明几乎不可能,演示起来却很简单。
**大流量的窄分类任务。**在一个定义清晰的任务上,一个小的微调模型能追平一个大得多的、靠 prompt 驱动的模型,而且每次调用便宜得多。
**降低成本。**把一长串 prompt 搬进权重,就等于把它从每次请求里拿掉了。量大时,这一个理由就够了。
**领域惯例。**专门的记号和写作范式——是形式,不是事实。
它不擅长什么
**教事实。**这是最关键的局限,在 RAG 还是微调?里有详细讨论。一条只出现过几次的事实,会变成一个微弱的模式,重建出来的东西几乎对——比没有更糟,因为它跟真正的知识无法区分。
**任何会变的东西。**每次更新都意味着再跑一轮训练。
**给基础模型补上它没有的能力。**微调是在重新分配已有的能力,它不会凭空创造出原本不存在的推理能力。
微调之前
按顺序试:更好的 prompt 写法、few-shot 例子、更强的模型、受约束的输出。
如果例子就管用,你不用训练也能拿到答案。只有当这些手段都停滞、而量出来的差距还在时,才去动微调——这意味着你动之前得有一个评测,否则你无法判断它到底有没有帮上忙。
还要为重复工作留出预算。基础模型会更新,而你的微调结果不会自动跟上。
要点回顾
- 微调是在输入-输出对上做真训练,损失通常只算在输出上。
- 数据集质量就是全部工作:一致性优先于数量、边界用例、贴合生产的分布、完全一致的格式。
- 学习率要低、轮次要少——否则灾难性遗忘会在你毫无察觉的情况下拉低通用能力。
- 擅长格式、语气、窄分类和降低成本;不擅长事实、会变的信息,以及缺失的能力。
- 先把 prompt 和 few-shot 用尽,动手之前先有一个评测。
下一篇: LoRA 与参数高效微调