一、Few-shot 学习基础

Few-shot Prompt 是 In-Context Learning(上下文学习)的核心实现方式——在 Prompt 中提供少量输入-输出示例,让模型"学会"目标任务模式,无需更新模型参数即可完成新任务。

1.1 三种模式对比

模式示例数适用场景优势劣势
Zero-shot0通用任务、简单分类最简洁、最少 token 消耗复杂任务准确率低
Few-shot1-5格式约束、新概念、风格迁移精准控制输出格式和风格需要设计示例
Many-shot10-100+复杂模式学习、规则归纳接近微调效果token 开销大

1.2 In-Context Learning 的工作原理

Few-shot 学习的本质是:模型在前向传播过程中,利用注意力机制从示例中提取出输入-输出的映射模式,并在推理时复用该模式。

# Few-shot 学习示意代码
def build_few_shot_prompt(examples: list, test_input: str) -> str:
    """
    examples: [{"input": "...", "output": "..."}, ...]
    """
    prompt_parts = []
    
    for i, example in enumerate(examples):
        prompt_parts.append(f"输入 {i+1}: {example['input']}")
        prompt_parts.append(f"输出 {i+1}: {example['output']}")
        prompt_parts.append("")
    
    prompt_parts.append(f"输入 {len(examples)+1}: {test_input}")
    prompt_parts.append(f"输出 {len(examples)+1}: ")
    
    return "\n".join(prompt_parts)

# 示例:情感分类
examples = [
    {"input": "这部电影太精彩了!", "output": "正面"},
    {"input": "质量很差,不推荐购买。", "output": "负面"},
    {"input": "还可以吧,一般般。", "output": "中性"},
]

test_input = "服务态度非常好,下次还来!"
prompt = build_few_shot_prompt(examples, test_input)
print(prompt)

二、示例选择策略

2.1 代表性选择

示例要覆盖任务的典型模式,而非随机选取。

import numpy as np
from sentence_transformers import SentenceTransformer

def select_diverse_examples(candidates: list, n: int = 3):
    """
    基于嵌入相似度选择多样化的 Few-shot 示例
    """
    model = SentenceTransformer('all-MiniLM-L6-v2')
    
    texts = [c["input"] for c in candidates]
    embeddings = model.encode(texts)
    
    # 使用 k-means++ 初始化策略:选择彼此距离最远的样本
    selected_indices = []
    remaining = list(range(len(candidates)))
    
    # 第一个随机选
    first = np.random.choice(remaining)
    selected_indices.append(first)
    remaining.remove(first)
    
    # 后续选与已选距离最大的
    for _ in range(n - 1):
        distances = []
        for r in remaining:
            min_dist = min(np.linalg.norm(embeddings[r] - embeddings[s]) 
                         for s in selected_indices)
            distances.append(min_dist)
        
        best_idx = remaining[np.argmax(distances)]
        selected_indices.append(best_idx)
        remaining.remove(best_idx)
    
    return [candidates[i] for i in selected_indices]

2.2 难度递进策略

策略描述适用场景
简单→复杂从易到难排列示例分类、格式转换
相似优先与目标问题最相似的示例放在最后QA、推理
边界覆盖选决策边界附近的样本精细分类
随机排列打乱示例顺序消除位置偏差

2.3 示例数量与效果关系

实验数据(GSM8K 数学推理):

示例数 | 准确率 | Token 消耗(avg)
0      | 58.2%  | 120 tokens
1      | 68.5%  | 280 tokens
3      | 74.1%  | 520 tokens
5      | 76.8%  | 760 tokens
8      | 77.3%  | 1100 tokens
16     | 77.5%  | 1900 tokens

关键发现:3-5 个示例性价比最高,再增加收益递减。

三、示例格式设计

3.1 格式一致原则

# ❌ 不规范的格式混搭
examples_bad = [
    {"input": "翻译:Hello → ", "output": "你好"},
    {"input": "请将 'Goodbye' 翻译成中文", "output": "再见"},
]

# ✅ 格式必须严格一致
examples_good = [
    {"input": "English: Hello\nChinese:", "output": "你好"},
    {"input": "English: Goodbye\nChinese:", "output": "再见"},
    {"input": "English: Thank you\nChinese:", "output": "谢谢"},
]

3.2 标记对齐

输入和输出的分隔标记在整个 Prompt 中保持一致,避免混淆。

✅ 推荐格式 1(XML 标签):
<example>
<input>电影评价:好看</input>
<output>positive</output>
</example>

✅ 推荐格式 2(结构化文本):
## 输入
电影评价:好看
## 输出
positive

✅ 推荐格式 3(Markdown 表格):
| 输入 | 输出 |
|------|------|
| 电影评价:好看 | positive |

3.3 System Prompt + Few-shot 混合设计

system_prompt = """你是一个文本分类专家。请根据输入文本的情感倾向,输出"正面"、"负面"或"中性"。

分类规则:
1. 包含积极正面词汇 → 正面
2. 包含消极负面词汇 → 负面
3. 无明显倾向或中立 → 中性
4. 讽刺或反语 → 根据实际意图判断"""

few_shot_examples = """
输入: 这个产品质量很棒,性价比高
输出: 正面

输入: 垃圾产品,用三天就坏了
输出: 负面

输入: 普普通通,没什么特别之处
输出: 中性
"""

