第 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 None0.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 / 原语 / 置信度 (中文镜像站有对应页)。此处只提醒实验会反复用到的三点:
- 请求模型:同一个
state+ 一组问题 → 类型化答案;问题彼此独立、按 ID 返回; - 三种原语:
Choice选命名选项;Score返回概率加权期望分(可为小数);Noul返回“是”的概率(无 confidence); - 代码掌控制权:阈值、路由、合并答案都在你的代码里;模型只回答狭窄、原子的判断。
💡 问题 ID 不会发给模型——完整语义写在
instructions/criteria里。
1. 小型中文行业分类
官方实战指南用完整 SIC:75 个行业组、60 份 10-K。本笔记压到:
- 4 个大类(division)
- 8 个行业组(group)
逻辑不变:一次 Choice 选组;若 confidence < 0.9,不二次调用,直接报告该组所属大类。
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。