前端转 AI 100 天 Day 37:Few-shot 与 CoT——让模型学会“推理”

前端转 AI 第 37 天:Few-shot 与 CoT - 让模型学会"推理"

昨日回顾

Day 36 讲了 Prompt 工程的方法论:四要素、System Prompt、六条原则、反模式。

但只讲了"怎么说",没讲"怎么让模型想"。

复杂问题,光说清楚还不够------还得让模型"分步思考"。

Day 37 目标:掌握 Few-shot / CoT / Self-Consistency / ReAct 四大推理技巧,让模型从"答对"到"会推理"。


一、为什么模型需要"推理技巧"

1. 一个测试

问一个简单的数学题:

复制代码
一个商店进了一批苹果,第一天卖了一半加半个,
第二天卖了剩下的一半加半个,
第三天又卖了剩下的一半加半个,正好卖完。
问:原来有多少个苹果?

直接问模型:

python 复制代码
response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[{"role": "user", "content": "..."}],
)

输出:

复制代码
原来有 7 个苹果。

对吗? 我们验证一下:

  • 初始:7 个
  • 第一天:7/2 + 0.5 = 4 个 → 剩 3 个
  • 第二天:3/2 + 0.5 = 2 个 → 剩 1 个
  • 第三天:1/2 + 0.5 = 1 个 → 剩 0 个 ✅

对了!但这个问题的正确答案是 7,模型蒙对的概率也不低。

再看一个更难一点的:

复制代码
一个班级有 40 个学生,其中 60% 是女生。
女生中有 1/3 会弹钢琴。会弹钢琴的女生中有 1/4 参加过比赛。
问:参加过比赛的女生有多少人?

直接问,模型可能答"2 人"或"3 人"------但到底对不对不一定。

加上"让我们一步步思考":

复制代码
一个班级有 40 个学生...
让我们一步步思考。

输出:

复制代码
好的,让我一步步计算:

第一步:计算女生人数
40 × 60% = 24 人

第二步:计算会弹钢琴的女生
24 × 1/3 = 8 人

第三步:计算参加过比赛的女生
8 × 1/4 = 2 人

答案:2 人。

稳了。逻辑清晰,答案正确。

2. 差距在哪

关键差异:加了"让我们一步步思考"这一句。

这六个字值多少钱?

在 GSM8K(小学数学题数据集)上的测试:

方法 准确率
直接回答 17.9%
加"一步步思考" 78.7%

准确率提升了 4 倍!

这就是 CoT 的威力。

3. 原因分析

为什么"一步步思考"这么有效?

核心原因: 大模型是"自回归生成"的 ------它每输出一个 token,就基于前面所有 token 来预测下一个。

这意味着:

  • 输出越多:模型"思考时间"越长
  • 显式推理:每一步的结果都成了后续计算的"上下文"
  • 少走捷径:不会直接跳到答案,中间过程逼着它推

类比前端:

你写一个复杂函数:

js 复制代码
// ❌ 直接写:容易漏边界
function calcPrice(order) {
  return order.items.reduce((s, i) => s + i.price, 0);
}

// ✅ 先理清步骤再写:不易出错
function calcPrice(order) {
  // 1. 校验输入
  if (!order || !order.items) return 0;

  // 2. 过滤无效商品
  const validItems = order.items.filter(i => i.price > 0);

  // 3. 求和
  const total = validItems.reduce((s, i) => s + i.price, 0);

  // 4. 应用折扣
  const discount = order.coupon?.discount || 0;
  return Math.max(0, total - discount);
}

先想清楚步骤,再写代码------这就是人类版的 CoT。

模型也一样。


二、Few-shot:给例子是最强的引导

1. 三种方式对比

Zero-shot(不给例子):

复制代码
判断这个评论的情感:
"这个产品太差了,浪费钱"

One-shot(给一个例子):

复制代码
判断情感,例如:
"东西不错,物流很快" → 正面
"这个产品太差了,浪费钱" →

Few-shot(给多个例子):

复制代码
判断情感,例如:
"东西不错,物流很快" → 正面
"一般般吧,没惊喜" → 中性
"再也不会买了" → 负面
"这个产品太差了,浪费钱" →

效果对比:

方式 准确率 Token 消耗
Zero-shot 70% 低
One-shot 82% 中
Few-shot 90% 高

付出 token,收获准确率。

2. Few-shot 的三大要素

1. 例子的数量:1-3 个最合适

复制代码
1 个 → 通常够用
3 个 → 覆盖多种情况
5+ 个 → 边际收益递减,浪费 token

