9.2 可靠性曲线与 ECE:验证校准


9.2 可靠性曲线与 ECE:验证校准

本节摘要:第 5.2 节说 Jev 的概率经 RLCD 校准、"说 0.9 约等于九成命中"——本节教你亲手验证这句话。方法:把回归集里每个 Noul 判断的预测概率按 0.1 步长分桶,比较每桶的"预测均值"与"实际命中率",画成可靠性曲线;量化指标 ECE(期望校准误差)= 各桶 |预测均值 − 实际命中率| 的样本量加权平均,经验读法:<0.05 优秀、0.05~0.15 可接受、>0.15 过度自信/不足,概率不能直接当合同用。本节内嵌完整脚本(05_calibration_check.py)——带 MOCK 模式,无 Key 也能本地跑通算法;把 LABELED 换成你的回归集即可投入生产。

学习目标

阅读完本节,你应当能够:

  1. 手画一张可靠性曲线并解读过度自信/不足两种病相。
  2. 计算 ECE 并按经验档位定性。
  3. 运行内嵌脚本(含 MOCK),并把 LABELED 换成自己的数据。

一、方法:分桶、画线、算 ECE

以"工单是否紧急"(Noul)为例,对回归集的每条样本:Jev 给出 p(紧急的概率),人工标注 y(0/1)。

桶 [0.8, 0.9):预测均值 0.84,实际命中 0.83 ✅ 校准良好(点贴近对角线) 桶 [0.8, 0.9):预测均值 0.84,实际命中 0.61 ❌ 过度自信(点在对角线下方) 桶 [0.5, 0.6):预测均值 0.55,实际命中 0.72 ❌ 信心不足(点在对角线上方)

把每桶的 (预测均值, 实际命中率) 画散点、叠一条对角线,就是可靠性曲线(reliability diagram)——健康的校准,点都贴着对角线

ECE(Expected Calibration Error)

ECE = Σ (桶样本量 / 总量) × |桶预测均值 − 桶实际命中率|
ECE 定性 处置
< 0.05 优秀 概率可直接当合同(阈值放心用)
0.05 ~ 0.15 可接受 阈值保守化一档(0.9 的阈值当 0.85 用)
> 0.15 过度自信/不足 概率不可直接用:重校准或回影子改 schema

二、完整脚本

以下脚本即本教程的 05_calibration_check.py,完整内嵌于此。无 Key 时用 MOCK 数据源(模拟一个"轻微过度自信"的模型,演示算法行为),设了 TYPESAFE_API_KEY 则真实调用:

