Skip to content
This page has been auto-translated and may contain errors.View in English

微调 ​

docs.scrimba.com

**微调**指的是拿一个已有的模型,继续用你自己的样例来训练它,让它的默认行为向你想要的方向偏移。这一章要讲清楚的是:这样做到底改变了什么,它适合用在什么地方,以及什么时候该用它而不是用提示词或检索。

训练到底做了什么 ​

要弄清楚微调改变了什么,先得知道模型是从哪儿来的。一个模型的诞生分为两大阶段。

第一阶段是**预训练:模型读取海量文本,反复学习预测下一个词元,直到语法、事实和推理模式都作为副产品沉淀在了模型里。这样得到的是一个基础模型**,它很流畅,但没有方向感。它会很乐意地把你的文本接下去写,但没人教它去当一个能回答问题的、有用的助手。

第二阶段是后训练:在基础模型的基础上,用精心整理的优质回答样例继续训练,再配合关于哪个答案更好的人类反馈。正是这一步把一个单纯预测文本的模型,变成了你实际对话的那个会听指令的助手。你从 API 调用的模型,两个阶段都已经走完了。

这两个阶段用的是同一套机制:给模型看样例,微调它内部的数字——也就是它的参数或权重——让它的预测越来越接近这些样例。训练就是预测练习,而这种练习会永久地改变模型。

Juno训练到底做了什么 一个模型的诞生分两个阶段。预训练让它在海量文本上反复预测下一个词元,产出一个流畅但没有方向的基础模型。后训练接着用精心整理的优质回答和人类反馈继续训练它,把它变成一个有用的助手。两者用的是同一种方式:调整模型内部的权重,让它的预测和展示给它的样例相匹配。

一个模型要经过两个训练阶段才能到你手上,了解这两个阶段各自的样子,就能知道微调能撬动的是哪部分。

预训练是第一步:模型读取海量文本,反复做同一件事——预测下一个词元——做上几十亿次。语法、世界知识和推理模式作为副产品沉淀在权重里。结果是一个基础模型,擅长把文本接续下去,但还不是一个助手。它不知道该去回答一个问题,而不是去续写它。

后训练是第二阶段,也是微调所模仿的那个阶段。基础模型会在精心整理的优质回答样例上继续训练,再配合"哪个答案更好"的人类偏好信号。正是这一步产生了你从 API 调用时体验到的、听指令行事的行为。

两个阶段跑的是同一个循环:给模型一个样例,把它的预测和目标对比,再调整权重去缩小差距。微调就是这同一个循环,只不过缩小到你自己的样例上,并且是在提供方完成那两个阶段之后才运行的。训练是通过编辑权重来改变行为的,而不是靠添加一张查询表——正是这个区别,决定了微调在某些事情上好用,在另一些事情上不好用。

Juno训练到底做了什么 让一个模型到你手上要经过两个阶段:预训练把预测下一个词元这件事练进一个流畅的基础模型里,然后后训练用精心整理的样例和偏好数据把它塑造成一个助手。两者跑的是同一个循环——给一个样例,把权重往它那边调——而微调就是把这个循环缩小到你自己的数据上。它编辑的是权重,不是添加一张查询表,这也是为什么它能很好地改变行为,却不擅长存储事实。

一个模型要经过两个训练阶段才能问世,微调正是生长在这两个阶段的交界处。

预训练是那个昂贵、每个模型只做一次的阶段:巨量的文本语料,单一的目标是预测下一个词元,权重(模型可调整的数字)作为副产品吸收了语法、知识和推理能力。产出的是一个基础模型,它很流畅,但没有被引导去"有用"。后训练接着用精心整理的示范和人类偏好数据,把这个基础模型对齐成一个助手。等你调用一个 API 模型的时候,这两步都已经跑完了。

