跳转至

P7-4.3 表示标准化练习

Section ID: P7-4.3 Version: v2026.08.01

标准化会改变表示,因此会改变模型认为哪些差异重要。本练习在固定任务上比较标准化前后,再分别阅读恢复案例、剩余案例和新错误案例。

让比较可以追责

  • 固定任务、划分和评估行。
  • 写明标准化变换及它使用的统计或规则。
  • 同时比较指标变化和样本级预测转变。
  • 记录限制以及下一条要收集的边界案例。

平均值改善不表示标准化自动适合每个输入。项目记录必须说明改变了什么、对谁改变、哪些内容仍不确定。

学习问题与标准

  • 哪个表达直接造成错误的操作路由,哪个只降低覆盖?
  • 为什么规则除了提高覆盖外,还必须检查预测回归?
  • 如何把零信号或分数相同的未决定输入,与有把握的类别预测分开?

当你能在同一评估行上运行一张明确的标准化映射、说出每个恢复或回归案例,并选择一个可逆下一行动时,即完成本练习。

比较两个路由表达的标准化前后

使用与 P7-4.2 相同的 p7-4-support-routing-dataset.zh.csv 评估集。练习映射是 캔슬 → 취소스케줄 → 일정하자 → 불량。它只在按空格切分的边界替换表达,避免在较长词元内部替换。

评估样本 原始文本 标准化文本 应检查什么
평가-05 캔슬 후 송장 남아 있어요 취소 후 송장 남아 있어요 缺少的取消意图是否恢复。
평가-07 하자 제품 환불 스케줄 알고 싶어요 불량 제품 환불 일정 알고 싶어요 即使标签保持正确,覆盖是否改善。

案例流程把它变成两种不同优先级。低覆盖是共享证据;结果和关键表达决定行动。

flowchart TD
  A[比较两个低覆盖样本] --> B[检查正确性与关键 OOV 词]
  B --> C[评估-05:取消词导致错误路由]
  B --> D[评估-07:表达降低覆盖但路由正确]
  C --> E[优先采用标准化规则]
  D --> F[保留为后续清理候选]
  E --> G[检查回归或剩余 OOV]
  F --> G

当前数据集的比较输出为:

1
2
3
4
5
6
原始准确率:0.857
标准化准确率:1.000
覆盖提高:평가-05、평가-07
预测改变:평가-05
原始未决定样本:无
标准化未决定样本:无
样本 之前 之后 复核决定
평가-05 覆盖 0.200;预测配送,错误。 覆盖 0.400;预测退款,正确。 优先标准化:캔슬 直接造成错误。
평가-07 覆盖 0.333;预测退款,正确。 覆盖 0.667;预测退款,正确。 后续清理候选:하자스케줄 降低覆盖但未造成该错误。

优先级不是 OOV 词数量。先标准化造成操作错误的表达,再安排当前预测正确的低覆盖表达。若规则提高覆盖却使正确预测变错或变成未决定,应在任何覆盖收益之前记录“撤销或重新检查”。

flowchart TD
  A[固定评估文本] --> B[运行原始表示]
  B --> C[应用边界感知标准化规则]
  C --> D[运行标准化表示]
  D --> E[比较覆盖、OOV、预测与状态]
  E --> F{正确结果是否回归?}
  F -->|是| G[撤销或重新检查规则]
  F -->|否| H{错误或未决定案例是否恢复?}
  H -->|是| I[优先采用标准化]
  H -->|否| J[增加规则或数据,或记录清理候选]

留下标准化记录

字段 应写的内容
原始表达 原始短语,例如 캔슬
标准化表达 替换短语,例如 취소
覆盖变化 已知词比例的前后值。
预测变化 团队标签、错误状态或未决定状态。
剩余 OOV 仍没有训练词汇匹配的表达。
复核决定 优先、后续清理、增加数据/规则,或撤销/重查。

캔슬 후 송장 남아 있어요 在标准化前被错误路由到配送。将 캔슬 替换为 취소 后,覆盖从 0.200 提升到 0.400,预测改为正确的退款团队。하자 제품 환불 스케줄 알고 싶어요 在覆盖从 0.333 升至 0.667 时仍正确路由。先增加造成错误的同义词;已经正确的低覆盖短语可作为后续清理优先级。

运行边界感知比较

请从仓库根目录运行下列代码。它使用与 P7-4.2 相同的空格分词和类别轮廓评分器,却将零特征或分数相同的行标为 未决定,而不是静默选择第一个类别。

