Chapter 04

少样本学习

通过精心设计的示例,引导 AI 学习你的输出格式、推理模式和风格偏好

什么是少样本学习?

核心概念

少样本学习(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)

本章小结

本章核心要点