Chapter 20: 附录 B:OpenCC、脚本映射与正则工具箱(深度实战版)

20.1 开篇段落

在 ASR(自动语音识别)和 Speaker Diarization(说话人日志)的训练与评测中,文本处理的“卫生状况”往往决定了模型的上限。数据清洗不仅仅是“去掉空格”那么简单,它涉及字符编码的底层原理、语言学的正字法(Orthography)规范以及多语种混合时的冲突治理。

为什么这一章至关重要?

  1. Tokenizer 爆炸:未规范化的文本会导致 BPE/SentencePiece 词表充斥着意义相同的冗余 Token(如全角/半角数字、异体字),稀释了每个 Token 的训练数据量。
  2. 评测虚高/虚低:如果 Ground Truth 是“OK”,而模型输出“okay”,在未做归一化的情况下会被判错,导致 WER 无法反映真实听感效果。
  3. MLLM 的幻觉诱因:对于多模态大模型,不一致的文本输入(如混用的繁简字)会作为一种“隐性提示”,诱导模型输出错误的方言词汇或产生幻觉。

本章将提供一套工业级的处理标准,涵盖 OpenCC 的高级用法、CJK(中日韩)脚本冲突的深度治理、粤语混写的标准化方案,以及 Unicode 字符层的“排雷”指南。


20.2 OpenCC 进阶配置与字典干预

OpenCC(Open Chinese Convert)是处理中文繁简转换的事实标准。在 ASR 领域,我们不仅要关注“字”的转换,更要关注“词”与“义”的对齐。

20.2.1 配置文件深度解析与选型

OpenCC 的配置文件本质上是多个词典(Dictionary)与转换链(Segmentation)的组合。

| 配置文件 (.json) | 转换逻辑 | ASR 场景适用性 | 深度解析 |

配置文件 (.json) 转换逻辑 ASR 场景适用性 深度解析
s2t.json 简 → 繁 (字符) 极低 仅做 Codepoint 映射。会将“后天”转为“后天”(正确应为“後天”),“皇后”转为“皇後”(正确应为“皇后”)。严禁在 ASR 语料处理中使用,否则会制造大量语义噪音。
s2twp.json 简 → 台湾正体 (含短语) 推荐 (台湾场景) 包含“短语映射”。如:计算机電腦出租车計程車。这对于训练面向台湾口音的 ASR 至关重要,因为 LM 需要学习当地的用词概率。
t2s.json 繁 → 简 (字符) 中 (通用场景) 适合将繁体语料并入大规模简体训练集。注意:它会将“電腦”转回“电脑”,这在做混合语种训练时是可接受的。
tw2sp.json 台湾正体 → 简 (含短语) 高 (回译增强) 当你需要利用台湾语料(如 PTT 语音、台剧)来增强陆普通话模型时,此配置能将台湾特有词汇“翻译”为大陆习惯,减少 OOV。
hk2s.json 香港繁体 → 简 中 (粤语辅助) 处理香港特有汉字(如“裏”转“里”,“羣”转“群”)。

20.2.2 “防误伤”机制:自定义词典与白名单

OpenCC 的短语转换有时会“过度纠正”,特别是涉及人名、地名或专有名词时。

  • 问题案例
  • 人名“周杰伦”在 s2twp 下通常正常,但某些生僻人名或品牌名可能会被错误地“本地化”。
  • 例如:某品牌名包含“内存”,在转台湾正体时变成了“記憶體”,但该品牌本身就是叫“XX内存”。

  • 解决方案:占位符保护法 (Placeholder Protection) 在调用 OpenCC 之前,执行以下逻辑:

  1. 扫描:利用 AC 自动机或正则,匹配白名单词汇(如专有名词表)。
  2. 替换:将命中词汇替换为唯一哈希值或特殊 Token(如 __PROTECTED_001__)。
  3. 转换:运行 OpenCC。
  4. 还原:将特殊 Token 替换回原文。

20.2.3 异体字标准化的“幂等性”

在数据清洗流水线中,幂等性(Idempotency) 是关键——即处理一次和处理十次,结果应该一样。

  • 常见陷阱:繁体字内部存在大量异体字(如“真”与“眞”,“为”与“爲”)。
  • 最佳实践:在所有繁体数据进入训练前,强制运行一次 t2t.json(繁体到繁体标准字)。OpenCC 默认倾向于将异体字归一化为通用字形,这能显著降低 Character-level 的词表大小(Vocab Size),提升模型收敛速度。