import csv
import re
from pathlib import Path
import numpy as np

rows = list(csv.DictReader(Path("docs/assets/part-07/chapter-04/p7-4-support-routing-dataset.zh.csv").open(encoding="utf-8")))
train_rows = [row for row in rows if row["split"] == "train"]
test_rows = [row for row in rows if row["split"] == "test"]
normalization_map = {"캔슬": "취소", "스케줄": "일정", "하자": "불량"}
def tokenize(text): return text.split()
def normalize(text):
    for raw, replacement in sorted(normalization_map.items(), key=lambda item: len(item[0]), reverse=True):
        text = re.sub(rf"(?<!\S){re.escape(raw)}(?!\S)", replacement, text)
    return text
vocabulary = sorted({token for row in train_rows for token in tokenize(row["text"])})
index = {token: position for position, token in enumerate(vocabulary)}
def vectorize(texts):
    matrix, coverages, oov_lists = np.zeros((len(texts), len(vocabulary))), [], []
    for row_number, text in enumerate(texts):
        tokens = tokenize(text); known, oov = 0, []
        for token in tokens:
            if token in index: matrix[row_number, index[token]] += 1; known += 1
            else: oov.append(token)
        coverages.append(round(known / len(tokens), 3) if tokens else 0.0); oov_lists.append(oov)
    return matrix, coverages, oov_lists
X_train, _, _ = vectorize([row["text"] for row in train_rows])
y_train = np.array([int(row["label"]) for row in train_rows])
profiles = np.vstack([X_train[y_train == label].sum(axis=0) for label in (0, 1)])
def predict(matrix):
    scores = matrix @ profiles.T; winner = scores.argmax(axis=1)
    return np.where((matrix.sum(axis=1) > 0) & (np.ptp(scores, axis=1) > 0), winner, -1)
def status(prediction, actual): return "未决定" if prediction == -1 else "正确" if prediction == actual else "错误"
original = [row["text"] for row in test_rows]; normalized = [normalize(text) for text in original]
X_before, coverage_before, oov_before = vectorize(original)
X_after, coverage_after, oov_after = vectorize(normalized)
y_test = np.array([int(row["label"]) for row in test_rows]); before, after = predict(X_before), predict(X_after)
print({"原始准确率": round(float((before == y_test).mean()), 3), "标准化准确率": round(float((after == y_test).mean()), 3), "覆盖提高": [row["sample_id"] for row, old, new in zip(test_rows, coverage_before, coverage_after) if new > old], "预测改变": [row["sample_id"] for row, old, new in zip(test_rows, before, after) if old != new]})
for row, old_text, new_text, old_cov, new_cov, old_oov, new_oov, old_pred, new_pred, actual in zip(test_rows, original, normalized, coverage_before, coverage_after, oov_before, oov_after, before, after, y_test):
    if row["sample_id"] in {"평가-05", "평가-07"}:
        print({"sample": row["sample_id"], "前": old_text, "后": new_text, "覆盖": (old_cov, new_cov), "OOV": (old_oov, new_oov), "状态": (status(old_pred, actual), status(new_pred, actual))})

代码明确了两条安全规则。第一,替换从最长表达至最短表达并只发生在空格边界,单词规则不能静默修改较长词元的一部分。第二,程序不会把平局当作有把握的退款预测。这两条规则使前后记录更可信。

阅读前后输出

观察 标准化前 标准化后 对记录的含义
汇总准确率 0.857 1.000 一条此前错误的评估行恢复。
Test-05 覆盖 0.200 0.400 取消同义词成为已知退款表达。
Test-05 状态 错误配送路由 正确退款路由 该规则是即时标准化优先级。
Test-07 覆盖 0.333 0.667 两个熟悉表达可供评分器使用。
Test-07 状态 正确退款路由 正确退款路由 是清理候选,不是已修复错误的证据。

汇总改善有用,却不能单独决定。样本转变说明准确率为何改变,以及哪条规则应先保留。保留两个层次可防止一行恢复变成关于所有同义词的宽泛主张。

四种可能的标准化结果

每个提出的映射条目都可按样本级转变分类。

之前状态 之后状态 复核决定 原因
错误或未决定 正确 优先标准化 变化恢复了可用决策。
正确 正确且覆盖提高 后续清理候选 规则可能提高稳健性,但未校正本案例。
正确 错误或未决定 撤销或重新检查 覆盖增加不能压过预测回归。
错误或未决定 错误或未决定 增加规则或数据 当前变化没有修复决策。

