项目:工单意图路由

项目目标

训练一个完全离线的小型分类器,把中文工单路由到“账号、退款、技术”三类。项目同时运行多数类 baseline 与字符 TF-IDF + 逻辑回归 Pipeline,输出宏 F1、混淆矩阵和带置信度的错误样本。重点不是追求高分,而是走通数据校验、分层切分、预处理边界、评估和测试闭环。

实现:ticket_router.py|测试:test_ticket_router.py|依赖:requirements.txt。

数据与决策边界

  • 24 条人工编写的教学样本,不是真实用户数据;三类各 8 条。
  • 固定 random_state=42 做分层切分:训练 18 条、测试 6 条,每类测试 2 条。
  • 相同文本会被显式拒绝,防止明显重复跨集合;语义近重复仍需人工审查。
  • 项目只评估一次固定离线切分,不含独立验证集,不能用测试结果继续调参。
  • 路由结果只用于教学展示;真实系统还要有低置信度人工兜底、隐私审查和漂移监控。

环境与运行

在 Windows 11 PowerShell 7 中,从本项目根目录运行。虚拟环境放在 vault 之外的临时目录,并始终显式调用该环境的解释器:

$exampleDir = (Resolve-Path '.\docs\机器学习\examples').Path
$practice = Join-Path $env:TEMP ("ticket-router-{0}" -f [guid]::NewGuid())
$python = Join-Path $practice 'Scripts\python.exe'
 
py -3.11 -m venv $practice
& $python -m pip install --upgrade pip
& $python -m pip install -r (Join-Path $exampleDir 'requirements.txt')
& $python -m pip check
& $python -B (Join-Path $exampleDir 'ticket_router.py')
& $python -B -O (Join-Path $exampleDir 'ticket_router.py')
& $python -B -W error (Join-Path $exampleDir 'ticket_router.py')
& $python -B -O -W error (Join-Path $exampleDir 'ticket_router.py')
& $python -B -m unittest discover -s $exampleDir -p 'test_*.py' -v
& $python -B -O -m unittest discover -s $exampleDir -p 'test_*.py' -v
& $python -B -W error -m unittest discover -s $exampleDir -p 'test_*.py' -v
& $python -B -O -W error -m unittest discover -s $exampleDir -p 'test_*.py' -v

$practice 位于系统临时目录,不会把虚拟环境写进知识库;验证后可删除这个已解析的精确目录。脚本运行阶段不访问网络、不使用真实用户数据或密钥;只有安装依赖时需要访问包索引。requirements.txt 固定本轮验证的直接依赖 scikit-learn==1.9.0,但没有锁定 NumPy、SciPy 等传递依赖,因此不是可重建生产环境的完整 lockfile。升级直接或传递依赖后,都必须重跑所有测试并检查变更记录。

读代码时抓住七件事

  1. 先验证数据:validate_samples 拒绝空值、空白文本、完全重复文本、单一类别和样本数过少的类别。它只能发现“字面完全相同”,语义近重复仍要人工检查。
  2. 按索引分层切分:split_dataset 使用 stratify=labels,让三个类别各有 2 条测试样本;固定随机种子只是为了复现,不等于获得了多次独立实验。
  3. 先建立弱基线:DummyClassifier(strategy="most_frequent") 永远猜训练集中的多数类。模型如果连这个基线都没有稳定超过,复杂特征没有工程价值。
  4. 把拟合边界封进 Pipeline:字符 TF-IDF 只在训练集上 fit,随后用同一份词表转换测试集;先对全量文本 fit_transform 会泄漏测试信息。
  5. 固定可复现的模型配置:字符 2~4 gram 能捕捉短语片段,逻辑回归负责分类。锁定的 scikit-learn 1.9.0 默认使用 L2 正则;示例不显式传入已进入弃用流程的 penalty,也不传入旧版本会产生无效警告的 l1_ratio=0.0。换版本时应查对应版本文档并重跑测试。
  6. 组合阅读指标:accuracy 回答“总体猜对多少”,宏 F1 让每个类别拥有相同权重,混淆矩阵显示错误流向,classification report 中的 support 则提醒每类实际只有 2 条测试样本。
  7. 逐条审查错误:脚本输出误分类文本、真实标签、预测标签和最大预测概率。这里的 probability 未做概率校准,只是排序和排查线索,不能直接解释为“真实正确率”或自动上线阈值。

实际输出与解释

本轮固定数据与切分在 scikit-learn 1.9.0 上得到以下摘要:

train=18 test=6 random_state=42
labels=技术,账号,退款
baseline-accuracy=0.333
accuracy=0.667 macro-f1=0.656
confusion-matrix rows=true columns=predicted
- 技术: (1, 1, 0)
- 账号: (0, 2, 0)
- 退款: (1, 0, 1)