2. 例子的多样性

❌ 都是同一类型:

复制代码
"很好" → 正面
"不错" → 正面
"很棒" → 正面

模型学会"一切都正面"------因为它没看过其他类型。

✅ 覆盖多种情况:

复制代码
"很好" → 正面
"一般" → 中性
"太差" → 负面
"有点贵但质量不错" → 偏正面

覆盖正/中/负 + 复杂情况。

3. 例子的格式要和实际输入一致

❌ 不一致:

复制代码
示例输入:"今天天气不错" 
示例输出:{"sentiment": "positive"}

实际输入:"这家餐厅的服务员态度非常恶劣,食物也难吃"

格式差异会让模型"跑偏"。

✅ 一致:

复制代码
【示例】
输入:"今天天气不错"
输出:{"sentiment": "positive", "confidence": 0.95}

【示例】
输入:"这家餐厅的服务员态度非常恶劣,食物也难吃"
输出:

3. 实战:给"工单分类"加 Few-shot

需求:把用户工单分到正确的部门。

创建 day37/few_shot_demo.py:

python 复制代码
"""Few-shot 演示:工单分类"""
import os
from openai import OpenAI


client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com/v1",
)


# ============ 零样本版本 ============
ZERO_SHOT_SYSTEM = """你是一个工单分类助手。
把用户工单分到以下部门之一:技术支持、售前咨询、投诉、财务、其他。

直接返回部门名称,不要解释。"""


# ============ Few-shot 版本 ============
FEW_SHOT_SYSTEM = """你是一个工单分类助手。
把用户工单分到以下部门之一:技术支持、售前咨询、投诉、财务、其他。

参考示例:

工单:"网站打不开,报 502 错误" → 技术支持
工单:"你们家产品支持私有化部署吗?" → 售前咨询
工单:"客服态度太差,我要投诉!" → 投诉
工单:"发票什么时候能寄到?" → 财务
工单:"你们公司成立几年了?" → 其他
工单:"登录后一直转圈,换了浏览器也不行" → 技术支持
工单:"想了解一下企业版的报价" → 售前咨询

直接返回部门名称,不要解释。"""


def classify(ticket: str, use_few_shot: bool = True) -> str:
    system = FEW_SHOT_SYSTEM if use_few_shot else ZERO_SHOT_SYSTEM
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": f"工单:{ticket}"},
        ],
        temperature=0,
    )
    return response.choices[0].message.content.strip()


if __name__ == "__main__":
    test_tickets = [
        "上传文件的时候提示'文件太大',怎么解决",
        "想问下 5 人团队用什么套餐合适",
        "已经付款 3 天了,订单还是'待发货',太让人失望了",
        "需要开发票,抬头是个人",
        "你们的 API 支持流式返回吗",
    ]

    print(f"{'工单':<40} {'Zero':<10} {'Few':<10}")
    print("=" * 70)

    for t in test_tickets:
        zero = classify(t, use_few_shot=False)
        few = classify(t, use_few_shot=True)
        print(f"{t[:38]:<40} {zero:<10} {few:<10}")

输出示例:

复制代码
工单                                      Zero       Few
======================================================================
上传文件的时候提示'文件太大',怎么解决      技术支持    技术支持
想问下 5 人团队用什么套餐合适              售前咨询    售前咨询
已经付款 3 天了,订单还是'待发货'...       投诉        投诉
需要开发票,抬头是个人                     财务        财务
你们的 API 支持流式返回吗                  技术支持    售前咨询  ← Few-shot 修正

注意最后一条: Zero-shot 判成"技术支持",但实际是售前咨询。Few-shot 给对了。

因为例子里的"支持私有化部署"让模型学到了"问能力=售前"的模式。

4. Few-shot 的"坑"

坑 1:例子有偏见

如果例子全是"技术支持":

复制代码
工单:"登录不了" → 技术支持
工单:"密码忘了" → 技术支持
工单:"页面卡顿" → 技术支持

模型会倾向把一切归到"技术支持"。

解法:例子要均衡。

坑 2:例子和实际输入差异太大

例子里是长工单,实际是短工单------模型可能"照搬格式",输出一堆无关内容。

解法:例子要和实际输入同分布。

坑 3:例子太多

例子超过 5 个,边际收益陡降,还占用大量 token。

解法:3-5 个精心挑选的例子,胜过 10 个普通例子。


三、CoT:让模型"思考后再回答"

1. Zero-shot CoT:最简单的一招

什么都不用改,只在 Prompt 最后加一句:

复制代码
Let's think step by step.
(让我们一步步思考。)