20.3 CJK 脚本冲突与字符集治理 (中日韩混同问题)

ASR 模型最怕的就是“看着一样,读音不同”。Unicode 的 Han Unification (汉字统合) 给多语种 ASR 带来了巨大挑战。

20.3.1 汉字统合灾难 (Han Unification)

Unicode 将中文、日文、韩文中起源相同、字形相似的字符映射到了一个码位(Code Point)。

  • 典型冲突
  • (U+76F4): 中文 zhí, 日文 choku/nao
  • (U+9AA8): 中文 , 日文 kotsu/hone (字形写法在不同字体下甚至不同)。

  • 对 ASR 的影响:如果训练数据混合了中日文且未加区分,Shared Encoder 会在该 Token 上看到两组完全不同的声学分布(Acoustic Distribution)。结果是模型在解码时出现“犹豫”,导致置信度降低或输出乱码。

20.3.2 解决方案:LID Token 与 脚本提示

  1. LID (Language ID) Prefix: * 数据格式:<|zh|> 今天天气不错 vs <|ja|> 今日はいい天気だ。 * 原理:利用 Transformer 的 Attention 机制,将语种信息注入到整个序列的上下文中,使模型针对同一个 字根据 Prefix 激活不同的声学特征提取路径。

  2. Prompt 约束 (针对 MLLM): * 在 Instruction Tuning 阶段,明确要求输出脚本。 * Prompt: Transcribe the audio into Japanese script (Kanji/Kana). * 这能抑制模型输出简体中文特有汉字的倾向。

20.3.3 日文假名 (Kana) 的特殊清洗

日语书写系统极其复杂,以下清理步骤是必须的:

  1. 半角片假名 (Half-width Katakana) → 全角: * (U+FF71) → (U+30A2)。 * 原因:半角假名在 Tokenizer 中往往被切分得很碎,且在视觉上不美观。 * 正则范围[\uFF61-\uFF9F] (半角假名区域)。

  2. 浊音与半浊音的 NFD 问题 (macOS/Linux 差异): * NFC (Precomposed): (U+304C) - 推荐。 * NFD (Decomposed): (U+304B) + (Combining Dakuten U+3099)。 * 陷阱:macOS 的文件系统倾向于使用 NFD。如果你的数据是在 Mac 上解压或处理的,可能会得到 NFD 形式。这会导致 Tokenizer 将 切分为两个 Token,严重破坏对齐。 * Rule-of-Thumb:所有文本入库前,强制执行 unicodedata.normalize('NFKC', text)

  3. 长音符标准化: * 日语长音符 (U+30FC) 极易与中文破折号 (U+2014) 或 英文连字符 - (U+002D) 混淆。 * 清洗逻辑:如果文本被标记为日语,且出现非 U+30FC 的横线符号,需根据上下文判断是否替换为长音符。


20.4 粤语 (Cantonese) 的“三态”混写治理

粤语 ASR 的难点在于“写什么”没有绝对标准。

20.4.1 文本的三种形态

  1. 书面语 (SW): “他在这里” —— 读音可以是普通话,也可以是粤语读文(Reading pronunciation)。
  2. 口语汉字 (Spoken Hanzi): “佢喺度” —— 记录真实的粤语口语词汇。
  3. 粤拼/火星文混合: “keoi hai dou”, “佢hai度”。

20.4.2 混写治理流水线

针对训练数据参差不齐的情况,建议采取以下标准化步骤:

  • Step 1: 英文/粤拼隔离
  • 检测句子中的拉丁字母。如果是有效的英文单词(查词典),保留(Code-switching)。
  • 如果是无意义串或类粤拼,尝试通过 WFST 或查表法转回汉字(慎用,错误率高)。如果无法恢复,建议丢弃该句。

  • Step 2: 粤语特有字标准化 (Orthography) 粤语有很多同音异字写法,需强制统一:

  • / → 区分用法: (Possessive/Particle, 的), (Since/Already)。若无法区分,ASR 训练通常统一为 (因为更高频)。

  • D / → 统一为
  • / → 复数人称统一为 (如:我哋)。

  • Step 3: 语气助词 (Final Particles) 处理

  • 粤语包含大量语气词:la1, wo3, ge3, zek1。
  • 策略:如果目标是字幕输出,通常保留对应的汉字(啦、沃、葛、杰)。如果目标是语义理解,有时会训练模型输出特殊 Tag <|particle|> 或直接忽略,以减少 ASR 的插入错误(Insertion Error)。