微调是后训练的延续,只是缩小到了你自己的数据上,并且是在一个已经对齐过的模型之上运行的。机制完全一样:给出一个样例,衡量预测离目标有多远,调整权重来缩小这个差距。真正决定实际操作方式的后果是:微调动的是同一套支撑其他一切能力的全局参数。你不是在追加一个模块,而是在拨动一个共享的表面——这正是为什么一次微调可能在提升你的目标任务的同时,悄悄削弱一个不相关的能力。

这种"共享表面"的特性,是这一章后面所有失效模式的根源:过拟合、遗忘、漂移。微调重塑的是行为,它从来不会安装一个事实库。记住这一点,你就会为了正确的理由去用它,也会为了错误的理由而放弃它。

Juno训练到底做了什么 预训练是每个模型只跑一次的重头戏,把预测下一个词元这件事练进一个基础模型里;后训练把它对齐成一个助手;而微调是在你的数据上延续那第二个阶段。每次都是同一个循环:给出一个样例,把权重往它那边调。麻烦在于,你拨动的是共享的全局参数,不是外挂一个模块,所以一次微调可能同时提升你的任务、又损伤一个不相关的任务——这正是过拟合和遗忘的来源。

微调到底是什么 ​

微调是那第二阶段的一个小型、有针对性的版本,由你自己来运行。你拿一个已经训练好的模型,继续用一组你自己的样例来训练它,每个样例是一个输入,配上你希望模型给出的输出。样例喂得够多之后,模型的权重就会偏移,让这种风格的回答变成它的默认表现,而不需要你每次在提示词里都重新要求一遍。

它和提示词、RAG 真正的区别在于:改变发生在哪里。提示词和 RAG 让模型保持不变,通过你在调用那一刻提供的上下文去引导它。微调改变的是模型本身,所以新的行为被固化进去了,即便提示词很短也会体现出来。你不是在运行时给出更好的指令,而是直接交付了一个本身就倾向于你想要的方向的模型。

实际操作中,你把一份样例对话的文件交给提供方,它跑一个训练任务,然后返还给你一个新的**模型 id**。你调用这个 id 的方式和调用任何其他模型完全一样,只是它给出的答案现在会反映你训练时用的那些内容。微调把行为搬进了模型里,提示词和 RAG 只是在引导一个不变的模型。

Juno微调到底是什么 微调是你自己运行的一个小规模后训练:在已有模型上,用你自己的输入-输出样例继续训练,直到你想要的行为变成它的默认表现。和提示词、RAG 通过上下文引导一个不变的模型不同,微调改变的是模型的权重,所以行为被固化进去了,即便提示词很短也会体现出来。你提交一份样例文件,换回一个新的模型 id,用法和调用其他模型一样。

微调是缩小到单一任务、由你自己运行的后训练。你从一个已经训练好的模型出发,继续用样例训练它,每个样例都是一个输入,配上你希望它当时给出的输出。喂给它足够多的样例,权重就会稳定下来,让那种回答成为默认表现,提示词里不再需要专门去要求它。

真正重要的分界线是:引导发生在哪里。提示词和RAG让权重保持不变,通过调用时提供的上下文去塑造行为,所以每一次调用都要携带那些指令。微调是提前一次性编辑权重,所以行为直接封装在**模型 id**里,即便提示词很短也能保留下来。运行时引导 vs 固化好的行为:这就是你在做的取舍。

具体的工作流程是这样的。你整理一份样例对话的文件,启动一个训练任务,提供方会返回一个新的模型 id。你调用这个 id 的方式和调用其他模型一样。

python
# 每个样例是一段对话,格式和你在运行时发送的一模一样,
# 结尾是你希望模型学会给出的输出。
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"},
    ]},
    # ... 还有成百上千个,覆盖你预期会遇到的各种输入
]
# 消息的格式和训练任务的 API 因提供方而异,但思路是一样的。
Juno微调到底是什么 微调是缩小到你的任务上的后训练:在输入-输出样例上继续训练一个模型,直到你想要的行为变成默认表现。提示词和 RAG 是每次调用都通过上下文引导一个不变的模型,微调是一次性编辑权重,让行为随着模型 id 一起交付。你搭建一份样例对话文件,跑一个训练任务,然后像调用其他模型一样调用返回的 id。

