微调


**微调**指的是拿一个已有的模型,继续用你自己的样例来训练它,让它的默认行为向你想要的方向偏移。这一章要讲清楚的是:这样做到底改变了什么,它适合用在什么地方,以及什么时候该用它而不是用提示词或检索。
训练到底做了什么
要弄清楚微调改变了什么,先得知道模型是从哪儿来的。一个模型的诞生分为两大阶段。
第一阶段是**预训练:模型读取海量文本,反复学习预测下一个词元,直到语法、事实和推理模式都作为副产品沉淀在了模型里。这样得到的是一个基础模型**,它很流畅,但没有方向感。它会很乐意地把你的文本接下去写,但没人教它去当一个能回答问题的、有用的助手。
第二阶段是后训练:在基础模型的基础上,用精心整理的优质回答样例继续训练,再配合关于哪个答案更好的人类反馈。正是这一步把一个单纯预测文本的模型,变成了你实际对话的那个会听指令的助手。你从 API 调用的模型,两个阶段都已经走完了。
这两个阶段用的是同一套机制:给模型看样例,微调它内部的数字——也就是它的参数或权重——让它的预测越来越接近这些样例。训练就是预测练习,而这种练习会永久地改变模型。
微调到底是什么
微调是那第二阶段的一个小型、有针对性的版本,由你自己来运行。你拿一个已经训练好的模型,继续用一组你自己的样例来训练它,每个样例是一个输入,配上你希望模型给出的输出。样例喂得够多之后,模型的权重就会偏移,让这种风格的回答变成它的默认表现,而不需要你每次在提示词里都重新要求一遍。
它和提示词、RAG 真正的区别在于:改变发生在哪里。提示词和 RAG 让模型保持不变,通过你在调用那一刻提供的上下文去引导它。微调改变的是模型本身,所以新的行为被固化进去了,即便提示词很短也会体现出来。你不是在运行时给出更好的指令,而是直接交付了一个本身就倾向于你想要的方向的模型。
实际操作中,你把一份样例对话的文件交给提供方,它跑一个训练任务,然后返还给你一个新的**模型 id**。你调用这个 id 的方式和调用任何其他模型完全一样,只是它给出的答案现在会反映你训练时用的那些内容。微调把行为搬进了模型里,提示词和 RAG 只是在引导一个不变的模型。
它擅长什么,不擅长什么
当你需要一种提示词很难拿捏住的**一致行为**时,微调就有了用处:每次都要用同一种精确的输出格式,某种特定的语气或者品牌风格,或者是像分类这样一个狭窄的任务、要在成千上万次调用中都可靠地完成。它还能让调用变得更便宜、更快,因为原本要用很长的提示词才能说清楚的行为,现在被固化进了模型里,你的提示词也就能变短了。
它的短板在知识上。微调很不擅长教模型学习新的事实,遇到会变化的事实就更糟了。训练样例会模糊地融合成一般性的模式,而不是被存成精确的、可查找的条目,所以一个微调过的模型仍然会编造细节,一旦你的信息更新了,它照样会过时。每次价格或政策一变就重新训练一遍,又慢又贵。当问题是"模型不知道 X"时,答案几乎总是RAG,不是微调。
一条经验法则:用微调来改变形式,不要用它来添加事实。教模型怎么去回答,把"用什么内容去回答"这件事交给检索来处理。
提示词、RAG,还是微调?
大多数时候你不该从这一步开始。这三种技术构成了一个**阶梯**,最便宜、最快能改的排在最前面:
- 提示词。 永远先试这个。它能瞬间迭代,不产生额外成本,一个清晰的提示词加上几个例子就能解决比人们预期的更多的问题。如果你还没让提示词发挥作用,那你还没到该微调的时候。
- RAG。 当差距在于知识——模型需要它没有的事实,或者是会随时间变化的事实——就该用它。RAG 在提问的那一刻提供这些信息,不需要动模型本身。
- 微调。 当差距是行为而不是知识,而且提示词无法让它足够一致时,或者当你的提示词已经变得太长,以至于把指令固化进模型对于规模化调用来说更便宜、更快时,就该用它。
它们并不是互斥的,最强的系统会把它们结合起来:先微调一个模型来定形式和语气,然后用 RAG 在运行时喂给它最新的事实。行为来自训练,知识来自检索。
所以在微调之前先做一个快速测试。换个更好的提示词能不能解决问题?能,那就去做那件事。差距是缺失的知识吗?那就是 RAG 该做的事。
沿着阶梯往上爬;微调是最后一级,不是第一级。只有当提示词已经写对了、事实已经能拿到了,模型仍然不能足够一致地表现出你想要的行为时,微调才是适合的工具。
实操
微调用的数据没什么神秘之处。每个样例是一段简短的对话,格式和你运行时发送的一模一样,结尾是你希望模型给出的答案:
# 这里只展示了几条;真实的数据集是成百上千个样例,
# 覆盖你预期在运行时会遇到的各种输入。
training_examples = [
{"messages": [
{"role": "system", "content": "Classify the ticket's urgency as low, medium, or high."},
{"role": "user", "content": "I was charged twice this week and need it fixed today."},
{"role": "assistant", "content": "high"},
]},
{"messages": [
{"role": "system", "content": "Classify the ticket's urgency as low, medium, or high."},
{"role": "user", "content": "Is there a dark mode setting anywhere?"},
{"role": "assistant", "content": "low"},
]},
# ...
]你把这些存成一个文件,向提供方启动一个训练任务,然后等它跑完。返还给你的是一个新的模型 id,之后你就像调用任何其他模型一样调用它:
# 微调后的模型是另一个 id;其他一切都不变。
response = client.chat.completions.create(
model="your-org/urgency-classifier-v1",
messages=[
{"role": "system", "content": "Classify the ticket's urgency as low, medium, or high."},
{"role": "user", "content": "The export button does nothing when I click it."},
],
)
# response.choices[0].message.content -> "low" (只是这个标签,这正是你训练进去的行为)它的好处在于:行为现在已经活在模型里了。指令可以更短,格式在成千上万次调用中都能保持稳定,而你不需要靠加长提示词来做到这些。它的代价体现在周边的一切:你需要一份高质量的**数据集**、一次训练任务,并且每次想改变它学到的东西都要重新跑一个任务——这正是它排在阶梯最底部的原因。上传样例,得到一个模型 id,像调用其他模型一样调用它。
这是塑造一个模型知道什么、以及它怎么回答的最后一种方式了——所有这些方式,都只是让它在一张你事先安排好的桌子上预测文字。接下来的几章会赋予它行动的能力:在工具使用这一章,模型会调用你自己写的函数,然后作为一个智能体循环运行它们。

