Zero-shot与Few-shot选择指南-实际项目中如何决策
> **本文适合谁**
Zero-shot 与 Few-shot 选择指南:实际项目中如何决策
本文适合谁
在实际项目中不确定该用 Zero-shot 还是 Few-shot 的开发者。本文给出可操作的决策框架,不是理论讨论,而是根据任务特征的具体判断标准。
两种提问方式
Zero-shot vs Few-shot 决策树——根据任务特点、数据量和成本约束做出最优选择
Zero-shot(零样本提示):直接告诉模型做什么,不给任何例子。就像面试时问候选人"请自我介绍",没有示范,考察的是候选人本来的能力。
请判断以下评论的情绪倾向,输出"正面"、"负面"或"中性"。
评论:这款手机拍照效果真的很惊艳,电池也很耐用。
Few-shot(少样本提示,即给模型几个示例示范):在提问之前先给几个示范例子,让模型学习"应该怎么做"的模式,再处理实际任务。就像面试前给候选人看几个参考答案,期望他按照类似格式回答。
请判断以下评论的情绪倾向。
示例1:
评论:发货速度很快,包装也很用心。
情绪:正面
示例2:
评论:质量一般,不值这个价格。
情绪:负面
示例3:
评论:东西收到了。
情绪:中性
现在请判断:
评论:这款手机拍照效果真的很惊艳,电池也很耐用。
情绪:
为什么这个选择很重要
很多人的直觉是"给例子总是更好,所以每次都用 Few-shot"。这个直觉是错误的。
Few-shot 的代价是真实存在的:
首先是 Token 成本。每个示例都消耗 Token,而 API 按 Token 计费。如果每次请求都携带 5 个示例,每个示例 200 Token,就是额外的 1000 Token/次。100 万次调用就是 10 亿 Token 的额外成本。对于高频接口,这个数字不可忽视。
其次是延迟增加。Prompt 越长,生成速度越慢。用户等待时间变长。
更关键的是,Few-shot 并不总是比 Zero-shot 效果更好。当任务本身很简单,模型已经充分理解,强行加入示例反而可能引入偏差——模型会过度学习示例的特定表述,而不是抽象出任务的本质。
因此,选择 Zero-shot 还是 Few-shot,应该是一个有意识的工程决策,而不是习惯性地选一种。
系统化决策框架
任务类型
不同任务类型对 Few-shot 的依赖程度差异很大:
| 任务类型 | 说明 | Zero-shot 通常够用? | 推荐策略 |
|---|---|---|---|
| 情绪分类 | 正面/负面/中性 | 是 | Zero-shot 优先 |
| 标准摘要 | 对文章做总结 | 是 | Zero-shot 优先 |
| 特定格式提取 | 从文本中提取字段 | 取决于格式复杂度 | 格式复杂则 Few-shot |
| 自定义分类体系 | 公司内部的类别定义 | 否 | Few-shot 必要 |
| 风格化写作 | 模仿特定文风 | 否 | Few-shot 必要 |
| 复杂推理 | 多步骤逻辑推理 | 通常否 | Few-shot + CoT |
| 代码生成 | 通用代码 | 是 | Zero-shot 优先 |
| 领域特定代码 | 内部框架/API 使用 | 否 | Few-shot 必要 |
模型对任务的熟悉程度
可以这样判断:这个任务是否在模型的训练数据里大量出现过?
- 通用任务(写邮件、翻译、摘要、常见分类):模型训练数据覆盖充分,Zero-shot 通常效果很好
- 垂直领域任务(法律条款分类、医疗实体识别、金融风险评级):模型对领域知识掌握程度参差不齐,Few-shot 能显著补足
- 公司内部规范(内部工单分类、自定义标签体系):模型完全不知道你的分类逻辑,Few-shot 是必须的
输出格式要求
这是最直接的判断维度:
- 标准格式(JSON、Markdown、纯文本段落):Zero-shot 通常能产生符合要求的格式
- 特殊结构(公司定制的报告格式、特定 XML 结构、带嵌套逻辑的 JSON):Few-shot 示例能大幅降低格式错误率
- 精确的字段命名(字段名必须完全匹配,不能有歧义):Few-shot 示例是最直接的规范手段
Few-shot 示例设计原则
决定使用 Few-shot 之后,示例的设计质量至关重要。坏的示例比没有示例更糟糕——它会把模型引向错误方向。
原则一:数量 2 到 8 个通常足够
大量实验表明,Few-shot 的效果在 2 到 8 个示例之间基本达到饱和,继续增加示例的边际收益很小,但 Token 成本持续增加。特别情况:
- 任务极简单:2-3 个足够
- 分类类别多(比如 20 个类别):每个类别至少 1 个,示例数量需要更多
- 格式非常特殊:3-5 个能覆盖各种变体即可
原则二:覆盖边界情况
示例不应该只展示"典型"情况,更要覆盖容易出错的边界情况。
比如情绪分类,不要只给明显正面和明显负面的例子,应该包含:
- 讽刺性表达("质量好得让我哭了"——实际是负面)
- 混合情绪("功能强大但价格贵"——中性或混合)
- 简短模糊的表达("还行"——中性)
原则三:示例质量决定输出质量
Few-shot 的核心机制是模式学习:模型会提取示例中的隐含规律,并应用到新输入上。如果示例本身有错误、不一致或存在歧义,模型就会学习这些错误。
建立示例库时,建议:
- 每个示例都由领域专家验证
- 同一类别的标签必须完全一致(不能有时写"正面"有时写"积极")
- 定期审查示例是否仍然符合当前业务规范
原则四:最相关的示例放最后
研究发现,模型对上下文的处理存在近因效应——最靠近问题的内容影响力更大。因此:
- 如果示例有长有短:把与当前输入最相似的示例放在最后
- 如果示例有难有易:把最接近当前输入难度的示例放在最后
- 固定示例库时:可以根据当前输入动态选择最相关的 K 个示例,按相关度从低到高排列
对同一任务的 Zero-shot 与 Few-shot 实现对比
非程序员可跳过代码,重点看文字说明。
以下是一个"客户工单分类"任务的对比实现,同时演示如何快速测试效果差异:
from openai import OpenAI
import json
client = OpenAI()
# ---- Zero-shot 实现 ----
def classify_zero_shot(ticket: str) -> str:
prompt = """请将以下客户工单分类到以下类别之一:
- 账单问题
- 技术故障
- 退款申请
- 账户安全
- 其他
只输出类别名称,不要其他内容。
工单内容:{ticket}""".format(ticket=ticket)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
max_tokens=20
)
return response.choices[0].message.content.strip()
# ---- Few-shot 实现 ----
def classify_few_shot(ticket: str) -> str:
# 精心设计的示例,覆盖各类别和边界情况
examples = [
{
"input": "我的账单上有一笔我没有消费的扣款,要求退回",
"output": "账单问题"
},
{
"input": "APP 一直崩溃,打开就闪退,已经重装了三次",
"output": "技术故障"
},
{
"input": "我已经申请退款两周了,还没有收到,要投诉",
"output": "退款申请"
},
{
"input": "我收到短信说账户在异地登录,我没有操作过",
"output": "账户安全"
},
{
"input": "想知道你们下午几点关门", # 边界情况:非标准问题
"output": "其他"
},
]
# 构建 Few-shot Prompt
examples_text = "\n\n".join([
f"工单:{ex['input']}\n类别:{ex['output']}"
for ex in examples
])
prompt = f"""请将以下客户工单分类到以下类别之一:
- 账单问题
- 技术故障
- 退款申请
- 账户安全
- 其他
只输出类别名称,不要其他内容。
以下是一些示例:
{examples_text}
现在请分类:
工单:{ticket}
类别:"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
max_tokens=20
)
return response.choices[0].message.content.strip()
# ---- 快速对比测试 ----
test_cases = [
# 清晰案例
"我的信用卡被重复扣款了",
# 模糊案例(可能是账单问题也可能是退款)
"我购买了会员但无法使用,要退钱",
# 边界案例
"你们的客服态度很差",
# 组合问题
"登录不上去,而且上个月多收了我钱",
]
print(f"{'工单内容':<25} {'Zero-shot':<12} {'Few-shot':<12}")
print("-" * 55)
for ticket in test_cases:
zero_result = classify_zero_shot(ticket)
few_result = classify_few_shot(ticket)
match = "✓" if zero_result == few_result else "✗ 不一致"
print(f"{ticket[:22]:<25} {zero_result:<12} {few_result:<12} {match}")
特殊情况:One-shot 的适用场景
One-shot(单样本提示) 只给 1 个示例,是 Few-shot 的最小化版本。它特别适合以下场景:
- 格式规范:你只需要告诉模型输出格式长什么样,内容多样性不重要。比如"输出一个包含 name 和 score 字段的 JSON",一个例子就够了。
- 防止零样本的格式偏差:有时候 Zero-shot 对格式的理解存在歧义(比如"简洁"对你意味着 100 字,对模型意味着 500 字),一个 One-shot 示例能校正理解偏差,代价比 Few-shot 小得多。
- Token 极度敏感的场景:响应速度要求极高或成本极度敏感时,One-shot 是比 Few-shot 更经济的折中方案。
决策流程图
实验方法:快速测试效果差异
不要凭直觉决定,用数据说话。标准的测试流程:
- 准备 20-50 个测试用例,覆盖典型情况和边界情况,每个用例都有人工标注的正确答案
- 分别测试 Zero-shot、One-shot、Few-shot(2/4/6 个示例)各版本
- 记录两个指标:准确率(与正确答案的符合程度)和 Token 消耗(每次请求的平均 Token 数)
- 计算性价比:(准确率提升) / (Token 增加量),找到边际收益最大的那个点
通常会发现:从 Zero-shot 到 2-shot,准确率提升最大;从 4-shot 到 8-shot,收益明显下降。这个"拐点"就是你的最优示例数量。
对 Java 工程师来说,这个过程完全可以类比为 JVM 调优:先用基准测试摸清当前性能,再逐步调整参数,观察收益,找到最优的配置点,而不是凭经验拍一个数字。