Chapter 20: 附录 B:OpenCC、脚本映射与正则工具箱(深度实战版)
20.1 开篇段落
在 ASR(自动语音识别)和 Speaker Diarization(说话人日志)的训练与评测中,文本处理的“卫生状况”往往决定了模型的上限。数据清洗不仅仅是“去掉空格”那么简单,它涉及字符编码的底层原理、语言学的正字法(Orthography)规范以及多语种混合时的冲突治理。
为什么这一章至关重要?
- Tokenizer 爆炸:未规范化的文本会导致 BPE/SentencePiece 词表充斥着意义相同的冗余 Token(如全角/半角数字、异体字),稀释了每个 Token 的训练数据量。
- 评测虚高/虚低:如果 Ground Truth 是“OK”,而模型输出“okay”,在未做归一化的情况下会被判错,导致 WER 无法反映真实听感效果。
- 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 之前,执行以下逻辑:
- 扫描:利用 AC 自动机或正则,匹配白名单词汇(如专有名词表)。
- 替换:将命中词汇替换为唯一哈希值或特殊 Token(如
__PROTECTED_001__)。 - 转换:运行 OpenCC。
- 还原:将特殊 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): 中文gǔ, 日文kotsu/hone(字形写法在不同字体下甚至不同)。 -
对 ASR 的影响:如果训练数据混合了中日文且未加区分,Shared Encoder 会在该 Token 上看到两组完全不同的声学分布(Acoustic Distribution)。结果是模型在解码时出现“犹豫”,导致置信度降低或输出乱码。
20.3.2 解决方案:LID Token 与 脚本提示
-
LID (Language ID) Prefix: * 数据格式:
<|zh|> 今天天气不错vs<|ja|> 今日はいい天気だ。 * 原理:利用 Transformer 的 Attention 机制,将语种信息注入到整个序列的上下文中,使模型针对同一个直字根据 Prefix 激活不同的声学特征提取路径。 -
Prompt 约束 (针对 MLLM): * 在 Instruction Tuning 阶段,明确要求输出脚本。 * Prompt:
Transcribe the audio into Japanese script (Kanji/Kana).* 这能抑制模型输出简体中文特有汉字的倾向。
20.3.3 日文假名 (Kana) 的特殊清洗
日语书写系统极其复杂,以下清理步骤是必须的:
-
半角片假名 (Half-width Katakana) → 全角: *
ア(U+FF71) →ア(U+30A2)。 * 原因:半角假名在 Tokenizer 中往往被切分得很碎,且在视觉上不美观。 * 正则范围:[\uFF61-\uFF9F](半角假名区域)。 -
浊音与半浊音的 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)。 -
长音符标准化: * 日语长音符
ー(U+30FC) 极易与中文破折号—(U+2014) 或 英文连字符-(U+002D) 混淆。 * 清洗逻辑:如果文本被标记为日语,且出现非U+30FC的横线符号,需根据上下文判断是否替换为长音符。
20.4 粤语 (Cantonese) 的“三态”混写治理
粤语 ASR 的难点在于“写什么”没有绝对标准。
20.4.1 文本的三种形态
- 书面语 (SW): “他在这里” —— 读音可以是普通话,也可以是粤语读文(Reading pronunciation)。
- 口语汉字 (Spoken Hanzi): “佢喺度” —— 记录真实的粤语口语词汇。
- 粤拼/火星文混合: “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 将 Space 和 Double 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)
通常遵循 Kaldi 或 Whisper 的归一化标准:
- Lowercasing: 转小写。
-
Punctuation Removal: 移除除
'(Apostrophe) 以外的所有标点。 * 注:don't保留为don't还是拆分为dont或do not,需看 Tokenizer 训练时的决定。通常保留'。 -
Number Expansion (ITN): 将
20转为twenty(推荐) 或反之。必须统一。
20.6.2 中文评测标准 (Mandarin Protocol)
- 空格移除: 中文无空格分词。
我 爱 你→我爱你。 - 全角转半角: 尤其针对英文和数字部分。
- 繁转简: 绝大多数 Benchmarks(如 AISHELL)是简体的。如果模型输出繁体,必须转简后再评测。
- 多音字容错 (高级):
* 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)
-
BOM 头噩梦: * Windows 记事本保存的 UTF-8 文件开头可能有
\ufeff(Byte Order Mark)。 * Python 读取时如果不指定encoding='utf-8-sig',这会变成一个不可见字符ZWNBSP,导致该文件第一句话的对齐全部错位。 -
OpenCC 的副作用: * 将“干货”转繁体时,可能变成“乾貨”(Dry goods)或“幹貨”(Doing stuff),取决于上下文。
s2twp通常能处理得比较好,但仍非 100% 准确。 * 调试:抽样检查高频多义字(干、发、面、斗)的转换结果。 -
JSONL 读取错误: * 如果在 JSON 字符串内部包含未转义的控制符(如换行符
\n),标准 JSON Parser 会报错。 * Fix: 使用json.loads(line, strict=False)或在写入前做严格的字符串清洗。
20.8 习题与实战
基础题
- OpenCC 选型:如果你要利用大量来自香港的新闻语来训练一个通用的简体中文 ASR 模型,你应该使用哪个 OpenCC 配置文件?会对专有名词产生什么潜在影响?
- 正则实战:写出一个正则逻辑,能够清洗掉字符串中所有的
(鼓掌)、[笑声]等标注,但保留(IBM)、[NASA]这种包含英文大写的有效内容。 - Unicode:为什么在 macOS 上处理完的日语文本,传到 Linux 服务器上训练时,Tokenizer 可能会对“が”这个字产生不同的切分结果?
挑战题
- 粤语标准化:设计一个决策树,用于处理包含
"佢好happy既啫"这句话的数据。要求保留英文,统一粤语常用字,并去除无实义的语气助词(假设目标是生成简洁字幕)。 - 混语评测:Ref 是
"Project A 的进度是 50%",Hyp 是"project a 的进度是百分之五十"。请设计一个归一化流程,使得 CER 计算结果合理(趋近于 0)。
点击展开参考答案
- OpenCC:应使用
hk2s.json。影响:香港特有的翻译名词(如“碧咸”->“贝克汉姆”通常不会自动发生,除非词典包含此映射)。如果不转换,“碧咸”会被当作普通词汇训练,导致在大陆语境下识别出 OOV。 - 正则:
re.sub(r'[\[\(](?![A-Z0-9]+[\]\)])[\u4e00-\u9fa5]+[\]\)]', '', text)(思路:负向先行断言,排除全大写/数字的内容)。 - Unicode:macOS 倾向于使用 NFD(分解形式,
ka+dakuten),Linux/Windows 倾向于 NFC。如果 Tokenizer 是在 NFC 数据上训练的,遇到 NFD 的ka+dakuten会将其视为两个未知或独立的字符,导致 OOV 或对齐错误。 -
粤语: * Step 1: 英文保留 -> "happy" * Step 2: 正字法 -> "既" 修正为 "嘅" (若视为语气词则在下一步删) * Step 3: 语气词过滤 -> 识别 "既(嘅)" 和 "啫" 为语气助词,删除。 * 结果 -> "佢好 happy"
-
混语评测: * Step 1 (Lower):
Project A->project a* Step 2 (ITN):50%->百分之五十(或者反向:百分之五十->50%)。关键是统一。 * Step 3 (Space): 移除中文字间空格,保留英文周边空格。 * 结果:Ref 与 Hyp 字符序列一致。