第 27 章

第 27 章:基于置信度的分类

第 27 章:基于置信度的分类

本文整理自 Datawhale 开源项目 datawhalechina/jev-cookbook(CC BY-NC-SA 4.0),源文件:main/04_实战指南/18_基于置信度的分类.ipynb。

针对官方文档 实战指南 · Classification Using Confidence 的可运行实验笔记, 用真实 TypeSafe API(Jev 模型)复刻核心流程并中文化。中文翻译版见 datawhalechina.github.io/jev-cookbook。

笔记本结构

章节 内容 实验
0. 准备 安装、客户端、连通性、离线回退 —
1. 小型行业树 3–4 个大类下约 8 个行业组(非完整 SIC) 打印映射
2. Choice + 阈值 置信度 ≥0.9 报组,否则报大类 定义 classify()
3. 五家公司 清晰银行、模糊综合企等中文简介 逐条分类

每个主题按固定节奏展开:原理 → 理论根基 → 定义数据 → 定义问题 → 调用 → 解读结果, 每个单元格只做一件事,可直接顺着跑完(约 5 次 API 调用)。

运行要求

  • Python ≥ 3.10(官方 SDK 要求;macOS 系统自带 python3 是 3.9,装不上 SDK)
  • 一个 TypeSafe API Key(console.typesafe.ai/keys 获取)

推荐:一键创建本地环境(在本 notebooks 目录下)

./setup_env.sh                                  # 创建 .venv:Python 3.12 + 全部依赖
export TYPESAFE_API_KEY=你的key
.venv/bin/jupyter lab <本文件>.ipynb

或者手动创建:python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt

🔑 API Key 安全提示:本笔记从环境变量 TYPESAFE_API_KEY 读取密钥, 不要把 Key 硬编码进笔记本(尤其打算提交到公开仓库时)。

🈶 关于语言:实验全部使用中文 state 与中文提示词。三种原语的选项 key (如 billing、verified)属于代码标识符,保持英文以便代码分支判断; 它们的描述文字(criteria 值)均为中文,模型据此理解语义。

0. 准备

0.1 安装所需的库

如果已经用 ./setup_env.sh 创建过环境,本节通常显示“依赖已满足”;在其他环境里首次运行时会自动安装。

%pip install -q -U typesafe-sdk          # 本笔记本必需(要求 Python ≥ 3.10)
# %pip install -q -U jupyterlab         # 如本机还没有 Jupyter,取消注释运行一次
# %pip install -q -U nbformat nbclient  # 仅在需要重新生成/批量执行笔记本时安装

0.2 导入库并创建客户端

  • Choice / Score / Noul:三种问题原语的构造器(对应官方文档原语一章);
  • TypeSafeClient:同步客户端,model="jev-latest" 表示使用官方旗舰模型的最新别名;
  • Key 从环境变量 TYPESAFE_API_KEY 读取;临时调试也可以直接赋值给 API_KEY(不要提交)。
import os
import time

from typesafe_sdk import (
    Choice,                       # 选择题:从命名选项中选一个,返回 choice + probabilities + confidence
    Score,                        # 打分题:按有序量表打分,返回期望分 + probabilities + confidence
    Noul,                         # 是非题:返回"是"的概率(0~1),本身就是概率所以没有 confidence 字段
    TypeSafeClient,
    TypeSafeAuthenticationError,  # 401 鉴权失败时抛出
)

API_KEY = os.environ.get("TYPESAFE_API_KEY", "")
# API_KEY = "apikey_..."   # ← 仅在临时调试时使用,注意不要提交到公开仓库

# Key 为空时不构造客户端:SDK 在无 Key 时抛 TypeSafeError(非 401 的
# TypeSafeAuthenticationError),不会被下面的回退捕获,会让整本笔记本中断。
client = TypeSafeClient(api_key=API_KEY, model="jev-latest") if API_KEY else None

0.3 离线回退用的两个替身类

真实 API 不可用时,我们需要一个与官方 SDK 响应对象同构的替身,让后续分析代码不用改。官方 SystemOneResponse 的访问方式是:

