
我准备对一个 1.5B 级别的小模型做 SST-2 情感分类微调动手之前先按流程测了个基线。第一次做的时候没有这一步LoRA 跑完准确率从 52% 升到 88%当时觉得是调参调对了。后来出于好奇把同一批开发样本喂给未微调的基座发现零样本就已经能到 85%。也就是说那次微调的净收益只有 3 个百分点不是 36 个百分点。这就是基线缺失造成的最典型误判。这篇想分享的是一套很轻量的做法用 SST-2 开发集中挑出的 64 条样本在微调前给小模型建立两条基线——能力基线它到底会不会做这个任务和格式基线它能不能按要求稳定输出。适合谁手里只有 1B~3B 小模型、想在有限算力上做 LoRA 微调的人以及被“微调完效果提升多少”这种问题困扰过的朋友。文章最后会给一张决策表告诉你什么样的情况值得微调什么样的情况换提示词或换基座更划算。1. 先搞清楚基座“本来就会”什么再谈微调很多人在微调前没有任何评估直接拿训练集开跑这种行为我见得太多。开源小模型在海量语料上预训练过SST-2 这种经典二分类任务太常见了。一个 1.5B 模型很可能已经见过大量“电影评论 情感标签”的语料所以它不一定需要你教你只需要把已有的能力用提示词激发出来。没搞清楚这一点就微调最后得到的“提升”可能只是幻觉。1.1 你无法从名称判断一个开源模型会不会做 SST-2只看模型名字你根本猜不出它在情感分类上的表现。同系列不同尺寸的模型差距非常大有的 7B 模型在简单情感句上反而输给某些经过对齐的 1.5B 模型有的 3B 模型对“positive/negative”这两个词特别敏感换一个等价说法就崩掉。更麻烦的是预训练语料不一定包含 SST-2 本身但一定包含大量的影评和评价文本模型对“正负面情感”这个概念的把握程度只能通过一次受控测试来量化。这里说的“受控测试”不是拿几条样本跑一下看个热闹。你要知道的是如果任务本身模型已经会了微调的数据组织方式就得完全不同如果模型只是勉强会用LoRA 的作用就变成了稳定输出如果模型完全不会那要担心的就不是 LoRA 参数而是基座选错了。1.2 微调基线解决的不只是准确率问题我习惯把基线拆成两条分开记录能力基线在给定提示下模型能不能正确区分 positive 和 negative。格式基线在给定输出约束下模型能不能一直按你预期的格式返回结果。这两条经常被混在一起。你可能会看到某个模型准确率还行但输出永远是 “Positive.”、“positive sentiment”、“pos” 这类变体解析器没法统一处理。如果没有格式基线你设计微调数据时就会很盲目到底要不要加“只输出标签”的强调要不要用 JSON要不要在系统提示里写格式示例这些决策完全取决于基座在零样本下的格式稳定性。1.3 为什么偏偏是 64 条64 不是个数量的执念而是工程上的权衡。对二分类任务来说正负各 32 已经能覆盖不少情况包含否定句、转折句、长句、口语化表达之后你至少能发现“模型是否存在结构性失效”。而且评估脚本要反复调模板、调温度、调种子用完整 dev 集 872 条来跑每次迭代要多花好几倍时间。在小模型 CPU 推理的环境里这个差距会被放大到让人失去耐心。64 条样本是为了筛错不是为了发论文。它的定位是快速回答三个问题这个基座能不能做这个任务输出格式稳不稳值不值得进入 LoRA 微调流程后面如果要出正式对比报告再扩到完整 dev 集不迟。2. 64 条开发样本的构造抽样逻辑决定基线可信度从 SST-2 dev 集里抽 64 条听起来很简单但抽样方式直接决定基线结论靠不靠谱。你绝不能从原始 dev 文件里直接取前 64 条因为 GLUE 的 dev 文件不是按难度均匀排列的前几十条往往存在主题或句式上的偏向。取偏了基线的“高”或“低”都没有参考价值。2.1 直接取前 64 条会得到什么我试过偷懒直接切前 64 条结果 64 条里有 21 条都带 “great”、“amazing” 这种高频正面词。零样本测试准确率高达 92%看起来很强换一批样本测立刻跌到 78%。这不是模型不稳定是样本集没代表性。反过来如果前几条恰好都是否定句式、转折句你又会得出“模型不会做情感分类”的错误结论。SST-2 的样本分布大概有这样一个特点短句常常是直白的褒贬长句才有转折和反讽。忽略句子长度分布基线就会偏向某一种难度。2.2 分层抽样先按标签分再按长度分我的做法是三步按标签把 dev 分成 positive 和 negative 两组。每一组内部按 token 长度分成三桶短10、中10~20、长20。设定随机种子从每个桶里按比例抽取最后合并成正负各 32 的样本集。参考代码如下import pandas as pd import random df pd.read_csv(dev.tsv, sep\t, headerNone, names[label, text]) # GLUE 版 dev.tsv 中 label 是 0/1先映射成 sentiment label_map {0: negative, 1: positive} df[sentiment] df[label].map(label_map) df[token_len] df[text].str.split().str.len() def bucket(x): if x 10: return short elif x 20: return mid return long df[bucket] df[token_len].map(bucket) selected [] for senti in [negative, positive]: sub df[df[sentiment] senti] for b in [short, mid, long]: bucket_df sub[sub[bucket] b] n round(32 * len(bucket_df) / len(sub)) selected.append( bucket_df.sample(nn, random_state42) ) sub_dev pd.concat(selected).sample(frac1, random_state42) print(sub_dev[sentiment].value_counts()) print(sub_dev[bucket].value_counts())然后我会把抽出来的样本手工过一遍。重点看有没有出现连续十几条同样句式的情况以及否定词、转折词、比较级、讽刺语气的样本占比是否明显低于真实分布。如果明显偏低就手动替换几条保证 64 条不是“均匀随机”的壳子。2.3 加入控制组模板语义与标签语义分离分层抽样之外我会在 64 条之外单独准备一份控制组大约 8~12 条。控制组的用途是区分“模型理解了任务指令”和“模型只是对情感词产生了表面联想”。比如一条真实样本是 “A great movie with a silly plot.”控制组可以改成 “A fine film with a silly storyline.”甚至换成 “这部电影还不错但剧情很傻” 这样的中文表述。如果模型在真实样本上准确率不错但控制组准确率骤降说明它依赖的是训练语料里高频触发词而不是真正在做正负判别。控制组不计入基线准确率它的作用是提醒你微调时需要补充更泛化的数据而不是堆更多相同句式的样本。3. 能力基线怎么测零样本、少样本和置信度有了 64 条开发样本接下来就是正经的测量。测量工具很简单一个生成函数 一批固定模板。真正花功夫的是理解测试结果背后代表的能力层次。3.1 零样本测试的第一句话是模板零样本测试不是把评论直接丢给模型而是必须构造合理的提示。同一句话不同模板可能带来超过 10 个百分点的准确率波动在小模型上尤其明显。我的建议是先测一个自己最接近真实部署场景的模板再测一个“填空式”模板因为填空往往更稳定。参考模板def build_prompt(text): return ( 请阅读下面这条电影评论判断它的情感倾向。\n f评论{text}\n 答案 )生成时设置温度 0、贪心解码限制 max_new_tokens8然后截取模型输出的第一个词做映射from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_name Qwen/Qwen2.5-1.5B-Instruct tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained( model_name, torch_dtypetorch.float16, device_mapauto ) def predict(text): prompt build_prompt(text) inputs tokenizer(prompt, return_tensorspt).to(model.device) with torch.no_grad(): out model.generate( **inputs, max_new_tokens8, do_sampleFalse, temperature1.0, ) answer tokenizer.decode(out[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue) return answer.strip().lower()这样跑完 64 条你会得到原始准确率。注意别在第一次跑的时候就把模板改来改去模板一改之前跑的结果就不能直接并列比较。3.2 少样本演示的边际收益曲线零样本只是起点我更看重“少样本演示”的边际收益。从开发集剩余样本里挑 2、4、8 条带正确答案的演示塞进提示里然后观察准确率怎么变化准确率随 k 上升明显比如从 60% 到 82%说明模型底层表示是够的只是需要被“点拨”一下。这种情况微调成本很低少量数据就能见到效果。准确率不升甚至下降说明模型对任务格式和语义都不太适应。这时候盲目加 LoRA 数据也只是在强行记住训练集换一批样本大概率掉点。演示样本不能从待测的 64 条里选否则等于开卷考试。我习惯从 dev 剩余样本里随机选保持和运行环境一致。少样本演示的收益曲线还能告诉你另一件事如果你只有几十条标注数据到底该拿去当“演示样例”还是“训练数据”。如果模型靠 4 条演示就能大幅提升说明问题出在“触发”而不是“学习”那你完全可以先不微调靠提示工程上线。3.3 logits 不只用来算准确率还可看“敢于作答”的程度准确率只是结果我更关心模型的置信度形态。具体做法是取生成第一个 token 时刻的 logits看 positive 和 negative 两个候选 token 的概率差inputs tokenizer(build_prompt(text), return_tensorspt).to(model.device) with torch.no_grad(): logits model(**inputs).logits[:, -1, :] log_probs torch.log_softmax(logits[0], dim-1) pos_prob log_probs[tokenizer.convert_tokens_to_ids( positive)].exp().item() neg_prob log_probs[tokenizer.convert_tokens_to_ids( negative)].exp().item() conf_gap abs(pos_prob - neg_prob)如果一条样本模型答对了但 positive/negative 概率差只有 0.02那它本质上是在猜只是运气好而已。我会把每个样本的置信度记录下来分别算“答对样本的平均置信度”和“答错样本的平均置信度”。两者差距越大说明模型确实在区分不是瞎蒙。两者都很低说明模板或者基座有问题即使准确率超过 80% 也要警惕。下面是一张我用来记录能力基线的示意表格数字量级贴合常见小模型但不必对应具体版本模型零样本准确率4 条演示后准确率答对平均置信度答错平均置信度Qwen2.5-1.5B-Instruct84.4%87.5%0.780.55Llama-3.2-3B-Instruct76.6%81.3%0.610.52某 7B 基座模型90.6%90.6%0.880.63注意准确率接近时置信度形态能帮你选出更值得微调的基座。4. 格式基线小模型在“怎么回答”上比“答得对不对”更容易翻车能力基线看内容格式基线看容器。小模型在怎么回答这个问题上翻车率远高于普通人预期。你要求模型“只输出一个标签”它偏偏给你输出一整句解释你要求它输出 JSON它给你写一段 Markdown 列表。格式基线的目的就是精确测量这种浪费。4.1 让模型输出 positive/negative 其实不是格式约束严格来说让模型输出 “positive” 或 “negative” 并不算格式约束因为这两个词在预训练语料里天然高频。真正的格式问题是模型是否愿意不加修饰、不带标点、不附带解释地把标签词交出来。我在小模型上经常见到三种输出“Positive”“positive sentiment”“The sentiment of this review is positive because the movie is great.”第一种好处理第二种用前缀匹配也能勉强解析第三种基本没法在短输出场景里直接使用。这些都在格式基线的统计范围内。4.2 三种输出约束的通过率对比在小模型上做格式测试时我习惯把输出约束分成三个等级分别统计通过率输出约束典型失败形式实际用途纯标签词带句号、带解释、输出同义词快速推理、低算力部署JSON 键值键名写错、引号缺失、嵌套多余字段供下游程序直接消费中文标签输出“正面”和“正面情绪”混用面向中文业务场景一份 1.5B 模型的格式基线通常长这样温度 0 时纯标签词通过率 85%一到采样解码掉到 60%JSON 通过率可能只有 40%。这说明模型不是不会输出格式是对格式的记忆太浅一旦解码引入随机性就露馅。4.3 格式失败不是小概率事件很多人把格式失败归因为“prompt 没写清楚”但在小模型上这更像是指令遵循能力的边界问题。模型在预训练阶段最擅长的是“续写”你要求它严格输出标签词时它仍会顺着语言模型的惯性往解释性文本上走。格式基线具体测法是固定同一批 64 条样本分别用贪心解码和温度 0.7 的采样解码各跑一次统计三种解析规则下的通过率精确匹配、前缀匹配、正则抽取。然后记录“同一句话在采样下产生了几种不同输出形式”比如同一句话第三次输出 “positive”第四次输出 “positive.”。这一步做完你就知道微调数据里“格式约束样本”到底要占多大比例。5. 基线与 LoRA 微调的衔接一张决策表搞定起步配置基线的意义不是好看而是指导下一步动作。把能力基线和格式基线交叉可以得到四种组合每种组合对应的微调策略完全不同。5.1 决策表能力基线、格式基线交叉后该做什么能力基线格式基线我的处理方式高85%且置信度健康稳定优先尝试提示工程微调非必需真要微调就用小秩、小学习率轻量校准中65%~85%且少样本能涨稳定值得微调重点提升难例LoRA 秩设 8 左右中65%~85%但少样本不涨不稳定先换模板和基座微调时在数据里加强格式约束低60%且少样本不涨乱严肃反思基座选型问题微调大概率只是记住训练集表面模式看起来很朴素但这张表是我频繁踩坑后的经验总结。通常情况下能力弱 格式乱是最难救的LoRA 会强行让模型输出符合训练集的标签词但换一批样本很快就掉。此时与其加数据不如换一个更适合的基座。5.2 LoRA 参数从哪开始设如果决策表告诉你“值得微调”我会用这组参数作为起点r: 8 alpha: 16 target_modules: [q_proj, k_proj, v_proj, o_proj] lr: 3e-4 batch_size: 16 epochs: 3 max_length: 128为什么 SST-2 用 r8 就够因为二分类任务需要的适应性变化不多主要改变的是输出层的偏好和格式记忆。如果你想同时解决格式乱的问题可以适度把 r 提到 16给模型更多自由度去模仿你给的格式样本。学习率不建议超过 5e-4小模型在几百条数据上很容易过拟合微调后回到开发样本上掉点的情况很常见。微调数据组织上我的原则是如果格式基线不稳训练样本统一使用“评论xxx\n答案标签”的结构并在开头加一句格式约束如果格式基线很稳就少放格式样板把资源花在难例上。5.3 微调后必须跑同一份基线微调跑完之后评估脚本必须和微调前完全一致。同一批 64 条、同一个模板、同一个种子、同一个解析脚本。然后记录两个数微调前零样本准确率、微调后准确率。两者之差才是模型真实的增量。我见过不少项目微调后拿测试集一测说提升了 20 个点但根本没跑过零样本对照。真实情况是基座本来就会一半以上微调只是补齐了模板差异。用 64 条开发样本做基线最大的价值就在这里你能给自己的实验一个诚实归因。6. 64 条样本的局限性方差、模板敏感和过期习惯64 条样本不完美你要清楚它的边界。我自己用它的过程中有几个坑必须单独说。6.1 小样本方差会把 1% 的差距变成“看起来的 3%”64 条样本的二分类准确率是一个带噪声的估计。64 条里答对 54 条是 84.4%答对 52 条是 81.3%差的这 2 条会让人误以为两个模型差 3 个百分点。但在统计意义上这两个结果可能根本没有显著差异。所以我做小样本评估时不会因为一两条的差距就换模型。我会在同一批样本上跑多个随机种子、多个演示顺序取平均结果再下结论。如果时间允许让温度 0.7 采样再多跑一遍观察波动范围。6.2 同一个模板在另一个模型上不 workSST-2 的中文提示模板在 Qwen 上很好用换到 Llama 上可能准确率掉 10 个点英文模板则反过来。这在小模型上特别明显因为不同基座的对齐语言和习惯差异很大。因此每个模型都应该有一个自己的基线模板版本。我通常把模型名、模板、种子、解码参数打包成一个版本字符串比如 baseline_v3_qwen15_zh所有后续对比都基于这个版本。改模板、改种子后的结果不参与历史对比否则一开始记录的就是一笔糊涂账。6.3 基线的“保质期”基座更新后一切重新测开源小模型迭代很快今天测的 Qwen2.5 基线不代表几个月后的 Qwen3 也适用。新基座可能在指令遵循、格式稳定性上有明显变化也可能在某些能力上反而退化。我在本地跑量化小模型时也发现同一个模型在 FP16 和 4bit 量化下格式通过率会有差异。如果你最终部署的是量化版本基线就应该在同一个量化版本上测而不是用全精度结果代替。否则微调完部署上线格式输出变样了你根本分不清是模型问题还是量化问题。6.4 什么时候该把 64 条扩成完整开发集64 条只解决“该不该微调”“怎么组织微调数据”这两个问题。如果已经决定微调并且需要评估最终模型效果就把它扩到完整 dev 集。到那时你手里已经有可信的 64 条基线做参照完整开发集上的提升数据才有说服力。我个人的习惯是每次拿到新模型、新任务第一件事不是开训练脚本而是写一个 eval_baseline.py 跑二十分钟。这个习惯让我砍掉了很多没必要的微调。有两个任务测完发现零样本已经 90% 以上直接跳过微调上线还有一个任务表面看着简单基线只有 51%后来换基座比调 LoRA 参数有用得多。64 条样本不神圣神圣的是你有一个能反复对照、诚实归因的基线。微调前花这二十分钟比微调后花两天解释那些说不清的提升要划算得多。