Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

如何阅读一篇机器学习论文

翻开论文,第一页可能就出现十几个缩写。往后翻,公式越来越长,表格里还挤满了你没有听过的模型。读了半小时,似乎每句话都认识,合起来却说不清作者究竟做了什么。

这很常见。论文通常写给已经熟悉问题的同行:背景讲得快,很多实现细节藏在附录里,作者还希望用有限篇幅突出自己的贡献。我们不必照着它的排版顺序,一页一页硬啃。

这一章一起读一篇具体论文。我们会带着一个问题进去,沿着公式、图表和代码来回寻找答案,再把其中一个说法变成能运行的计算。读到最后,希望你能合上论文,用自己的话讲清楚:它想解决什么,做了什么,哪些证据支持它,还有什么没有弄明白。

1. 先选一个真的想知道答案的问题

假设你训练了一个图片分类器。它说:“这张图是猫,我有 90% 的把握。”

我们以前常问的是“猫猜对了吗”。现在换个问题:把所有声称有九成把握的预测放在一起,真的大约有九成正确吗?

如果一百次里只有六十次正确,模型的语气就比表现更自信。即使它仍是候选分类器中准确率最高的那个,也不意味着它报出的概率值得照单全收。这就是置信度校准关心的问题。

本章精读的是 Chuan Guo、Geoff Pleiss、Yu Sun 和 Kilian Q. Weinberger 在 ICML 2017 发表的 《On Calibration of Modern Neural Networks》。阅读对象固定为 PMLR 会议版,正文与补充材料分开提供。它研究分类模型的置信度,并比较几种训练后的校准方法,其中一种叫温度缩放。

这篇论文适合接在前面的章节之后:分类、softmax、交叉熵和验证集都已经见过。陌生的部分集中在“怎样判断概率报得准不准”,不需要同时补上好几门新课。

打开论文时,顺手留下标题、作者、年份、版本和原文地址。只记一个模型简称,过几个月很容易找错材料;预印本、会议版和后续修改版的图表编号也可能不同。这里使用会议版正文和单独的补充材料,后文的编号都按这两个文件。

2. 阅读顺序,可以跟着问题走

第一次阅读,可以先看标题、摘要、引言末尾和结论,写下一句朴素的问题:

分类器的概率是否过于自信?能否在保留原有分类器的情况下,让这些概率更符合实际正确率?

这一步先不要求理解每个公式。摘要是作者对工作的概括,还不是你已经核实的结论。遇到“有效”“优于”“简单”时,给自己留个小问号:在哪些数据上有效,和谁比较,简单到什么程度?

接着跳到主要图表。看横纵轴、表头、数据集名称和图注,找出作者用什么东西回答这个问题。在本例里,结果表报的是校准误差;如果把它当成分类错误率,后面整段理解都会跑偏。

然后回到方法,沿着一条结果追踪:模型原来输出什么,新增方法改变了哪里,参数从什么数据中得到,最后怎样评分。等这条路径连起来,再补与它有关的推导、相关工作和附录。

这不是固定的“三遍阅读法”。碰到自己很熟悉的论文,也许可以直接看实验;遇到全新领域,可能要先读一篇入门材料。顺序的作用是减少无目的地翻页,不是给阅读增加一套打卡流程。

可以随手写三个短栏目:

已经弄清楚还不明白接下来去哪里找
方法作用于分类分数之后为什么不改变类别?温度缩放公式
表中主要报告 ECE它怎样把很多样本合成一个数?正文第 2 节
温度需要拟合是否使用了测试标签?方法、实验设置与代码

“还不明白”最好写成能回答的问题。写“数学太难”很难推进;写“这里为什么要对类别求和,而不是对样本求和”,就已经找到了下一步。

3. 把问题、方法和结论分开摆好

读完大意后,可以试着讲出下面这条关系:

原来的分类器报出置信度 → 我们怀疑它与实际正确率不匹配 → 增加一个可拟合的温度 → 在保留数据上检查概率与正确率。

其中有几件事需要分开。