20.5 Unicode 清洗与正则工具箱 (Regex Toolkit)

这是数据工程师的“瑞士军刀”。

20.5.1 Unicode Normalization Forms (NFC vs NFD)

这是最容易被忽视的 Bug 头。

  • NFC (Normalization Form Composition): 字符尽可能合并。e + ´é(ASR 标准)
  • NFD (Normalization Form Decomposition): 字符尽可能拆分。ée + ´
  • NFKC: 在 NFC 基础上,处理兼容字符(全角变半角,4)。

Gotcha: 某些开源数据集(特别是来自老旧系统或 Mac 处理过的)可能是 NFD 格式。如果不转 NFKC,你的词表里会出现裸露的音标符号,导致训练发散。

20.5.2 常用正则逻辑表 (Regex Recipes)

以下正则逻辑假设环境为 Python re 模块。

| 目标场景 | 正则逻辑描述 | 示例与说明 |

目标场景 正则逻辑描述 示例与说明
时间戳移除 匹配 \[\d{2}:\d{2}(\.\d{2,3})?\]<.*?> 字幕文件常见 [00:12.50] 或 XML 标签,必须彻底清除。
不可见字符 匹配 [\x00-\x1f\x7f\u200b-\u200f\u2028-\u202f] 包含 ASCII 控制符、零宽空格 (ZWSP)、行分隔符等。ASR 杀手,必须删。
括号内容清洗 匹配 \(.*?\)\(.*?\) 小心使用。如果是 (笑声) 可以删;如果是 (IBM) 则包含语义。建议改为:仅删除包含特定关键词(笑、掌声、背景音)的括号。
多余标点 匹配 [^\w\s\u4e00-\u9fa5] (反向逻辑) 仅保留字、数、空白。用于构建纯文本语料。
重复标点 匹配 ([。,!?])\1+ 替换为 \1 真的吗??!!!真的吗?! (或标准化为单个)。
连续空格压缩 匹配 \s+ 替换为 (Space) 防止 Tokenizer 将 SpaceDouble Space 视为不同 Token。

20.5.3 脚本映射示例 (Python Pseudocode)

处理中日同形字的简单映射逻辑:

def script_aware_normalization(text, lang):
    # 1. Unicode Normalize first
    text = unicodedata.normalize('NFKC', text)

    # 2. Language specific rules
    if lang == 'ja':
        # 强制长音符标准化
        text = text.replace('—', 'ー').replace('-', 'ー')
        # 全角英数转半角 (NFKC已做,但可加固)
        # 假名标准化...
    elif lang == 'zh':
        # 移除日文特有假名 (防止标注错误混入)
        if re.search(r'[\u3040-\u30ff]', text):
            logging.warning(f"Japanese Kana found in Chinese text: {text}")
            # 策略:丢弃样本 或 视为垃圾字符删除

    # 3. Whitespace cleanup
    text = re.sub(r'\s+', ' ', text).strip()
    return text

20.6 评测前统一化 (Normalization for Evaluation)

为了计算 WER/CER,必须制定一套不可动摇的规则(Protocol)。

20.6.1 英文评测标准 (English Protocol)

通常遵循 KaldiWhisper 的归一化标准:

  1. Lowercasing: 转小写。
  2. Punctuation Removal: 移除除 ' (Apostrophe) 以外的所有标点。 * don't 保留为 don't 还是拆分为 dontdo not,需看 Tokenizer 训练时的决定。通常保留 '

  3. Number Expansion (ITN): 将 20 转为 twenty (推荐) 或反之。必须统一

20.6.2 中文评测标准 (Mandarin Protocol)

  1. 空格移除: 中文无空格分词。我 爱 你我爱你
  2. 全角转半角: 尤其针对英文和数字部分。
  3. 繁转简: 绝大多数 Benchmarks(如 AISHELL)是简体的。如果模型输出繁体,必须转简后再评测。
  4. 多音字容错 (高级): * Ref: 了解 (liao3 jie3) * Hyp: 了结 (liao3 jie2) * 这是错误。 * Ref: 什么 * Hyp: 甚么 * 如果在定义上视为同义词,可在评测脚本中加入同义词映射表,但在严格学术评测中通常不算对

