一个意图识别器的准确率从 90% 提升到 93%,并不一定意味着助手更好用了。多出来的正确结果可能全部来自高频咨询,而取消订单仍然经常判断错误;也可能模型变得更保守,大量请求被送去追问,被接收请求的准确率因此提高。
评测要回答一个更具体的问题:这次改动改善了哪种能力,付出了什么代价,又有没有让用户更容易完成任务?本文介绍分类、槽位、对话状态和业务结果的评测方法,并提供可以直接运行的评测脚本。下载配套工程。
一、评测对象与指标
酒店助手可以拆成四个环节:理解用户表达、更新对话状态、选择处理路径、执行业务流程。每个环节的正确答案不同,因此不能只用一个“准确率”覆盖所有问题。
用户说“入住改成明天”,意图识别可能判断为继续预订,这是分类层的答案;状态更新还要保留城市和房型,只修改入住日期,这是状态层的答案;最终预订还要满足日期、房源、价格和确认要求,这是任务层的答案。
如果只看最终是否订房成功,一旦失败,很难知道哪里出了问题。如果只看标签是否正确,又可能漏掉状态被覆盖、条件被丢弃和工具执行错误。合理的评测既保留分层诊断,也保留端到端结果。
| 层次 | 典型问题 | 指标或检查 |
|---|---|---|
| 分类 | 咨询规则是否被误判为操作 | 每类召回、Macro-F1、混淆矩阵 |
| 拒识 | 不支持的请求是否被强行接收 | 覆盖率、接收错误率、范围外误接收 |
| 参数与状态 | 日期变化是否作用于正确任务 | 槽位匹配、状态完全匹配、任务绑定 |
| 多轮任务 | 用户目标是否真正完成 | 完成率、追问次数、恢复成功率 |
| 业务 | 是否产生错误或重复动作 | 误操作、重复执行、处理成本 |
二、划分训练集、验证集和测试集
训练集用于学习参数,验证集用于挑选超参数和阈值,测试集用于在方案固定之后检查结果。三者的区别是参与决策的方式,而不仅是文件名。
假设一段原始对话生成了五种改写。把六条记录随机分进训练和测试,模型可能在测试中再次看到几乎相同的表达。对话、改写家族、用户或来源等关联关系,应先形成分组,再做划分。
例如,同一次会话里的“我要订北京酒店”和后面的“改成明天”不能分别放进训练和测试,否则无法检验模型对新对话的理解能力。测试所需的前文必须随整个对话保存。
实际操作可以先给每条记录加上 conversation_id、source_id、family_id,按任务选择合适的分组键。对已经上线的系统,还可以用时间划分观察新表达:较早的数据训练,后续一段时间的数据测试。时间划分也要防止同一工单或会话跨边界。
动态示例检索尤其容易被忽略。即使分类器没有训练过测试句子,只要 Few-shot 示例库或最近邻索引收录了测试答案,整个系统仍然使用了测试信息。检索库、Prompt 示例、阈值和模型都属于被评测方案的一部分。
三、准确率与 Macro-F1
准确率是正确预测数除以全部预测数,直观,但受类别频率影响。
设有 100 条请求,其中 90 条查询订单,10 条取消订单。系统全部猜成查询,准确率仍有 90%,取消类召回却是 0。对于取消流程,这个系统几乎没有提供识别能力。
对某个类别,先把预测结果看成“属于该类”与“不属于该类”两个集合:
Precision = TP / (TP + FP)
Recall = TP / (TP + FN)
F1 = 2 × Precision × Recall / (Precision + Recall)
Macro-F1 = 各类别 F1 的算术平均
Precision 关心选中的结果有多少是真的,Recall 关心真实存在的结果找回了多少。F1 同时考虑两者;Macro-F1 给每个类别相同权重,更容易暴露低频类别的问题。计算时需要固定标签列表,并明确没有预测或没有支持样本时如何处理。scikit-learn F1 文档
在上面的 90/10 例子中,查询类 F1 约为 0.947,取消类为 0,Macro-F1 约为 0.474。这组数字揭示了准确率掩盖的类别偏差。
但 Macro-F1 也不会自动表达业务代价。把咨询误判成取消,和把退款咨询误判成订单查询,后果可能不同。高代价类别仍应单列召回、误接收及业务流程结果。
四、用混淆矩阵分析错误
混淆矩阵的行表示真实类别,列表示预测类别。对角线上的数量是正确分类,其余格子告诉我们错误流向哪里。