这张表也说明覆盖不能孤立优化。规则可以增加已知词数量,同时以不安全方式改变类别证据。实际标签和未决定状态都是评估契约的一部分。

将安全规则与宽泛替换分开

本练习的映射是狭窄表达替换,不是允许替换任何靠近类别标签的词。规则应有记录的范围、预期改变的例子和能暴露过度替换的例子。

规则 预期效果 当前行中的证据 要测试的边界风险
캔슬 → 취소 恢复取消请求。 Test-05 从错误变正确。 该词是否出现在非取消语境?
하자 → 불량 使缺陷同义词熟悉。 Test-07 覆盖提升。 替换是否改变另一种产品状态含义?
스케줄 → 일정 使日程同义词熟悉。 Test-07 覆盖提升。 日程是否用于无关技术术语?

练习评分器使用空格词元,因此行为有意简单。生产分词器中的同一问题会变成:替换哪个词元序列、规则范围如何、如何为审计保留原文本?即使模型输入被标准化,原文本也必须可用。

测试独立场景

每个场景都从原 CSV 和默认三条映射开始。不要把已修改的行或故意错误规则留给下一个场景,否则后一个结果无法归因于单一干预。

  1. 将 test-05 改为 캔슬 후 남아 있어요
  2. 标准化前没有已知词,输出应为 未决定,而不是第一个类别的猜测。
  3. 标准化后覆盖为 0.250,退款路由恢复。
  4. 记录恢复来自评分器重新获得已知取消信号。

  5. 在 test-07 末尾增加未见词 ASAP

  6. 标准化后覆盖可提高,同时仍有 OOV。
  7. 若路由保持正确,应写分词或数据覆盖问题,而不是“问题已解决”。

  8. 增加多词规则 환불 요청 → 환불

  9. 确认最长的边界感知规则作为一个表达运行。
  10. 测试更长词元 환불 요청서,验证它没有被部分替换。

  11. 故意增加不安全规则 환불 → 배송 到默认 test-07。

  12. 覆盖可能仍高,但正确退款路由会变错。
  13. 唯一有效复核决定是撤销或重新检查;测试后删除规则。

  14. 在默认 test-05 上增加 취소 → 배송

  15. 取消表达变为已知,但错误路由仍存在。
  16. 将此归为规则或数据强化需要,而不是成功。

这些场景是教学控制,不是生产推荐规则。它们的价值在于:不改变任务、标签或评估行的情况下,让回归和残留错误分支可见。

建立项目标准化日志

每个受影响表达一行,每个受影响评估案例一行。不要合并两个层次:一个表达可出现在多个案例,一个案例也可含多个 OOV 表达。

规则标识:
原始表达与标准化表达:
边界或分词规则:
训练词汇证据:
评估样本标识:
变化前文本与标准化文本:
变化前后覆盖:
变化前后 OOV:
变化前后预测与状态:
复核决定:
下一数据或规则测试:

当前运行的简短交接可以写为:

Test-05 在覆盖 0.200 时被错误送到配送。边界感知 캔슬 → 취소 规则使覆盖升至 0.400,并恢复退款路由。将规则保留为优先候选,在新取消措辞上测试,并为审计保留原文本。Test-07 在覆盖从 0.333 升至 0.667 时保持正确,因此其两个同义词规则仍是次要清理候选。

这段措辞区分事实与计划。它不说规则普遍正确,也不隐藏 Test-07 在变化前已正确。

限制与下一个问题

这是一个固定词汇与透明类别轮廓评分器的小型合成数据集。它不能建立更大语言模型、子词分词器或生产支持队列的行为,却能建立采用标准化变化前所需的比较纪律。

  • 规则在新收集的取消措辞上仍有帮助吗?
  • 标签指南是否足以区分取消与配送状态问题?
  • 哪些剩余 OOV 表达同时出现在正确和错误案例中?
  • 更灵活的分词器是否减少手动标准化,同时不引入另一种失败模式?
  • 在再次改变映射前,哪个回归测试必须留在评估集?

回答可能通向更多数据、修订标签、新分词器或受限规则表。本节证据只足以支持第一个可逆下一步。

最终自我复核

检查 问题
固定条件 任务、标签、划分与评估行是否未变?
规则范围 替换是否有边界意识并已记录?
证据 是否同时保留原始与标准化文本?
覆盖 日志是否显示已知词比例和剩余 OOV?
预测 是否显示变化前后的类别或 未决定
恢复 哪条错误或未决定行变为正确?
回归 哪条正确行变为错误或未决定?
残留风险 哪条行仍错误、未决定或低覆盖?
下一行动 决定是优先规则、清理、撤销还是增加数据?

