600 条工单分给 5 个部门,字二元组朴素贝叶斯准确率 87.2%,23 条错分。 但同一个模型、同一批概率,只把「取概率最大」换成「取代价最小」,准确率掉到 82.8%, 平均代价从 0.69 降到 0.52——省 26%。 因为「消防件派给环卫」和「环卫件派给消防」不是一个量级。
上一节的 DID 是一年做一次的评估。 这一节是每分钟都在发生的判断:12345 热线进来一条工单,系统得决定派给哪个部门。
一、先看问题长什么样
Demo 造了 600 条工单,5 个部门(环卫、城管、消防、交通、水务)× 每个部门 6 个典型短语 × 120 条。 关键在于:每个部门有 18 条(15%)只写了通用描述—— 「影响通行」「存在安全隐患」「已经持续一周」「无人处理」「居民多次反映」。 这 15% 是故意留的。真实工单里有一大批市民只说得清「有问题」,说不清「什么问题」, 如果不埋进去,分类器会直接跑出 100%,整个「准确率」的讨论就成了自欺欺人。 分类器用的是字二元组(每两个字拼一个词)加朴素贝叶斯。 为什么用这么土的方法?因为它可解释、训得快、 几百条样本就能跑——政务场景里样本量往往就只有几百上千条,上大模型反而过拟合。
二、87.2% 的两个错因
| 真实\分到 | 环卫 | 城管 | 消防 | 交通 | 水务 |
|---|---|---|---|---|---|
| 环卫 | 33 | 2 | 0 | 0 | 0 |
| 城管 | 0 | 35 | 0 | 1 | 0 |
| 消防 | 4 | 3 | 31 | 0 | 1 |
| 交通 | 1 | 5 | 0 | 30 | 0 |
| 水务 | 2 | 4 | 0 | 0 | 28 |
| 180 条测试里错 23 条,准确率 87.2%。错误集中在两处: | |||||
| ① 消防件被分到环卫(4 条)和城管(3 条)。因为「楼道堆物」和「垃圾堆放」、 | |||||
| 「堵塞通道」和「占道经营」字面上高度重叠。 | |||||
| ② 那 15% 的通用描述件,落在哪一格都是猜。「存在安全隐患」这句话对 5 个部门都成立, | |||||
| 模型手里没有任何区分线索。 | |||||
| 这两类错的性质完全不同:第一类靠更多数据能改善(多看几条「楼道堆物」就分得清了), | |||||
| 第二类再多数据也没用(信息本身就不在文本里)。分不清这两类,就会一直往错的方向投人力。 |
三、同一个概率,换规则就省 26%
| 决策规则 | 准确率 | 平均代价 |
|---|---|---|
| 取概率最大 | 87.2% | 0.69 |
| 取代价最小 | 82.8% | 0.52 |
| 模型没动,概率没动,只换了最后那一步怎么决策。准确率从 87.2% 掉到 82.8%(降了 4.4 个点), | ||
| 平均代价从 0.69 降到 0.52(降了 26%)。有 8 条(4%)的预测因此改变。 | ||
| 它做的事很朴素: | ||
| 把没把握的件,往「猜错了也不贵」的部门推。比如一条模糊件在环卫和消防之间五五开, | ||
| 「取概率最大」看谁高一点点就选谁; | ||
| 「取代价最小」会想:万一真是消防,派给环卫的代价是 10,反过来只有 2,那还是派给消防。 | ||
| 这就是不对称代价的意义。Demo 里的代价矩阵是: | ||
| 漏派消防(该去消防却派给别人)记 10,把别部门件派给消防记 2,分对了记 0。 | ||
| 这个不对称不是拍脑袋,是业务事实——延误一个消防隐患和多跑一趟环卫,后果差得远。 | ||
| 反过来也要想清楚:「取代价最小」会系统性地往高代价部门倾斜。上面那个规则跑久了, | ||
| 消防会收到明显偏多的工单,人力被浪费,而且真件会被淹没在假件里。 | ||
| 所以代价矩阵要配一个「误报预算」——比如「消防的误报率不能超过 30%」。 | ||
| 光有代价矩阵没有约束,等于把问题从一头推到另一头。 |
四、代价矩阵不能由算法定
准确率是技术指标,代价是业务判断。「延误一条消防隐患值多少」这个问题, 数据科学家答不了,得由业务部门和责任人答。 实际做法是把代价写成一张表,让每个部门自己填: 「本该派给我、结果派给别人」算几分,「不该派给我、结果派给我」算几分。填完还要对一遍—— 所有部门都把自己的漏派填成 100 分,那这个矩阵就没法用了,得先做相对标定。 还有一个更硬的约束:错了要能兜底。代价矩阵只能降低「错得贵」的概率,不能消除。 所以高代价的那一类(消防、燃气、危房)必须有二次确认机制:系统分完,人工再过一遍, 或者至少给一个「一键转派」。 真正能用的系统是「模型 + 规则 + 人」三层。模型给概率,规则定代价,人兜最贵的那一类。 只报一个准确率就上线的系统,一定会在某个深夜出事。
五、落地:五步
① 先把历史工单的「最终处理部门」当标签。 注意这不是真值——历史上派错的件,标签也是错的。 所以准确率的上限被历史记录锁死了,别指望跑到 99%。 ② 把 15% 的模糊件单独标出来。训练时留着(模型要见过),评估时单独报一个准确率。 混在一起报,会掩盖「模糊件全靠猜」这个事实。 ③ 定代价矩阵,并且写进配置而不是代码。代价会随季节、随专项行动变(汛期水务的权重就该调高), 写死在代码里,改一次要走一次发版。 ④ 上线先跑影子模式。系统照常分派,模型在后台也算一遍, 比对差异,不改实际流程。跑两周看那 4% 的差异件到底谁对。 ⑤ 监控漂移。 新政策、新片区、新表述会改变用词分布。 准确率是慢慢掉的,不会突然崩——所以要定期抽检,不要等投诉。
六、动手 Demo:工单分派:准确率 87.2%,换决策规则再省 26%
造 600 条工单文本,5 个部门 × 6 个典型短语 × 120 条, 其中每个部门 18 条只有通用描述(模拟说不清问题的市民)。 按字二元组切词、朴素贝叶斯分类,8 成训练 2 成测试,先算混淆矩阵和准确率, 再把决策规则从「取概率最大」换成「取代价最小」,比较两者的准确率和平均代价。
def _tok(s): # 字二元组
cs = [c for c in s if u"\u4e00" <= c <= u"\u9fff"]
if len(cs) < 2:
return cs
return [cs[i] + cs[i + 1] for i in range(len(cs) - 1)]
cost = np.zeros((K, K))
cost[2, :] = 10.0 # 该去消防却派给别人:贵
cost[:, 2] = 2.0 # 别部门的件派给消防:便宜
np.fill_diagonal(cost, 0.0)
pick = np.argmax(P, axis=1) # 取概率最大
risk = np.argmin(P * cost, axis=1) # 取代价最小
实跑输出:
--------------------------------------------------------------------
工单分派:准确率 87.2%,换决策规则再省 26%
--------------------------------------------------------------------
工单总数 600 条
训练/测试 420/180
字二元组词表 264 个
真实\分到 环卫 城管 消防 交通 水务
--------------------------------------------
环卫 33 2 0 0 0
城管 0 35 0 1 0
消防 4 3 31 0 1
交通 1 5 0 30 0
水务 2 4 0 0 28
错分有两个来源:**① 15% 的件只写了通用描述**(「影响通行」「无人处理」),
**没有部门线索,怎么分都是猜**;**② 词面重叠**——「占道经营」和「占道停车」、「楼道堆物」和「垃圾堆放」。
表里 23 条错分,准确率 **87.2%**。
决策规则 准确率 平均代价
-----------------------------
取概率最大 87.2% 0.69
取代价最小 82.8% 0.52
**同一个模型、同一批概率,只换决策规则:**准确率从 87.2% 掉到 82.8%,
平均代价从 **0.69 降到 0.52,省了 26%。**
它做的事是:**把没把握的件往「错了也不贵」的部门推。**
有 8 条(4%)的预测因此改变。
**这就是不对称代价的意义:**把消防件分给环卫,和把环卫件分给消防,**后果不是一个量级。**
只报准确率的系统,会在这件事上系统性地犯险。
混淆矩阵里最该看的是消防那一行:31 条对,4 条被分到环卫、3 条被分到城管。 这两处就是「楼道堆物」「堵塞通道」的字面重叠导致的。 准确率 87.2%(180 条错 23 条)。 这个数不是「模型不行」,是「文本里就这么多信息」。15% 的模糊件理论上就是猜,5 个部门猜一个, 期望只对 20%。 换规则那一段:准确率降 4.4 个点、代价降 26%,只有 4% 的件改判。 这说明代价矩阵不需要改动很多判断就能省很多钱——它动的是「最没把握的那一小撮」。 注意代价的两个数字是我设的(10 和 2)。 真实场景里这两个数要由业务定,而且比值一变,改判的比例会明显变。 所以这套东西的产出不是「准确率」,是「这张矩阵配出来的行为符合预期吗」。 要说明:工单文本是按模板造的,分类器也是简化版(字二元组 + 朴素贝叶斯,没有词向量)。 但混淆矩阵、代价矩阵、两种决策规则的算法都是真的,换成真实工单数据,流程不变。
30 秒实验
- 把消防的漏派代价从 10 改成 30,看改判比例。会明显上升——代价比值越大,越倾向「宁可多派」。
- 把每部门的模糊件从 18 条加到 36 条(30%),看准确率。 会掉到 80% 以下,但换规则省的比例会更大——模糊件正是可优化的部分。
- 把测试集里模糊件单独拿出来算准确率。 会低到 20% 附近,接近「随机猜」——这才是这套系统的真实边界。
常见误区
- 「准确率 87% 就能上线了」。 消防件被错分那 7 条里,每一条都是真实隐患。要按代价算,不能只按条数算。
- 「取代价最小会提高准确率」。它会降低准确率(87.2 到 82.8),它优化的是代价不是准确率。
- 「代价矩阵让数据科学家定」。 这是业务判断,而且不限幅的话,所有部门都会把自己填成最高优先级。
自测三问(点开对答案)
1. 为什么准确率 87.2% 的模型还要改决策规则? —— 因为不同错误代价不同。 把消防件分给环卫、和把环卫件分给消防,后果差一个量级,只看准确率会系统性地犯险。 2. 取代价最小的规则为什么准确率反而更低? —— 它不追求「判对最多」,而是追求「总代价最小」。愿意牺牲几条准确率,换取避免高代价的错。 3. 上线前为什么必须做影子模式? —— 因为历史标签本身可能带错,而且代价矩阵是人为设的。 影子模式能先看清 4% 的改判件到底谁对,不动实际流程。政务这边的三件事讲完了:窗口要开几个、政策有没有用、工单派给谁。 下一节换到教育——第一个问题是「怎么知道一个学生哪里不会」。
- ☐ (本节完,下一节 9.3.1 个性化学习支持)