微调是在你自己掌控的数据集上延续后训练,产出一个挂在新 id 之下的衍生模型。每个训练样例都是一个输入配一个目标输出,训练任务会不断微调权重,直到你想要的目标回答变成模型的默认表现。这种行为直接封装在权重里,所以一个很短的提示词就足以唤起它。

把它和其他方案精确地对照一下,因为这个差别是操作层面的,不是哲学层面的。提示词和RAG让权重保持不变,通过**上下文**(模型在调用时读取的文本)来注入行为,这意味着你只需要编辑一段字符串、几秒内重新部署就能改变行为。微调把行为固化进权重里,这意味着要改变它,就需要一次全新的训练任务、一轮评估,以及重新部署一个新的模型 id。运行时引导可以在几秒内编辑,而微调只能在一个训练周期内才能编辑。

python
# 微调后的模型是另一个 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"  (只是这个标签,这正是你训练进去的行为)
# 这里展示的调用形式是 OpenAI SDK 风格;不同提供方会有差异。

在你投入之前,一定要先想清楚这个成本上的不对称性。一个微调过的 id 是一个有版本号的产物:它的生命周期、评估、以及每次期望行为发生变化时的重新训练,都归你负责。把这个 id 固定下来、给它打版本号,并且要预算好——它学到的内容每变化一次,就是又一次训练任务,不是又一次代码提交。

Juno微调到底是什么 微调是在你自己的数据上延续后训练,产出一个挂在新 id 之下、行为封装在权重里的衍生模型。和提示词、RAG 的对比是操作层面的:运行时引导是一段可编辑的字符串,几秒内就能重新部署,而微调是一个有版本号的产物,只能通过一次训练任务、一轮评估、一次重新部署来改变。把这个 id 固定下来,把它当作合同对待,并且要预算好——每一次行为变化都是又一次训练任务。

它擅长什么,不擅长什么 ​

当你需要一种提示词很难拿捏住的**一致行为**时,微调就有了用处:每次都要用同一种精确的输出格式,某种特定的语气或者品牌风格,或者是像分类这样一个狭窄的任务、要在成千上万次调用中都可靠地完成。它还能让调用变得更便宜、更快,因为原本要用很长的提示词才能说清楚的行为,现在被固化进了模型里,你的提示词也就能变短了。

它的短板在知识上。微调很不擅长教模型学习新的事实,遇到会变化的事实就更糟了。训练样例会模糊地融合成一般性的模式,而不是被存成精确的、可查找的条目,所以一个微调过的模型仍然会编造细节,一旦你的信息更新了,它照样会过时。每次价格或政策一变就重新训练一遍,又慢又贵。当问题是"模型不知道 X"时,答案几乎总是RAG,不是微调。

一条经验法则:用微调来改变形式,不要用它来添加事实。教模型怎么去回答,把"用什么内容去回答"这件事交给检索来处理。

Juno它擅长什么,不擅长什么 微调适合用在一致的行为上:固定的输出格式、特定的语气,或是可靠完成的一个狭窄任务,而且它能缩短提示词,让调用成本更低。它不擅长知识:它不能可靠地存储事实,一旦事实变化就会过时,而且重新训练很贵。经验法则是:用微调改变形式,不要用它添加事实,事实交给 RAG 处理。

微调在**形式**上能带来回报:每次调用都保持精确的输出格式,固定的语气或品牌风格,或者是像分类这样的狭窄任务,在成千上万次请求里都稳如一。当单靠提示词一直不稳定——模型大多数时候遵守格式,但没到能可靠地被下游解析的程度——这个收益就会被放大。把行为固化进去之后,格式就不再是你每次调用都要祈祷才能得到的东西了。