研究问题是想弄清楚什么,例如准确率与置信度是否相配。方法是做了什么,例如把所有类别分数除以一个正数。实验结果是某个设置下实际观察到什么。解释或假设则是在说为什么可能如此。

还有一种容易混淆的“假设”:它可能是作者准备检验的猜想,也可能是方法成立所依赖的条件。比如“一个温度是否足以改善校准”可以做实验;“校准数据能代表后续使用情形”则是把结果用于新样本时需要考虑的条件。后者不能靠在校准集上得到漂亮分数就自动满足。

本章会用两句话练习这种区分:

  1. 共同的正温度不会改变哪个类别分数最大。
  2. 在校准集上拟合温度以后,任意新数据上的校准误差都会下降。

第一句可以从运算性质推出;第二句强得多,需要面对数据变化和有限样本,而且我们稍后就能构造反例。两句话都出现“温度”,证据要求却完全不同。

4. 读公式时,先问每个数从哪里来

论文中的公式不必一眼看懂。先把符号换回计算过程,往往就顺了。

设一个样本有 KK 个候选类别。分类器最后输出的原始分数为:

z=(z1,…,zK) \mathbf{z}=(z_1,\ldots,z_K)

它还不是概率,各项可以为负,也不必加起来等于 1。原来的 softmax 概率为:

pk=exp⁡(zk)∑j=1Kexp⁡(zj) p_k=\frac{\exp(z_k)}{\sum_{j=1}^{K}\exp(z_j)}

温度缩放在 softmax 前增加一步:

qk(T)=exp⁡(zk/T)∑j=1Kexp⁡(zj/T),T>0 q_k(T)= \frac{\exp(z_k/T)} {\sum_{j=1}^{K}\exp(z_j/T)}, \qquad T>0

本例把原始概率写成 pkp_k,缩放后的概率写成 qk(T)q_k(T)。这是为了方便对照;读论文时要以作者对符号的定义为准,不必要求每篇文章都用同一套字母。

符号在一次预测里是什么形状或范围
z\mathbf{z}分类器输出的原始分数长度为 KK 的向量
TT所有类别共用的温度一个正数
q(T)\mathbf{q}(T)缩放后的类别概率长度为 KK,和为 1
y^\hat{y}概率最高的类别编号KK 个编号之一
q^\hat{q}这个最高类别的概率00 到 11 之间

正文的温度公式还在概率外面取了一个最大值,得到 q^\hat{q}。整条概率向量、最高概率、最高概率对应的类别,是三个不同对象。 这是值得在纸上圈出来的地方。

把三个数放进去试试

令原始分数为 [2,1,0][2,1,0],分别使用三个温度:

温度类别 0类别 1类别 2
0.50.8670.1170.016
1.00.6650.2450.090
2.00.5060.3070.186

图 1:数值由本章代码计算,保留三位小数,所以显示值的和可能稍有舍入误差。它们不是论文的实验数据。

T=1T=1 时没有改变;T>1T>1 时差距变小,分布更平缓;0<T<10<T<1 时差距放大,分布更尖。把温度调大并不自动等于“更正确”:原来若已经不够自信,继续调大反而可能更糟。

第一句判断现在可以直接检查。对于任意两个类别,如果 za>zbz_a>z_b,由于 T>0T>0:

za/T>zb/T⟹qa(T)>qb(T) z_a/T>z_b/T \quad\Longrightarrow\quad q_a(T)>q_b(T)

除以相同正数、取指数、除以相同正分母,都不改变大小顺序。因此,最大类别保持不变。原来并列最大时,并列关系也保留;实现还需要使用一致的并列处理规则。

这支持的是通常的 top-1 分类结果不变。如果业务规则是“最高概率超过 0.9 才自动处理”,温度改变概率后,自动处理的数量仍可能变化。读到“不改变预测”时,要先弄清论文所说的预测是哪一种决定。

5. 从公式画出一条数据流

公式明白以后,可以自己画张小图。即使原文已经有图,重新画一遍也能暴露理解中的空缺。

图 2:按本章的理解自绘温度缩放过程。橙色部分是新增的可拟合量;它不重新训练前面的分类器。