访问方式 返回
resp.answers["名称"] 全部答案(按问题名)
resp.choices["名称"] 选择题答案:.choice .confidence .probabilities
resp.scores["名称"] 打分题答案:.score .confidence .legend .probabilities
resp.nouls["名称"] 是非题答案:.noul(本身就是概率,无 confidence)
resp.usage.input_tokens 本次请求计费的 input token 数

下面的替身类暴露完全相同的属性,仅用于离线模式。

class _FakeAnswer:
    """单个答案的替身:按需挂属性(choice/score/noul/confidence/...)。"""

    def __init__(self, type_, **kw):
        self.type = type_
        for k, v in kw.items():
            setattr(self, k, v)


class _FakeResponse:
    """整个响应的替身:与 SystemOneResponse 同构(answers/nouls/choices/scores/usage)。"""

    def __init__(self, answers):
        self.answers = answers
        self.nouls = {k: v for k, v in answers.items() if v.type == "noul"}
        self.choices = {k: v for k, v in answers.items() if v.type == "choice"}
        self.scores = {k: v for k, v in answers.items() if v.type == "score"}
        self.model = "jev-latest(离线示例)"
        self.usage = _FakeAnswer("usage", input_tokens=0, output_tokens=0)

0.4 调用助手 ts.call()

统一入口:优先请求真实 API;只有当鉴权失败(401)时,才回退到各实验预置的离线示例数据,并在第一次回退时给出显著警告。这样拿到无效 Key 也能跑通全流程,而有效 Key 下全程真实。

class TS:
    real_calls = 0     # 成功的真实调用计数
    offline = False    # 一旦回退过就置 True,后续单元格据此跳过真实计时等逻辑
    _warned = False    # 完整警告只打印一次,避免刷屏

    def call(self, state, questions, offline_answers=None):
        if client is None:          # 未配置 Key:直接走离线示例,不触碰任何客户端
            TS.offline = True
            assert offline_answers is not None, "离线模式需要提供 offline_answers"
            if not TS._warned:
                TS._warned = True
                print("⚠️  离线示例模式:未设置 TYPESAFE_API_KEY,以下为内置示例数据而非真实模型结果;"
                      "设置有效的 TYPESAFE_API_KEY 后重跑本笔记本即可得到真实输出。")
            else:
                print("⚠️ (本次为离线示例数据,非真实 API 输出)")
            return _FakeResponse(offline_answers)
        try:
            resp = client.system_one(state, questions)
            TS.real_calls += 1
            return resp
        except TypeSafeAuthenticationError:
            TS.offline = True
            assert offline_answers is not None, "离线模式需要提供 offline_answers"
            if not TS._warned:
                TS._warned = True
                print("⚠️  离线示例模式:API Key 无效(401),以下输出为内置示例数据而非真实模型结果;"
                      "设置有效的 TYPESAFE_API_KEY 后重跑本笔记本即可得到真实输出。")
            else:
                print("⚠️ (本次为离线示例数据,非真实 API 输出)")
            return _FakeResponse(offline_answers)


ts = TS()

0.5 连通性测试

用一条最简单的是非题试连官方 API:Key 有效则提示通过;若返回 401,后面的实验会自动切换到离线示例模式(见下一节说明),流程照样能走通。

if client is None:
    TS.offline = True
    print("⚠️  未设置 TYPESAFE_API_KEY,以下实验以【离线示例模式】运行:")
    print("    代码路径与真实调用完全一致,仅数据换成本笔记内置的示例值;")
    print("    在有效 Key 下重跑本笔记本即可得到真实模型输出。")
else:
  try:
    ping = client.system_one(
        "你好",
        {"is_greeting": Noul(instructions="这段文字是在打招呼吗?")},
    )
    print("✅ API 连通正常,Key 有效。将进行真实实验。")
  except TypeSafeAuthenticationError:
    TS.offline = True
    print("⚠️  API Key 无效(401)。以下实验将以【离线示例模式】运行:")
    print("    代码路径与真实调用完全一致,仅数据换成本笔记内置的示例值;")
    print("    在有效 Key 下重跑本笔记本即可得到真实模型输出。")

0.6 本章离线示例数据

五家公司各自的 Choice 答案:含高置信银行、低置信综合企等,用于演示 ≥0.9 报组 / 否则报大类。