不要折叠的概念

  • 覆盖改善与预测改善相关,但不相同。
  • 当前正确与对新措辞稳健是不同主张。
  • 规则候选与经过验证的生产规则有不同证据要求。
  • 无信号/分数平局与有把握地预测类别零是不同状态。

当分数表看似改善时,这种区分尤其重要。一个结果可以同时含有真正恢复、无害清理和不安全回归;复核记录必须使每条路径可见。

交接到下一轮

再次改变标准化映射前,冻结当前映射、评估行与前后记录。把恢复的 test-05 表达和至少一个可能回归案例加入下一回归集。之后只提出一条映射或数据变化,重跑比较,并解释每个变化的转变。这种小纪律防止标准化变成无法追踪的文本编辑集合。

设计安全的后续实验

下一轮应回答当前比较不能回答的一个问题。编辑映射前先选择问题。

问题 固定证据 允许的一项变化 预期观察
取消规则能泛化吗? 当前训练行和回归行。 向评估增加新取消措辞。 统计正确、错误与未决定转变。
缺陷同义词安全吗? 当前映射,除被测条目外。 加入一个含 하자 的语境。 检查标准化路由是否仍合适。
剩余 OOV 词重要吗? 当前分词与分数。 加入一个重复的陌生词。 比较覆盖与状态,而不只看覆盖。
新数据是否优于规则? 相同评估行。 加入一条有文档的训练例。 映射不变地重新运行。

“只允许一项变化”不是官僚要求。它让复核者能把变化结果连接到候选原因。若映射与训练数据同时改变,恢复再也不能归给任一干预。

正确解释无变化

规则没有带来可见指标变化可能有多种原因:表达不在评估集、它已被另一个已知词表示,或它与类别分数无关。记录规则与无变化结果;不要因为它没有提升展示的准确率就从实验历史删除。

同样,不要仅因规则改变一个数字就保留它。没有可识别输入含义的微小覆盖提升可能不值得维护负担。项目应定义谁拥有映射、条目何时过期或复核,以及哪个回归集保护路由行为。

比较源文本与模型文本

标准化改变模型输入,不一定改变支持人员或审计者应看到的文本。两种形式都要保留。

字段 示例 为什么保留
源文本 캔슬 후 송장 남아 있어요 客户实际措辞的证据。
模型文本 취소 후 송장 남아 있어요 使应用的表示明确。
规则 ID cancel_synonym_v1 让另一位复核者找到变换。
预测记录 配送 → 退款 记录操作后果。

映射随时间维护时,这种区分尤其重要。后来的复核者即使不同意规则,也能在保留原句和准确规则版本时复现旧预测。

给复核者的问题

  1. 改变了哪个准确表达,发生在什么词元边界?
  2. 哪条固定评估行恢复、回归或保持未解决?
  3. 结果是预测、错误还是未决定平局?
  4. 哪些证据使规则成为优先,而不是后续清理?
  5. 哪次下一实验会推翻当前解释?

若记录不能单独回答这些问题,标准化结果还不能指导下一轮。

保留当前映射版本与比较输出。 每个独立场景中保持原始源行不变。 将恢复和回归样本保留到下一回归集。 使因果解释比观察到的转变更窄。

完成检查

  • 能说明 캔슬 → 취소 恢复的是哪一条行和哪一种路由。
  • 能说明 하자 → 불량스케줄 → 일정 提高覆盖却没有修复原错误。
  • 能说明未决定状态为何不能被悄悄当作类别零。
  • 能保存规则范围、原文本、模型文本、覆盖、分数和状态。
  • 能在覆盖提升造成正确行回归时选择撤销而不是保留规则。
  • 能把下一次实验限制为一条映射或一项数据变化。

标准化是实验变量。 保留原行。 固定评估集。 逐项比较。 不要从一项恢复外推所有表达。

最终记录模板

运行日期:
映射版本:
固定训练与评估版本:
唯一改变的规则:
主要错误的前后状态:
预防案例的前后状态:
新错误或未决定案例:
覆盖和 OOV 转变:
保留或撤销决定:
有限结论:
下一问题:

模板的目的不是制造更多文书。 它确保规则、样本和判断可以在下一次运行中重新找到。

记录规则。 记录边界。 记录转变。 记录限制。

来源与参考

来源与参考

本节使用本书创建的支持路由练习数据与标准化比较。