在你正式跑起微调脚本之前,先花二十分钟做一件看起来有点反直觉的事:不要急着把整理好的数据丢进训练循环,而是先拿一小撮开发样本,把模型当前的真实水平摸清楚。我说的就是给模型建立“微调前基线”——用一个像 SST-2 这样的经典任务,从开发集里抽 64 条样本,就能把能力基线和格式基线一次性测明白。
为什么要做这件事?因为微调小模型的绝大多数翻车现场,都不是训练没跑完,而是你根本不知道模型在任务上的起点在哪里。是压根不会做这道题,还是会做但输出格式乱成一团,还是已经会了只是边界判断差一点——这三类问题对应的微调策略完全不同。先测基线,再决定要不要调、怎么调、数据怎么构造,这才是正常的流程。
这篇文章我就拿 SST-2 的 64 条开发样本为例,把基线测试的完整思路、实操步骤、结果解读,以及从基线到微调参数决策的整个链路讲清楚。不管你是准备给 Qwen、Llama 这类小模型做 LoRA 微调,还是想把某个领域数据灌进去让模型更听话,这套方法都直接能用。
1. 微调前先建立基线,到底在测什么
1.1 能力基线:模型到底会不会这道题
能力基线回答的问题很朴素:在完全不微调的情况下,模型拿这个任务能得多少分。
比如 SST-2,任务是给一句电影评论判情感极性,正面或负面。零样本情况下,一个 1.5B 参数的指令微调模型,直接拿这段评论去问它“正还是负”,它大概率能答对一部分。这部分“不需要训练就有的能力”,就是模型自带的任务理解力。
这个数字非常重要,但它通常被忽略。很多人拿到任务后的第一反应是“先微调”,好像微调是把一个完全不会的模型教会。实际不是这样的——小模型经过指令微调后,对很多常见任务已经有一定程度的理解,只是稳定性和边界控制不好。你先测出这个起点,才能判断微调是“在已有能力上做校准”,还是“从零开始教它一个新能力”。
举个具体例子。我用 Qwen2.5-1.5B-Instruct 在 SST-2 的 64 条开发样本上做过一次零样本摸底,准确率在 70% 上下。这意味着它已经懂“电影评论情感分类”这件事了,只是有大概三成样本判断不稳。这种情况下微调的重点就不是“教会它分类”,而是“让它在你的数据分布上更准、更稳定”。
反过来,如果你测出来的准确率接近随机(50%),那说明模型对任务完全没有概念,这时候再往下微调,你就得重新考虑任务设计、标签定义、样本量,甚至是不是该换更大的底座模型。同样的训练预算,用在这两种不同起点上,结果会差非常多。
1.2 格式基线:模型能不能“好好说话”
第二个维度是格式基线。这个经常被低估,但它在实际工程里比能力基线更容易坑人。
格式基线回答的问题是:模型输出的东西,你的下游能不能直接消费?拿 SST-2 来说,你期望的答案就是 “Positive” 或 “Negative” 二选一。但真实模型会怎么输出?我见过花式百出的情况:
- 输出 “Positive.” 带个英文句号
- 输出 “The sentiment of this review is positive.” 一整句话
- 输出 “正面的” 中文混杂
- 输出 “Positive, with high confidence” 还带附加说明
- 输出一段分析,最后才给出结论
- 甚至有时候温度稍高,它会换着花样输出 “Positive / Negative” 都给你列出来
如果你的微调训练数据是严格按 “输入 + 标签” 构造的,而模型在推理阶段延续这种发散输出的习惯,那你的评估脚本、后处理解析、上下游系统全部都要跟着遭殃。更麻烦的是,格式问题会在微调之后被放大——训练数据如果格式统一,模型会学到统一格式;训练数据如果不统一,模型输出会更乱。
所以格式基线一定要测,而且要带着明确的“可解析率”指标来测:64 条样本里有多少条输出能精确匹配到你预定义的答案集?这个数字如果连 80% 都不到,你首要解决的不是能力问题,而是格式对齐问题。
1.3 为什么偏偏是 64 条开发样本
你可能想问:为什么不用更多样本?或者为什么不用训练集?
三个理由。第一,开发集是模型没见过的数据,用它测出来的结果才代表模型的真实泛化能力。用训练集测基线,等于开卷考试,没有参考价值。第二,SST-2 的开发集总共 872 条,抽 64 条出来只占 7.3%,跑一轮推理在 1.5B 到 3B 的小模型上不过几十秒,成本几乎可以忽略。第三,64 条这个量级在统计上刚好能支撑你做分层分析——按正负标签均衡抽样,每类 32 条,再看模型在不同类型样本上的表现差异,这个信息密度在一两百条样本范围内就已经很充足了。
再补一句,64 条不是一个神圣的数字。你完全可以用 32 条或者 128 条,原则只有一条:正负样本均衡,且能覆盖你关心的样本类型。我之所以锁定 64,是因为它在“快速”和“可靠”之间是个很舒服的中间值——比 32 条稳定,比 128 条更快。
2. 搭建能力基线:SST-2 探测实验怎么做
2.1 数据准备与模型加载
先准备数据。Hugging Face 的 datasets 库可以直接加载 GLUE 里的 SST-2:
from datasets import load_dataset dev = load_dataset("glue", "sst2", split="validation") print(len(dev)) # 872 # 按标签均衡抽样,各有 32 条 pos = dev.filter(lambda x: x["label"] == 1).select(range(32)) neg = dev.filter(lambda x: x["label"] == 0).select(range(32)) sample = pos.concatenate(neg).shuffle(seed=42)注意 shuffle 时固定一个随机种子,这样你后续微调、复测用的都是同一批样本,结果才可对比。如果你后续要拿这 64 条做微调后的回归测试,最好保存一份快照到本地。
模型加载不用多说,按你手头资源选。如果是纯 CPU 推理,1.5B 级别的小模型也扛得住,就是慢一点。有 GPU 更好,batch size 开大点一次性跑完。
2.2 推理参数与提示模板
提示模板是基线测试里最影响结果的一环。我常用的模板很简单:
Review: {sentence} Question: What is the sentiment of this review? Options: Positive, Negative Answer:这里有两个细节。第一,输出约束尽量写清楚,把候选答案直接列出来,让模型在限定集合里做选择,这能显著降低格式混乱的概率。第二,推理温度设成 0(或使用贪婪解码),保证输出是确定性的——基线测试要的是可复现,不是多样性。max_new_tokens 给 16 就够,不用多,给多了反而容易引出附加解释。
from transformers import pipeline pipe = pipeline("text-generation", model="Qwen/Qwen2.5-1.5B-Instruct", device=0) prompt_template = ( "Review: {sentence}\n" "Question: What is the sentiment of this review?\n" "Options: Positive, Negative\n" "Answer:" ) # 跑完 64 条,收集原始输出,先别急着判对错 outputs = [] for ex in sample: p = prompt_template.format(sentence=ex["sentence"]) out = pipe(p, max_new_tokens=16, temperature=0.0, do_sample=False)[0]["generated_text"] outputs.append(out)收集原始输出后,先把“判断对错”和“解析格式”分开,这是基线测试里一个重要的原则。你先把所有输出原样存下来,然后两步走:先看格式可解析率,再看解析后的准确率。这两个指标分开统计,才能定位问题到底出在哪一层。
2.3 指标设计与结果解读
能力基线的主指标是准确率和宏平均 F1。因为抽样是均衡的,精确率和召回率也都能算,但宏平均 F1 更有参考价值——它能反映出模型对正负两类是否对称。
我实测时发现一个典型现象:模型往往对 Negative 类别的召回率偏低。很多带否定词、转折结构的评论,模型容易误判成 Positive。比如 “good cast, but a bad script” 这种句子,前半段是正面词,后半段才是真正的负面标签,模型很容易被前半段带偏。这种 64 条样本里的规律,往后大概率会在你的业务数据里重演。
所以务必做分层分析:按句子长度分桶(短句 < 10 词、中句 10-20 词、长句 > 20 词),按是否含否定词分桶,按是否含强烈情感词分桶。这个分析不用写复杂代码,pandas 分组统计一下就行。每一层都记录准确率,你就能得到一张“模型的优劣势地图”。
这张地图直接作用在微调数据构造上:如果长句容易错,你的训练数据里就要多放长句;如果否定句容易错,就要加强这类样本的占比。没有基线测试,你根本不知道往哪个方向加重采样。
3. 格式基线的全面体检
3.1 输出可解析性测试
格式基线的第一个指标是“可解析率”:64 条原始输出里,有多少条能解析成我们预定义的答案标签。
这里我给一个简单的判定逻辑:去掉首尾空白、去掉句号、统一小写之后,判断输出是否属于 {positive, negative, positive., negative.} 这个集合。属于就算可解析,不属于就记成一次格式失败。
我用 Qwen2.5-1.5B-Instruct 跑出来的可解析率一般在 90% 以上,主要失败模式是输出 “Positive, with high confidence” 或 “The sentiment is positive”。这些小尾巴会让严格匹配失败。但注意,不同模型的格式习惯差异很大,有些小模型特别爱“解释性输出”,解析率可能直接掉到 70% 以下。
可解析率的统计逻辑类似这样:
def is_parseable(text): t = text.strip().lower().rstrip(".") return t in {"positive", "negative"} parseable = sum(is_parseable(t) for t in outputs) print(f"Parseable rate: {parseable / 64:.2%}")这个数字很诚实。它直接告诉你,如果现在立刻接一个自动化下游系统,有多少样本会被卡在解析环节。
3.2 温度敏感性与指令遵循
确定性的贪婪解码只是第一步。微调后的模型在真实推理时往往使用采样(temperature > 0),所以格式基线还要测一件事:温度对输出稳定性的影响。
同一批 64 条样本,跑一遍 temperature=0.7 的采样,看答案漂移有多严重。我的经验是,好的微调模型在同一 prompt 下,温度升高后应该保持主答案稳定,只是措辞略有变化。如果模型在温度 0.7 下频繁在 Positive 和 Negative 之间横跳,甚至开始输出大段分析文本,说明模型对这个任务的内在置信度很低,微调时要通过大量强化格式的样本来纠偏。
还有一个更容易被忽略的测试:指令遵循反向测试。你把模板里的 Options 从 “Positive, Negative” 改成 “Negative, Positive”,看模型是跟着选项顺序走,还是坚持“它觉得对”的答案。如果模型完全无视选项顺序,说明它对这个任务有自己的内隐判断,而这种判断有时反而是噪音。这个测试不用跑满 64 条,抽 10 条看看趋势就够了。
3.3 定位格式问题的根因
格式基线不合格时,先别急着骂模型。大部分格式问题有三个来源:
第一种是提示模板本身太弱。模板里没有明确说“只输出一个词”,模型就默认按对话习惯补一句解释。这时候先用 prompt 工程解决,往模板里加一行 “Output only the selected option, no explanation.”,格式就稳很多。
第二种是底座模型本身偏好附加解释。有些模型在指令微调阶段就被训成了“话痨”,它的输出习惯很难靠 prompt 压制。这种只能靠微调来纠正——训练数据里全部用严格的“输入到标签”格式,模型见过足够多的干净样本后,会逐步收敛输出风格。
第三种是解码参数问题。max_new_tokens 给太多、温度给太高,都会诱发冗余输出。基线测试时先把这些变量固定成“最严格模式”,后续再逐步放宽。
所以格式基线的完整结论应该是这样一句话:“在我用的这个提示模板、这个解码参数下,模型的输出可解析率是多少,失败模式长什么样,以及我把模板改成什么之后能优化到多少。”带着这个结论去设计微调数据,你就不会盲目堆样本量了。
4. 从基线到微调:参数与策略的决策参考
4.1 能力基线决定 LoRA 配置
基线测试做完,你就会面对一个最直接的问题:这个模型到底值不值得微调,以及用多大的力气去调。
我把结果分为三档。第一档,零样本准确率已经有 70% 以上,说明任务能力基本在线,缺的是分布对齐和稳定性。这种场景用 LoRA 就很轻量:rank 设 8 到 16,学习率 1e-4 到 2e-4,训练 1 到 3 轮就够。不需要大动干戈,重点是把数据质量和格式统一性做好。
第二档,准确率在 55% 到 65% 之间,模型对任务半懂不懂。这种场景建议 rank 设 16 左右,学习率 2e-4,训练轮数放到 3 到 5 轮。同时注意训练数据里要保证足够的“关键样本”覆盖——把基线下层分析中表现差的那几类样本加大比重。
第三档,准确率贴着随机线(50% 上下),甚至低于随机。这种情况我建议你先停下来,别急着烧算力。重新检查任务定义——标签设计是不是合理?prompt 是不是产生了歧义?数据抽样本身有没有出问题?如果这些都排除了,模型还是不会,那可能需要换一个更大的底座模型,或者通过继续预训练去补基础能力。LoRA 微调不是万能的,它对“模型已经会但还不够好”的任务增益最大,对“从零掌握复杂语义规律”的任务增益很有限。
下面是实际项目里常用的配置参考表:
| 基线准确率 | 建议数据量 | LoRA rank | 学习率 | 训练轮数 | 微调目标 |
|---|---|---|---|---|---|
| 70% 以上 | 数百到数千条 | 8-16 | 1e-4 | 1-3 轮 | 分布校准与格式对齐 |
| 55%-65% | 数千条 | 16 | 2e-4 | 3-5 轮 | 能力提升与稳定 |
| 接近随机 | 重新审视任务 | 不建议直接调 | - | - | 先解决任务定义 |
这个表不是金科玉律,每个底座模型的脾气不一样,只是一个经过多次验证的起点。但从基线结果出发做决策,总比拍脑袋定参数靠谱得多。
4.2 格式基线决定数据构造方式
格式基线直接决定了你的训练数据长什么样。这是我反复强调的一点:微调数据的构造方式,不应该照抄别人的模板,而应该根据你基线测试里看到的失败模式来定制。
如果你的基线可解析率低,失败模式是“输出整句解释”,那你的训练数据里就要大量使用“输入到标签”的严格格式,让模型反复看到“看到模板就只吐标签”的样例。你甚至可以在每个样本的 instruction 部分写明 “Answer with the label only.”,让格式约束成为任务的一部分。
如果你的失败模式是“多标签输出”(把 Positive 和 Negative 都列出来),则在数据构造时要刻意避免任何带多个候选标签的文本,同时可以在 loss 计算时只对答案部分做监督,忽略 prompt 部分的 loss。这样做能让模型把注意力集中在学习输出的尾端。
另外,标签词汇本身也要统一。SST-2 可以有两个版本:英文标签 “Positive / Negative” 或中文标签 “正面 / 负面”。基线测试时试一下哪种标签词模型输出更稳定,微调时就用哪个。这个小实验花不了两分钟,但能省掉很多后续解析的麻烦。
4.3 微调后的回归验证
基线测试的最后一个用途,是给微调效果提供对照坐标。
微调跑完之后,把同一批 64 条开发样本原样跑一遍,对比前后的准确率和可解析率。如果准确率上升、可解析率保持高位,说明微调方向正确。如果准确率上升但可解析率崩了,说明训练数据格式不统一,模型学到了坏习惯。如果两个都原地踏步,说明这次微调基本无效,需要回查数据质量、学习率、训练轮数。
这里有个经验提醒:不要只看微调后的绝对准确率,要看和基线的差值。如果你零样本基线准确率是 72%,微调后变成 75%,虽然数字不算高,但三个点的提升可能就是数据质量的真实信号。如果你基线只有 55%,微调后跳到 68%,那这次微调效果就很显著。没有基线对照,你连“效果显著”这种基本判断都做不出来。
回归测试用同一批样本还有一个额外好处:你能看到每条样本的前后变化。模型从错变对是进步,从对变错则是退化。把退化样本单独拉出来看,往往能发现有意思的现象——比如模型因为微调数据里某种表述过多,学会了过度偏向某一类。
5. 实操中的常见陷阱与排查实录
5.1 常见问题速查表
我在多个微调项目里反复遇到一些典型问题,整理成一张速查表,方便你对照排查。
| 现象 | 可能原因 | 排查方法 | 建议处理 |
|---|---|---|---|
| 零样本准确率远低于随机 | 提示模板有误导;标签映射错误;数据抽样不均衡 | 换个模板重测;检查抽样代码 | 修正模板;重新抽样 |
| 可解析率偏低,输出带解释 | 模型习惯附加输出;prompt 约束不足 | 看输出的完整文本;跑一次带强约束的模板对比 | 修改模板加入明确限制;微调时用严格格式 |
| 同一批样本两次测试结果不一致 | 推理未固定种子;温度非 0;模型量化波动 | 确认 do_sample=False;固定随机种子 | 基线测试统一用贪婪解码 |
| 正负类准确率差距很大 | 模型对某一类有系统性偏见;数据分布不均 | 查看混淆矩阵和分层分析 | 微调时加重弱势类采样 |
| 微调后准确率提升但格式崩坏 | 训练数据格式不统一;标签词汇不一致 | 检查训练数据的 output 字段是否有噪声 | 清洗训练数据;统一标签词 |
| 微调后出现明显退化样本 | 训练数据与开发样本冲突;过度拟合 | 对比前后答案变化;检查训练集是否有重复样本 | 查重;降低学习率或轮数 |
这里面最常踩的坑,其实是“数据泄漏”。SST-2 这类公开数据集,语料已经存在很多年了,模型的预训练语料里很可能已经包含部分相关文本。所以零样本准确率会显得偏高,但这不是模型真实任务能力的体现。如果你做的是业务场景,一定要用自己业务场景里真实的开发样本做基线,而不是拿公开数据集的结果当作模型真实水平的全部证据。
顺带一提,这类“先测后调”的思路并不只适用于文本分类。无论是让大模型输出结构化 JSON,还是做信息抽取、问答、知识库问答的结构化回答,都需要微调前先跑一遍基线把输出的形状摸清楚,再做数据构造的决策。
5.2 三个值得长期坚持的习惯
最后分享三个我在实操中沉淀下来的习惯,它们帮我躲过了不少坑。
第一个习惯:基线测试永远保留原始输出。不要只记录准确率就完事,把 64 条原始输出保存成一个 JSON 文件,后续做格式分析、找失败模式要用。哪怕当时看完整输出很费眼,这些原始痕迹所有后续决策都离不开。
第二个习惯:抽样的种子号也写进实验记录。哪怕只是微调一个小模型,也要有基本的实验管理意识。模型版本、抽样种子、prompt 版本、温度参数、测试日期,这些一条都不能少。我发现很多团队复现问题,不是复现不了,而是根本没记录当初是怎么抽的样。
第三个习惯:在业务数据上也要建“固定回归集”。从你的真实业务数据里挑 50 到 100 条多样性足够强的样本,固定下来,每次微调前后都跑一遍。公开数据集测的是模型常识推理能力,回归集测的才是你的业务落地指标。这两个分数结合着看,对模型整体状态的判断就立体多了。
这套 64 条开发样本的基线方法,我自己在 Qwen、Llama 这些常见小模型上都验证过,改动最小、见效最快。微调开始前,花二十分钟把能力基线和格式基线都摸透,再回头调整数据构造和训练参数,你会明显感觉到整个微调过程踏实很多——因为你做的每一步都有了依据。