# 05_calibration_check.py —— 验证校准(可靠性曲线 + ECE) import os import random import requests API_URL = "https://api.typesafe.ai/v1/systemone" N_BUCKET = 10 # [0.0,0.1), [0.1,0.2), ..., [0.9,1.0] # ---- 1) 你的回归集:影子模式日志 + 人工标注(这里以"工单是否紧急"为例) ---- LABELED = [ ("客户已付款但订单三天未发货,再不处理就去 12315 投诉", 1), ("请问你们支持开发票吗", 0), ("线上支付全挂了,每分钟都在损失订单,立刻处理!", 1), ("App 的夜间模式什么时候上线呀", 0), ("已经等了两周没人回复,你们到底有没有客服?", 1), ("这个按钮的颜色换成蓝色会更好看", 0), ("账号被盗了,有人正在下单,快点冻结!", 1), ("怎么修改收货地址", 0), ("再不给我退款我就发微博曝光", 1), ("建议增加导出 Excel 的功能", 0), ] QUESTION = { "is_urgent": { "type": "noul", "instructions": "该消息表达了紧迫性或时间敏感性", } } def predict_mock(text: str, label: int) -> float: """无 key 时的演示数据源:模拟一个'轻微过度自信'的模型。""" rng = random.Random(hash(text) & 0xFFFF) if label: return 0.65 + rng.random() * 0.30 # 0.65~0.95 return 0.10 + rng.random() * 0.50 # 0.10~0.60(部分越过 0.5) def predict_jev(text: str) -> float: resp = requests.post( API_URL, headers={"Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}"}, json={"model": "jev-latest", "state": text, "questions": QUESTION}, timeout=10, ) resp.raise_for_status() return resp.json()["answers"]["is_urgent"]["noul"] def reliability_curve(pairs: list[tuple[float, int]]): """按预测概率分桶 -> 各桶(预测均值, 实际命中率, 样本数),并计算 ECE。""" buckets: list[list[tuple[float, int]]] = [[] for _ in range(N_BUCKET)] for p, y in pairs: buckets[min(N_BUCKET - 1, int(p * N_BUCKET))].append((p, y)) ece, rows = 0.0, [] for i, b in enumerate(buckets): if not b: continue n = len(b) mean_p = sum(p for p, _ in b) / n hit = sum(y for _, y in b) / n ece += n / len(pairs) * abs(mean_p - hit) rows.append((f"[{i / N_BUCKET:.1f},{(i + 1) / N_BUCKET:.1f})", mean_p, hit, n)) return rows, ece def main() -> None: has_key = bool(os.environ.get("TYPESAFE_API_KEY")) print(f"数据源:{'Jev 真实调用' if has_key else 'MOCK(设置 TYPESAFE_API_KEY 后用真实调用)'}\n") pairs = [] for text, label in LABELED: p = predict_jev(text) if has_key else predict_mock(text, label) pairs.append((p, label)) print(f" p={p:.3f} label={label} {text[:24]}") rows, ece = reliability_curve(pairs) print("\n可靠性曲线(预测概率 vs 实际命中率):") print(f" {'桶':<12}{'预测均值':>8}{'实际命中':>8}{'样本':>5}") for name, mean_p, hit, n in rows: bar = "█" * int(hit * 20) print(f" {name:<12}{mean_p:>8.2f}{hit:>8.2f}{n:>5} {bar}") verdict = "优秀" if ece < 0.05 else "可接受" if ece < 0.15 else "过度自信,阈值需上调" print(f"\nECE = {ece:.3f} ({verdict})") if __name__ == "__main__": main()

本地 MOCK 模式的一次输出(10 条样本,噪声大、仅供算法演示):

桶 预测均值 实际命中 样本 [0.1,0.2) 0.15 0.00 1 [0.2,0.3) 0.29 0.00 1 ... [0.6,0.7) 0.68 1.00 1 ████████████████████ ... ECE = 0.257 (过度自信,阈值需上调)

MOCK 数据刻意做成"低桶全空、高桶部分命中"的过度自信形态,让你在跑真数据前先认识这种病相。

三、从体检到处方

病相 处方
过度自信(点在对角线下方) 阈值整体上调一档;检查 instructions 是否引导了过强表态
信心不足(点在对角线上方) 阈值下调;通常是问题措辞含糊——按第 3 章准则收紧判别标准
两端准、中间乱 正常(0.5 附近本来就是难例区)——用三段式阈值把中间带送人工(第 7.3 节)
某一桶系统性偏 该区间的样本有共同特征(如短文本、外语)→ 补专项样本或加一道前置闸门

⚠️ 样本量纪律:每桶 <10 条时该桶读数只是噪声——总样本不足时先并桶(0.2 步长)再读;这也是回归集要 100 条起步的原因。

本节要点回顾

  1. 方法:按 0.1 分桶 → 比较预测均值与实际命中率 → 可靠性曲线贴对角线即健康。
  2. ECE:加权平均偏差;<0.05 优、>0.15 概率不能直接当合同。
  3. 处方:过度自信上调阈值、信心不足收紧措辞、中间乱送人工——体检报告直接翻译成生产参数。

概率诚实不诚实量出来了,最后一节把"阈值"这个最常被拍脑袋的参数变成代价函数的解。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U