它还能缩短提示词,规模一大,这就是一个实实在在的成本杠杆。如果你现在每次请求都要在提示词里把格式和几个示例讲清楚,那些指令性的词元每次调用都要计费。把它们折叠进权重里之后,每次调用的提示词就会缩短。这笔账值得算一算:一次微调的训练成本是固定的、只付一次,而一个长提示词的成本是按次付费、永远要付,所以只有当调用量大到省下的词元成本能超过训练费用时,缩短提示词才划算。

它的短板在知识上。训练样例会模糊地融合成一般性模式,而不是精确的、可检索的条目,所以一个微调过的模型仍然会编造细节,一旦价格或政策变化,它照样会过时。更新一个事实意味着又要跑一次训练。用微调来处理形式,用RAG来处理事实,然后把两者结合起来。最强的模式是两者同时使用:微调模型来定形式和语气,然后在运行时检索最新的事实,让微调后的模型去转述它们。

在你决定要不要爬到微调这一层之前,先确认提示词真的失败了。"失败"指的是:一个清晰的指令加上几个精心挑选的示例,在各种各样的输入上测试过,仍然达不到目标准确率——不是说你第一版一句话的提示词看起来不太行。如果你还没做过这个测试,那你还没到该微调的时候。

Juno它擅长什么,不擅长什么 微调适合用在形式上:固定的输出格式、设定好的语气、稳定完成的一个狭窄任务,外加更短的提示词、每次调用都省词元。成本账是"训练一次性付清"对"提示词永远按次付费",所以调用量大的时候缩短提示词才划算。它在事实上比较差,事实会模糊成模式并且会过时,所以把微调模型和 RAG 搭配起来:形式交给权重,事实交给检索。而且要在爬这一层之前证明提示词确实失败了——真正的提示词加示例,在各种输入上测过。

微调在形式上很可靠,在事实上不可靠,原因是机制性的。形式——输出结构、语气、任务惯例——是一种可以在大量样例中被权重吸收的行为规律。事实是离散且会变化的,权重只能把它们有损地存成混合在一起的模式,而不是可检索的行;所以一个微调过的模型仍然会杜撰内容,一旦底层事实发生变化,它就会立刻过时。

再往深挖一层,真正的风险在于微调会以哪些方式退化。**过拟合**是模型死记硬背了你的训练集,连它的怪癖也一并记下来,而不是学到一般性的行为,所以它在你的样例上表现完美,遇到有差异的输入就露怯。灾难性遗忘是另一面:在一个狭窄任务上训练太狠,可能侵蚀基础模型原本具备的、不相关的通用能力——因为你动的是共享的权重,所以你的分类器变准了,但它在其他方面的推理却变钝了。分布漂移是慢性的:一个基于上个季度输入分布调优的模型,随着真实流量偏离它当时看到的数据而逐渐失效,而这种失效在你测量之前是无声的。

这两个后果决定了你实际该怎么操作这件事。第一,你不能凭感觉发布一次微调;你需要一个评估集——一批留出来的、有已知正确答案的输入,在微调前后各评分一次,来证明目标真的提升了,同样重要的是,证明通用能力没有退化。第二,每一次期望行为的变化都是又一整个任务:重新整理数据、重新训练、重新评估、重新部署一个新 id。正是这种操作上的拖累,才让参数高效微调方法有了存在的意义——这类方法通过只训练一小组新增的权重而不是整个模型来实现微调,运行和存储的成本更低,尽管"要不要微调"这个决策本身并不会因此改变。无论用哪种方法,在发布之前都要证明这次微调没有让效果退步。

Juno它擅长什么,不擅长什么 形式能被权重吸收,事实不能:一次微调仍然会杜撰内容、仍然会过时,所以把事实留在检索里。风险包括过拟合(死记硬背你的训练集,遇到新输入就露怯)、灾难性遗忘(狭窄任务的训练侵蚀了不相关的能力,因为权重是共享的),以及分布漂移——随着真实流量偏离你的训练数据而发生。所以每次微调都要用前后对比的留出评估集去把关,证明没有退化,并且要预算好——每次行为变化都是一整个"重新训练-评估-重新部署"的周期,这正是参数高效方法存在的全部理由。

