25 实践课-意图识别模型优化与验证

意图识别模型优化与验证

关联:索引

术语小抄(初学者版)

cd ".\01_intent_training_project"
py -m pip install -U pip
py -m pip install -r .\requirements.txt

解释与自检:


1)什么叫“优化成功”

本讲不把“单一指标变高”直接等同于优化成功,而是同时看 3 件事:

  1. 验证集或留出测试集指标是否提升,至少要看 accuracymacro_f1
  2. 关键混淆类别是否得到改善,而不是“强势类别更强、弱势类别更弱”。
  3. 实际场景指令测试是否更稳,尤其是模糊表达、边界样本、口语化表达是否更可控。

2)意图识别准确率低的常见原因

3)优化优先级(避免一上来乱调参数)

  1. 先检查数据质量,再调模型参数。
  2. 先补“最容易混淆的类别对”数据,再盲目补总量。
  3. 先修分类规则与标注规范,再重训。
  4. 只有在数据与规则基本稳定后,才做学习率、轮数、阈值等参数微调。

4)双重验证口径

本讲要求每组至少做两类验证:


AI 工具使用:诊断原因 / 生成优化方案 / 生成扩充脚本 / 辅助答疑(学生可直接复制)

使用方法:把你们的“意图集合、错例、混淆矩阵、数据样本、训练日志、当前指标”粘贴到 {你的内容}。要求 AI 输出结构化结论,便于人工审计与落地执行。

模板目录:

模板 1:生成模型问题诊断清单

你是意图识别模型诊断助手。下面是我的模型测试结果(含 accuracy、macro-F1、混淆矩阵、错例列表、意图定义)。
请输出:
1)<<<CAUSES>>>:按优先级排序的低准确率原因,必须区分“数据量不足 / 分类边界模糊 / 标注误差 / 参数问题”
2)<<<EVIDENCE>>>:每个原因对应的证据(从错例、混淆类别、样本分布中提取)
3)<<<ACTIONS>>>:每个原因的最小修复动作(先做什么,后做什么)
4)<<<CHECKLIST>>>:我下一轮优化前的自检清单(至少 10 条)

输出要求:
- 必须严格包含 CAUSES/EVIDENCE/ACTIONS/CHECKLIST 标记
- 不要只给结论,必须指出证据来自哪里

我的数据与日志:{你的内容}

模板 2:生成个性化优化方案

你是课堂项目助教。请根据我的意图识别模型问题,生成一份“个性化优化方案”,要求:
1)分成“数据优化 / 规则优化 / 参数优化 / 验证方案”四部分;
2)每部分给 3~5 条可执行动作;
3)参数优化只能基于课堂已使用的方法,不能引入新框架;
4)最后输出“本轮优化优先级排序”和“预计收益最大的 2 个动作”。

我的项目情况:{你的内容}

模板 3:生成数据扩充脚本或扩充样本

你是 Python 实践课助教。请为我的意图识别项目生成“数据扩充脚本”或“扩充样本草案”,要求:
1)围绕指定意图类别生成同义表达、口语表达、边界表达;
2)输出必须保持课堂 JSONL 字段口径:sample_id/raw_text/intent/slots/scene/guideline_version/ts_ms;
3)不能直接复制已有样本,必须做表达变化;
4)如生成脚本,只能用 Python 标准库;
5)最后附带“人工审计清单”,提醒我检查标签是否正确、字段是否一致、是否存在重复样本。

我的意图集合与示例数据:{你的内容}

模板 4:优化过程报错排查与答疑

下面是我在意图识别模型优化过程中的报错、日志片段或异常现象:{你的内容}
请按“定位→修复→预防”输出:
1)先判断属于哪类问题:数据格式 / 标签集合不一致 / 训练参数 / 预测阈值 / 验证方法 / 其他
2)给最多 8 步排查路径,每步写清楚要检查什么、预期结果是什么
3)给一份回归验证清单(至少 8 条),确保修复后没有引入新的问题

  1. 为什么同样训练过的模型,有的组准确率比较高,有的组却总在几个意图之间来回混淆?
  2. 如果你只看到 accuracy=0.82,能不能直接判断模型“够用了”?为什么还要看错例和场景测试?

