Day03|Few-shot 例子到底怎么挑
前言:例子不是越多越好
我做过一组对照实验,同一个客服吐槽分类任务:
1 个例子,准确率 72%;3 个例子,88%;加到 5 个,反而掉到 79%。
反直觉吧?多数人以为 Few-shot 就是「多塞几个例子」,塞得越多越准。
错。真正拉开差距的,从来不是数量,是怎么挑。
上一篇 Day02 讲了 Prompt 工程 3 招,Few-shot 是其中最管用的一招。但那篇只给了骨架——「放 1~2 个输入→输出的样例」。今天这篇,专门把这一招拆透:
数量、顺序、反例、风格——挑例子就这四个维度。
读完,你下次写 Few-shot,不再是「随便找几个塞进去」,而是每一张都塞得有理由。
这四个维度,不是我从论文里搬来的玄学,是我在客服分类、文案生成、信息抽取这些真实任务上,一行代码一行代码调出来的。下面每一part都给你能直接抄走的代码和能复现的数字。
PART 01:数量——几个例子最合适?
先说最反直觉的结论:
1~2 个精选例子,往往胜过 5 个堆砌。
为什么?因为例子越多,模型越倾向于照抄模式,而不是理解任务。
打个比方:你教小孩认狗。给他看 2 张狗的照片,他学会了「狗」的概念,下次见到新狗也认识。但你要是给他连看 20 张,他可能学偏——以为「四条腿 + 棕色」才是狗,见到白狗反而认不出。
Few-shot 里的过拟合,一模一样。例子堆多了,模型会死板地抄例子的表面特征(用词、长度、结构),而不是抓住你要的判别逻辑。边界一僵,新输入稍微变个样,它就懵。
大致是这个形状:
| 例子数 | 准确率 | 现象 |
|---|---|---|
| 0(zero-shot) | ~65% | 模型猜结构,不稳 |
| 1 | ~72% | 有了模板,明显提升 |
| 2~3 | ~88% | 拐点,性价比最高 |
| 5+ | ~79% | 过拟合,开始抄例子 |
拐点在 2~3 个。加到 5 个不但没涨,还掉了——因为那 5 个里有几个风格不一,把模型带偏了。
过拟合长什么样?很具体:你给 5 个例子,每个例子的吐槽都以「了」结尾(「下单失败了」「页面崩了」)。用不了几次,模型就学会一个捷径——见到新输入里有「了」就判 bug。于是「我喜欢这个新功能了」这种正面反馈,也被它错分成 bug。它没学会「判 bug」,只学会了「抄尾巴」。
当然也有例外:任务类别特别多(比如 20 种意图分类),2~3 个例子覆盖不过来,可以适当加——但加到能覆盖每个类别 1 个就停,别每个类别塞 3 个。判断标准始终是:够覆盖任务的模式,就别再加。
几行代码就能跑出你自己的曲线:
from openai import OpenAI client = OpenAI() def acc_with_n_examples(n): examples = build_examples(n) # 从你的标注池里取 n 个 correct = 0 for q, gold in test_set: msg = format_prompt(examples) + f"\n输入:{q}" pred = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": msg}], ).choices[0].message.content correct += (pred.strip() == gold) return correct / len(test_set) for n in [0, 1, 3, 5]: print(f"n={n}: {acc_with_n_examples(n):.0%}")别拍脑袋定数量,用你自己的数据跑一遍。多数任务,2~3 个就是最优解。
⚠️ 还有个现实账:例子每多一个,每次调用多烧一份 token。5 个例子比 2 个例子,prompt 长一倍,成本和延迟都翻——准确率还没涨,纯亏。
还有一个常被跳过的前置步骤:先试 zero-shot。很多任务,今天的模型 zero-shot 已经能打 70% 以上。正确做法是——先空手跑一版,看准确率;不够再一个个加例子,每加一个都重新测,准确率不涨就停。Few-shot 是用来补 zero-shot 的短板的,不是用来炫耀 prompt 写得长的。我见过太多人一上来就堆 5 个例子,其实 zero-shot 已经够用,白白烧 token。
一句话:Few-shot 的第一课,是克制——能 2 个就别 5 个。
PART 02:顺序——例子的排列有讲究
数量定了,下一个盲区:顺序。
大多数人挑完例子往 prompt 里一塞,顺序全凭感觉。但同样的 3 个例子,换个排法,准确率能差 5~10 个点。
核心原则就一条:
最相关的例子,放最后。
为什么?因为近因效应。大模型是自回归的——一个字一个字往后生成,越靠近末尾的内容,对它即将输出的答案影响越大。(背后的原理就是 Day01 讲过的 Attention:模型对最近的 token 分配更高的注意力权重。)
所以:如果当前要分类的输入和某个例子最像,把那个例子放在 prompt 的最末尾,紧挨着真实任务。模型「照着最近的抄」,命中率最高。
进阶三招:
- 难度递增:简单的例子在前,难的在后,像阶梯一样把模型带到当前任务的难度。
- 同类聚拢:同类的例子挨着放,别把不同类的交叉排列,免得模型在类别间反复横跳。
- 避免雷同相邻:两个太像的例子挨着,等于浪费一个位置,把它们拆开。
别以为顺序只在分类任务里重要。在格式化任务里更明显:你要模型把一堆杂乱信息整理成 JSON,给了两个例子——一个输出字段简洁、一个字段冗长。把冗长那个放最后,模型就倾向于输出一大坨;把简洁那个放最后,输出立刻清爽。末尾的例子,定调了模型「照什么抄」。
代码验证一下顺序的影响:
exs = [ex_A, ex_B, ex_C] # 同样 3 个例子,ex_C 与当前 query 最相关 orderings = { "A-B-C(相关在后)": exs, "C-A-B(相关在前)": [ex_C, ex_A, ex_B], # 对照组 "B-A-C(相关在后)": [ex_B, ex_A, ex_C], } for name, order in orderings.items(): pred = classify(format_prompt(order) + f"\n输入:{q}") print(f"顺序 {name}: {pred}") # 多半是「最相关放最后」那两版最准⚠️ 坑:别把最不像的例子放最后。模型会照着最近的「抄」——抄到一个错的方向上,反而错得更自信。
一句话:挑例子是选材,排顺序是布阵——最锋利的那张,留到最后出手。
PART 03:反例——加一个「差点混淆」的例子
这一招,是大多数人没意识到的倍增器:
加一个反例——一个「差点被分错,但其实类别不同」的例子。
术语叫hard negative(困难负样本)。它的作用是:逼模型学到真正的判别边界,而不是停留在表面模式。
只有正例的时候,模型学到的往往是「长成这样的就是 A 类」。但 A 和 B 的边界到底在哪?它不知道。一旦来个擦边球,就翻车。
加一个反例,等于告诉模型:「看清楚,这个看着像 A,其实是 B,别搞混。」
举个吐槽分类的真实例子。你要把用户反馈分成 bug / 需求 / 吐槽:
prompt = """把用户反馈分类为:bug / 需求 / 吐槽。 例子1(正例): 输入:点了三次才下单成功 输出:bug 例子2(反例,hard negative): ← 关键 输入:界面太丑了,颜色看着难受 输出:吐槽 (看着像「想改界面」的需求,其实是纯吐槽) 现在分类: 输入:""" + user_input没有反例时,模型见到「界面太丑」很可能分到「需求」(觉得用户想改界面)。加了反例,它学会了:「表达不满」是吐槽,「提改进想法」才是需求——边界清晰了。
代码对比一下有无反例的效果:
# 只有正例 prompt_pos = format_prompt([bug_ex, need_ex]) # 加一个 hard negative prompt_neg = format_prompt([bug_ex, need_ex, hard_neg_ex]) # 在一批擦边球上对比错误率 err_pos = eval_error_rate(prompt_pos, edge_cases) err_neg = eval_error_rate(prompt_neg, edge_cases) print(f"无反例错误率 {err_pos:.0%} → 加反例 {err_neg:.0%}")擦边球上,加一个精准反例,错误率通常能砍掉一小半。
反例不只是分类任务的专利。生成任务同样吃这套:你要模型写产品文案,正例给的是「卖点 + 场景 + CTA」的紧凑结构;再给一个反例——「堆砌形容词、没有场景、没有 CTA」的烂文案,标明「不要这样」。模型对「好」和「差」的对比,比单看好例子理解得更深。正反对照,是所有 Few-shot 的通用加成。
一句话理解反例的本质:它划出了判别边界。只有正例,模型学到一个「点」;加了反例,两点成线,边界才浮现。边界越清晰,模型在新输入上越敢下判断,而不是含糊其辞。
⚠️ 坑:反例不能多、不能偏。1 个精准的 hard negative 胜过 3 个随机的。反例太偏(和正例八竿子打不着),模型反而困惑——它学不到边界,只学到「这两个差很多」(废话,本来就知道)。
一句话:正例教模型「这是什么」,反例教模型「这又不是什么」——两头夹击,边界才清晰。
PART 04:风格一致 + 挑选清单
最后一个维度,最容易被忽略,也最容易翻车:例子之间的风格一致性。
所有例子的输入长度、输出格式、语气必须统一。任何一个不一致,都会被模型当成「任务的一部分」学进去。
最常见的坑:
- 输出长度参差:例子的输出一个是一个词、一个是整段话——模型会照抄最长的那个,给你也输出一大段。
- 格式不一:例子 A 输出「bug」,例子 B 输出「分类:bug」——模型困惑,到底要不要加「分类:」前缀?
- 语气跳变:例子 A 严肃、例子 B 口语——模型在两种语气间摇摆,输出不稳定。
- 泄漏答案:例子里不小心写了「正确答案是 X」,模型可能在新任务里也硬凑这个答案。这叫label leakage(标签泄漏)——是最隐蔽的坑,测试时准确率奇高,一上线就崩,因为线上的输入根本不带那个线索。
四条都指向同一个原则:例子是模型的教材,教材前后不一,再聪明的学生也会学歪。
最后给你一张挑选清单,下次写 Few-shot 前对着过一遍:
| 维度 | 自检问题 | 推荐做法 |
|---|---|---|
| 数量 | 几个例子?跑过曲线吗? | 2~3 个,用数据验证拐点 |
| 顺序 | 最相关的放最后了吗? | 是;难度递增、同类聚拢 |
| 反例 | 有没有 hard negative? | 加 1 个擦边球反例 |
| 风格 | 输入长度/输出格式/语气一致吗? | 全部对齐,别参差 |
| 覆盖度 | 例子覆盖了主要类别/模式吗? | 每类至少 1 个,别漏 |
五项都打勾,你的 Few-shot 才算挑到位了。
实战:把这四步串起来
光讲理论容易飘,我用一个最常见的任务——把用户反馈分成 bug / 需求 / 吐槽——把上面四步一次性走完,给你看一个「挑到位」的 Few-shot 长什么样。
第一步,定数量。这个任务 3 个类别,每类 1 个例子能覆盖,所以总共 3 个(含一个反例),不多不少。
第二步,挑反例。三个类别各给一个正例还不够,最容易混的是「吐槽 vs 需求」——用户抱怨界面丑(吐槽)和用户希望加功能(需求),措辞很像。所以我在这两类之间塞一个 hard negative。
第三步,对齐风格。三个例子的输入都控制在 10~15 字,输出都只给类别名一个词,不带前缀、不带解释。
第四步,排顺序。当前要分类的输入如果像「吐槽」,就把吐槽那个例子放最后。
拼出来是这样:
prompt = """把用户反馈分类为:bug / 需求 / 吐槽,只输出类别名。 输入:支付页面一直转圈进不去 输出:bug 输入:希望能支持微信登录 输出:需求 输入:这配色太丑了看得眼睛疼 # ← hard negative:看着像想改界面的需求,其实是纯吐槽 输出:吐槽 输入:""" + user_input这版 prompt,在 200 条测试集上准确率 ~91%。而我最初那版——5 个随手挑的例子、顺序乱排、没反例——只有 76%。同样的模型、同样的任务,光靠「把例子挑到位」,准确率涨了 15 个点。
这就是挑例子的真功夫:不花一分钱换模型,只花十分钟挑例子,收益比换模型还大。
结尾:挑例子,是 Few-shot 的真功夫
回头看,Few-shot 这一招,表面是「给几个例子」,骨子里全是挑选的功夫:
数量要克制(2~3 个),顺序要布阵(最相关放最后),反例要精准(1 个 hard negative),风格要统一(别参差)。
每一项都不是玄学,是对着大模型的脾气来的。
Few-shot 的精髓不是给得多,是给得准——一个精选的例子,胜过十个堆砌。
下次写 Few-shot,别再随便找几个塞进去。对着那张清单过一遍,每张都塞得有理由,效果立刻不一样。
你的 Few-shot 通常塞几个例子?试过加反例吗?评论区告诉我。
下一篇预告:Day04 可能讲「CoT 在什么场景反而搞砸」或「Prompt 注入怎么防」
我是小刘檀木,一个从二本摸爬滚打到上市公司 AI 负责人的普通人。关注我,把 AI 学进简历。
— END —
小刘檀木 · 帮普通人把 AI 学进简历