什么是少样本学习?
核心概念
少样本学习(Few-shot Learning)是指在 prompt 中提供少量示例(demonstrations),让模型理解任务的输入输出模式,然后对新的输入执行相同模式。这利用了 LLM 强大的模式识别和类比能力,是提示词工程中最有效的技巧之一。
Zero-shot(零样本)
不提供任何示例,只给指令。适用于简单任务或模型已有充分训练的标准任务(如基础翻译、简单分类)。成本最低,但对复杂格式要求效果有限。
One-shot(单样本)
提供一个示例。当任务有特殊格式要求但通过语言难以描述清楚时,一个好示例往往胜过长篇说明。
Few-shot(少样本)
提供 2-10 个示例。用于复杂格式、特定风格、需要示范多种边界情况的任务。示例数量通常 3-5 个效果最佳,过多反而可能导致模型过度拟合示例格式。
Many-shot(多样本)
提供数十甚至数百个示例,通常借助超长上下文窗口(如 Gemini 1.5 Pro 的 100 万 token)。适用于需要学习复杂分类规则或特定领域专业术语的任务。
三种模式对比实战
任务:情感分析
Zero-shot 版本
判断以下评论的情感倾向(积极/消极/中性):
「虽然价格偏贵,但质量确实很好,下次还会买。」
One-shot 版本
判断评论的情感倾向,格式如下示例:
评论:「物流很快,商品完美符合描述!」
情感:积极
原因:用户对物流速度和商品质量均表示满意
现在判断:
评论:「虽然价格偏贵,但质量确实很好,下次还会买。」
Few-shot 版本(处理复杂情感)
判断评论的情感倾向,注意区分复杂情感。格式:
评论:「快递太慢了,等了10天!但客服解决问题很及时。」
情感:混合(主要消极)
正面点:客服响应及时
负面点:物流时间过长
主导情感:消极
评论:「价格一般,功能也普通,没什么特别亮点。」
情感:中性
正面点:无明显
负面点:无明显亮点
主导情感:中性
评论:「用了三年了,偶尔有小问题,但总体很稳定,性价比高。」
情感:混合(主要积极)
正面点:长期稳定性、性价比
负面点:偶有小问题
主导情感:积极
现在分析:
评论:「虽然价格偏贵,但质量确实很好,下次还会买。」
示例选择的四个原则
什么样的示例最有效?
原则1:代表性
示例应覆盖任务的主要类型和边界情况,不要全部选择"简单"的示例。如果任务中有 30% 是"混合情感"的评论,示例中也应该有对应比例。
原则2:格式一致性
所有示例的输入/输出格式必须完全一致。模型会从示例中学习格式模式,任何不一致都会导致输出格式混乱。
原则3:相关性
示例应与目标输入在领域和风格上尽量接近。处理医学文本时,用医学领域的示例比用科技领域的示例效果更好。
原则4:标签均衡
分类任务中,各类别的示例数量应尽量均衡。如果"积极"有 4 个示例而"消极"只有 1 个,模型会倾向于偏向"积极"判断。
Few-shot 在代码生成中的应用
教会模型你的代码风格
根据以下风格示例,生成 Python 函数:
示例1 - 输入描述:
「计算两个数字的和」
示例1 - 输出代码:
def add_numbers(a: float, b: float) -> float:
"""
计算两个数字的和。
Args:
a: 第一个数字
b: 第二个数字
Returns:
两数之和
Raises:
TypeError: 如果输入不是数字类型
Example:
>>> add_numbers(3, 4)
7.0
"""
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError(f"期望数字类型,得到 {type(a)} 和 {type(b)}")
return float(a + b)
示例2 - 输入描述:
「检查字符串是否是有效的邮箱地址」
示例2 - 输出代码:
import re
def is_valid_email(email: str) -> bool:
"""
检查字符串是否是有效的邮箱地址。
...(同样的文档字符串风格)
"""
if not isinstance(email, str):
raise TypeError(f"期望字符串类型,得到 {type(email)}")
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
现在生成:
「将摄氏温度转换为华氏温度」
动态示例选择(高级技巧)
基于相似度的示例检索
在生产系统中,不应该使用固定的示例集,而应该根据用户输入动态选择最相关的示例。这通常通过向量相似度搜索实现:
from sentence_transformers import SentenceTransformer
import numpy as np
# 加载嵌入模型
model = SentenceTransformer('all-MiniLM-L6-v2')
# 示例库(输入-输出对)
example_library = [
{"input": "这个产品真的很棒!", "output": "积极"},
{"input": "质量一般,不推荐", "output": "消极"},
# ... 更多示例
]
# 预计算所有示例的嵌入向量
example_embeddings = model.encode(
[e["input"] for e in example_library]
)
def get_relevant_examples(query: str, top_k: int = 3):
"""根据查询动态选择最相关的示例"""
query_embedding = model.encode([query])
# 计算余弦相似度
similarities = np.dot(example_embeddings, query_embedding.T).flatten()
top_indices = similarities.argsort()[-top_k:][::-1]
return [example_library[i] for i in top_indices]
实践建议:从 3 个示例开始
在不确定需要多少示例时,从 3 个开始测试。如果输出格式不稳定,增加到 5 个;如果 token 成本过高,尝试减少到 2 个。一般来说,3-5 个高质量示例的效果比 10 个平庸示例更好。
Few-shot 的工作机制
为什么示例有效
LLM 在预训练时学习了大量的"模式匹配":看到 A 格式的输入后,它见过无数类似的"输入→输出"对。Few-shot 示例相当于在上下文中激活了模型对特定任务模式的记忆,让模型明白"啊,这是这类任务",从而调用相应的内部能力。这不是"教"模型新技能,而是"提醒"模型使用哪个已有技能。
示例顺序的影响
研究表明,示例的顺序会影响模型输出。最后一个示例(与目标输入最近的)影响力最大,因为 LLM 的注意力机制对近期上下文有更强偏好。一般建议将与目标输入最相似的示例放在最后,将格式示范放在开头。
示例标签的重要性
有趣的是,实验发现 Few-shot 示例的输入内容对性能的影响,有时比标签(输出)的正确性影响更大。即使示例的标签有些随机,模型仍能从输入格式和任务类型中学到有用信息。这说明 Few-shot 的作用不仅是学习输入输出映射,更多是"激活任务理解模式"。
Few-shot vs Fine-tuning
Few-shot 是在推理时提供示例(每次调用都消耗 token);Fine-tuning 是修改模型参数,将示例"固化"进模型(推理时不需要重复传入)。Few-shot 灵活、无需训练、适合快速迭代;Fine-tuning 在大量重复场景下成本更低、效果更稳定,但需要训练时间和数据准备。
Few-shot 的常见陷阱
示例污染问题
如果示例中包含与目标任务无关的特征,模型可能学到错误的模式。例如:示例中的"积极"评论恰好都很长,"消极"评论都很短,模型可能把长度误认为情感指标。设计示例时要控制无关变量,确保示例只在目标特征上有系统差异。
# 生产环境的 Few-shot 提示词构建器
class FewShotBuilder:
def __init__(self, task_description: str):
self.task_description = task_description
self.examples = []
def add_example(self, input_text: str, output_text: str):
"""添加一个示例到示例库"""
self.examples.append({"input": input_text, "output": output_text})
return self # 支持链式调用
def build(self, target_input: str) -> str:
"""构建完整的 Few-shot 提示词"""
lines = [self.task_description, ""]
for i, example in enumerate(self.examples, 1):
lines.append(f"示例{i}:")
lines.append(f"输入:{example['input']}")
lines.append(f"输出:{example['output']}")
lines.append("")
lines.append("现在处理:")
lines.append(f"输入:{target_input}")
lines.append("输出:")
return "\n".join(lines)
本章小结
本章核心要点
- Few-shot 的本质:不是"教"模型新技能,而是通过示例激活模型已有的任务理解模式。示例的作用是"告诉"模型这是什么类型的任务,期望什么格式的输出。
- 示例数量选择:Zero-shot 适合简单/标准任务;1-3 个示例通常足以传达格式;3-5 个示例适合复杂格式和边界情况;超过 10 个时考虑是否需要 Fine-tuning。
- 示例设计四原则:代表性(覆盖主要类型和边界)、格式一致(严格统一的输入输出结构)、相关性(示例领域与目标任务接近)、标签均衡(分类任务各类别数量接近)。
- 示例顺序:最后一个示例影响最大;将与目标最相似的示例放最后;格式示范放开头。避免示例顺序产生偏向性。
- 动态示例选择:生产环境中用向量相似度从示例库中检索最相关的 3-5 个示例,而非固定示例集。这显著提升了 Few-shot 在多样化输入上的鲁棒性。