# 离线:每家公司一条 Choice 答案
CONF_OFFLINE = [
    # 0 清晰区域银行 → commercial_banks,高置信
    _FakeAnswer(
        "choice",
        choice="commercial_banks",
        confidence=0.96,
        probabilities={
            "commercial_banks": 0.92,
            "securities": 0.03,
            "insurance": 0.02,
            "software": 0.01,
            "hardware": 0.01,
            "pharma": 0.00,
            "retail_stores": 0.01,
            "logistics": 0.00,
        },
    ),
    # 1 清晰药企
    _FakeAnswer(
        "choice",
        choice="pharma",
        confidence=0.94,
        probabilities={
            "commercial_banks": 0.01,
            "securities": 0.01,
            "insurance": 0.01,
            "software": 0.02,
            "hardware": 0.01,
            "pharma": 0.90,
            "retail_stores": 0.02,
            "logistics": 0.02,
        },
    ),
    # 2 模糊综合企:金融+地产+零售,置信度低 → 应回退大类
    _FakeAnswer(
        "choice",
        choice="securities",
        confidence=0.52,
        probabilities={
            "commercial_banks": 0.18,
            "securities": 0.28,
            "insurance": 0.12,
            "software": 0.05,
            "hardware": 0.04,
            "pharma": 0.03,
            "retail_stores": 0.20,
            "logistics": 0.10,
        },
    ),
    # 3 软件公司,高置信
    _FakeAnswer(
        "choice",
        choice="software",
        confidence=0.91,
        probabilities={
            "commercial_banks": 0.01,
            "securities": 0.01,
            "insurance": 0.01,
            "software": 0.85,
            "hardware": 0.08,
            "pharma": 0.01,
            "retail_stores": 0.02,
            "logistics": 0.01,
        },
    ),
    # 4 物流,中等偏高但仍 <0.9 → 报大类 trade_transport
    _FakeAnswer(
        "choice",
        choice="logistics",
        confidence=0.78,
        probabilities={
            "commercial_banks": 0.02,
            "securities": 0.02,
            "insurance": 0.03,
            "software": 0.05,
            "hardware": 0.04,
            "pharma": 0.02,
            "retail_stores": 0.15,
            "logistics": 0.67,
        },
    ),
]

📖 理论速览(精简)

本笔记本默认你已读过 System One / 原语 / 置信度 (中文镜像站有对应页)。此处只提醒实验会反复用到的三点:

  1. 请求模型:同一个 state + 一组问题 → 类型化答案;问题彼此独立、按 ID 返回;
  2. 三种原语:Choice 选命名选项;Score 返回概率加权期望分(可为小数);Noul 返回“是”的概率(无 confidence);
  3. 代码掌控制权:阈值、路由、合并答案都在你的代码里;模型只回答狭窄、原子的判断。

💡 问题 ID 不会发给模型——完整语义写在 instructions / criteria 里。


1. 小型中文行业分类

官方实战指南用完整 SIC:75 个行业组、60 份 10-K。本笔记压到:

  • 4 个大类(division)
  • 8 个行业组(group)

逻辑不变:一次 Choice 选组;若 confidence < 0.9,不二次调用,直接报告该组所属大类。

出处:Classification Using Confidence · 中文镜像

1.1 📖 理论根基

  • Choice 返回 choice + probabilities + confidence;
  • confidence 刻画分布有多尖——赢家 0.45、亚军 0.44 与赢家 0.45、其余稀薄,是两种情形;
  • 低置信不等于“没选出来”,而是“选了但不该按细标签行动”;
  • 层级标签让补救几乎零成本:细标签不可信时,上卷到宽标签,无需第二次 API 调用。

1.2 定义大类与行业组

DIVISIONS = {
    "finance": "金融保险",
    "tech": "信息技术",
    "health": "医药健康",
    "trade_transport": "贸易与运输",
}

# group_key → (中文名, 所属大类, 描述供 criteria)
GROUPS = {
    "commercial_banks": (
        "商业银行",
        "finance",
        "吸收存款、发放贷款的商业银行与信用合作社",
    ),
    "securities": (
        "证券与资管",
        "finance",
        "证券公司、基金、资产管理与投资银行业务",
    ),
    "insurance": (
        "保险",
        "finance",
        "人寿、财产及再保险等保险业务",
    ),
    "software": (
        "软件与互联网服务",
        "tech",
        "软件产品、SaaS、互联网平台与信息技术服务",
    ),
    "hardware": (
        "计算机硬件",
        "tech",
        "电脑、服务器、芯片与电子设备制造",
    ),
    "pharma": (
        "制药",
        "health",
        "药品研发、生产与销售的制药企业",
    ),
    "retail_stores": (
        "零售门店",
        "trade_transport",
        "连锁商超、百货与品牌零售门店",
    ),
    "logistics": (
        "物流运输",
        "trade_transport",
        "货运、快递、仓储与第三方物流",
    ),
}