提示词、RAG,还是微调? ​

大多数时候你不该从这一步开始。这三种技术构成了一个**阶梯**,最便宜、最快能改的排在最前面:

  1. 提示词。 永远先试这个。它能瞬间迭代,不产生额外成本,一个清晰的提示词加上几个例子就能解决比人们预期的更多的问题。如果你还没让提示词发挥作用,那你还没到该微调的时候。
  2. RAG。 当差距在于知识——模型需要它没有的事实,或者是会随时间变化的事实——就该用它。RAG 在提问的那一刻提供这些信息,不需要动模型本身。
  3. 微调。 当差距是行为而不是知识,而且提示词无法让它足够一致时,或者当你的提示词已经变得太长,以至于把指令固化进模型对于规模化调用来说更便宜、更快时,就该用它。

它们并不是互斥的,最强的系统会把它们结合起来:先微调一个模型来定形式和语气,然后用 RAG 在运行时喂给它最新的事实。行为来自训练,知识来自检索。

所以在微调之前先做一个快速测试。换个更好的提示词能不能解决问题?能,那就去做那件事。差距是缺失的知识吗?那就是 RAG 该做的事。

沿着阶梯往上爬;微调是最后一级,不是第一级。只有当提示词已经写对了、事实已经能拿到了,模型仍然不能足够一致地表现出你想要的行为时,微调才是适合的工具。

Juno提示词、RAG,还是微调? 把这三者当作一个阶梯:先用提示词,因为它瞬间见效又不花钱;当模型缺知识时加上 RAG;只有当差距是提示词无法让其一致的行为时,或者是为了在规模化场景下缩短冗长的提示词时,才用微调。它们搭配起来效果很好:微调定形式,RAG 提供事实。微调是你最后才会想到的一步,不是第一步。

这三种技术构成了一个**阶梯**,按照改起来有多快、多便宜排序,你要从底部往上爬。

  1. 提示词。 永远第一位。你能在几秒内迭代,不产生额外的训练成本,一个清晰的指令加上几个示例,能覆盖比人们想象中更多的场景。除非一个真正的提示词已经被测试过并且证明不够用,往上爬一级都是操之过急。
  2. RAG。 当差距是知识——模型缺失的事实,或者会随时间变化的事实——检索能在提问的那一刻提供这些信息,而不用动权重。更新一个事实,只是更新你的数据源,不需要重新训练。
  3. 微调。 当差距是提示词无法拿捏住的行为时,或者当提示词已经变得足够长,把指令固化进权重在规模化场景下反而更便宜时。

真正让你落在正确那一级的诊断,是给差距命名。格式或语气不对是行为上的差距,所以先用提示词,再用微调。缺失或过时的事实是知识上的差距,所以要用检索。如果诊断错了差距,你就会用微调去修一个知识问题,然后看着它过时;或者把本该由检索去取的事实硬塞进提示词里。

它们并不是互斥的,而生产环境里真正该追求的,是把它们结合起来用:微调定形式,RAG 提供事实,两者并用。一个针对你的格式和语气调过的模型,在运行时被喂上最新的事实,能同时给你一致的形式和新鲜的内容——单靠任何一种技术都做不到这一点。

至于你到底能不能微调,取决于模型本身。封闭的提供方会提供他们自己模型的托管式微调服务;而一个开放权重的模型,你可以按照自己的方式自行微调——这正是选择开放权重的理由之一(参见开放与封闭模型)。

Juno提示词、RAG,还是微调? 从最便宜的那一级开始往上爬:先用提示词,差距是知识就加 RAG,只有差距是提示词拿捏不住的行为、或者为了在规模化场景下缩短提示词时,才用微调。诊断的关键是给差距命名,格式或语气意味着行为,缺失或过时的事实意味着检索。生产环境的做法是两者并用:微调定形式,RAG 提供事实,这样你才能同时得到一致的形式和新鲜的内容。