上一讲我们已经能输出 accuracymacro_f1confusion_matrix。本讲的重点不是“再跑一次指标”,而是回答下面 3 个问题:

  1. 哪些类别最容易被混淆?
  2. 这些混淆是因为样本少、标签乱、边界不清,还是参数不合理?
  3. 本轮优化先改哪里,最可能带来真实提升?

1)数据量不足

典型表现:

判断依据:

2)分类边界模糊

典型表现:

判断依据:

3)标注误差

典型表现:

判断依据:

4)参数或阈值问题

典型表现:

判断依据:

说明:

示例输入格式(最小可用版):

{
  "accuracy": 0.81,
  "macro_f1": 0.72,
  "per_label": {
    "sort_by_grade": {"support": 18, "recall": 0.61},
    "query_apple_quality": {"support": 16, "recall": 0.56},
    "device_control": {"support": 20, "recall": 0.90}
  },
  "top_confusions": [
    {"true": "query_apple_quality", "pred": "sort_by_grade", "count": 5},
    {"true": "sort_by_grade", "pred": "query_apple_quality", "count": 4}
  ],
  "errors": [
    {"text": "最近10秒A级有多少", "true": "query_apple_quality", "pred": "sort_by_grade"},
    {"text": "把A级苹果送到1号口", "true": "sort_by_grade", "pred": "query_apple_quality"}
  ]
}

解释与自检:

把下面脚本保存为 diagnose_intent_model.py

from __future__ import annotations

# diagnose_intent_model.py(课堂诊断脚本)
# 目标:把“评估报告 eval_report.json”转成可读结论,帮助你快速定位:
# - 哪些意图是弱类别(recall 低)
# - 哪些意图对互相混淆最严重(top_confusions)
# - 可能原因是什么(样本不足 / 边界不清 / 标签质量问题)
#
# 输入:eval_report.json(来自你们训练/评估流程的输出,或按示例手工整理)
# 输出:打印 4 段内容:
# - accuracy/macro_f1(整体指标概览)
# - weak_labels(弱类别列表)
# - likely_causes(推测原因列表)
# - sample_errors(错例样本,便于人工复盘或交给 AI 分析)

import json
import sys
from pathlib import Path
from typing import Dict, List

def load_report(path: Path) -> Dict:
    # 读取评估报告 JSON:
    # - 这里假设文件是 UTF-8 编码
    # - 返回值类型用 Dict 简化,课堂更关注“能跑通 + 可解释”
    return json.loads(path.read_text(encoding="utf-8"))

def weak_labels(per_label: Dict[str, Dict], *, recall_threshold: float) -> List[Dict]:
    # 从 per_label 中筛选“召回率低于阈值”的弱类别
    # per_label 结构示例:
    # {
    #   "sort_by_grade": {"support": 18, "recall": 0.61},
    #   "query_apple_quality": {"support": 16, "recall": 0.56}
    # }
    items: List[Dict] = []
    for label, stats in per_label.items():
        # support:该类别样本数(支持数),少数类通常更难学
        support = int(stats.get("support", 0))
        # recall:召回率,反映“该类样本被模型找回的比例”
        recall = float(stats.get("recall", 0.0))
        if recall < recall_threshold:
            items.append({"label": label, "support": support, "recall": recall})
    # 排序策略:先看 recall(越低越优先),再看 support(越少越可能是数据不足)
    items.sort(key=lambda x: (x["recall"], x["support"]))
    return items