这里的关键不在于方框画得漂亮,而在于箭头有没有解释清楚。

模型已经训练好。我们让它给一份带真实标签的校准集打分,然后只调整 TT。为避免与模型训练时的验证用途混淆,本章把用于拟合这个温度的数据明确叫校准集。

假设有 nn 条校准样本,真实类别是 yiy_i,缩放后分配给真实类别的概率是 qi,yi(T)q_{i,y_i}(T)。拟合目标使用平均负对数似然:

NLL(T)=−1n∑i=1nlog⁡qi,yi(T) \mathrm{NLL}(T) =-\frac{1}{n}\sum_{i=1}^{n}\log q_{i,y_i}(T)

这就是以前见过的平均交叉熵。这里的可训练对象只有温度,分类器的参数保持不变。对固定样本数,使用损失总和或平均值会得到相同的最优温度,但报告数值时仍要说明采用哪个口径。

原文在验证集上最小化 NLL 来选择温度。注意:拟合目标是 NLL,不是结果表中的 ECE。 一个方法可以按某个目标训练,再用多个指标评价;不要看到表头就反过来猜它的训练损失。

换到新的测试样本时,分类器和温度都应已经固定。若看完测试标签再调整温度,得到的就不再是未参与拟合的数据上的评价。至于校准集大小、重复使用验证集和随机划分会带来什么影响,后面两章会继续做实验。

6. 先读表头,再看最小的数字

论文表 1 的主要指标是 ECE,即期望校准误差的一种分箱估计。先按预测的最高概率把样本分组,比如把 (0.8,0.9](0.8,0.9] 的预测放进同一组。

对于第 mm 组,记:

  • ∣Bm∣|B_m| 为样本数;
  • acc⁡(Bm)\operatorname{acc}(B_m) 为这一组实际正确的比例;
  • conf⁡(Bm)\operatorname{conf}(B_m) 为这一组最高预测概率的平均值。

然后计算:

ECE=∑m=1M∣Bm∣n∣acc⁡(Bm)−conf⁡(Bm)∣ \mathrm{ECE} =\sum_{m=1}^{M}\frac{|B_m|}{n} \left| \operatorname{acc}(B_m) -\operatorname{conf}(B_m) \right|

空组不贡献这一项。每组先算“说得多自信”与“实际有多准”的差,再按样本数加权。十条预测都报 0.9,实际八条正确,那么这一组的差就是:

∣0.8−0.9∣=0.1 |0.8-0.9|=0.1

ECE 较小不等于分类错误较少,也不等于每个样本或每类人群都可靠。分组会汇总不同样本,正负偏差可能在组内抵消;换分箱数,数字也可能变。阅读时需要连同分箱约定一起记下来。

下面只摘取原文表 1 的三行和两个方法列,用来练习阅读;不是完整方法排名。原表使用 15 个等宽区间,数值按百分数显示:

数据与模型未校准 ECE温度缩放 ECE
CIFAR-10,ResNet 1104.60%0.83%
CIFAR-100,ResNet 11016.53%1.26%
Reuters,DAN 30.85%0.91%

这些数字来自正文表 1,PDF 第 6 页,由原论文作者报告;本章没有运行这些网络。

第一眼可以看见两行下降、一行略升。接着还有几件值得追问的事。

CIFAR-100 那一行下降了 16.53−1.26=15.2716.53-1.26=15.27 个百分点,不是准确率提高了 15.27 个百分点。Reuters 的变化则提醒我们,不能把“很多设置有效”改写成“每个数据集必然改善”。

再回到完整表格:CIFAR-10 的 ResNet 110 那一行,温度缩放也不是所有列中最小的数。只抽取两列,可以说明相对未校准模型的变化,不能据此宣布它赢过所有校准方法。

最后看不见的部分:这张表没有给每个结果附上重复运行的波动范围。仅凭表格,我们无法判断一处很小的差距是否会随着划分或种子改变。它提供了实测点,不自动提供误差条、显著性或跨任务保证。

图又能补充什么