提示词—RAG—微调这个阶梯,是按**迭代成本**排序的:一旦上线之后,改变行为有多快、多便宜。一个提示词是一段字符串,编辑之后几秒内就能重新部署。一个检索数据源是可以更新的数据,不需要动模型。一次微调是一次训练任务、一轮评估,加上一个新的模型 id。按这个顺序往上爬,意味着你只有在证明便宜的那几级确实到不了的时候,才会去付出更高的迭代成本。

真正的本领在于正确诊断差距,因为失败的模式往往是选了一个根本不对症的工具。行为上的差距(结构不对、语气不对、任务执行不稳定)该由提示词、然后是微调来修。知识上的差距(模型缺失的一个事实,或者会变化的一个事实)该由检索来修,而用微调去修知识差距是经典的昂贵错误:你把事实训练进去,下一次更新它们就过时了,而且你还没法给它标出处。

把操作层面的不对称性和这个诊断放在一起看。RAG 反复出现的成本是每次调用的检索质量和延迟,永远要付,但可以实时调整。微调反复出现的成本是每次学到的行为发生变化时都要重新跑一次,外加每次运行都要承担的退化风险。

所以两者结合的架构不是一种妥协,而是本该如此的设计:把形式微调进权重里,把事实留在检索里,各自独立打版本。形式变化不频繁,能容忍慢一点的更新周期;事实一直在变,需要快的更新周期。把两者分开,能让每一部分按自己的节奏演进——你可以重新评估微调过的模型而不用重建你的语料索引,也可以刷新语料库而不用重新跑一次训练任务。

Juno提示词、RAG,还是微调? 这个阶梯是按迭代成本排序的:提示词几秒内可编辑,数据源不用动模型就能更新,微调是一整套训练-评估-重新部署。选之前先诊断差距,行为上的差距交给提示词、然后微调,知识上的差距交给检索,而用微调去修知识差距是那种代价高昂的经典错误,会产出过时、无法标注出处的事实。生产环境的架构是故意把两者分开:把形式微调进权重里,把事实留在检索里,各自独立打版本,这样形式可以慢慢演进,事实可以快速更新。

实操 ​

微调用的数据没什么神秘之处。每个样例是一段简短的对话,格式和你运行时发送的一模一样,结尾是你希望模型给出的答案:

python
# 这里只展示了几条;真实的数据集是成百上千个样例,
# 覆盖你预期在运行时会遇到的各种输入。
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,之后你就像调用任何其他模型一样调用它:

python
# 微调后的模型是另一个 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,像调用其他模型一样调用它。

Juno实操 微调用的数据就是以你想要的答案结尾的对话样例,数量在几百到几千条之间。你上传文件,跑一个训练任务,换回一个新的模型 id,用法和调用其他模型一样。之后行为就固化进去了,提示词能变短,格式也能保持一致,代价是要构建数据集,并且每次需要改变的时候都要重新跑一次任务。

这个流程是上传、训练、调用,但真正决定它是否管用的是**数据集**,不是任务本身。质量和覆盖面比原始数量更重要。几百个干净、一致、覆盖真实输入范围的样例,胜过几千个嘈杂或自相矛盾的样例,因为模型学到的是数据里实际存在的规律——包括其中的错误。

训练之前先把数据切分开。留出一部分模型在训练时永远看不到的数据,也就是验证集,在这部分上检验准确率,而不是在训练用过的样例上检验。一个模型可能在它已经见过的数据上得分接近完美,却在任何新东西上表现很差,所以那个留出来的分数才是告诉你行为是否具有泛化能力的那个数字。

python
# 保留一部分训练任务永远看不到的数据,用来衡量真实表现。
split = int(len(training_examples) * 0.8)
train_set = training_examples[:split]        # 任务在这部分上训练
validation_set = training_examples[split:]   # 你在这部分上给结果打分
# 提供方会返回一个新的模型 id;你像调用其他模型一样调用它。
# 任务 API 和切分方式因提供方而异;有些会直接接收验证文件。