def guess_causes(report: Dict) -> List[str]:
    # 根据“弱类别 + 高混淆对”给出第一轮推测原因(可直接用于制定优化优先级)
    reasons: List[str] = []
    # 做健壮性处理:避免 report 字段缺失导致脚本崩溃(课堂机房常见)
    per_label = report.get("per_label", {}) if isinstance(report.get("per_label"), dict) else {}
    confusions = report.get("top_confusions", []) if isinstance(report.get("top_confusions"), list) else []

    # 弱类别原因推测:
    # - support 太少:优先补该类样本(尤其是口语/边界表达)
    # - support 不少但 recall 低:优先排查边界定义与标签质量
    for item in weak_labels(per_label, recall_threshold=0.70):
        if item["support"] < 12:
            reasons.append(f"{item['label']}: 样本可能不足(support={item['support']},recall={item['recall']:.2f})")
        else:
            reasons.append(f"{item['label']}: 虽然样本不算少,但 recall 偏低,需检查边界或标签质量")

    # 混淆对原因推测:
    # - true -> pred 的 count 越高,说明该对类别边界可能不清或错例表达高度重叠
    for item in confusions[:5]:
        true_label = str(item.get("true", ""))
        pred_label = str(item.get("pred", ""))
        count = int(item.get("count", 0))
        if true_label and pred_label and count > 0:
            reasons.append(f"{true_label} -> {pred_label}: 高混淆(count={count}),优先检查分类边界与错例表达")

    return reasons

def main() -> None:
    # 命令行入口:
    # python diagnose_intent_model.py <eval_report.json>
    if len(sys.argv) < 2:
        print("usage: python diagnose_intent_model.py <eval_report.json>")
        sys.exit(2)

    report_path = Path(sys.argv[1])
    report = load_report(report_path)

    # 取整体指标(若字段不存在则回退为 0.0,便于脚本继续跑)
    acc = float(report.get("accuracy", 0.0))
    macro_f1 = float(report.get("macro_f1", 0.0))
    # per_label:按类别统计;errors:错例列表(用于抽样打印)
    per_label = report.get("per_label", {}) if isinstance(report.get("per_label"), dict) else {}
    errors = report.get("errors", []) if isinstance(report.get("errors"), list) else []

    # 1)整体指标
    print(json.dumps({"accuracy": acc, "macro_f1": macro_f1}, ensure_ascii=False))

    # 2)弱类别列表
    print("=== weak_labels ===")
    for item in weak_labels(per_label, recall_threshold=0.70):
        print(json.dumps(item, ensure_ascii=False))

    # 3)推测原因(用于制定下一步优化动作)
    print("=== likely_causes ===")
    for reason in guess_causes(report):
        print(f"- {reason}")

    # 4)错例样本(只打印前 10 条避免刷屏;课堂上可以把这 10 条交给 AI 做归因与扩写)
    print("=== sample_errors ===")
    for item in errors[:10]:
        print(json.dumps(item, ensure_ascii=False))

if __name__ == "__main__":
    main()

逐段解释与自检要点:

运行示例(PowerShell):

py .\diagnose_intent_model.py .\eval_report.json

解释与自检:

六、练习(至少完成 2 题)

1)用你们组当前模型的测试结果,整理一份 eval_report.json,至少包含:整体指标、按类别统计、前 5 个混淆类别对、前 10 条错例。

2)根据诊断结果,为你们组写出一份“本轮优化优先级表”,至少列出 3 项问题与对应动作。

  1. 如果你已经知道问题来自“类别混淆”,接下来应该先补数据、先改规则,还是先调参数?为什么?
  2. 如果测试集指标提升了,但现场口语指令仍然容易误判,这算不算真正优化成功?

1)扩充标注数据

适用场景:

2)调整分类规则与标注规范

适用场景:

3)微调训练参数与推理阈值

适用场景:

说明:

把下面脚本保存为 merge_augmented_data.py

from __future__ import annotations