可靠性图把分组置信度与分组正确率放在一起,能看出偏差主要出现在哪个置信区间。读图时先确认两条轴、分组规则和柱子的含义,再看离对角线多远。

还要问每组有多少样本:一个只有两条记录的组与一个有两千条记录的组,画出的柱子可能一样宽。没有样本计数,就很难判断某一处偏离是否稳定。原文第 2 节也提醒,可靠性图本身并不显示各组的样本占比。画自己的图时,可以额外标出计数。

7. 附录与代码,是回来查细节的地方

主文没有回答的问题,不一定真的缺失。先顺着文中的引用去找补充材料。

例如,我们刚证明正温度不会改变最大类别,那么实验里分类错误率怎样?补充表 S2,PDF 第 3 页单独报告了分类错误率。CIFAR-100 的 ResNet 110 在未校准和温度缩放下同为 27.83%。它和主表中 ECE 的变化并不矛盾:类别没变,概率变了。

补充材料还把校准指标的讨论、温度缩放的推导和更多可靠性图放在不同小节。第一遍阅读不必把每个推导全部重做,但应知道:自己需要的论据在哪一节,它依赖哪些条件。如果暂时跳过证明,可以记“理解了方法的计算形式,尚未核对这一推导”,比含糊地写“已读懂”更有用。

代码可以帮忙回答另一些问题。例如,“公式中的分数”究竟是 softmax 前还是之后的数?模型在收集校准分数时是否处于评估模式?校准指标的边界点算进哪一格?

作者提供了演示仓库。本章核对的代码提交为 ce1154ec7d5235eee046f28875c3dd8421f11d45;其中 temperature_scaling.py使用 logits 输入、NLL 拟合温度,并另外计算 ECE。仓库已经标明停止维护,所以它适合用来理解作者的实现选择,不能假设旧环境命令在今天直接可用。

代码与论文也可能存在实现层面的差别。正文要求 T>0T>0,作者这份演示把 TT 直接作为可优化参数;读代码时仍要检查优化过程是否满足这个条件。本章的小程序明确只搜索正温度区间。我们没有复制原仓库的训练流程,也不会因为写出相同的一行公式,就称自己已经复现整篇论文。

如果使用搜索工具或语言模型辅助阅读,可以让它帮忙解释一个符号、列出待核查的问题。表格数字、实验条件和作者究竟说了什么,仍要回到原文。一个流畅的解释也可能混用了不同版本,或替作者补上并不存在的实验。

8. 让一句说法接受一次小检查

现在动手检验前面那两句话。程序只用 Python 标准库,不下载模型。

uv run --python 3.12 python src/examples/calibration_reading.py

前面三个类别的概率可以直接计算。接下来构造十条二分类记录,每条的原始分数都为 [log⁡9,0][\log 9,0]。由于 exp⁡(log⁡9)=9\exp(\log9)=9,模型始终给类别 0 分配 9/(9+1)=0.99/(9+1)=0.9 的概率。

在校准组中,八条真实类别是 0,两条是 1。我们只用这十条记录选择温度。代码在事先给定的 [0.05,20][0.05,20] 范围内,按平均 NLL 搜索;在对数温度轴上不断缩小区间,保证搜索过程中温度始终为正。

这个小例子还能手算检查。若十条记录都使用同一个类别 0 概率 qq,损失为:

L(q)=−0.8log⁡q−0.2log⁡(1−q) L(q)=-0.8\log q-0.2\log(1-q)

在 0<q<10<q<1 内求导并令它为零:

−0.8q+0.21−q=0⟹q=0.8 -\frac{0.8}{q}+\frac{0.2}{1-q}=0 \quad\Longrightarrow\quad q=0.8

当概率趋近 0 或 1 时,这个例子的损失都会趋向无穷;区间内只有这一个导数为零的点,因此它就是最低点。而 q(T)=91/T/(91/T+1)q(T)=9^{1/T}/(9^{1/T}+1),所以:

91/T=4⟹T=log⁡9log⁡4≈1.584963 9^{1/T}=4 \quad\Longrightarrow\quad T=\frac{\log9}{\log4}\approx1.584963

