2867 字
约 9 分钟
14
S22-X14 · AI 应用的评测集建设与灰度

传统测试的断言是二元的:输入确定,输出必须等于某值。AI 应用的输出是一段自然语言,同一个输入两次跑出两个版本,都"说得通"。于是团队很容易滑向两个极端:要么不测,靠人工抽检;要么追求一个绝对分数,最后拿着一堆没人解释的数字做发布决策。

评测集真正的用途只有一个:判断这次改动相比基线是变好还是变差。它是测量工具,不是成绩单。这篇讲怎么建这把尺子,以及尺子怎么接进发布流程。


1. 与普通软件测试的差异

维度 普通测试 AI 应用评测
输出确定性 确定 同输入多次不同输出
正确性标准 唯一答案 多维(正确、有据、得体、不越界)
通过判据 断言 断言 + 参考对比 + 打分
用例构造 等价类、边界值 等价类 + 真实分布抽样
回归含义 不破坏既有行为 不破坏既有行为,且不劣于基线

最关键的一行是最后:AI 应用的"回归"不是"通过率 100%",而是**"关键用例不退化"**。基线分数永远不是满分,所以门禁要卡的是"相对基线的变化",而不是绝对值。


2. 评测集的四个来源

来源 获取方式 覆盖什么 注意
真实输入抽样 线上日志按场景分层抽样 真实分布 需脱敏,避免只抽到高频简单问
历史失败回放 事故与差评沉淀 已知坑 只增不删
构造边界用例 空输入、超长、多语言、歧义 极端输入 少而精,别造"现实中不存在"的输入
对抗用例 越权提问、注入、诱导编造 边界与拒答 需定期更新,模型迭代会改变反应
# 分层抽样:按场景 × 难度 × 语言分层,避免高频场景淹没长尾
def sample(logs, per_stratum=30):
    buckets = defaultdict(list)
    for r in logs:
        buckets[(r.scene, r.difficulty, r.lang)].append(r)
    return [r for k, v in buckets.items() for r in random.sample(v, min(per_stratum, len(v)))]

抽样最容易犯的错是只抽"有反馈"的样本。点过赞或踩过的用户只是少数,按反馈抽样会把评测集变成"投诉集",测出来的分数与真实体验无关。


3. 标注口径:先定义"对",再谈分数

维度 取值 判定依据
答案正确性 正确 / 部分正确 / 错误 与权威资料一致
引用准确性 有据 / 无据 / 错引 引用是否真支撑该句
是否应答 应答 / 应拒答却答 / 不该拒却拒 依业务边界
有无有害内容 有 / 无 依内容规范
def cohen_kappa(a, b):
    po = sum(x == y for x, y in zip(a, b)) / len(a)
    pe = sum((a.count(c) / len(a)) * (b.count(c) / len(b)) for c in set(a))
    return (po - pe) / (1 - pe)      # 一致性扣除随机巧合

多人标注必须先对齐口径再开标:同一句"部分正确"两个人的理解可能完全不同。做法是先各自标 20 条,算一致性,不一致的逐条讨论定标准,再全量开标。一致性低时不要硬算平均分,先修口径。

标注还要记录判定依据的来源(哪份文档、哪一版),否则半年后没人能解释某条为什么是"错误"。


4. 自动评测的三层实现

层次 手段 覆盖 偏差来源
规则断言 正则、字段校验、必含/必不含 格式、硬约束 覆盖窄,但确定性高
参考答案对比 关键点命中、语义相似度 事实性 表述差异导致误判
模型打分 LLM-as-judge 语义质量、语气 位置偏差、自评偏好、过宽
def evaluate(case, output):
    r = {"structure": parses(output, case.format)}
    r["keywords"] = all(k in output for k in case.must_contain) \
                    and not any(k in output for k in case.must_not)
    if case.get("rubric"):
        r["judge"] = judge(output, case.rubric, scale=1..5)   # 需固定 rubric 与温度
    return r

模型打分的三个已知偏差要主动对冲:位置偏差(成对比较时偏好先出现的答案)→ 交换顺序各评一次;长度偏好(偏好更长答案)→ rubric 里明确"简洁不加分";自评偏好(用自己的模型评自己)→ 换一个不同家族的模型做评委。

打分只适合做相对比较(A 版本 vs B 版本),不适合当绝对质量分。把 judge 分数当 KPI,很快就会被优化到失真。


5. 线上线下的一致性

漂移来源 表现 缓解
输入分布变化 新场景涌入,评测集没覆盖 定期从线上重抽样
知识过期 资料更新但评测集仍是旧答案 评测集与知识库版本绑定
用户行为变化 提问方式变短、更多口语 季度重标一轮
模型版本升级 旧用例的期望答案失效 升级时重跑并复核失败项
def drift_check(eval_set, online_sample):
    # 对比长度、语言、场景占比等粗粒度分布
    return {k: wasserstein(eval_set[k], online_sample[k])
            for k in ["len_tokens", "lang", "scene"]}

