本节摘要:微调后的 laya 有一个典型病症:排序对、数值偏——它说 0.9 的时候,真实正确的频率可能是七成也可能是九成五。温度拟合是官方校准流程(官方口径)的解法:在留出集上拟合一个标量温度 T,把决策头的输出过一遍 softmax(z/T) 再分布——T 大于 1 整体压平(模型过笃定),T 小于 1 拉开差距(模型过保守)。本节讲四件事:温度拟合的直觉与流程;拟合数据的三条要求(与上线同分布、与训练集隔离、规模与覆盖);拟合代码骨架(写法示意);以及一条贯穿全书的铁律——每次微调后必须重新拟合温度,因为训练改变分布形状,旧温度对新分布就是错刻度。ECE 等度量工具的完整方法学在《Evals 实战:LLM 评测工程》第 07 章《概率系统评测》。
第 5 章的 RLCD 只教排序——5.1 节说过,logsigmoid 损失不逼概率逼近目标值,只逼偏好方压住被拒方。训练出来的模型因此常有这样的形态:分类全对(argmax 没问题),但概率数值与真实正确频率脱钩——模型报 0.9 的那批样本,实际答对的只有七成;模型报 0.6 的那批,实际反而对了八成。对只消费 answer 的下游这是无感的(第 1.2 节的硬消费),但第 7.3 节的置信门控要把概率数字直接当依据用——错刻度的尺子量不出可信的阈值。
排序对、数值偏(示意) 模型报的概率 真实正确频率 ┌────────────┐ ┌────────────┐ │ 0.9 ██████ │ │ 0.7 ████ │ ← 报 0.9 的样本实际七成对: │ 0.6 ███ │ │ 0.8 █████ │ ← 报 0.6 的反而八成对 └────────────┘ └────────────┘ argmax 全对 ✓ 数值当刻度用 ✗ → 需要温度拟合
温度的直觉:T 是分布的「音量旋钮」。模型系统性过笃定(报高了的兑现不了),T 调大于 1 压平;系统性保守(敢对的却说得很低),T 调小于 1 拉开。它不改排序(单调变换保序),只调数值与真实频率的对齐——恰好对症「排序对、数值偏」。
流程本身很短:收集留出集上模型的原始输出;用最大似然拟合标量 T,使温度化后的概率最能解释留出集的真实结果;把 T 固化进推理配置,线上推理统一过温度层。
# fit_temperature.py —— 温度拟合骨架(写法示意,接口以官方文档为准) import numpy as np def fit_temperature(logits, labels, grid=None): """logits: 留出集上决策头的原始输出 [N, 类别数] labels: 留出集真实答案的类别下标 [N]""" grid = grid or np.linspace(0.05, 5.0, 200) # 搜索范围(示意) best_t, best_ll = 1.0, -np.inf for t in grid: z = logits / t z = z - z.max(axis=1, keepdims=True) # 数值稳定 p = np.exp(z) p = p / p.sum(axis=1, keepdims=True) ll = np.log(p[np.arange(len(labels)), labels] + 1e-12).sum() if ll > best_ll: best_ll, best_t = ll, t return best_t # logits, labels = 在留出集上跑第 3.2 节的 predict_batch 并取原始输出 # t = fit_temperature(logits, labels) # print("拟合温度:", t) # 官方校准入口的封装形式以官方文档为准
拟合的产出与验收:一个数(T)加一张对照(拟合前后「报的概率 对 实际频率」的偏差,可靠性曲线是标准画法,见《Evals 实战:LLM 评测工程》第 07 章《概率系统评测》)。拟合有效的标志是对角线上的桶贴合了;拟合救不了的标志是曲线呈拱形或锯齿形——那说明偏差不是单一「音量」问题,要回到数据与题面找病根(比如类不平衡、选项措辞歧义),温度不是万能扳手。
走一遍完整流程(数值均为示意)。工单四分类模型,留出集 800 条:拟合前「报 0.9 的桶实际对了 0.71、报 0.6 的桶反而对了 0.80」——过笃定为主;网格搜索给出 T=1.4(示意值),温度化后复测:报 0.9 的桶实际 0.88、报 0.6 的桶实际 0.63——桶贴着对角线了。接着做两件容易被忘的事:把 T=1.4 写进推理配置并与检查点版本绑定;用新刻度重扫第 7.3 节的门控阈值——0.62 这个数字在旧刻度下量的是另一件事。误判复盘:若拟合集的类别占比与线上差得远(拟合集里投诉占一半、线上投诉不到一成,示意),T 会朝错误方向修正,可靠性曲线在拟合集上好看、线上照旧失真——数据资格(本节第三部分)比拟合算法先出问题。
# reliability_bins.py —— 可靠性分桶:把「报的概率对实际频率」算成表(示意) def reliability(scores, labels, n_bins=5): """scores: 模型报给首选答案的概率列表 labels: 同长度 0/1 列表,该答案是否答对""" bins = [[] for _ in range(n_bins)] for score, hit in zip(scores, labels): idx = min(int(score * n_bins), n_bins - 1) # 1.0 落进最后一桶 bins[idx].append(hit) rows = [] for i, bucket in enumerate(bins): if not bucket: continue lo, hi = i / n_bins, (i + 1) / n_bins rows.append({ "概率区间": f"{lo:.1f}~{hi:.1f}", "样本数": len(bucket), "报的概率(区间中点)": round((lo + hi) / 2, 2), "实际频率": round(sum(bucket) / len(bucket), 2), }) return rows # rows = reliability([0.91, 0.88, 0.62, 0.58, 0.95, 0.90], # [1, 0, 1, 1, 1, 1]) # 六条示意记录 # for row in rows: print(row) # 「报的概率」列与「实际频率」列逐行贴近即校准健康;这张表 # 就是可靠性曲线的数据形态,画法见本节开头引用的评测方法学
这段代码是「验收看可靠性曲线」的最小实现:首选概率按等宽桶分箱,每桶对比「报的概率(区间中点)」与「实际答对频率」。两个读法:两列贴近即校准健康;记录每行的偏离方向——高桶报多实际少是过笃定(T 应大于 1),低桶报少实际多是欠笃定。它顺手还做拟合集的覆盖体检:某桶样本数个位数时,那一行的「实际频率」基本是噪声,先补数据再谈校准(本节第三部分的要求三)。
温度是拿数据换来的,数据的资格比算法更重要。
其一,与上线分布同分布。拟合集要像线上流量:同样的类别比例(线上八成是咨询、拟合集里投诉占一半,T 就学错方向)、同样的文本来源、同样的题面。用「随手标的均匀样本」拟合「严重不均衡的线上流量」是最常见的翻车姿势。其二,与训练集严格隔离。拟合数据混进过训练集,T 会学到「训练集上模型都对」的假象——线上立刻打回原形。这份数据正是第 5.1 节从第一天就划出的留出集,一份数据两次复用(微调验收与温度拟合),但绝不能与训练集有交集。其三,规模与覆盖。量级参考:每类至少几十条、总量几百到几千条(示意值,类多则相应放大);每个选项都要有足够的正例——某个选项在拟合集里只出现两次,它在温度化后的概率基本是猜的。
| 要求 | 违反的后果 | 自检动作 |
|---|---|---|
| 与上线同分布 | T 学到错方向的修正 | 拟合集与线上流量的类别占比对照 |
| 与训练集隔离 | T 高估模型可信度 | 按 ID 交集训斥检查 |
| 规模与覆盖 | 长尾选项概率失准 | 逐选项统计正例数 |
这是本章最重要的一句话:校准不自动跟随微调。每一次微调——哪怕是「只加了两百条新偏好样本」的轻量再训练——都在重塑决策头的分布形状,旧的 T 对新分布就是错刻度。把它落实成流水线的固定顺序:
微调流水线的固定收尾(顺序不可交换) 攒偏好数据 ──▶ RLCD 训练 ──▶ 三查验收(5.1)──▶ 重拟合温度 ──▶ 重定阈值(7.3)──▶ 上线 │ │ └── 缺这一步:概率是旧刻度 ──────────┘── 缺这一步:门控是旧标准
对应的工程约束:温度 T 与检查点版本必须成对管理——存检查点的地方必须存拟合它的 T 与拟合数据版本(第 5.2 节的产物四元组在这里加一元:数据版本、训练步数、留出集指标、温度 T)。线上发现概率「集体飘了」(第 10.3 节的漂移监控报警),第一反应不是重训练,而是先用近期流量重拟合温度——重拟合的成本比重训练低一个量级(一次标量拟合对一轮 GPU 训练,定性判断),先便宜后昂贵的排障顺序永远是工程美德。
第 2 章的 Router 意味着线上可能有多个检查点在同时作答(英语、多语言、typed 特化各管一摊)。温度拟合因此要按检查点分别做:每个检查点的分布形状不同,各自的 T 也应不同(示意判断:多语言检查点在低资源语言上往往更保守,同值温度压不住)。管理与观测上的对应动作:T 随 checkpoint 一起配置、一起记录进日志(第 2.2 节的可观测性习惯平移过来)——排查「概率不对」时,第一步永远是确认这次作答的检查点与它配套的 T 是否匹配。
| 问题 | 先查什么 | 处置 |
|---|---|---|
| 拟合出 T 很接近 1 | 模型本来就校准得好,或拟合集与训练集有泄漏 | 先按 ID 交集复查隔离,再接受 T 接近 1 的结论 |
| T 拟合了,曲线还是拱形或锯齿形 | 偏差不是单一音量问题:类不平衡、选项措辞歧义 | 回数据与题面找病根,温度不是万能扳手(本节第二部分原话) |
| 线上概率整体飘了 | 近期流量分布是否变化(新话题、新语言) | 先用近期流量重拟合温度,便宜的动作排前面(本节第四部分) |
| 找不到这个检查点配的 T | 版本管理缺位 | 补成对管理:存检查点必存 T 与拟合数据版本 |
| 两个检查点能不能共用一个 T | 各自分布形状是否真的相同(多半不同) | 按检查点分别拟合,日志带配对记录(本节第五部分) |
概率的刻度对齐了,下一个问题是「拿什么当置信」:首选答案自身的概率,还是整个分布的熵——两种度量不是同义反复,下一节把它们的语义与场景分开。