test_input = "这款手机拍照效果超出预期!"

full_prompt = f"{system_prompt}\n\n{few_shot_examples}\n\n输入: {test_input}\n输出:"

四、高级 Few-shot 技术

4.1 动态示例检索

根据测试输入实时检索最相似的示例,而非固定示例集。

import chromadb
from chromadb.utils import embedding_functions

class DynamicFewShot:
    def __init__(self, example_db: list):
        self.client = chromadb.Client()
        self.collection = self.client.create_collection(
            name="examples",
            embedding_function=embedding_functions.DefaultEmbeddingFunction()
        )
        # 索引示例
        for i, ex in enumerate(example_db):
            self.collection.add(
                ids=[str(i)],
                documents=[ex["input"]],
                metadatas=[{"output": ex["output"]}]
            )
    
    def retrieve(self, query: str, n: int = 3) -> list:
        results = self.collection.query(
            query_texts=[query],
            n_results=n
        )
        examples = []
        for i, doc in enumerate(results['documents'][0]):
            examples.append({
                "input": doc,
                "output": results['metadatas'][0][i]['output']
            })
        return examples

# 使用示例
db = [
    {"input": "今天天气真好", "output": "positive"},
    {"input": "又下雨了,真烦", "output": "negative"},
    # ... 更多示例
]
dfs = DynamicFewShot(db)
relevant = dfs.retrieve("这家餐厅太难吃了", n=3)

4.2 标签重排序

当输出空间很大时,先让模型生成候选标签,再基于候选进行 Few-shot。

def two_stage_few_shot(text: str, all_labels: list) -> str:
    """两阶段 Few-shot:先缩小标签范围,再精确分类"""
    
    # Stage 1: 标签精简
    stage1_prompt = f"""从以下标签中选出最可能相关的 5 个标签:
{', '.join(all_labels)}

文本: {text}
最相关的标签:"""
    
    # ... 获取模型输出的 5 个候选标签
    
    # Stage 2: 用候选标签相关的 Few-shot 示例精确分类
    candidate_labels = ["投诉", "咨询", "建议", "表扬", "售后"]  # 假设输出
    few_shot_examples = get_examples_for_labels(candidate_labels)
    
    final_prompt = f"{few_shot_examples}\n\n文本: {text}\n标签:"
    # ... 调用模型获取最终标签

4.3 带推理的 Few-shot

每个示例不仅包含输入和输出,还包含中间推理步骤。这比纯示例更有效。

形式示例适用场景
输入→输出好评→正面简单分类
输入→推理→输出好评→含积极词汇…→正面复杂推理
输入→规则→输出好评→规则:积极关键词匹配→正面规则学习
输入→约束→输出好评→约束:正面;格式:单个词格式约束

五、实际案例

5.1 代码生成 Few-shot

# Code generation with few-shot
code_examples = """
输入: Python 函数,计算列表平均值
输出:
def average(lst):
    return sum(lst) / len(lst) if lst else 0

输入: Python 函数,将字符串反转
输出:
def reverse_string(s):
    return s[::-1]

输入: Python 函数,检查是否为回文
输出:
def is_palindrome(s):
    clean = ''.join(c.lower() for c in s if c.isalnum())
    return clean == clean[::-1]
"""

test_prompt = f"{code_examples}\n\n输入: Python 函数,合并两个字典\n输出:"

5.2 数据抽取 Few-shot

## 任务:从简历中抽取结构化信息

## 示例 1
文本: 张三,2018年毕业于清华大学计算机系,曾在字节跳动担任高级工程师3年。
输出:
- 姓名: 张三
- 毕业院校: 清华大学
- 毕业年份: 2018年
- 专业: 计算机系
- 最近公司: 字节跳动
- 职位: 高级工程师
- 工作年限: 3年

## 示例 2
文本: 李四,北京大学金融硕士,CFA持证人,目前就职于高盛。
输出:
- 姓名: 李四
- 毕业院校: 北京大学
- 学历: 硕士
- 专业: 金融
- 证书: CFA
- 最近公司: 高盛

## 目标文本
文本: {target_text}
输出:

六、性能调优

6.1 Few-shot 检查清单

  • 示例覆盖了所有主要的输出类别
  • 示例的输入-输出映射关系清晰无歧义
  • 示例格式严格一致(标点、空格、换行)
  • 示例数量在 3-5 个之间
  • 最复杂或有挑战的示例放在最后
  • 示例和测试输入之间没有格式跳跃
  • 考虑了示例的 bias(正向/负向平衡)

6.2 常见问题

问题原因解决
模型复制示例输出示例与目标太相似增大输入差异性
对示例顺序敏感位置偏差多次随机排列取多数
复杂任务效果差示例不够增加示例或转为 Many-shot
格式不一致输出格式混搭严格对齐格式标记

七、总结

Few-shot Prompt 是 Prompt 工程中最实用、最灵活的技术之一。核心要点:

  1. 示例质量 > 数量 — 3 个好示例远胜 10 个
  2. 格式一致性是关键 — 模型对格式极其敏感
  3. 动态检索是进阶方向 — 告别固定示例,拥抱相似度检索
  4. 与 System Prompt 互补 — 规则 + 示例是最强组合
  5. 始终考虑 token 预算 — Few-shot 不是免费的

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。