# merge_augmented_data.py(合并扩充数据 + 最小清洗脚本)
# 目标:把“人工标注 base.jsonl”与“AI 生成 aug.jsonl(已人工审计)”合并成新训练集 output.jsonl。
# 只做最小清洗(兜底),不替代人工审计:
# - 必填字段检查(字段齐全且类型基本正确)
# - 过滤空文本
# - intent 必须在 intents.json 中
# - slots 必须是 dict(保持结构一致,便于后续扩展)
# - 去重:按 sample_id 去重;同时按 (raw_text, intent) 去重防止“换 id 重复写入”
#
# 用法:
# python merge_augmented_data.py <base.jsonl> <aug.jsonl> <intents.json> <output.jsonl>

import json
import sys
from pathlib import Path
from typing import Dict, List, Set

REQUIRED_FIELDS = {
    "sample_id",
    "raw_text",
    "intent",
    "slots",
    "scene",
    "guideline_version",
    "ts_ms",
}

def load_jsonl(path: Path) -> List[Dict]:
    # 读取 JSONL(JSON Lines):每行一个 JSON 对象
    # 课堂上更推荐 JSONL 而不是单个巨大 JSON 数组,因为它更易追加与排错
    rows: List[Dict] = []
    for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
        s = line.strip()
        if not s:
            continue
        try:
            rows.append(json.loads(s))
        except json.JSONDecodeError as exc:
            # 明确指出第几行坏了,便于学生快速定位格式问题
            raise RuntimeError(f"json decode error at line {i}: {exc}") from exc
    return rows

def load_intents(path: Path) -> Set[str]:
    # 读取合法 intent 集合(intents.json 必须是 JSON 数组)
    # 目的:防止 AI 生成不存在的标签,导致训练时“标签漂移”
    data = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(data, list):
        raise RuntimeError("intents.json must be a JSON array")
    return {str(x) for x in data}

def validate_row(row: Dict, *, intents: Set[str]) -> bool:
    # 行级校验:不通过就返回 False(在 merge_rows 里会被过滤掉)
    # 注意:这里是“最小校验”,不做复杂业务校验(比如 slots 的字段范围),课堂后续可扩展
    if REQUIRED_FIELDS - set(row.keys()):
        return False
    if not isinstance(row.get("raw_text"), str) or not row["raw_text"].strip():
        return False
    if row.get("intent") not in intents:
        return False
    if not isinstance(row.get("slots"), dict):
        return False
    return True

def merge_rows(base_rows: List[Dict], aug_rows: List[Dict], *, intents: Set[str]) -> List[Dict]:
    # 合并与去重:
    # - seen_ids:防止 sample_id 重复
    # - seen_pairs:防止同一句 raw_text 在同一 intent 下重复出现(AI 扩写时常见)
    seen_ids: Set[str] = set()
    seen_pairs: Set[tuple[str, str]] = set()
    merged: List[Dict] = []

    for row in base_rows + aug_rows:
        # 先做基本校验,避免后续处理 KeyError/类型错误
        if not validate_row(row, intents=intents):
            continue
        sample_id = str(row["sample_id"]).strip()
        raw_text = str(row["raw_text"]).strip()
        intent = str(row["intent"]).strip()
        dedup_key = (raw_text, intent)

        # 去重 1:同 sample_id 的样本只保留第一次出现的
        if sample_id in seen_ids:
            continue
        # 去重 2:同一句 raw_text + intent 只保留一次(减少训练集的“重复表达污染”)
        if dedup_key in seen_pairs:
            continue

        seen_ids.add(sample_id)
        seen_pairs.add(dedup_key)
        merged.append(row)

    return merged

def save_jsonl(path: Path, rows: List[Dict]) -> None:
    # 保存为 JSONL:每行一条 JSON,方便 diff、追加与排障
    text = "\n".join(json.dumps(r, ensure_ascii=False) for r in rows)
    path.write_text(text, encoding="utf-8")