模型比 0.333 的多数类基线高,但测试集只有 6 条,0.667 只代表 4 条预测正确,不足以证明泛化能力。两条误分类分别是:

  • “发票金额不对想撤销付款”:退款 → 技术,最大预测概率约 0.334;
  • “网络正常但是无法同步”:技术 → 账号,最大预测概率约 0.372。

两个数都接近三分类的均匀概率 1/3,更像“模型拿不准”而不是可信判断。下一轮应先扩大并审查数据,在训练区内建立验证切分或交叉验证,再选择特征、阈值或模型;不能看完这 6 条测试结果后反复调参。

测试覆盖与复现边界

测试文件包含 10 项回归检查,覆盖样本平衡与去重、切分可复现且互斥、分层结果、Pipeline 结构、指标边界、混淆矩阵形状、错误记录、非法输入、命令行输出,以及把所有 warnings 提升为异常后的评估;非法输入检查还覆盖 random_state 超出 scikit-learn 支持的非负 32 位范围。

  • 历史隔离环境(2026-07-20)在 Python 3.11.9、scikit-learn 1.9.0 下记录了本页的数值快照和 9 项测试;该环境已清理。
  • 当前审阅环境(2026-07-22)已有 scikit-learn 1.7.1,未安装或替换依赖;本次修正后的 10 项测试和脚本在 normal、-O、-W error、-O -W error 下均通过。它验证跨版本的警告处理,不替代锁定 1.9.0 环境的复验。
  • 直接依赖由 requirements.txt 固定为 scikit-learn==1.9.0;传递依赖由安装时解析,本项目尚未提供完整 lockfile。

这些检查证明示例在已验证环境中可复现,不证明教学数据代表真实工单,也不证明其他 scikit-learn 版本兼容。

锁定环境的历史快照(2026-07-20)

在 vault 外的 Python 3.11.9 隔离环境中,scikit-learn 1.9.0(当时解析到 NumPy 2.4.6、SciPy 1.17.1)的 9 项测试在 normal、-O、-W error 与 -O -W error 四模式各通过一次。该记录解释上方数值快照;临时环境已清理,传递依赖版本不表示已锁定。

验收任务

  • 依次运行 normal、-O、-W error、-O -W error 和 10 项测试,并解释各模式能发现什么问题。
  • 根据输出手算 accuracy:6 条中 4 条正确,因此为 4 / 6 ≈ 0.667。
  • 逐行解释混淆矩阵,指出“技术 → 账号”和“退款 → 技术”各有 1 条。
  • 说明为什么不能用当前测试集选择 2~4 gram 还是 2~5 gram,并画出训练、验证、测试三段边界。
  • 把一条明显的退款句子改成含糊表达,作为新输入观察预测;不要据此修改模型后再次汇报同一测试分数。
  • 设计“发票”新类别的数据计划:先定义标签边界、收集并去重,再重新分层切分;不能只追加一两条样本。
  • 为两条现有错误分别提出一项数据层改进和一项系统层兜底,并说明最大预测概率为何不是已校准置信度。

进阶但仍可控

  • 把内置样本移到 UTF-8 CSV,并记录数据版本。
  • 用独立验证数据选择人工复核阈值;若概率要表达可靠程度,再比较校准前后的可靠性图或 Brier score。
  • 按来源或时间构造测试集,检查分布变化。

常见错误与排查

错误为什么有问题修正
固定一个随机种子后把结果当作稳定结论它只让同一次切分可复现增加数据,并用交叉验证或多个预先定义的切分评估波动
看测试结果后继续改 n-gram测试集变成了隐形验证集在训练区内部调参,测试集只用于最后一次评估
把最大 predict_proba 当作真实置信度分类概率可能没有校准,小数据下尤其不可靠先验证校准质量,再用验证集选择人工复核阈值
只去除完全重复文本同义改写仍可能跨集合泄漏按会话、用户、来源或时间分组,并审查语义近重复
直接换成真实工单可能包含姓名、账号、手机号和业务机密先做授权、最小化、脱敏、留存和访问控制设计
升级依赖后只看脚本能否启动默认值、弃用参数和指标行为可能变化查官方变更记录,重跑普通、-O、警告提升和单元测试

自测

  1. 为什么不能先对全部文本运行 fit_transform 再切分?
  2. 为什么小样本上的单次高分不足以证明可上线?
  3. 宏平均 F1 与按样本加权 F1 分别容易暴露、掩盖什么?
  4. 若模型把“钱”当成退款捷径,如何用错误分析修复?

完成标准:能独立复现、解释数据边界、展示错误样本,并提出至少一项数据层和一项模型层改进。

返回 机器学习目录。

参考资料

资料获取与运行复核日期:2026-07-22。项目的 API 说明以 scikit-learn 1.9.0 官方文档为准;数值输出保留 2026-07-20 的锁定环境快照,当前未在本机安装依赖以重建该环境。若安装其他版本,以该版本文档和实际测试为准。