中文意图识别的一次完整实做:微调一个 encoder,然后把它和「直接调大模型」放在同一张表上比。
这个仓库要回答的不是"能不能训出一个分类器",而是生产里真正要做的那个决策:
一句用户输入进来,什么时候该走几毫秒的小模型,什么时候该交给大模型,什么时候该谁都不接、直接兜底?
MASSIVE zh-CN —— Amazon 的多语言语音助手语料。
| 训练 / 验证 / 测试 | 11,514 / 2,033 / 2,974 |
| 意图 | 60 个,横跨 18 个领域 |
| 标注 | 意图标签 + slot 的 BIO 标注(annot_utt) |
| 长尾 | 单个意图的训练样本从 4 条到 810 条 |
最后那一行是这份数据最像生产的地方,也是 macro-F1 会比准确率难看很多的原因。
留出 10 个意图完全不参与训练,它们在测试集里的样本就是 OOS(未覆盖意图)。
留出的 10 个分散在 10 个不同领域。这一步不是随手选的:如果留出的意图集中在同一个领域, 模型只要认出"这句话属于某个没见过的领域"就能拒识——那测出来的是领域分类能力,不是拒识能力, 数字会好看得多,也没有意义。
python3 src/train_intent.py --epochs 5python3 src/eval_intent.pyGROQ_API_KEY=... python3 src/llm_baseline.py --n 300 --n_oos 100| 文件 | 干什么 |
|---|---|
src/data.py |
加载、标签映射、OOS 划分 |
src/train_intent.py |
微调与推理,只产出概率,不定阈值 |
src/eval_intent.py |
长尾表现、混淆对、拒识阈值曲线 |
src/llm_baseline.py |
LLM 直接分类的对照组:准确率 / 延迟 / token 成本 |
两个刻意的选择:
- 不用
transformers.Trainer。 它把 loss、优化器、调度器和早停全藏起来,讲不动。这份代码要能不看笔记讲完。 - 阈值不在训练脚本里定。 阈值是运营参数,不是模型参数——生产里它会随业务容忍度反复调, 藏进训练脚本等于把一个产品决策焊死成一个超参。
做完这件事不等于"做过 NLU 引擎研发"。真实的引擎还包含标注体系设计、badcase 数据回流、 线上版本管理与灰度——这三样这里都没有。这里有的是:训练循环、评估口径、阈值决策,以及一次和 LLM 路径的成本对照。