20.6.3 混合语种 (Code-Switching) 评测

这是一个难点。

  • Tokenization: 英文按词(Word),中文按字(Char)。
  • 操作
  • Ref: 我 love 你
  • Hyp: 我love你
  • 预处理:在汉字与英文之间插入空格。
  • Ref → 我 love 你 (Tokens: 我, love, 你)
  • Hyp → 我 love 你 (Tokens: 我, love, 你)
  • 这样计算 MER (Mixed Error Rate) 才准确。

20.7 常见陷阱与调试技巧 (Gotchas)

  1. BOM 头噩梦: * Windows 记事本保存的 UTF-8 文件开头可能有 \ufeff (Byte Order Mark)。 * Python 读取时如果不指定 encoding='utf-8-sig',这会变成一个不可见字符 ZWNBSP,导致该文件第一句话的对齐全部错位。

  2. OpenCC 的副作用: * 将“干货”转繁体时,可能变成“乾貨”(Dry goods)或“幹貨”(Doing stuff),取决于上下文。s2twp 通常能处理得比较好,但仍非 100% 准确。 * 调试:抽样检查高频多义字(干、发、面、斗)的转换结果。

  3. JSONL 读取错误: * 如果在 JSON 字符串内部包含未转义的控制符(如换行符 \n),标准 JSON Parser 会报错。 * Fix: 使用 json.loads(line, strict=False) 或在写入前做严格的字符串清洗。


20.8 习题与实战

基础题

  1. OpenCC 选型:如果你要利用大量来自香港的新闻语来训练一个通用的简体中文 ASR 模型,你应该使用哪个 OpenCC 配置文件?会对专有名词产生什么潜在影响?
  2. 正则实战:写出一个正则逻辑,能够清洗掉字符串中所有的 (鼓掌)[笑声] 等标注,但保留 (IBM)[NASA] 这种包含英文大写的有效内容。
  3. Unicode:为什么在 macOS 上处理完的日语文本,传到 Linux 服务器上训练时,Tokenizer 可能会对“が”这个字产生不同的切分结果?

挑战题

  1. 粤语标准化:设计一个决策树,用于处理包含 "佢好happy既啫" 这句话的数据。要求保留英文,统一粤语常用字,并去除无实义的语气助词(假设目标是生成简洁字幕)。
  2. 混语评测:Ref 是 "Project A 的进度是 50%",Hyp 是 "project a 的进度是百分之五十"。请设计一个归一化流程,使得 CER 计算结果合理(趋近于 0)。
点击展开参考答案
  1. OpenCC:应使用 hk2s.json。影响:香港特有的翻译名词(如“碧咸”->“贝克汉姆”通常不会自动发生,除非词典包含此映射)。如果不转换,“碧咸”会被当作普通词汇训练,导致在大陆语境下识别出 OOV。
  2. 正则re.sub(r'[\[\(](?![A-Z0-9]+[\]\)])[\u4e00-\u9fa5]+[\]\)]', '', text) (思路:负向先行断言,排除全大写/数字的内容)。
  3. Unicode:macOS 倾向于使用 NFD(分解形式,ka+dakuten),Linux/Windows 倾向于 NFC。如果 Tokenizer 是在 NFC 数据上训练的,遇到 NFD 的 ka+dakuten 会将其视为两个未知或独立的字符,导致 OOV 或对齐错误。
  4. 粤语: * Step 1: 英文保留 -> "happy" * Step 2: 正字法 -> "既" 修正为 "嘅" (若视为语气词则在下一步删) * Step 3: 语气词过滤 -> 识别 "既(嘅)" 和 "啫" 为语气助词,删除。 * 结果 -> "佢好 happy"

  5. 混语评测: * Step 1 (Lower): Project A -> project a * Step 2 (ITN): 50% -> 百分之五十 (或者反向:百分之五十 -> 50%)。关键是统一。 * Step 3 (Space): 移除中文字间空格,保留英文周边空格。 * 结果:Ref 与 Hyp 字符序列一致。