print(f"{len(GROUPS)} 个行业组 → {len(DIVISIONS)} 个大类\n")
for gk, (gname, div, desc) in GROUPS.items():
    print(f"  {gk:20} {gname:12} ⊂ {DIVISIONS[div]:8}  | {desc}")

2. 一次 Choice + 置信度阈值

阈值取官方同款 0.9:高于则报告行业组,否则报告大类。 宽标签由细标签推导,因此低置信路径不再发请求。

2.1 定义 Choice 问题与 classify()

CONFIDENT = 0.9

INDUSTRY_CHOICE = Choice(
    instructions=(
        "根据公司业务描述,选择最匹配的行业组。"
        "依据其当前主要经营活动,而非计划进入的业务或历史残留业务。"
    ),
    criteria={k: f"{GROUPS[k][0]}——{GROUPS[k][2]}" for k in GROUPS},
)


def classify(text, offline_answer):
    resp = ts.call(
        text,
        {"industry_group": INDUSTRY_CHOICE},
        offline_answers={"industry_group": offline_answer},
    )
    ans = resp.choices["industry_group"]
    group_key = ans.choice
    conf = float(ans.confidence)
    gname, div_key, _ = GROUPS[group_key]
    if conf >= CONFIDENT:
        return {
            "level": "group",
            "label_key": group_key,
            "label_zh": gname,
            "division_key": div_key,
            "division_zh": DIVISIONS[div_key],
            "confidence": conf,
            "probabilities": dict(ans.probabilities),
        }
    return {
        "level": "division",
        "label_key": div_key,
        "label_zh": DIVISIONS[div_key],
        "division_key": div_key,
        "division_zh": DIVISIONS[div_key],
        "fallback_from": group_key,
        "fallback_from_zh": gname,
        "confidence": conf,
        "probabilities": dict(ans.probabilities),
    }


print(f"阈值 CONFIDENT = {CONFIDENT}")
print(f"选项数 = {len(GROUPS)}")

3. 五家中文公司简介

覆盖:清晰银行、清晰药企、模糊综合企、软件公司、物流(中等置信)。

3.1 定义公司描述

COMPANIES = [
    {
        "name": "江城农商银行",
        "blurb": (
            "本行主要在省内吸收公众存款、发放短中长期贷款,办理国内外结算与银行卡业务,"
            "分支机构以县域网点为主,利息净收入占总营收八成以上。"
        ),
    },
    {
        "name": "青禾制药",
        "blurb": (
            "公司从事化学药与生物药的研发、生产与销售,核心产品为抗肿瘤与代谢类处方药,"
            "在国内医院渠道销售,并推进创新药临床试验。"
        ),
    },
    {
        "name": "瀚海控股(综合)",
        "blurb": (
            "集团业务横跨证券承销、商业地产租赁与连锁便利店经营;近年出售了部分制造资产,"
            "年报同时强调金融牌照与线下零售扩张,收入结构多极且波动大。"
        ),
    },
    {
        "name": "云杉软件",
        "blurb": (
            "提供企业级 SaaS 协作套件与行业定制开发,收入以订阅费为主,"
            "客户覆盖国内中大型企业的信息化部门。"
        ),
    },
    {
        "name": "迅达物流",
        "blurb": (
            "以公路干线货运与同城配送为主,自营车队加加盟网点,"
            "同时开展仓储管理;亦试点少量社区零售柜,但物流仍是营收主体。"
        ),
    },
]

for c in COMPANIES:
    print(f"· {c['name']}: {c['blurb'][:40]}…")

3.2 逐条分类并打印组/大类决策

