核心原理
Self-Consistency (SC) 的核心思想极其简洁:对同一个问题生成多条推理路径,通过投票选出最一致的答案。
传统 Chain-of-Thought (CoT) 只采样一次推理路径,若该路径在某一步出错,最终答案就会错。SC 利用了一个关键观察——正确答案往往比错误答案更容易被多种推理路径到达。
# 单次 CoT:一条路径
问题 → [推理路径 A] → 答案 A
# Self-Consistency:多条路径 + 投票
问题 → [推理路径 A] → 答案 A ─┐
→ [推理路径 B] → 答案 B ─┼→ 多数投票 → 最终答案
→ [推理路径 C] → 答案 A ─┘
→ [推理路径 D] → 答案 A ─┘
采样策略详解
温度参数 (Temperature)
温度控制采样的随机性,是 SC 效果的关键变量:
| 温度范围 | 效果 | 适用场景 |
|---|---|---|
| 0.0-0.3 | 路径高度相似,多样性不足 | 简单问题 |
| 0.5-0.7 | 多样性与质量的最佳平衡 | 推荐默认 |
| 0.8-1.0 | 路径差异大,但质量下降 | 复杂推理需高多样性 |
Top-p 采样
Top-p (nucleus sampling) 限制了候选 token 的概率质量:
import openai
def self_consistency_query(prompt, n=10, temperature=0.7, top_p=0.95):
"""Self-Consistency 多次采样"""
responses = []
for i in range(n):
resp = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=temperature,
top_p=top_p,
max_tokens=1024,
)
responses.append(resp.choices[0].message.content)
return responses
采样数量与收益曲线
| 采样数 N | GSM8K 准确率 | 相对成本 | 边际收益 |
|---|---|---|---|
| 1 (CoT) | 56.2% | 1x | 基线 |
| 5 | 68.4% | 5x | +12.2% |
| 10 | 72.8% | 10x | +4.4% |
| 20 | 75.2% | 20x | +2.4% |
| 40 | 76.1% | 40x | +0.9% |
收益递减明显:N=10 是性价比最优的选择,之后每增加 10 次采样仅提升 ~1%。
投票策略
简单多数投票
from collections import Counter
def majority_vote(answers):
"""简单多数投票"""
counter = Counter(answers)
return counter.most_common(1)[0][0]
加权投票
不是所有路径都同等可靠。可以根据路径长度、置信度等加权:
import re
from collections import defaultdict
def weighted_vote(responses_and_answers):
"""
加权投票:推理路径越短,权重越高(奥卡姆剃刀)
responses_and_answers: [(reasoning_text, answer), ...]
"""
scores = defaultdict(float)
for reasoning, answer in responses_and_answers:
# 路径越短权重越高
length = len(reasoning.split())
weight = 1.0 / (1.0 + length / 100)
scores[answer] += weight
return max(scores, key=scores.get)
def extract_answer(response_text):
"""从推理文本中提取最终答案"""
# 匹配 "答案是 X" 或 "The answer is X"
match = re.search(r'(?:答案|answer)\s*[::]??\s*([^\n。]+)',
response_text, re.IGNORECASE)
return match.group(1).strip() if match else response_text.strip()
log-probability 加权
更精确的方法是使用每条路径的 log-probability 作为权重:
import math
def logprob_weighted_vote(paths_with_logprobs):
"""
paths_with_logprobs: [(answer, avg_logprob), ...]
avg_logprob: 该路径 token 的平均 log probability
"""
scores = defaultdict(float)
for answer, logprob in paths_with_logprobs:
# 转换为概率权重
scores[answer] += math.exp(logprob)
return max(scores, key=scores.get)
SC + CoT 完整实现
import openai
import re
from collections import Counter
class SelfConsistency:
def __init__(self, model="gpt-4", n_samples=10,
temperature=0.7, top_p=0.95):
self.model = model
self.n_samples = n_samples
self.temperature = temperature
self.top_p = top_p
def _build_prompt(self, question):
return f"""请逐步推理以下问题,并在最后给出答案。
问题:{question}
请按以下格式回答:
推理过程:...
答案:[最终答案]
解题步骤:"""
def _sample(self, prompt):
resp = openai.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=self.temperature,
top_p=self.top_p,
max_tokens=1024,
)
return resp.choices[0].message.content
def _extract_answer(self, text):
match = re.search(r'答案[::]\s*(.+?)(?:\n|$)', text)
return match.group(1).strip() if match else text.strip()
def solve(self, question):
prompt = self._build_prompt(question)
# 并行采样(实际生产环境用 asyncio)
responses = [self._sample(prompt) for _ in range(self.n_samples)]
# 提取答案
answers = [self._extract_answer(r) for r in responses]
# 多数投票
counter = Counter(answers)
best_answer, vote_count = counter.most_common(1)[0]
confidence = vote_count / len(answers)
return {
"answer": best_answer,
"confidence": confidence,
"all_answers": answers,
"responses": responses,
}
# 使用示例
sc = SelfConsistency(n_samples=10)
result = sc.solve("一个水池有两个进水管A和B,A单独20小时注满,B单独30小时注满。同时开3小时后关闭A,B还需几小时注满?")
print(f"答案: {result['answer']}")
print(f"置信度: {result['confidence']:.0%}")
成本优化策略
自适应采样
不需要对所有问题都采样 10 次——简单问题 3 次就够了:
def adaptive_self_consistency(question, solver,
initial_n=3, max_n=10,
confidence_threshold=0.8):
"""自适应采样:达到置信阈值即停止"""
responses = []
for i in range(max_n):
resp = solver._sample(solver._build_prompt(question))
responses.append(resp)
if i + 1 >= initial_n:
answers = [solver._extract_answer(r) for r in responses]
counter = Counter(answers)
top_answer, votes = counter.most_common(1)[0]
confidence = votes / len(answers)
if confidence >= confidence_threshold:
return {
"answer": top_answer,
"confidence": confidence,
"samples_used": i + 1,
}
return {
"answer": top_answer,
"confidence": confidence,
"samples_used": max_n,
}
成本对比表
| 策略 | 平均采样数 | GSM8K 准确率 | 相对成本 |
|---|---|---|---|
| 固定 N=1 (CoT) | 1 | 56.2% | 1x |
| 固定 N=10 | 10 | 72.8% | 10x |
| 自适应 (阈值0.8) | 4.3 | 71.5% | 4.3x |
自适应策略以 4.3x 成本达到接近 10x 的效果,节省 57% 的 API 调用。
实战建议
- 数学/逻辑问题首选 SC:这类问题有唯一正确答案,投票效果最佳
- 开放生成问题慎用:创意写作等没有唯一答案的场景,SC 可能导致趋同
- 注意答案归一化:
"3"和"3.0"和"三"需要归一化后才能投票 - 并行化采样:用
asyncio或批量 API 并行发送,避免串行等待 - 监控 token 消耗:SC 成本随 N 线性增长,务必设置 budget 上限
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
