离线 0.89 线上 0.83:评测集漂移与分桶评级
离线评测集跑到 0.89,上线一周客诉却集中在多轮追问和工具失败重试上。这类事故里,模型权重没有改动、prompt 没有修改,发生变化的却是评测样本的来源——评测集的分布一旦和真实流量分叉,离线分数就不再是质量指标,只不过是在说明“我们在哪些题上练过”。更麻烦的是,这种分叉通常不会自行暴露,分数仍然很高,直到用户开始抱怨。
一、离线分数失灵的三种形态
先分清原因,再讨论修改。评测集偏离真实流量,常见的有三类,且能同时出现。
便利样本偏差。 评测集往往来自内测用户、demo 脚本和标注同学自造的 query。这类样本天然偏向结构清晰、单轮、意图明确、措辞规范的请求。真实流量是另一回事:错别字、半句话、语音转写残留、一次贴三段材料、在第三轮才说出真实目的。
静态快照腐化。 评测集冻结在某个时间点,业务侧却一直在变化。新接入一个租户、新开一个小语种入口、把入口从 App 首页挪到搜索框,流量构成就变了,评测集却完全没有同步。
长尾坍缩。 为了让“覆盖全面”,构造时给每个类别都塞 20 条,结果把线上占比 1% 的长尾提到了 8%;或者反过来,长尾只有 3 条样本,桶内分数 0.33 和 0.67 在统计上根本没法区分。
还有一个容易被忽视的变体:把线上 badcase 全部追加进评测集。半年后评测集等价于历史事故合集,分数变成了“不重复犯同一类错”的比率,反而对新流量的代表性更差。抽样集和回归集必须分开管理,这一点在第五节展开。
把偏差量化出来看,问题的形状就很清楚。假设某问答应用有五个主要场景桶:
| 桶 | 线上流量占比 | 旧评测集占比 | 代表偏差 | 桶内分 | 拟设门槛 |
|---|---|---|---|---|---|
| 简单 FAQ / 单轮 | 15% | 50% | 3.3× 过采样 | 0.97 | ≥0.95 |
| 多轮任务 | 30% | 12% | 0.4× 欠采样 | 0.83 | ≥0.85 |
| 检索增强问答 | 28% | 23% | 0.8× | 0.90 | ≥0.88 |
| 长尾语言 / 方言 | 12% | 5% | 0.4× | 0.70 | ≥0.70 |
| 工具调用与失败恢复 | 15% | 10% | 0.7× | 0.65 | ≥0.75 |
按旧评测集自身分布加权,总分约 0.89;按线上流量分布重加权,总分约 0.83。如果放行线定在 0.85,同一批模型权重在两种算法下会得到完全相反的结论。结论本身没有错,错的是加权用的分布。
二、把流量画像量出来,漂移才有数字
重加权的前提是知道线上分布。这一步不需要复杂工具,需要的是把维度定对并固定下来。
必采维度:入口渠道、租户或用户分层、意图分类(哪怕先用规则或小模型打粗标)、query 字符长度分位、语言与方言标识、单轮还是多轮及轮数分布、是否携带检索上下文、是否触发工具调用、是否发生超时或错误码、请求时段。
漂移指标:类别型维度用 PSI,数值型先分箱再当类别型算。PSI 的实现很轻:
import math
from collections import Counter
def psi_categorical(base: Counter, cur: Counter, eps=1e-6) -> float:
keys = set(base) | set(cur)
nb, nc = sum(base.values()), sum(cur.values())
total = 0.0
for k in keys:
b = base.get(k, 0) / nb + eps
c = cur.get(k, 0) / nc + eps
total += (c - b) * math.log(c / b)
return total
经验阈值:PSI < 0.1 视为稳定;0.1~0.25 进入观察,评测集需要重加权;> 0.25 视为显著漂移,应触发重建。这不是绝对标准,但比“凭感觉觉得流量变了”要可靠得多。
两个坑要提前避开。
其一,PSI 对低占比层不敏感。一个占比从 0.5% 涨到 2% 的层,PSI 贡献很小,但它可能正是客诉集中区。对长尾层要单独看绝对样本数和层内错误率,不要只看总 PSI。
其二,日志采样器本身会丢长尾。网关按 1% 均匀采样,长尾层本就少,采完一周只剩几十条,画像直接失真。正确做法是分层采样:头部层低采样率、长尾层全量或高采样率,落盘后按采样率倒数加权还原。
三、评测集构造:按流量分层,而不是按方便程度
有了画像,评测集的构造规则就可以从“尽量覆盖”换成“按配额分配”。
分层配额 ∝ 线上流量占比,同时设最小样本数。 每层配额取 max(n_min, round(share * n_total)),n_min 建议按二项分布的置信区间反推——要在 95% 置信下分辨 5 个点的差异,单层样本量大致需要 100 以上。层内样本不足时过采样,并在打分时做权重校正:w_i = p_traffic(layer) / p_eval(layer)。这样既保证了长尾的代表性,又不让长尾在总分里被放大。
样本要带完整上下文。 只存“最后一问”的评测样本,无法复现线上失败。多轮对话要保留历史轮次,检索增强要保留当次召回文档与排序,工具调用要保留入参、返回结构和错误码。缺了这些,模型在评测里答对,上线答错,你连归因的入口都没有。
冷启动不要用验收用例充数。 新功能没有真实流量时,先跑一到两周影子流量或内部灰度,采到分布再建集。产品经理的 20 条验收用例是需求验证材料,不是评测集,它的分布是“我们认为用户会怎么问”,而不是“用户实际怎么问”。
版本化。 每次发布评测集都记录:版本号、流量快照日期区间、各层配额与实际样本数、已知缺口(例如“方言层仅覆盖粤语与川渝,缺闽南语”)。没有这些元信息,三个月后没人能解释某次分数跳变是模型变了还是评测集换了。
脱敏先行。 回流样本进集前完成 PII 替换,映射表放受控环境,设定留存期限。这一步放在管道最前端,不要等到入集后再补。
四、分桶评级:让总分可归因
单一加权总分的问题是会被高占比、易分的桶主导。头部桶掉 1 个点、长尾桶掉 10 个点,总分可能只动 0.3——而客诉恰恰来自长尾。所以要把“能不能上”从单点分数改成一组约束。
定桶。 按业务关键度 × 流量规模切,一般 5~8 个桶足够。核心交易意图、多轮任务、检索问答、长尾语言、工具调用与失败恢复,是很常见的一组。
每桶独立门槛,核心桶一票否决。 核心桶不允许回退,非核心桶允许在置信区间内波动。加权总分降级为参考指标,用于横向对比版本。
每桶报 bootstrap 置信区间,避免 40 条样本的桶因为两个样本波动就被判死:
import random
def weighted_score(rows, field="score", w="share"):
tw = sum(r[w] for r in rows)
return sum(r[w] * r[field] for r in rows) / tw
def bootstrap_ci(rows, fn, n=2000, alpha=0.05, seed=17):
rng = random.Random(seed)
stats = sorted(fn([rng.choice(rows) for _ in rows]) for _ in range(n))
lo = stats[int(n * alpha / 2)]
hi = stats[int(n * (1 - alpha / 2)) - 1]
return fn(rows), lo, hi
判定规则可以写成三条:核心桶点估计不低于门槛且 CI 下界不低于门槛减 2 个点;非核心桶 CI 下界不低于门槛;加权总分不低于上一基线。满足则放行,核心桶不满足则阻塞,其余情况进灰度并挂观察项。
这样输出的不是“0.83 分”,而是“多轮任务与工具恢复两个桶阻塞,长尾桶处于门槛边缘,头部桶通过”。后者才能直接决定发不发、发多少流量、修哪里。
五、线上回流闭环:样本要持续更新而非一次建好
评测集是一次性资产的想法必须放弃。需要一条常态化的回流管道:
线上日志 → 脱敏 → 分层采样(按配额) → 标注 → 候选池 → 评审 → 评测集新版本
└→ badcase 单独进回归集(不混入抽样集)
关键设计点有四个。
抽样集与回归集分离。 抽样集按流量配额采,代表分布,用于评级;回归集收历史 badcase,用于防复发。两者混在一起,抽样集会被逐渐污染成事故合集,评级随之失真。
影子评测。 新版本发布前,对当日抽样流量离线跑一遍,得到“当日分布下的分数”,而不是“上周建好的固定集上的分数”。这一步成本很低,但对分布漂移的抵抗力最强。
监控三件事:分布漂移(各维度 PSI,按周计算)、桶级分数趋势(7 日滑动窗口)、样本覆盖率(评测集各层样本量 / 线上各层流量,低于阈值说明该层缺乏覆盖)。
告警必须挂动作。 PSI 超 0.25 → 自动触发重加权并生成新配额建议;某桶分数连降 3 天 → 触发该桶人工抽检 50 条;覆盖率低于下限 → 提升该层采样率。只发通知不挂动作的告警,两周后就会被静音。
落地建议
- 本周做一次偏差对照:从线上日志按 5 个维度出流量画像,与现有评测集逐层算占比和 PSI,产出一张偏差表。这张表通常就能解释最近一次“离线好、线上差”。
- 把放行标准从单点分数改成桶级约束:定义 5~8 个桶,每桶设门槛与最小样本数,核心桶一票否决,加权总分只作参考。
- 建分层采样回流任务:按配额每周入集,抽样集与回归集分库存储,评测集带版本号、流量快照日期与已知缺口。
- 上新前跑影子评测:用当日抽样流量出报告,与固定集结果并列对比,两者差异超过 3 个点时先查分布再查模型。
- 给漂移和桶级回退配动作:每条告警写明触发后的处置(重加权 / 重建 / 人工抽检),并在值班手册里固化。
- 点赞
- 收藏
- 关注作者
评论(0)