漂移检测不需要复杂:看输入长度分布与场景占比这两个粗指标就能发现大部分问题。评测集分数很高但线上体验差,八成是分布对不上。


6. 灰度:新旧版本并行对比

分流方式 适用 风险
按用户比例 大多数场景 同一用户需稳定命中同一版本
按请求类型 高风险功能小流量试 需明确路由规则
按租户 B 端客户可控试点 样本量小,统计力弱
影子流量 只对比不返回新结果 无用户影响,但成本翻倍
// 稳定分流 + 版本指纹进日志,便于事后归因
int bucket = Math.floorMod(Objects.hash(userId), 100);
String ver = bucket < rolloutPct ? "v2" : "v1";
log.info("route", "bucket", bucket, "ver", ver, "reqId", reqId);

灰度必须解决两件事:同一用户不能来回跳版本(否则体验抖动、指标失真),两个版本不能共享缓存(缓存键不带版本时,新版本会读到旧版本的答案)。

上线后的核心指标:拒答率(该答不答)、追问率(答了但用户还得再问)、人工接管率(转人工)、显式反馈(点赞/踩)、以及同一问题的重问率——最后这个指标最能反映"答了等于没答"。


7. 回归门禁

门禁项 判据 不通过的处置
硬约束用例 必须全通过 阻断发布
关键用例集 不允许任何一条由通过转失败 阻断
总体分数 不低于基线,允许小幅波动 需人工复核
拒答边界 对抗用例拒答率不降 阻断
成本与延迟 不超过设定上限 评估后再发
gate:
  hard_fail: 0                 # 硬约束失败数必须为 0
  regressions: 0               # 关键用例不允许由通过转失败
  score_delta: -0.02           # 相对基线的容忍下限
  p95_latency_ms: BASELINE_P95 * 1.2

门禁的价值在于把"要不要发"从讨论变成判定。没有门禁时,发布决策依赖当天谁的嗓门大。


8. 评测集的维护

评测集会腐化。用例写于半年前,期望答案对应的是旧版知识库,标注人也早已离职。腐化的评测集比没有评测集更危险——它给出一个稳定但失真的数字。

维护项 频率 动作
分布重抽样 每季度或输入分布变化时 用线上日志重建部分用例
期望值复核 知识库更新时 逐条确认答案仍成立
失败项沉淀 每次事故后 新增用例,附来源与日期
过期清理 半年 删除业务已下线的场景
一致性抽检 每次扩标 重算 kappa,低于阈值先修口径
eval_set:
  version: 2026-11-01        # 与知识库版本一并记录
  cases: 420
  coverage: {scene: 12, adversarial: 38, regression: 96}
  kappa: 0.71                # 低于约定阈值时不用于门禁

每条用例带 created_at 与 source(线上抽样 / 事故 / 构造),这样"有多少条来自真实事故"就变成一个可追踪的数。


9. 排错

现象 成因 处理
评测集分数很高但线上差 分布漂移 从线上重抽样,重建评测集
只看平均分掩盖严重退化 用例不均衡 单列关键用例,卡"零回退"
打分模型评自己 同一模型做生成与评审 换模型家族,或改用规则+人工
灰度期间结果串味 两版本共享缓存 缓存键加版本指纹
同一用户答案时好时坏 请求级随机分流 改为用户级稳定分流
旧用例突然大面积失败 知识库更新后期望值过期 期望答案与知识库版本绑定
标注争议不断 口径未对齐就开标 先小样本对齐一致性再全量

10. 边界

问题 评测集能解决吗 说明
检索召回不足 部分 需专门的检索评测(召回、排序)
提示词措辞微调的效果 是 同集对比即可判断
用户是否满意 否 需线上反馈与访谈
长尾罕见问题 部分 构造用例可覆盖,但未必真实
成本与延迟 否 属于性能与容量问题

评测集与检索评测是两把尺子:前者量端到端体验,后者量召回与排序。混在一起看,会把"检索没召回"误判成"模型不会答"。


11. 自测

  1. AI 应用的"回归"为什么不是通过率 100%,而是关键用例不退化?
  2. 评测集四个来源里,为什么不能只按"有反馈"的样本抽样?
  3. 多人标注前为什么要先做小样本一致性对齐?
  4. 模型打分的三个已知偏差分别是什么,怎么对冲?
  5. 灰度期间两个版本共享缓存会造成什么后果?
  6. 回归门禁里,哪类用例应当直接阻断发布?
S22-X14 · AI 应用的评测集建设与灰度
https://www.clxhxhhr.top/posts/5876/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。