程序得到同一个温度。校准组上的概率由 0.9 变为约 0.8,准确率仍是 0.8,NLL 与 ECE 都下降。

接着单独构造一个反例组:仍是相同的原始分数,但这十条记录里有九条真实类别为 0。它不参与选择温度。此时原来的 0.9 正好符合这组的正确比例;套用刚才选出的温度,概率却被降到了约 0.8。

完整输出是:

T=0.5:三类概率=[0.867, 0.117, 0.016];最高分类别=0
T=1.0:三类概率=[0.665, 0.245, 0.090];最高分类别=0
T=2.0:三类概率=[0.506, 0.307, 0.186];最高分类别=0
十条手工校准样本:拟合 T=1.584963
校准组:准确率 0.800 → 0.800;NLL 0.5448 → 0.5004;ECE 0.100 → 0.000
另构造的反例组:准确率 0.900 → 0.900;NLL 0.3251 → 0.3618;ECE 0.000 → 0.100
这是手工分数的公式实验,没有训练分类器,也没有复现论文的基准成绩。

图 3:虚线温度只用八条正确、两条错误的校准组确定。另一组曲线用于展示反例,没有参与选择温度。

两组数据是专门构造出来的,不是从真实使用场景独立采集的测试集。因此,它们不能估计温度缩放在实际应用中改善的概率。它们能完成两件更小但清楚的工作:检查公式和解析解是否一致;推翻“对任意新数据必然改善”的过强说法。

这也没有推翻原论文报告的实测结果。一个反例否定的是全称保证,不会让别人已经做过的实验凭空消失。读论文时,既要防止把结果吹得太大,也要避免用不相干的失败否定整个方法。

展开完整计算代码
"""第 44 章:把温度缩放公式变成可检查的计算;数据全为手工构造。"""

from argparse import ArgumentParser
from hashlib import sha256
import json
from math import ceil, exp, isfinite, log, sqrt
from pathlib import Path
import platform


def scaled_logits(logits, temperature):
    if not isfinite(temperature) or temperature <= 0:
        raise ValueError("温度必须为有限正数")
    if len(logits) < 2 or not all(isfinite(z) for z in logits):
        raise ValueError("至少需要两个有限类别分数")
    # 先减最大值,避免所有分数同时很大时的指数溢出。
    maximum = max(logits)
    return [(z - maximum) / temperature for z in logits]


def probabilities(logits, temperature=1.0):
    values = [exp(z) for z in scaled_logits(logits, temperature)]
    total = sum(values)
    return [v / total for v in values]


def nll(logits, labels, temperature=1.0):
    if not logits or len(logits) != len(labels):
        raise ValueError("分数和标签必须非空、长度相同")
    losses = []
    for row, label in zip(logits, labels):
        if not isinstance(label, int) or not 0 <= label < len(row):
            raise ValueError("标签必须是有效类别编号")
        z = scaled_logits(row, temperature)
        losses.append(log(sum(exp(v) for v in z)) - z[label])
    return sum(losses) / len(losses)


def metrics(logits, labels, temperature=1.0, bins=15):
    loss = nll(logits, labels, temperature)
    if not isinstance(bins, int) or bins < 1:
        raise ValueError("分箱数必须为正整数")
    rows = []
    for row, label in zip(logits, labels):
        p = probabilities(row, temperature)
        prediction = max(range(len(row)), key=row.__getitem__)
        confidence = p[prediction]
        # 第 m 格为 (m/M, (m+1)/M],m 从 0 开始。
        bucket = min(bins - 1, max(0, ceil(confidence * bins) - 1))
        rows.append({"prediction": prediction, "label": label,
                     "confidence": confidence, "correct": prediction == label,
                     "bucket": bucket})
    grouped = []
    for bucket in range(bins):
        selected = [r for r in rows if r["bucket"] == bucket]
        if selected:
            grouped.append({"bin": bucket, "count": len(selected),
                            "confidence": sum(r["confidence"] for r in selected) / len(selected),
                            "accuracy": sum(r["correct"] for r in selected) / len(selected)})
    return {"nll": loss, "accuracy": sum(r["correct"] for r in rows) / len(rows),
            "confidence": sum(r["confidence"] for r in rows) / len(rows),
            "ece": sum(r["count"] * abs(r["accuracy"] - r["confidence"])
                       for r in grouped) / len(rows),
            "bins": grouped, "rows": rows}


