说 70% 的时候,真的会发生 70% 吗
0 一个尴尬的提问
有人给你看一份预测记录:2000 场比赛,准确率 66.90%。 看起来不错。但如果你换个问法——
核心提问 “在你说过 70% 的那些场次里,实际发生了多少次?”
这个问题准确率答不了。准确率只统计“把票投给谁”, 它把 0.51 和 0.99 都当成同一个“是”, 把 0.49 和 0.01 都当成同一个“否”。 也就是说,准确率把概率里最值钱的信息——强度——直接扔掉了。
这不是学术洁癖。IN-03 已经算过:
要判断一注值不值得下,你需要的是 EV = p × d − 1 里的那个
p,而不是“猜对没有”这个 0/1 结果。
IN-02 又说过,赔率倒数归一化后能给出市场的 p。
两件事拼起来,结论就很硬:如果你的 p 是虚的,
EV 算出来的每一个正号都可能是假的。
本文要做的,就是给 p 本身做一次体检。三步: 先画可靠性图看形状,再用 ECE / MCE 把形状压成一个数, 最后动手修,并检查修完之后钱包有没有变化。
1 校准是什么:一句人话的定义
校准(calibration)的定义朴素到有点让人失望:
定义 把所有你说过 “70%” 的场次挑出来,数一数实际发生了几成。 如果是七成,你在这个刻度上校准良好;如果只有五成,你吹了; 如果有九成,你过于谦虚。对每个刻度都这么查一遍,就是一次完整的校准检验。
实践中没人会说得刚好是 70.00%,所以要分箱: 把 0 到 1 切成 10 段,每段里的宣称概率算一个平均值, 再和这段里的实际发生频率比。差值就是这一箱的校准误差。
注意这里出现了一个和准确率完全不同的动作: 准确率横着看每一场,校准竖着看每一箱。 单场比赛没有校准可言——事情要么发生要么没发生, 你说 70% 是对是错无从判断。只有把同一刻度的许多场聚在一起, 那个百分数才第一次变得可以被证伪。
2 三个准确率一样的预测者
为了把话说清楚,我构造了一批数据:2000 场比赛, 每场有一个真实发生概率,结果按这个概率抛硬币决定。 然后请三位预测者对同一批比赛报数——
- 预测者 A(过度自信):把真实概率往两端拉伸,敢说 0.03,也敢说 0.97。
- 预测者 B(校准良好):报的就是真实概率,只带一点小噪声。
- 预测者 C(欠自信):把真实概率往 0.5 压,什么都说得含含糊糊。
数据口径 这是构造样本,不是真实赛事记录, 目的是把三种典型偏差干净地隔离出来。随机种子固定为 20260808, §7 给出完整源码,任何人跑一遍都会得到本文的每一个数字。
先看最常被拿出来汇报的那张表:
| 预测者 | 准确率 | Brier 分数 | ECE | MCE |
|---|---|---|---|---|
| A 过度自信 | 0.6690 | 0.2135 | 0.1015 | 0.1377 |
| B 校准良好 | 0.6685 | 0.2038 | 0.0267 | 0.0760 |
| C 欠自信 | 0.6690 | 0.2129 | 0.0775 | 0.2191 |
准确率那一列几乎是平的:66.90%、66.85%、 66.90%,最大差距 0.05 个百分点—— 在 2000 场的尺度上,这点差距连噪声都算不上。 如果汇报只到这一列,三个人是同一水平。
但 ECE 那一列,A 是 B 的 3.8 倍。 MCE 更夸张,C 在某个刻度上的偏差达到 0.2191—— 也就是说,C 报的某个百分数系统性地偏离真相 21.91 个百分点。 准确率对此完全没有反应,因为它压根不看这个百分数。
3 可靠性表:把偏差摊开来看
ECE 是一个压缩后的数,压缩就会丢信息。要知道错在哪里, 得把每一箱摊开。这张摊开的表就是可靠性图的数字版本—— 完美校准的预测者,“宣称”和“实际”两列应该处处相等。
| 宣称概率区间 | 场次 | 区间内平均宣称 | 实际发生频率 | 差值 |
|---|---|---|---|---|
| [0.0, 0.1) | 271 | 0.0284 | 0.1661 | +0.1377 |
| [0.1, 0.2) | 150 | 0.1477 | 0.2533 | +0.1056 |
| [0.2, 0.3) | 180 | 0.2477 | 0.3167 | +0.0690 |
| [0.3, 0.4) | 199 | 0.3502 | 0.4724 | +0.1221 |
| [0.4, 0.5) | 218 | 0.4510 | 0.4174 | -0.0336 |
| [0.5, 0.6) | 204 | 0.5532 | 0.4461 | -0.1071 |
| [0.6, 0.7) | 186 | 0.6505 | 0.5376 | -0.1129 |
| [0.7, 0.8) | 175 | 0.7498 | 0.6343 | -0.1156 |
| [0.8, 0.9) | 154 | 0.8470 | 0.7857 | -0.0613 |
| [0.9, 1.0) | 263 | 0.9705 | 0.8441 | -0.1264 |
A 的形状非常有辨识度:低端全是正号,高端全是负号。 最低那一箱他平均报 0.0284,实际发生了 0.1661—— 他说“几乎不可能”的事,每六次就来一次。 最高那一箱他平均报 0.9705,实际只有 0.8441—— 他说“几乎必然”的事,每六到七次就失手一次。
形状读法 低估低端 + 高估高端 = 过度自信。 在可靠性图上,这条曲线会以比对角线更平缓的斜率穿过中心, 两端各自倒向 0 和 1。直觉上就是:这个人的话要往中间打折。
| 宣称概率区间 | 场次 | 区间内平均宣称 | 实际发生频率 | 差值 |
|---|---|---|---|---|
| [0.0, 0.1) | 61 | 0.0574 | 0.0656 | +0.0082 |
| [0.1, 0.2) | 139 | 0.1617 | 0.1942 | +0.0326 |
| [0.2, 0.3) | 211 | 0.2536 | 0.2512 | -0.0024 |
| [0.3, 0.4) | 285 | 0.3499 | 0.3509 | +0.0010 |
| [0.4, 0.5) | 317 | 0.4504 | 0.4385 | -0.0119 |
| [0.5, 0.6) | 324 | 0.5513 | 0.4753 | -0.0760 |
| [0.6, 0.7) | 267 | 0.6493 | 0.6142 | -0.0351 |
| [0.7, 0.8) | 208 | 0.7480 | 0.7837 | +0.0356 |
| [0.8, 0.9) | 142 | 0.8467 | 0.8592 | +0.0124 |
| [0.9, 1.0) | 46 | 0.9410 | 0.9565 | +0.0155 |
B 的差值列几乎全在 ±0.04 以内,正负交替、没有方向性—— 这才是随机噪声该有的样子。唯一一处 −0.0760 出现在 [0.5, 0.6), 也是全表最大偏差,即它的 MCE。 值得强调的是:B 的准确率和 A 一模一样, 但 B 说的每个百分数都可以直接拿去算钱。
4 欠自信:另一个方向的谎
过度自信容易被抓,因为它张扬。欠自信更隐蔽——它听起来谨慎、专业、有分寸, 但它同样是错的,而且错得可能更贵。看 C 的表:
| 宣称概率区间 | 场次 | 区间内平均宣称 | 实际发生频率 | 差值 |
|---|---|---|---|---|
| [0.2, 0.3) | 96 | 0.2738 | 0.0833 | -0.1905 |
| [0.3, 0.4) | 358 | 0.3560 | 0.2402 | -0.1158 |
| [0.4, 0.5) | 564 | 0.4520 | 0.4096 | -0.0425 |
| [0.5, 0.6) | 531 | 0.5486 | 0.5348 | -0.0137 |
| [0.6, 0.7) | 364 | 0.6432 | 0.7665 | +0.1233 |
| [0.7, 0.8) | 87 | 0.7234 | 0.9425 | +0.2191 |
第一件该注意的事:这张表只有 6 行, 而 A 和 B 都有 10 行。C 的预测从来没有低于 0.2,也从来没有高于 0.8—— 有 4 个箱是空的。他把所有判断都塞进了中间那几档。
第二件:差值的符号排列和 A 正好相反。 低端是负号(说 0.2738,实际只有 0.0833),高端是正号(说 0.7234,实际高达 0.9425)。 翻译一下:C 说“可能性不大”的时候,实际上是“基本不会”; C 说“比较可能”的时候,实际上是“几乎肯定”。
形状读法 高估低端 + 低估高端 = 欠自信。 可靠性曲线以比对角线更陡的斜率穿过中心,两端伸不出去。 直觉上:这个人的话要往两边放大。
这里有个反直觉的结果值得停一下。C 的最大校准误差 0.2191 是三人中最高的,比 A 的 0.1377 还高出 8 个百分点; 但 C 的 ECE(0.0775)反而低于 A(0.1015)。 原因是 ECE 按场次加权,而 C 那个 0.2191 的巨大偏差只发生在 87 场里, 权重只有 87/2000 = 4.35%。
这正是必须同时看 ECE 和 MCE 的理由。 ECE 告诉你平均而言这个人偏多少,MCE 告诉你他最糟的时候能偏多少。 如果你只在高置信区间下注,那么 C 对你来说是三人中最危险的—— 你专门去用的那个刻度,恰好是他错得最狠的地方。
5 ECE 与 MCE:两个数怎么来的
把可靠性表压成一个数,只需要加权平均。设一共 B 个箱, 第 i 箱有 ni 场, 平均宣称 ci,实际频率 ai, 总场次 N:
期望校准误差:各箱偏差的绝对值,按该箱样本占比加权求和。
最大校准误差:所有箱里最坏的那一个偏差,不加权。
公式里没有任何超越加减乘除的运算。C 只有 6 个非空箱,索性整个算完:
手算 96/2000 × 0.1905 + 358/2000 × 0.1158 + 564/2000 × 0.0425 + 531/2000 × 0.0137 + 364/2000 × 0.1233 + 87/2000 × 0.2191 = 0.0775, 与表 1 中 C 的 ECE 0.0775 完全一致。 MCE 则不必加权,直接取差值列绝对值最大的那一格:0.2191, 来自 [0.7, 0.8) 这一箱。
有两个细节会影响结果,报数时必须一起说明。 第一是箱数:箱越多,每箱样本越少,实际频率的抖动越大,ECE 会被噪声推高; 箱越少,箱内不同的宣称概率被平均掉,偏差会被掩盖。本文统一用 10 箱, 这是文献里的常规选择,但它是一个约定,不是定理。 第二是等宽还是等频:本文用等宽分箱(每箱跨度 0.1), 所以 C 才会出现空箱;若改用等频分箱(每箱样本数相同), C 的空箱会消失,ECE 数值也会变。
可比性提醒 不同箱数、不同分箱方式算出的 ECE 不能直接比较。 看到别人报 ECE 而没说箱数和分箱方式,这个数字的信息量接近于零。
6 Brier 分解:校准误差藏在哪一项
表 1 里还有一列没解释:Brier 分数。它的定义比 ECE 还简单—— 每场把宣称概率和结果(0 或 1)相减、平方,最后取平均:
Brier 分数:越小越好,完美预测为 0,全押反面为 1。
Brier 分数好用,因为它同时惩罚校准和分辨能力—— 但也正因如此,单看一个 Brier 分数说不清问题出在哪。 Murphy 在 1973 年给出了一个漂亮的拆法:把 Brier 分成三块。
Murphy 分解:可靠性越小越好,分辨度越大越好,不确定度由数据本身决定、与预测者无关。
三块各自的含义,用人话说是这样:
- 可靠性(reliability):就是校准误差,只不过用平方代替绝对值。 它衡量“你报的数准不准”。越小越好。
- 分辨度(resolution):你的不同刻度之间,实际发生率能拉开多大差距。 如果你对每一场都报同一个数,分辨度为 0——你的预测毫无信息量。 越大越好。
- 不确定度(uncertainty):这批数据本身有多难猜,等于基准发生率乘以它的补数。 它不由预测者决定,换谁来预测这个数都一样。
这批数据的基准发生率是 0.4850, 所以不确定度固定为 0.4850 × 0.5150 = 0.2498。 三个人的这一项完全相同,可以直接约掉——差别只在前两项。
| 预测者 | 可靠性 ↓ | 分辨度 ↑ | 不确定度 | 三项合计 | 直接算 Brier |
|---|---|---|---|---|---|
| A 过度自信 | 0.0114 | 0.0472 | 0.2498 | 0.2140 | 0.2135 |
| B 校准良好 | 0.0013 | 0.0465 | 0.2498 | 0.2046 | 0.2038 |
| C 欠自信 | 0.0096 | 0.0443 | 0.2498 | 0.2151 | 0.2129 |
这张表把话说透了。分辨度那一列三个人几乎相同: 0.0472、0.0465、0.0443,最大差距只有 0.0029。 这不奇怪——三个人的预测都是同一个真实概率的单调变换, 排序能力没有任何损失。这也解释了为什么他们准确率一样: 准确率只依赖排序和阈值,而排序是三人共有的那部分本事。
真正的差别全在可靠性列:A 是 0.0114, C 是 0.0096,而 B 只有 0.0013—— A 是 B 的 8.4 倍。 Brier 分数的高低(0.2135 vs 0.2038)几乎全部由这一项贡献。
关键结论 A、B、C 拥有同样的排序能力,差别只在报数的诚实程度。 准确率只看排序,所以它看三个人一样好; 校准指标只看报数,所以它看出了 3.8 倍的差距。 这两个指标测的根本不是同一件事,谁也替代不了谁。
还有一个能说明问题的细节:三项合计与直接计算的 Brier 有微小差异 (A 是 0.2140 vs 0.2135)。这不是算错了—— Murphy 分解在分箱之后才成立,箱内把不同的宣称概率平均成了一个 ci, 这一步丢掉的信息就是这点残差。箱数越多,残差越小。 我把两列都列出来,正是为了让这个近似公开可见, 而不是悄悄用其中一个冒充另一个。
7 校准误差怎么变成亏损
到这里全是指标。指标不花钱,所以必须回答最后一个问题: 校准差,到底亏在哪里?
把 IN-03 的规则接过来:
只有当 EV = p × d − 1 超过某个门槛时才下注,
每注固定 100 元。这里的 p 是预测者报的概率,
d 是赔率。赔率按 IN-02 的办法构造:
在真实概率上扣掉 5% 抽水,再取倒数。
口径说明 赔率由真实概率加抽水反推,属构造盘口, 不代表任何真实市场报价。这样做的好处是“市场”本身是校准的, 亏损只能来自预测者自己的校准误差,因果关系干净。 抽水固定 5%,唯一变动的是 EV 门槛。
门槛就是“要求多大的账面优势才动手”。 门槛 0% 意味着只要 EV 是正的就下,门槛 8% 意味着要看到 8% 以上的优势才下。 直觉上门槛越高越安全。看真实结果:
| EV 门槛 | A 过度自信 | B 校准良好 | C 欠自信 | |||
|---|---|---|---|---|---|---|
| 注数 | 收益率 | 注数 | 收益率 | 注数 | 收益率 | |
| 0% | 802 | -6.79% | 455 | -5.71% | 836 | -3.20% |
| 2% | 710 | -5.03% | 335 | -2.67% | 778 | -3.25% |
| 5% | 579 | -5.00% | 205 | -5.13% | 703 | -5.48% |
| 8% | 420 | -4.46% | 134 | +7.70% | 648 | -5.24% |
先看注数怎么随门槛缩水。B 从 455 注一路砍到 134 注, 只剩下 29%;而 C 从 836 只降到 648, 还留着 78%。这是个危险信号。
为什么门槛对 C 几乎无效?因为 C 在低端系统性高报 (说 0.2738,实际只有 0.0833)。高报直接把 EV 算大, 这些虚高的正号轻松越过任何门槛。 门槛是一道对数值大小的筛子, 而系统性偏差恰恰是把数值整体推大——筛子越细,漏过来的越是偏差本身。 真正被门槛筛掉的,只有那些没有偏差、优势本来就薄的诚实判断。
再看收益率。最值得看的是 B 的那一列: -5.71% → -2.67% → -5.13% → +7.70%。 门槛提到 8% 时,B 转正了,而且是全表唯一的正号。 这正是校准良好该有的行为:它报的 p 可信, 所以“只挑优势最大的注”这个策略真的有效—— 哪怕代价是 2000 场里只出手 134 次。
对照 A 和 C,同样的门槛策略完全失灵。 A 从 -6.79% 到 -4.46%,在负区间里横盘, 抬门槛既没帮他也没害他——因为他的偏差在高低两端同时存在, 筛掉一批坏注的同时也筛掉了一批好注。 C 更彻底,-3.20% 到 -5.24%,越筛越差。
最贵的一课 三个人准确率都是 66.9%。8% 门槛下,收益率是 -4.46%、+7.70%、-5.24%。 准确率相同,钱包相差 12.95 个百分点。 差额全部由那个“看起来只是小数点后两位”的校准误差贡献。
还有一层更实用的推论。“抬高门槛能不能改善收益” 本身就是一个校准诊断工具——不需要标签,不需要画可靠性图。 如果你的策略在提高选注标准后收益反而变差, 那几乎可以断定:你的 p 有系统性偏差, 你正在用一个虚假的数字去挑“最好的机会”, 结果专挑出了偏差最大的那些。
8 怎么修:把可靠性表反过来用
诊断完了该谈治疗。好消息是:校准问题往往是最容易修的那一类模型缺陷, 因为它不要求你改模型、加特征、重训练。
思路直接来自可靠性表。表 2 告诉我们,A 说 0.9705 的时候实际只发生 0.8441。 那么下次 A 再说 0.97,我们就把它当成 0.84。 这就是分箱重映射(histogram binning): 把可靠性表的“实际频率”那一列,直接当成新的输出。
方法 第一步,在一批带标签的历史数据上分箱,算出每箱的实际发生频率,得到一张查表; 第二步,对新的预测,先看它落在哪个箱,再用查表里的频率替换它的输出。 整个过程只用到加法和除法。
但这里有个绝对不能省的动作:训练和检验必须用不同的数据。 如果在同一批数据上学映射又在同一批上评估, ECE 会被压到接近 0——那不是校准变好了,那是把答案抄了一遍。 所以我把 2000 场对半切开:前 1000 场学映射,后 1000 场检验, 映射表在检验集上是完全没见过的。
| 预测者 | 阶段 | 准确率 | Brier | ECE | MCE | ECE 降幅 |
|---|---|---|---|---|---|---|
| A 过度自信 | 修正前 | 0.6660 | 0.2107 | 0.0954 | 0.1400 | −78% |
| 修正后 | 0.6840 | 0.2019 | 0.0207 | 0.0483 | ||
| B 校准良好 | 修正前 | 0.6730 | 0.2016 | 0.0398 | 0.0850 | −12% |
| 修正后 | 0.6840 | 0.2028 | 0.0349 | 0.0787 | ||
| C 欠自信 | 修正前 | 0.6660 | 0.2125 | 0.0839 | 0.1928 | −66% |
| 修正后 | 0.6660 | 0.2055 | 0.0282 | 0.0677 |
结果值得逐行读。A 的 ECE 从 0.0954 降到 0.0207, 砍掉 78%;MCE 从 0.1400 降到 0.0483。 C 的 ECE 从 0.0839 降到 0.0282,降幅 66%。 而 B 本来就校准良好,修正后 ECE 只从 0.0398 微降到 0.0349—— 对已经诚实的预测者,这道工序几乎不做任何事,也不会把它弄坏。 这正是一个好的后处理该有的性质。
再看准确率列。A 修正后准确率反而从 0.6660 升到 0.6840, C 则一动不动(0.6660 → 0.6660)。 C 不变是必然的:分箱重映射是单调变换,不改变任何两场之间的排序, 而准确率只依赖排序和阈值。A 会变,是因为重映射把某些箱推过了 0.5 这条线—— 换了阈值两侧的归属,不是换了排序。
务实的结论 校准是后处理能解决的问题,排序能力不是。 如果你的模型排序能力尚可但报数虚假, 你离一个可用的模型只差一张查表; 反过来,如果排序能力本身不行, 再精致的校准手段也只是把一堆没有信息量的数字调得好看一点。 先看分辨度,再修可靠性——顺序不能颠倒。
分箱重映射不是唯一选择,只是最容易手算的那个。 实践中更常见的是 Platt scaling(拿一维逻辑回归拟合) 和 isotonic regression(拟合一条单调阶梯)。 前者假设偏差有固定的 S 形,参数只有两个,小样本下更稳; 后者不作形状假设,但需要更多数据才不过拟合。 分箱重映射介于两者之间,代价是箱边界处会有跳变。 三者的共同点是:都只动输出,不动模型。
9 复算脚本
本文所有数字都来自下面这段代码。它只用 Python 标准库, 没有 numpy、没有 sklearn,可以直接复制运行。 随机种子固定为 20260808,因此你在自己机器上应当得到逐位相同的结果。
数据性质 这批 2000 场是构造数据, 不是真实赛事记录。构造它的理由很实际: 只有先知道每场的真实概率,才能明确定义“过度自信”和“欠自信”, 并把三个预测者的排序能力控制成完全相同。 真实数据里做不到这种对照——那里的真实概率永远不可观测。
import random
SEED, N, BINS = 20260808, 2000, 10
def clip(x, lo=0.01, hi=0.99):
return max(lo, min(hi, x))
def make_data():
"""构造 N 场比赛:真实概率 p_true,结果 y,以及三个预测者的宣称概率。
A 过度自信(把 p 往两端推),B 校准良好(只加小噪声),C 欠自信(把 p 往中间压)。
"""
rnd = random.Random(SEED)
rows = []
for _ in range(N):
p = rnd.betavariate(2.0, 2.0) # 真实概率
y = 1 if rnd.random() < p else 0 # 按真实概率掷出结果
pA = clip(p + (p - 0.5) * 0.60) # 远离 0.5 -> 过度自信
pB = clip(p + rnd.gauss(0, 0.03)) # 轻微噪声 -> 校准良好
pC = clip(0.5 + (p - 0.5) * 0.55) # 靠近 0.5 -> 欠自信
rows.append((p, y, pA, pB, pC))
return rows
def reliability(ps, ys, bins=BINS):
"""等宽分箱,返回每箱 (下界, 上界, 场次, 平均宣称, 实际频率, 差值)。"""
acc = [[0, 0.0, 0] for _ in range(bins)] # [场次, 宣称之和, 命中数]
for p, y in zip(ps, ys):
k = min(int(p * bins), bins - 1)
acc[k][0] += 1
acc[k][1] += p
acc[k][2] += y
out = []
for k, (n, sp, sy) in enumerate(acc):
if n:
c, a = sp / n, sy / n
out.append((k / bins, (k + 1) / bins, n, c, a, a - c))
else:
out.append((k / bins, (k + 1) / bins, 0, 0.0, 0.0, 0.0))
return out
def ece_mce(table, total):
"""ECE = 按场次加权的平均绝对偏差;MCE = 最大绝对偏差。"""
ece = sum(n / total * abs(d) for _, _, n, _, _, d in table if n)
mce = max((abs(d) for _, _, n, _, _, d in table if n), default=0.0)
return ece, mce
def brier(ps, ys):
return sum((p - y) ** 2 for p, y in zip(ps, ys)) / len(ys)
def accuracy(ps, ys, thr=0.5):
return sum(1 for p, y in zip(ps, ys) if (1 if p > thr else 0) == y) / len(ys)
DATA = make_data()
ys = [r[1] for r in DATA]
print("N = %d base rate = %.4f" % (len(ys), sum(ys) / len(ys)))
print()
print("%-14s %8s %8s %8s %8s" % ("predictor", "acc", "brier", "ECE", "MCE"))
for name, i in (("A overconf", 2), ("B calibrated", 3), ("C underconf", 4)):
ps = [r[i] for r in DATA]
t = reliability(ps, ys)
e, m = ece_mce(t, len(ys))
print("%-14s %8.4f %8.4f %8.4f %8.4f"
% (name, accuracy(ps, ys), brier(ps, ys), e, m))
print()
print("reliability table of A (10 equal-width bins)")
print("%-12s %6s %9s %9s %9s" % ("bin", "n", "claimed", "actual", "gap"))
for lo, hi, n, c, a, d in reliability([r[2] for r in DATA], ys):
if n:
print("[%.1f, %.1f) %6d %9.4f %9.4f %+9.4f" % (lo, hi, n, c, a, d))
运行它,得到下面这段输出。这是脚本的真实 stdout,未经任何编辑:
N = 2000 base rate = 0.4850
predictor acc brier ECE MCE
A overconf 0.6690 0.2135 0.1015 0.1377
B calibrated 0.6685 0.2038 0.0267 0.0760
C underconf 0.6690 0.2129 0.0775 0.2191
reliability table of A (10 equal-width bins)
bin n claimed actual gap
[0.0, 0.1) 271 0.0284 0.1661 +0.1377
[0.1, 0.2) 150 0.1477 0.2533 +0.1056
[0.2, 0.3) 180 0.2477 0.3167 +0.0690
[0.3, 0.4) 199 0.3502 0.4724 +0.1221
[0.4, 0.5) 218 0.4510 0.4174 -0.0336
[0.5, 0.6) 204 0.5532 0.4461 -0.1071
[0.6, 0.7) 186 0.6505 0.5376 -0.1129
[0.7, 0.8) 175 0.7498 0.6343 -0.1156
[0.8, 0.9) 154 0.8470 0.7857 -0.0613
[0.9, 1.0) 263 0.9705 0.8441 -0.1264
对照一下就能确认:三人准确率 0.6690 / 0.6685 / 0.6690,
ECE 0.1015 / 0.0267 / 0.0775——与表 1 完全一致。
A 的可靠性表最后一行 [0.9, 1.0) 263 0.9705 0.8441 -0.1264,
也正是本文反复引用的那个“说 0.97 只发生 0.84”。
§6 的 Murphy 分解、§7 的下注回测和 §8 的分箱重映射需要额外几十行,
为了让代码 1 保持可读,没有全部贴进来。
完整脚本(含这三部分)就是本站生成本文所用的
_verify_calib.py,实现方式与上面完全同构。
10 写在最后
如果这篇文章只能留下一句话,那就是: 准确率检验你的排序,校准检验你的诚实。
三个预测者在同一批 2000 场上给出了几乎相同的准确率 (66.90% / 66.85% / 66.90%,最大差 0.05 个百分点), Brier 分解显示他们的分辨度几乎完全相同(相差 0.0029)—— 因为他们的预测本来就是同一个真实概率的单调变换,排序能力一模一样。 但可靠性一项差了 8.4 倍, ECE 差了 3.8 倍, 在 8% 门槛的同一套下注规则下收益率相差 12.95 个百分点。
这就是为什么“我的模型准确率 67%”这句话, 信息量远比说的人以为的要少。它没有告诉你: 这个模型说 90% 的时候是不是真的会发生 90%; 它有没有把“差不多”的场次说成“十拿九稳”; 以及最要紧的——当你按它的数字去做决策时, 你承担的风险是不是它宣称的那个数。
该报什么 报概率的模型,至少要同时给出 ECE、MCE 和分箱方式, 最好附一张可靠性表。只报准确率或只报 AUC, 等于只证明了排序能力,对报数的可信度一个字都没说。 而在按概率下注的场景里,报数的可信度就是全部。
好在结论并不悲观。§8 表明校准是后处理能修的—— 一张查表、几十行代码,A 的 ECE 就从 0.0954 降到 0.0207。 真正修不了的是分辨度: 如果模型根本分不清哪场更可能发生, 再漂亮的校准也只是把一串没有信息量的数字排整齐。 所以顺序永远是:先确认有分辨度,再去修可靠性。
最后留一个可以自己动手的检查。拿出你自己的模型, 把历史预测按 0.1 分箱,数一数每箱里实际发生了多少次, 和你报的数比一比。这件事不需要任何库, 一张纸和一支笔就够—— 而它往往比你调过的所有超参数都更能说明问题出在哪。
参考文献
- Murphy, A. H. A New Vector Partition of the Probability Score. Journal of Applied Meteorology, 1973, 12(4), 595–600. (Brier 分数的可靠性–分辨度–不确定度三项分解,本文 §6 与表 5 的依据)
- Brier, G. W. Verification of Forecasts Expressed in Terms of Probability. Monthly Weather Review, 1950, 78(1), 1–3. (Brier 分数原始定义,本文式 (3))
- Guo, C.; Pleiss, G.; Sun, Y.; Weinberger, K. Q. On Calibration of Modern Neural Networks. ICML, 2017, 1321–1330. (ECE 的分箱定义与现代模型普遍过度自信的实证,本文 §3、§5 的方法来源)
- Naeini, M. P.; Cooper, G. F.; Hauskrecht, M. Obtaining Well Calibrated Probabilities Using Bayesian Binning. AAAI, 2015, 2901–2907. (MCE 定义与分箱校准方法,对应本文 §5 与 §8)
- Zadrozny, B.; Elkan, C. Transforming Classifier Scores into Accurate Multiclass Probability Estimates. KDD, 2002, 694–699. (分箱重映射与 isotonic regression 校准,本文 §8 表 7 的方法出处)
- Platt, J. C. Probabilistic Outputs for Support Vector Machines and Comparisons to Regularized Likelihood Methods. In Advances in Large Margin Classifiers, 1999, 61–74. (Platt scaling,本文 §8 末尾提及的参数化校准)
- Gneiting, T.; Raftery, A. E. Strictly Proper Scoring Rules, Prediction, and Estimation. Journal of the American Statistical Association, 2007, 102(477), 359–378. (严格恰当评分规则的理论框架,说明为何 Brier 而非准确率适合评估概率预测)
- 本站 IN-02:《赔率 2.10 是不是就等于 47.6% 的概率?》 odds-implied-probability.html (隐含概率与过轮的算术,本文 §7 构造盘口的方法与之同源)
- 本站 IN-03:《命中 70% 却亏钱,命中 30% 却赚钱》 accuracy-vs-profit.html (EV = p × d − 1 的下注判据,本文 §7 直接沿用)
本文使用的 2000 场比赛为构造数据
(随机种子 20260808,生成代码见 §9),不对应任何真实赛事、平台报价或账户记录。
§7 的赔率由真实概率扣除 5% 抽水反推,属示例盘口。
表 1 至表 7 的全部数字由 _verify_calib.py 实际运行产生,
代码 2 的输出为该脚本真实 stdout,读者可按 §9 自行复算逐位核对。
本文是一篇模型评估方法论说明, 目的在于讲清准确率与概率校准的区别,不构成任何投注建议。 足球比赛结果具有高度不确定性,任何预测模型都无法保证盈利。