Brier分数更低,概率就更可靠吗?

黎 浩然/ 28 10 月, 2026/ 计算机/COMPUTER/ 0 comments

Brier分数下降,不足以单独证明概率更可靠。这个平方误差分数同时受概率偏差和区分能力影响:一个更能分开正负样本的模型,即使分组概率仍有偏差,也可能获得更低总分。判断“报80%的事件是否约80%发生”,需要把总分拆开,并保留分组样本量。

文章目录
  1. 先固定分数定义,避免差一个倍数
  2. 把同一个概率值的样本放在一起
  3. 一个反例:总分更低,分组偏差却更大
  4. 分箱过粗,会把相反偏差抵消
  5. 可运行的精确分数检查
  6. 评估校准时,先保护比较条件

先固定分数定义,避免差一个倍数

这里讨论二分类,把正类标签写作y∈{0,1},正类概率写作p。定义BS=平均值[(p−y)²],越小越好。只对正类概率计算一次,不把正类和负类的两个平方误差再相加;后者在二分类中会得到两倍结果。比较报告时,应先核对这个口径,以及是否使用样本权重。

分类准确率只看概率落在阈值哪一侧,Brier还看离标签有多远。例如对正例报0.9,比报0.6的平方误差更小。但不能由单个正例就说模型应该总报0.9;面对不确定事件,始终押向0或1会在判断错误时受到更大惩罚。概率评价要针对一组可比预测。

scikit-learn 1.5的校准说明明确区分可靠性、分辨能力与数据不确定性,指出Brier总分不能单独隔离校准好坏。下文使用原创的离散概率例子推导这一点,不复现文档实验,也不把已有方法包装成新研究。

把同一个概率值的样本放在一起

在一个组中,所有样本都报相同概率p,观察到的正例比例为q。把p−y改写成(p−q)+(q−y),平方后求组内平均。交叉项为零,因为组内平均(q−y)=0,于是得到:(p−q)²+q(1−q)。第一项测量该组预测概率与观察比例的差距;第二项是组内标签变化。

再令总体正例比例为q̄,对各组按样本占比加权,可得到精确关系:BS=可靠性误差REL−分辨项RES+不确定性UNC。记组g的样本占比为w_g,则REL=Σ_g w_g(p_g−q_g)²,RES=Σ_g w_g(q_g−q̄)²,UNC=q̄(1−q̄)。权重之和为1,不再除一次组数。

REL越小,说明在这些分组上概率与观察比例越贴合。RES越大,说明分组正例比例更偏离总体比例,提供了区分不同风险的能力;它在总分中被减去。UNC由该评估集合的标签比例决定。比较同一集合的两个模型,UNC不变,但REL和RES可以同时改变。

恒报0.5
REL 0
− RES 0
+ UNC 0.25
↓ BS 0.25
按两类分别报0.1/0.9
REL 0.01
− RES 0.25
+ UNC 0.25
↓ BS 0.01
图1:原创合成样本上的分解。分辨项的增加超过可靠性误差的增加,因此总分更低,并不要求REL同时下降。

一个反例:总分更低,分组偏差却更大

构造20个样本,10个负例、10个正例。方案A全部报0.5。只有一个预测组,观察正例比例也为0.5,所以样本上的REL为0;但它完全没有区分样本,RES为0,BS为0.25。

方案B对这10个负例报0.1,对这10个正例报0.9。每个预测都与标签相差0.1,BS为0.01。可是0.1组的观察正例比例是0,0.9组是1,两组都有0.1偏差,REL为0.01,比方案A大。它更低的总分来自RES增加到0.25,而不是来自更小的REL。

这些概率是为了构造反例而人为指定的,没有训练模型,也不能作为任何模型的泛化成绩。方案A在这个有限样本上分组贴合,不代表它在新数据上已经被证明校准。这个例子证明的是“总分更低必然意味着校准更好”的推理不成立,不能反过来得出“恒报总体比例就是最佳预测”。

分箱过粗,会把相反偏差抵消

若把方案B的所有样本强行合并为一个诊断箱,平均预测概率是0.5,正例比例也是0.5。图上这个点恰好落在对角线,两个方向的偏差被抵消。保留0.1与0.9两组,才能看见它们分别对应0和1的观察比例。

保留两个预测组
p=0.1 → 观察比例0
p=0.9 → 观察比例1
各偏差0.1
↓ 合并为一个箱
平均p=0.5
观察比例=0.5
偏差在汇总中消失
图2:原创分箱示意。合并后的一个点贴合对角线,不代表原来的两个预测值都可靠。

因此报告可靠性图时,应同时说明分箱规则和每箱样本数。箱太细,观察比例可能因样本少而波动;箱太粗,又会隐藏局部偏差。不能反复换箱数直到图变好,再把这个图当成独立证据。用于比较模型的规则应事先固定,并用不同合理分箱做敏感性检查。