def fit_temperature(logits, labels, bounds=(0.05, 20.0)):
    """在预先给定的正温度区间,用黄金分割搜索平均 NLL 的最低点。"""
    lower, upper = bounds
    if not (isfinite(lower) and isfinite(upper) and 0 < lower < upper):
        raise ValueError("温度边界必须为递增的有限正数")
    left, right = log(lower), log(upper)
    ratio = (sqrt(5) - 1) / 2
    a = right - ratio * (right - left)
    b = left + ratio * (right - left)
    fa, fb = nll(logits, labels, exp(a)), nll(logits, labels, exp(b))
    for _ in range(80):
        if fa < fb:
            right, b, fb = b, a, fa
            a = right - ratio * (right - left)
            fa = nll(logits, labels, exp(a))
        else:
            left, a, fa = a, b, fb
            b = left + ratio * (right - left)
            fb = nll(logits, labels, exp(b))
    candidates = [lower, exp((left + right) / 2), upper]
    return min(candidates, key=lambda t: nll(logits, labels, t))


def run():
    logits = [[log(9), 0.0] for _ in range(10)]
    calibration_labels = [0] * 8 + [1] * 2
    counter_labels = [0] * 9 + [1]
    temperature = fit_temperature(logits, calibration_labels)
    analytic = log(9) / log(4)
    assert abs(temperature - analytic) < 1e-6
    temperatures = [0.5 + i * 0.025 for i in range(141)]
    return {
        "source_sha256": sha256(Path(__file__).read_bytes()).hexdigest(),
        "python": platform.python_version(),
        "data_kind": "hand_constructed_logits_and_labels",
        "logits": logits,
        "calibration_ids": [f"cal-{i}" for i in range(10)],
        "counterexample_ids": [f"counter-{i}" for i in range(10)],
        "calibration_labels": calibration_labels, "counterexample_labels": counter_labels,
        "temperature": temperature, "analytic_temperature": analytic,
        "bounds": [0.05, 20.0], "bins": 15,
        "calibration": {"before": metrics(logits, calibration_labels),
                        "after": metrics(logits, calibration_labels, temperature)},
        "counterexample": {"before": metrics(logits, counter_labels),
                           "after": metrics(logits, counter_labels, temperature)},
        "three_class": [{"temperature": t, "probabilities": probabilities([2, 1, 0], t)}
                        for t in [0.5, 1.0, 2.0]],
        "curve": [{"temperature": t,
                   "calibration_nll": nll(logits, calibration_labels, t),
                   "counterexample_nll": nll(logits, counter_labels, t)}
                  for t in temperatures],
    }


def show(report):
    for row in report["three_class"]:
        formatted = ", ".join(f"{p:.3f}" for p in row["probabilities"])
        print(f"T={row['temperature']:.1f}:三类概率=[{formatted}];最高分类别=0")
    print(f"十条手工校准样本:拟合 T={report['temperature']:.6f}")
    for key, name in [("calibration", "校准组"), ("counterexample", "另构造的反例组")]:
        before, after = report[key]["before"], report[key]["after"]
        print(f"{name}:准确率 {before['accuracy']:.3f} → {after['accuracy']:.3f};"
              f"NLL {before['nll']:.4f} → {after['nll']:.4f};"
              f"ECE {before['ece']:.3f} → {after['ece']:.3f}")
    print("这是手工分数的公式实验,没有训练分类器,也没有复现论文的基准成绩。")


if __name__ == "__main__":
    parser = ArgumentParser(description=__doc__)
    parser.add_argument("--out", type=Path)
    args = parser.parse_args()
    result = run()
    show(result)
    if args.out:
        args.out.parent.mkdir(parents=True, exist_ok=True)
        args.out.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")

也可下载脚本,或查看包含全部分数、标签、温度和曲线的记录。程序每次默认重新计算;加 --out runs/calibration-reading.json 可以保存自己的结果。