或者中文版:

复制代码
请一步步思考后再给出答案。

测试数据(学术论文结果):

任务 直接回答 加 CoT
数学题 17.9% 78.7%
常识推理 40% 65%
符号推理 20% 60%

一句话,翻倍效果。

2. Few-shot CoT:给带推理过程的例子

更强的版本------示例包含推理过程:

复制代码
问:一个商店有 23 个苹果,卖了 20 个,又进货 6 个,现在有多少个?

答:让我一步步想:
- 初始:23 个
- 卖了 20 个后:23 - 20 = 3 个
- 进货 6 个后:3 + 6 = 9 个
- 答案:9 个

问:小明有 5 块钱,买了 3 个苹果每个 1.5 元,还剩多少钱?

答:

模型会模仿示例的格式,给出完整推理过程。

3. CoT 的最佳实践

1. 明确要求"分步"

复制代码
请按以下步骤回答:
1. 理解问题
2. 提取已知条件
3. 逐步推导
4. 得出结论

或者更简洁:

复制代码
请一步步思考。

2. 用"标记词"分隔

复制代码
【思考过程】
...
【最终答案】
...

这样模型会分开"思考"和"结论",避免思考混进答案。

3. 复杂任务用"分步骤指令"

复制代码
请按以下步骤分析这段代码:
步骤 1:梳理函数功能
步骤 2:检查边界条件
步骤 3:分析时间复杂度
步骤 4:给出优化建议

每一步"想什么"都说清楚。

4. CoT 的"坑"

坑 1:简单任务不需要 CoT

问"1+1等于几",让模型"一步步思考"是浪费。

判断标准:

  • 简单查表 → 不用 CoT
  • 多步推理 → 用 CoT
  • 需要计算 → 用 CoT

坑 2:CoT 会泄露推理过程

如果输出给用户,可能显得啰嗦。

解法:

复制代码
【思考过程】(内部推理,用户看不到)
...

【最终答案】
简洁的结论

然后在展示时只取"最终答案"部分。

坑 3:CoT 有时会"过度思考"

简单题给出一大堆推导,最后还可能"想歪了"。

解法:

  • 限制温度 :temperature=0
  • 限制长度 :max_tokens
  • 换模型:小模型不适合 CoT,用推理模型(如 DeepSeek R1)

四、Self-Consistency:让模型"多试几次"

1. 核心思想

同一个问题,让模型答 N 次,取出现次数最多的答案。

有点像"民主投票"。

复制代码
问题:X 是多少?

第 1 次:答案是 7(推理过程 A)
第 2 次:答案是 7(推理过程 B)
第 3 次:答案是 8(推理过程 C)
第 4 次:答案是 7(推理过程 D)
第 5 次:答案是 7(推理过程 E)

→ 投票结果:7(4 票)

准确率提升数据(GSM8K):

方法 准确率
CoT 78.7%
Self-Consistency (N=5) 85%
Self-Consistency (N=10) 90%
Self-Consistency (N=40) 93%

代价: N 倍 token 消耗。

2. 实现 Self-Consistency

创建 day37/self_consistency.py:

python 复制代码
"""Self-Consistency 演示"""
import os
from collections import Counter
from openai import OpenAI


client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com/v1",
)


def solve_with_cot(question: str) -> str:
    """单次 CoT 求解"""
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {
                "role": "system",
相关推荐
孟健1 小时前
我用Grok Bot本地剪口播视频,3段提示词搞定气口切除、字幕与封面
人工智能·python·aigc
2601_966949652 小时前
如何封装 qd.klines.batch(),让批量 K 线查询函数在多个策略中复用?
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
神仙别闹3 小时前
基于Python实现英词语自动对齐系统
开发语言·python
老歌老听老掉牙3 小时前
圆柱端面四平面交线及其在yoz面投影的几何解析与 Python 实现
开发语言·python·平面
我要见SA姐14 小时前
Chrome 扩展安全攻防实录:从权限滥用到供应链投毒
运维·人工智能·python·机器学习·自动化
2601_962885724 小时前
给 backtrader 接 A 股数据:用 AlphaFeed 喂PandasData
python
YYYing.4 小时前
【Agent系列 (十一) 】上下文工程
人工智能·python·ai·agent·上下文工程
计算机毕业编程指导师4 小时前
基于深度学习的光伏组件红外图像缺陷识别系统设计与实现
人工智能·python·深度学习·目标检测·毕业设计·课程设计·光伏
前端精髓5 小时前
langchain 短期记忆(状态)动态运行时上下文
python·langchain