def main() -> None:
    # 命令行入口:
    # python merge_augmented_data.py <base.jsonl> <aug.jsonl> <intents.json> <output.jsonl>
    if len(sys.argv) < 5:
        print("usage: python merge_augmented_data.py <base.jsonl> <aug.jsonl> <intents.json> <output.jsonl>")
        sys.exit(2)

    # 参数解析:按位置读取(课堂更直观;真实工程可换 argparse)
    base_path = Path(sys.argv[1])
    aug_path = Path(sys.argv[2])
    intents_path = Path(sys.argv[3])
    out_path = Path(sys.argv[4])

    # 1)加载合法意图集合
    intents = load_intents(intents_path)
    # 2)加载 base 与 aug 两份数据
    base_rows = load_jsonl(base_path)
    aug_rows = load_jsonl(aug_path)
    # 3)合并 + 清洗 + 去重
    merged = merge_rows(base_rows, aug_rows, intents=intents)
    # 4)写出新数据集版本(建议命名 intent_data_v2.jsonl / v3 ...)
    save_jsonl(out_path, merged)

    # 输出统计信息:课堂用于快速确认“合并是否生效、是否过滤过多”
    print(json.dumps({"base": len(base_rows), "aug": len(aug_rows), "merged": len(merged)}, ensure_ascii=False))

if __name__ == "__main__":
    main()

逐段解释与自检要点:

运行示例(PowerShell):

py .\merge_augmented_data.py .\intent_data.jsonl .\intent_data_ai_aug.jsonl .\intents.json .\intent_data_v2.jsonl

解释与自检:

说明:

1)进入项目目录并备份优化前模型:

cd ".\01_intent_training_project"
Copy-Item .\model.json .\model_v0.4_baseline.json

解释与自检:

2)把新数据写入项目目录并先做正式自检:

py .\check_intent_data.py .\intent_data_v2.jsonl .\intents.json

解释与自检:

3)用新数据重新训练模型:

py .\train_intent_model.py .\intent_data_v2.jsonl .\intents.json v0.2 v0.5

解释与自检:

4)读取训练输出,完成“优化前后”验证集对比:

解释与自检:

5)用实际场景指令做现场验证:

py .\predict_intent.py .\model.json "把A级苹果送到1号口"
py .\predict_intent.py .\model.json "最近10秒A级苹果有多少"
py .\predict_intent.py .\model.json "先别动,看看分拣线现在状态"

解释与自检:

1)验证集对比表

模型版本 数据版本 accuracy macro-F1 主要混淆对 结论
v0.4 v0.1 0.81 0.72 query_apple_quality ↔ sort_by_grade 基线模型(来自上一次训练输出)
v0.5 v0.2 0.87 0.81 同类混淆减少 本轮优化有效(来自本次训练输出)

2)场景指令验证表

测试指令 期望意图 优化前 优化后 是否改进
把A级苹果送到1号口 sort_by_grade query_apple_quality sort_by_grade
最近10秒A级苹果有多少 query_apple_quality sort_by_grade query_apple_quality
先别动,看看分拣线现在状态 query_sorting_status device_control query_sorting_status

七、练习(至少完成 2 题)

1)围绕你们组最严重的 2 个混淆类别,各补充至少 10 条高质量样本,并说明这些样本为什么有价值。

2)比较优化前后模型在同一测试集上的 accuracymacro_f1 与主要混淆类别对变化,写出 100 字左右结论。

课程思政(融入点:通过模型迭代培养攻坚克难的探索精神)

  1. 你们本轮优化里最难解决的问题是什么?你们是如何一步步定位出来的?
  2. 如果模型误判导致现场动作错误,你们准备如何通过“数据版本、模型版本、验证记录”去追溯并修复?

作业:布置

1)提交模型优化报告(含问题分析、优化方案、前后准确率对比,300 字左右)。

2)提交优化后的模型代码、测试集验证结果截图、实际指令测试记录。

3)提交 AI 交互记录(辅助诊断问题、生成优化方案的过程)。

参考与延伸

Markdown 自检清单(提交前自己勾一遍)