9. 合上论文,写一份能接着做事的笔记

笔记不用把论文缩写一遍。可以先试着写这样一段:

这篇工作关心分类模型的置信度与实际正确率是否相符。我重点读了温度缩放:冻结分类器,在保留数据上按 NLL 拟合一个共同正温度。正温度不改变类别排序,这一点可以直接推导。原文表 1 提供了多组 ECE 对照,也存在未改善的设置;补充表 S2 报告分类错误率。我的小程序目前只验证了公式和一个反例,尚未重新训练原文模型。

然后再留一个具体问题:

换成自己训练的小分类器,使用独立校准集拟合温度后,未参与拟合的数据上的 NLL 与 ECE 怎样变化?校准样本很少时,结果会不会不稳定?

这一问就能引出下一章的实验。它比“以后研究一下校准”更容易执行:对象、改变的部分、评价位置和指标都有了。

读到什么程度算暂时完成,也取决于用途。如果只是判断这篇是否值得细读,弄清问题与主要证据就够了;如果准备复现,数据划分、优化设置和代码版本还得继续追;如果准备把方法用在自己的系统里,还要检查它是否覆盖自己的输入分布和决策方式。

10. 带着几个问题再读一次

  1. 一篇论文报告 ECE 从 10% 降到 2%。能否说分类准确率提高了 8 个百分点?还需要查哪个指标?
  2. 为什么把所有类别分数除以同一个正温度,不改变 top-1 类别?如果分别除以不同的正数,还一定成立吗?
  3. 温度缩放在本例中按什么目标拟合?为什么“报告 ECE”不等于“直接最小化 ECE”?
  4. 有两组预测:第一组 4 条,平均置信度 0.6,其中 3 条正确;第二组 6 条,平均置信度 0.9,其中 3 条正确。按这两组计算的加权校准误差是多少?
  5. 为什么本章反例组上的 ECE 变差,不足以否定论文表 1 中的改善结果?
  6. 在可靠性图里,一个分组的柱子离对角线很远。除了差距大小,还应查什么?
  7. 若只看见一张表中的最优数字,没有随机种子、重复次数或误差范围,哪些结论应该暂缓?
  8. 给自己选的一篇论文写三句话:一个可回答的问题、一条对应证据、一个还没解决的疑问。为证据标上图表或章节编号。
展开参考答案与阅读提示
  1. 不能。ECE 衡量分组置信度与正确比例的偏差,分类错误率或准确率要另外看。温度缩放可以改变前者而保持后者不变。
  2. 共同的正数缩放保持分数大小关系,softmax 也保持大小关系。各类使用不同正数则可能改序,例如 [2,1][2,1] 分别除以 [4,1][4,1] 后变成 [0.5,1][0.5,1]。
  3. 按校准集的平均 NLL 拟合。拟合目标与评价指标承担不同工作;表格展示什么指标,不能代替阅读优化目标。
  4. 为 (4/10)∣0.75−0.6∣+(6/10)∣0.5−0.9∣=0.30(4/10)|0.75-0.6|+(6/10)|0.5-0.9|=0.30。不能直接把两个差平均,因为两组样本数不同。
  5. 反例用的是另构造的数据,只否定“所有新数据都会改善”的保证。原文的结论应放回它的模型、数据、划分和指标条件下判断。
  6. 分组样本数、置信区间边界、柱子究竟表示正确率还是数量,以及是否只展示了部分区间。少量样本形成的柱子可能很不稳定。
  7. 暂缓声称微小差距稳定存在、具有统计显著性或能推广到别的设置。可以先如实说“作者在所报告设置中观察到这个数值”。
  8. 例如:“共同正温度是否改变分类结果?”对应温度公式与补充表 S2;疑问可以是“加入概率阈值拒答以后,自动处理的覆盖率怎样变化?”问题要足够具体,方便下一次阅读或实验。

下一章会把阅读笔记里的问题接到可复现的实验上。到那时,读懂一条公式只是起点:我们还要让数据、版本、配置和结果彼此对得上。