除了这些机制之外,还有两个判断需要你自己做。第一,在花一次训练任务的成本之前,先确认提示词真的失败了:要用一个真正的指令加上几个示例,在各种各样的输入上测试过,不是你的第一版粗稿。第二,仔细权衡成本。一次微调是固定的训练成本、只付一次,对比一个长提示词按次付费、永远要付的成本,所以只有当调用量大到省下的词元值得那次训练任务时,缩短提示词才划算。别忘了那个组合模式:微调后的模型负责形式,RAG 负责那些不该让模型死记硬背的事实。

Juno实操 这个任务是"上传-训练-调用",但决定结果的是数据集:几百个干净、多样的样例胜过几千个嘈杂的样例,因为模型学到的是数据里实际存在的东西。留出一份验证数据,在这部分上打分,不要用训练样例打分,否则一个死记硬背的模型看起来会比实际表现更好。先确认提示词真的失败了,权衡"固定的训练成本"和"永远要付的提示词词元成本",并把微调后的模型和 RAG 搭配起来,让它负责形式,让检索负责事实。

这条流水线是上传、训练、评估、部署,真正的工程工作在于数据集和**评估集,不在于训练调用本身。两层切分纪律撑起了这一切。一个从训练中留出来的验证集**,能告诉你行为是被泛化了,还是被死记硬背下来的。一个单独留出来、训练前后各评分一次的评估集,才能证明这次微调帮到了你的目标任务、而没有让通用能力退化——这正是上一节讲的"灾难性遗忘检查"的具体落地。

python
# 三个互不重叠的切片:一个用来训练,一个用来在训练中做决策,一个用来最终判定。
train_set      = examples[:8000]        # 任务在这部分上训练
validation_set = examples[8000:9000]    # 在运行过程中留意有没有过拟合
eval_set       = examples[9000:]        # 前后各评一次,证明没有退化

# 在同一个 eval_set 上给基础模型和微调后的模型打分,然后对比。
base_score = run_eval(base_model_id, eval_set)
tuned_score = run_eval(fine_tuned_id, eval_set)
# 只有当微调后的分数在目标任务上超过基础模型,并且在通用能力检查上也保持住,才发布。
# 评估打分和训练 API 因提供方而异;但这套纪律不会变。

数据集质量决定了过拟合和漂移是赢是输。一个狭窄或有怪癖的数据集,训练出来的模型会死记硬背你数据集里的特殊之处,遇到不同的输入就露怯,所以对真实输入分布的覆盖面比数量更重要,而且你要随着生产流量偏离你训练时用的数据而重新测量。过时的评估集,正是一个悄悄漂移的模型能混过审查、却让用户吃亏的原因。

需要为之设计的操作现实是:每一次学到的行为发生变化,都是一次完整的重新运行,不是一次编辑。每次都要:重新整理数据、重新训练、重新评估、重新部署一个打好版本号的新 id。这个反复出现的成本,再加上每次运行都伴随的退化风险,正是为什么你要把事实留在能实时更新的 RAG 里,把微调留给那些变化缓慢的行为,并把每个微调过的 id 都当作一个有版本号的产物——当新的一轮运行出现退化时,你可以回滚到它。

Juno实操 这条流水线是"上传-训练-评估-部署",真正的工作在数据切分上,不在调用本身:验证集用来抓住死记硬背,前后各评一次的留出评估集用来证明没有退化。对真实输入分布的覆盖面,是你战胜过拟合和漂移的关键,所以要随着流量偏离训练数据而重新测量。并且要为反复出现的成本做好设计——每一次行为变化都是一整套"重新训练-评估-重新部署",这正是为什么事实该放在 RAG 里,以及为什么每个微调过的 id 都是一个可以回滚的、有版本号的产物。

这是塑造一个模型知道什么、以及它怎么回答的最后一种方式了——所有这些方式,都只是让它在一张你事先安排好的桌子上预测文字。接下来的几章会赋予它行动的能力:在工具使用这一章,模型会调用你自己写的函数,然后作为一个智能体循环运行它们。