上面的精确分解按相同预测值分组;真实连续分数常用区间分箱,把组内不同p替换成平均p后,不能不加说明就套用同一个无残差恒等式。原始BS仍应逐样本计算,分箱图只作为另外一层诊断。本例故意使用离散值,让分解与每一步计算都能直接核对。

可运行的精确分数检查

保存为brier_example.py,执行python3 brier_example.py,不要使用关闭断言的-O。程序已在Python 3.15.0执行;使用标准库Fraction避免显示舍入影响等式核验。输入是对齐的二元标签和精确概率,输出顺序为BS、REL、RES、UNC。

from collections import defaultdict
from fractions import Fraction as F


def evaluate(labels, probabilities):
    if not labels or len(labels) != len(probabilities):
        raise ValueError('nonempty aligned inputs required')
    if any(y not in (0, 1) for y in labels):
        raise ValueError('binary labels required')
    if any(not 0 <= p <= 1 for p in probabilities):
        raise ValueError('probabilities must be in [0, 1]')
    n = len(labels)
    prevalence = F(sum(labels), n)
    groups = defaultdict(list)
    loss = F(0)
    for y, p in zip(labels, probabilities):
        groups[p].append(y)
        loss += (p - y) ** 2
    reliability = resolution = F(0)
    for p, ys in groups.items():
        weight = F(len(ys), n)
        rate = F(sum(ys), len(ys))
        reliability += weight * (p - rate) ** 2
        resolution += weight * (rate - prevalence) ** 2
    uncertainty = prevalence * (1 - prevalence)
    brier = loss / n
    assert brier == reliability - resolution + uncertainty
    return brier, reliability, resolution, uncertainty


# Synthetic assigned predictions, not a trained classifier or benchmark.
y = [0] * 10 + [1] * 10
constant = [F(1, 2)] * 20
separating = [F(1, 10)] * 10 + [F(9, 10)] * 10
less_extreme = [F(1, 5)] * 10 + [F(4, 5)] * 10
assert evaluate(y, constant) == (F(1, 4), F(0), F(0), F(1, 4))
assert evaluate(y, separating) == (F(1, 100), F(1, 100), F(1, 4), F(1, 4))
assert evaluate(y, less_extreme)[0] == F(1, 25)
assert all((p >= F(1, 2)) == bool(label)
           for label, p in zip(y, separating))
assert all((p >= F(1, 2)) == bool(label)
           for label, p in zip(y, less_extreme))
# Merging everything into one diagnostic bin hides opposite errors.
mean_p = sum(separating, F(0)) / len(separating)
assert mean_p == F(sum(y), len(y)) == F(1, 2)
for labels, probs in [([], []), ([0, 1], [F(1, 2)]), ([0], [F(2)])]:
    try:
        evaluate(labels, probs)
    except ValueError:
        pass
    else:
        raise AssertionError('invalid input accepted')
for name, probs in [('constant', constant), ('separating', separating)]:
    print(name, tuple(str(x) for x in evaluate(y, probs)))
print('one-bin mean prediction and positive rate:', mean_p)

输出分别为(1/4,0,0,1/4)和(1/100,1/100,1/4,1/4)。另一个方案报0.2/0.8,与0.1/0.9方案在0.5阈值下都把所有样本分对,但BS分别为0.04和0.01。这两项是同一集合上不同概率预测的结果,不是校准训练前后成绩。这进一步说明分类正确率不能替代概率评分。程序也拒绝空输入、长度不一致和越界概率,避免漏样本或非法值改变分母。

评估校准时,先保护比较条件

实际评估应保留逐样本预测、标签、分组方式和样本数量,在相同目标集合上报告BS与可靠性图。若总体正例率改变,UNC也会改变;不同日期或不同人群的总分不宜直接当作模型版本差异。缺少标签的样本如何排除,也需要一致口径。

若还要拟合温度、sigmoid或其他校准映射,应区分模型训练、校准拟合与最终评估所用数据。用最终评估标签调好映射再在同一批标签上宣称改进,会把拟合效果当成泛化证据。样本少的分组只能给出带不确定性的诊断,不能把一条对角线当作稳定承诺。

站内PBC天气预报解读侧重有效分布、评分基准和数据可用时间;这里补充的是二分类BS分解及粗分箱抵消反例。English version。若这套分解和可运行例子帮你检查了概率评估报告,欢迎自愿支持。

原计划文章日期为2026年10月28日23:30(北京时间),实际于2026年10月30日补发。引用的是原计划日期前已公开的1.5版文档;1.5.2发布记录为2024年9月。本文没有真实模型训练、校准拟合或基准提升成绩。

支持

如果文中的解释或示例帮你解决了问题、节省了时间,欢迎自愿打赏支持本站。

微信支持二维码,点击查看大图
微信
支付宝支持二维码,点击查看大图
支付宝
Buy Me a Coffee,点击支持本站
Buy Me a Coffee

二维码可点击放大。更多支持方式见支持页面。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*