句向量基线在酒店已知输入上的混淆矩阵
例如,修改已有订单经常落入查房,就要检查模型是否只抓住了“房型”这个主题词,忽略“已经预订”的阶段信息。咨询取消规则经常落入取消操作,则需要检查正反例以及问句、条件和否定表达。
前一篇基础方法的预测结果,可以用 batch3/evaluate.py 重新计算。这里原始分类指标只统计 14 条已知类别输入,拒识指标另用完整的 21 条输入:
| 方法 | 原始准确率 | Macro-F1 | 策略接收数 |
|---|---|---|---|
| 规则 | 0.571 | 0.477 | 10/21 |
| TF-IDF 分类器 | 0.643 | 0.558 | 6/21 |
| 句向量近邻 | 0.714 | 0.695 | 4/21 |
这张表不把拒识当作分类正确。候选排序与是否接收是两个决策,应该分开检查,否则无法判断某个方案是更会分类,还是只接收了更容易的请求。
五、覆盖率与拒识效果
考虑两套策略:A 接收 100 条,错 5 条;B 接收 10 条,错 0 条。如果只看接收正确率,B 更高。但剩下 90 条需要继续处理,系统可能让大量用户反复解释需求。
因此至少同时报告:
覆盖率 = 接收并选择标签的数量 / 全部输入数量
接收错误率 = 错误接收数量 / 接收数量
范围外误接收率 = 被接收的范围外数量 / 全部范围外数量
没有接收任何样本时,接收错误率没有分母,应记为未定义。不能写成 0%,再把它当作优秀结果。
阈值扫描可以画出覆盖率与错误率的关系,但不能预设它一定平滑或单调。有限样本中,删掉某几个正确或错误结果,都可能使比例跳动。选阈值应依据验证数据与错误代价,固定后再检查测试表现;避免根据测试结果反复调整阈值。
六、多标签、槽位与状态评测
多标签任务可以把结果看成集合。预测 {查订单},真值为 {查订单, 退款咨询},属于漏识别;多预测一个取消任务,则是多识别。可以统计集合精确率、召回率,或要求整个集合完全一致。
但两个预订任务可能拥有相同标签。只比较标签集合会把它们合成一个。因此任务匹配应结合任务身份、关键参数和依赖关系,必要时先对预测节点与真值节点做匹配,再检查关系。
槽位匹配同样需要业务语义。“明天”和“2026-10-02”是否相等,取决于对话时间与时区;“北京市”和“北京”是否相等,取决于规范化规则。规范化必须统一应用于预测与真值,不能为了修复某个结果临时增加特殊规则。
状态完全匹配则检查这一轮结束后,所有受评字段是否正确。它比逐槽位平均更严格:城市、日期、房型中只错一个,整个状态就不匹配。两个指标一起看,可以分清局部字段进步和完整状态可用性。
七、多轮任务的成功标准
“助手说已经取消”不是取消成功的证据。成功标准应对应工具结果或业务状态,例如订单状态确实变为 cancelled,且没有产生重复动作。
同理,用户说“收费就不取消”,查询发现有费用后保留订单,应该算遵守目标,而不是简单算成取消失败。多轮评测需要先写清每个场景允许的终态。
可以为一个条件取消场景保存:初始订单、用户约束、允许操作、预期终态、禁止发生的动作。执行完对话后,同时检查结果和过程:是否追问了必要信息,是否展示了费用,是否错误取消,是否重复提交。
澄清也有成本。除了任务完成率,还应记录平均追问次数、同一缺口重复追问次数,以及用户在什么位置放弃。一个最终成功但需要十次重复解释的流程,仍有明确的改进空间。
八、测量延迟与成本
模型推理只占端到端耗时的一部分。用户感受到的还包括排队、状态读取、检索、工具调用和重试。
测量时先约定起点与终点:从 HTTP 请求到达开始,直到最终答复或明确的澄清结果返回。缓存命中、模型降级、失败请求和超时都要保留,不能只对成功返回的快请求算平均值。
平均值便于估算总资源,P95 等分位数帮助观察尾部体验。比较两个实现时,还需要保持并发数、输入长度、硬件和预热方式一致。调用成本则可以按每个完成任务统计,把重试和升级到大模型的额外调用算进去。
本系列本地工程把功能检查、模型分类实验与服务请求测试分别记录。三者回答不同问题:逻辑是否正确、模型如何分类、请求链能否按契约处理。
九、根据评测结果改进系统
先把错误分组:标签定义冲突、表达覆盖不足、上下文缺失、参数绑定错误、阈值过于保守、路由错误、工具故障。然后为某一种错误提出改动,并观察对应指标以及可能受影响的其他指标。
例如补充取消政策反例,应重点看咨询与取消之间的混淆;改变状态摘要,应重点看指代与短回复;提高阈值,应同时看错误接收和覆盖率。
多个随机种子帮助观察训练初始化的影响,但不能替代更多来源的测试数据。对多轮数据做重采样时,应以会话为单位,而不是把同一对话里的每一轮当作独立样本。使用模型评审答案时,也应先用人工检查过的样本核对评审标准,避免评审器只偏好熟悉的措辞。
运行本篇评测:
python batch3/evaluate.py
结果保存在 batch3/results/hotel-evaluation.json,包含每类指标、混淆矩阵和固定分差下的阈值扫描。下一步可以阅读小模型训练与蒸馏,了解如何根据评测发现的问题确定训练目标。