1. In-Context Learning 原理
In-Context Learning(ICL)是大语言模型的核心涌现能力之一:无需梯度更新,仅通过上下文中的示例就能学会新任务。
1.1 ICL 的工作机制
当你在 Prompt 中提供示例时,模型的注意力机制会将示例的模式映射到查询上:
# Zero-shot
任务:将以下句子分类为正面/负面/中性
输入:这家餐厅的服务态度很好
输出:
# Few-shot(1-shot)
任务:将以下句子分类为正面/负面/中性
输入:这道菜太难吃了 → 输出:负面
输入:这家餐厅的服务态度很好 → 输出:
模型通过示例"理解"了任务的输入输出映射规则,而非死记硬背。
1.2 ICL vs Fine-tuning
| 维度 | ICL (Few-shot) | Fine-tuning |
|---|---|---|
| 参数更新 | 无 | 有 |
| 数据需求 | 1-20 个示例 | 数百到数万 |
| 部署成本 | 低(同一模型) | 高(需部署多版本) |
| 灵活性 | 高(随时改示例) | 低(需重新训练) |
| 性能上限 | 中等 | 高 |
| 延迟 | 较高(长 Prompt) | 较低 |
2. 示例数量的影响
2.1 边际收益递减曲线
示例数量与效果的关系遵循对数增长曲线:
准确率
| ___________ ← 8-shot
| /
| / ← 4-shot
| /
|/ ← 1-shot
|________
| ← 0-shot
+--------- 示例数量
0 1 4 8 16
经验法则:
- 1-shot:适合简单分类任务,定义输出格式
- 2-3 shot:大多数 NLP 任务的甜蜜点
- 4-8 shot:复杂任务或需要展示多样性的场景
- >8 shot:边际收益极低,浪费 Token
2.2 实验数据
| 示例数 | SST-2 准确率 | GSM8K 准确率 |
|---|---|---|
| 0 | 88.2% | 14.6% |
| 1 | 91.5% | 23.4% |
| 4 | 93.1% | 30.2% |
| 8 | 93.4% | 33.1% |
| 16 | 93.6% | 34.8% |
分类任务 4-shot 后基本饱和,数学任务持续受益但增益递减。
3. 示例选择策略
3.1 随机选择(Random)
最简单的策略:从示例池中随机抽取。
import random
def random_select(examples, k=3):
return random.sample(examples, k)
优点:简单、无偏见 缺点:可能选到不具代表性的示例,效果波动大
3.2 相似度选择(KNN-based)
用嵌入向量找到与查询最相似的示例:
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
def similarity_select(examples, query, embedder, k=3):
"""基于语义相似度选择示例"""
# 编码所有示例和查询
ex_embeddings = np.array([embedder.embed(e["input"]) for e in examples])
query_embedding = np.array([embedder.embed(query)])
# 计算余弦相似度
sims = cosine_similarity(query_embedding, ex_embeddings)[0]
# 取 top-k 最相似
top_indices = np.argsort(sims)[-k:][::-1]
return [examples[i] for i in top_indices]
适用场景:分类、信息提取等输入分布敏感的任务 注意:过于相似的示例可能导致模型"复制"而非"推理"
3.3 多样性选择(Diversity-based)
选择互相之间差异大的示例,覆盖更大的模式空间:
def diversity_select(examples, embedder, k=3):
"""最大边际相关性(MMR)选择"""
embeddings = np.array([embedder.embed(e["input"]) for e in examples])
selected = []
selected_indices = []
# 第一个随机选
first = random.randint(0, len(examples) - 1)
selected.append(first)
selected_indices.append(first)
while len(selected) < k:
best_score = -1
best_idx = -1
for i in range(len(examples)):
if i in selected:
continue
# 与已选示例的最小距离(越大越好 = 越不同)
min_sim = min(
cosine_similarity(
embeddings[i].reshape(1, -1),
embeddings[j].reshape(1, -1)
)[0][0]
for j in selected
)
if min_sim > best_score:
best_score = min_sim
best_idx = i
selected.append(best_idx)
return [examples[i] for i in selected]
3.4 三种策略对比
| 策略 | 分类任务 | 生成任务 | 数学任务 | 计算开销 |
|---|---|---|---|---|
| 随机 | ★★★ | ★★ | ★★ | 极低 |
| 相似度 | ★★★★ | ★★★ | ★★★ | 中 |
| 多样性 | ★★★★ | ★★★★ | ★★★ | 中 |
| 相似+多样混合 | ★★★★★ | ★★★★★ | ★★★★ | 中高 |
4. 示例顺序的影响
4.1 近因效应
LLM 存在显著的近因偏差:最后一个示例对输出影响最大。
# 顺序 A:正面 → 负面 → 中性
示例1: "好看" → 正面
示例2: "难吃" → 负面
示例3: "还行" → 中性
查询: "服务一般"
→ 倾向输出"中性"(受最后一个示例影响)
# 顺序 B:中性 → 负面 → 正面
示例1: "还行" → 中性
示例2: "难吃" → 负面
示例3: "好看" → 正面
查询: "服务一般"
→ 倾向输出"正面"(近因效应)
4.2 顺序最佳实践
def order_examples(examples):
"""推荐的示例排列顺序"""
# 按难度递增排列,最复杂的放最后
examples_sorted = sorted(examples, key=lambda x: x.get("difficulty", 0))
# 如果有标签分布不均,交错排列避免聚类
return interleave_by_label(examples_sorted)
def interleave_by_label(examples):
"""按标签交错排列"""
from collections import defaultdict
by_label = defaultdict(list)
for e in examples:
by_label[e["label"]].append(e)
result = []
max_len = max(len(v) for v in by_label.values())
for i in range(max_len):
for label in by_label:
if i < len(by_label[label]):
result.append(by_label[label][i])
return result
4.3 顺序敏感度实验
| 排列方式 | 准确率 | 标准差 |
|---|---|---|
| 最优排列 | 94.2% | - |
| 随机排列 | 91.8% | ±2.1% |
| 最差排列 | 87.5% | - |
最差与最优排列之间差距可达 7%,这纯粹由顺序导致。
5. 示例格式的影响
5.1 格式一致性
示例的格式必须与期望的输出格式完全一致:
# ❌ 不一致:示例用句子,期望 JSON
示例: "好看" → "正面"
查询: "一般" →
输出: "中性" ← 不是 JSON
# ✅ 一致:示例和查询都用 JSON
示例: {"text": "好看", "label": "正面"}
示例: {"text": "难吃", "label": "负面"}
查询: {"text": "一般", "label":
5.2 格式模板化
FEW_SHOT_TEMPLATE = """
任务:情感分析,将输入文本分类为 positive/negative/neutral
示例:
输入: {ex1_input}
输出: {ex1_output}
输入: {ex2_input}
输出: {ex2_output}
输入: {ex3_input}
输出: {ex3_output}
输入: {query}
输出:
"""
# 确保格式严格一致
prompt = FEW_SHOT_TEMPLATE.format(
ex1_input="这部电影太精彩了",
ex1_output="positive",
ex2_input="浪费时间",
ex2_output="negative",
ex3_input="还行吧,不功不过",
ex3_output="neutral",
query="值得二刷"
)
5.3 Chain-of-Thought 与 Few-shot 结合
在示例中加入推理过程,效果显著优于直接给答案:
# Few-shot CoT
输入: 123 × 45 = ?
推理: 123 × 45 = 123 × 40 + 123 × 5 = 4920 + 615 = 5535
输出: 5535
输入: 789 × 12 = ?
推理:
6. 高级技巧
6.1 动态示例选择
class DynamicFewShotSelector:
"""运行时动态选择示例"""
def __init__(self, example_pool, embedder, k=3):
self.pool = example_pool
self.embedder = embedder
self.k = k
self.cache = {} # 缓存嵌入
# 预计算示例嵌入
for ex in self.pool:
self.cache[ex["input"]] = self.embedder.embed(ex["input"])
def select(self, query):
query_emb = self.embedder.embed(query)
scores = []
for ex in self.pool:
sim = cosine_similarity(
query_emb.reshape(1, -1),
self.cache[ex["input"]].reshape(1, -1)
)[0][0]
scores.append((sim, ex))
scores.sort(reverse=True)
return [ex for _, ex in scores[:self.k]]
6.2 示例去重与质量过滤
def filter_examples(examples, min_quality=0.7):
"""过滤低质量示例"""
filtered = []
seen_embeddings = []
for ex in examples:
emb = embed(ex["input"])
# 质量检查
if len(ex["output"].strip()) < 2:
continue
if ex.get("quality_score", 1.0) < min_quality:
continue
# 去重:与已选示例相似度 < 0.9
if seen_embeddings:
max_sim = max(
cosine_similarity(emb.reshape(1,-1),
prev.reshape(1,-1))[0][0]
for prev in seen_embeddings
)
if max_sim > 0.9:
continue
filtered.append(ex)
seen_embeddings.append(emb)
return filtered
7. 总结
Few-shot Prompting 的效果取决于三个关键决策:
- 选多少:2-4 个示例覆盖大多数场景,超过 8 个收益极低
- 选哪些:相似度+多样性混合策略效果最佳
- 怎么排:按难度递增、标签交错、格式严格一致
核心原则:示例是任务的"定义",不是"训练数据"。每个示例都应在教模型一个关于任务的明确信号。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