RESULTS = []
print(f"{'公司':12} {'置信度':>6}  {'级别':8}  报告标签")
print("-" * 56)
for co, off in zip(COMPANIES, CONF_OFFLINE):
    result = classify(co["blurb"], off)
    RESULTS.append((co, result))
    conf = result["confidence"]
    if result["level"] == "group":
        tag = f"组 · {result['label_zh']}({result['label_key']})"
    else:
        tag = (
            f"大类 · {result['label_zh']}(由 {result['fallback_from_zh']} 回退)"
        )
    print(f"{co['name']:12} {conf:>6.2f}  {result['level']:8}  {tag}")

print("\n— 概率分布摘要(前三)—")
for co, result in RESULTS:
    top3 = sorted(result["probabilities"].items(), key=lambda x: -x[1])[:3]
    parts = ", ".join(f"{k}={v:.2f}" for k, v in top3)
    print(f"{co['name']:12} {parts}")

观察要点

  • 江城农商银行 / 青禾制药 / 云杉软件:置信度 ≥0.9 → 直接报细组;
  • 瀚海控股:概率分散,置信度低 → 只报大类(可能是金融或贸易,取决于赢家组所属);
  • 迅达物流:赢家合理但置信度未过线 → 同样上卷,避免把“不够稳”的细标签交给下游;
  • 全程每家公司 一次 Choice,回退不花第二次调用。

小结

置信度 动作 成本
≥ 0.9 报告行业组 1 次 Choice
< 0.9 报告所属大类 仍是那 1 次(本地推导)

与官方 60 份 10-K / 75 组实验同构,只是标签集与样例缩小,便于课堂跑通。

延伸阅读

⚠️ 离线模式输出为内置示例;设置有效 TYPESAFE_API_KEY 后重跑即可。

知识补充

  • 三路分流是置信度的标准用法:高置信自动执行、中间进人工复核、低置信直接拒绝——比单一阈值多出"复核带",生产系统几乎都长这样。
  • 阈值必须独立验证:两个分界点要在未参与调参的标注集上选;否则就是你自己在给模型"放水"。
  • 进阶阅读:confidence 与概率分布的关系见 ../02_核心概念/04_置信度.ipynb;门控路由模式见 ../03_架构模式/01_架构模式.ipynb。

📑 Jev Cookbook:System One 判断模型实战教程

1 第 1 章:认识 Jev:模型、上手与场景 2 第 2 章:核心概念总览 3 第 3 章:System One:判断的核心心智模型 4 第 4 章:状态:让判断连续可追溯 5 第 5 章:原语:Choice、Score 与 Noul 6 第 6 章:置信度:让概率可信 7 第 7 章:应用构建:从原语到完整系统 8 第 8 章:架构模式 9 第 9 章:实战指南总览 10 第 10 章:自一致性 · Noul 11 第 11 章:自一致性 · Choice 12 第 12 章:并行提问 13 第 13 章:重排序 14 第 14 章:逐行语义搜索 15 第 15 章:结构恢复 16 第 16 章:函数调用 17 第 17 章:技能推荐 18 第 18 章:实体对齐 19 第 19 章:RAG 段落分类 20 第 20 章:引用核查 21 第 21 章:LLM 防护栏 22 第 22 章:SDE 级联 23 第 23 章:日期抽取 24 第 24 章:预解析值抽取 25 第 25 章:层级分类 26 第 26 章:自动研究特征发现 27 第 27 章:基于置信度的分类 28 第 28 章:智能家居实验 29 第 29 章:模型评测总览 30 第 30 章:模型评测实验 31 第 31 章:Laya vs Jev 对比基准 32 第 32 章:JevBench:LLM 评测体系 33 第 33 章:智能家居应用实战 34 第 34 章:Jev-Mem 研究总览 35 第 35 章:Jev-Mem 缩放实验 36 第 36 章:Jev-Mem 完整走查 37 第 37 章:Agent 集成总览 38 第 38 章:Pi 集成实验 39 第 39 章:DSH 决策协作 40 第 40 章:本地模型总览 41 第 41 章:本地模型介绍与对比 42 第 42 章:中文数据集构建方案 43 第 43 章:微调指南 44 第 44 章:RLCD 原理与实验优化 45 第 45 章:中文数据集构建实验 46 第 46 章:中文 Head 微调实验 47 第 47 章:全量 v2 微调实验 48 第 48 章:知识库
← 返回本书大纲