一文搞懂WeTextProcessing:让语音与文本处理项目告别数字乱码的归一化利器
一文搞懂WeTextProcessing:让语音与文本处理项目告别数字乱码的归一化利器
【免费下载链接】WeTextProcessingText Normalization & Inverse Text Normalization项目地址: https://gitcode.com/gh_mirrors/we/WeTextProcessing
一个让人头疼的场景
想象一下:你的语音识别系统刚上线,用户说了一句"下午三点二十分开会,花费二百九十九块九",结果系统输出的文字既不是标准数字,也不是规整的时间格式;反过来,TTS语音合成前,你丢给它一段"会议定于2023-12-25 14:30召开",它却把"2023-12-25"读得磕磕绊绊。数字、日期、货币、单位这些"非标准文本",在语音与文本系统之间来回转换时,总是容易翻车。
WeTextProcessing就是来解决这个问题的——一个专注文本归一化(Text Normalization,TN)与逆文本归一化(Inverse Text Normalization,ITN)的开源工具包,让你在不同格式之间一键切换,彻底告别乱码和误读。
它到底是什么,适合谁用
简单来说,WeTextProcessing做两件事:归一化是把"123"变成"一百二十三",逆归一化是把"一百二十三"变回"123"。它底层基于OpenFST和Pynini构建的有限状态转换器(FST)规则引擎,天然具备确定性高、处理速度快、规则可组合三大优点。
如果你在搞语音识别(ASR)后处理、语音合成(TTS)前处理、文本数据清洗,或者需要中英日多语言文本标准化,这个项目就是为你准备的。它最大的特点是"生产就绪"——不是实验室玩具,而是经历过真实业务打磨的成熟方案。
核心能力速览
| 能力 | 说明 |
|---|---|
| 🌍 多语言覆盖 | 内置中文、英文、日文三套独立规则引擎,互不干扰 |
| 🔄 双向转换 | TN(文本→可读语音文本)与 ITN(语音文本→标准格式)两条流水线 |
| 🧩 规则全覆盖 | 数字、分数、百分比、日期、时间、货币、度量衡、数学符号、电话号码、儿化音、白名单替换等 |
| ⚡ FST 高性能 | 规则编译成有限状态转换器,匹配速度接近 O(n) |
| 🧠 智能缓存 | 图缓存按内容寻址,规则或配置变化时自动重建,无需手工清理 |
| 📍 映射溯源 | 支持获取输入输出之间精确的字符跨度映射,知道每一处改动来自哪条规则 |
| 🔧 双形态部署 | 既提供 Python 接口,也提供 C++ 运行时,适合高性能场景 |
从零到跑通:10分钟上手
第一步:安装
最简单的途径是直接通过 pip 安装:
pip install WeTextProcessing如果你打算自定义规则、修复 badcase,建议克隆源码仓库:
git clone https://gitcode.com/gh_mirrors/we/WeTextProcessing cd WeTextProcessing pip install -r requirements.txt第二步:命令行快速体验
装好后,两个命令即可上手:
# 文本归一化:把数字转成中文读法 wetn --text "2.5平方电线" # 输出:二点五平方电线 # 逆文本归一化:把中文读法还原成数字 weitn --text "二点五平方电线" # 输出:2.5平方电线第三步:Python 调用
在代码里使用同样简单,以中文为例:
from tn.chinese.normalizer import Normalizer from itn.chinese.inverse_normalizer import InverseNormalizer # 归一化:数字、符号 → 可朗读文本 zh_tn_model = Normalizer(remove_erhua=True) text = "2023年12月25日花费¥299.99购买了5kg苹果" print(zh_tn_model.normalize(text)) # 二零二三年十二月二十五日花费二百九十九点九九元购买了五千克苹果 # 逆归一化:朗读文本 → 标准书写格式 zh_itn_model = InverseNormalizer(enable_0_to_9=False) text = "下午三点二十分开会" print(zh_itn_model.normalize(text)) # 15:20开会✅ 到这里,你已经能处理日常 90% 的转换需求了。
进阶玩法与技巧
技巧一:按需配置参数,让行为更贴合业务
中文归一化器提供了丰富的开关:
normalizer = Normalizer( remove_erhua=True, # 是否去除儿化音("这地儿"→"这地") traditional_to_simple=True, # 繁体转简体 remove_puncts=False, # 是否移除标点 full_to_half=True, # 全角转半角("IPHONE"→"IPHONE") tag_oov=False, # 是否标记未登录词 )逆归一化器侧同样灵活,比如控制"个位数是否单独转换":
# 小于10的单独数字不转换:"幸运一百"保持中文,而不是变成"幸运100" inv = InverseNormalizer(enable_0_to_9=False) # 默认情况下,"一百"这类独立数字会转成100 inv2 = InverseNormalizer(enable_0_to_9=True)这里有个小技巧:enable_standalone_number控制是否把句中独立出现的数字读法转成阿拉伯数字,exclue_one则决定"一年后"要不要变成"1年后"。业务上不确定时,先在测试数据上跑一遍再决定。
技巧二:用映射功能追查每一处改动
当你想知道"12"为什么被转成了"十二",以及改动发生在哪个位置时,用normalize_with_mapping:
result = zh_tn_model.normalize_with_mapping("今天中午12点") print(result.output_text) # 今天中午十二点 for mapping in result.mappings: print(mapping.token_type, mapping.input_text, "=>", mapping.output_text) # math 12 => 十二返回结果里不仅包含替换前后的文本,还带上了 Unicode 字符偏移量(半开区间),以及产出这次改动的规则类型。对排查 badcase 来说,这比"肉眼比对原文和结果"高效得多。注意:偏移量是 Python Unicode 字符偏移,不是 UTF-8 字节偏移。
技巧三:利用 n-best 输出兜底
两条 API 都支持联合 tagger/verbalizer 的 n-best 输出:
outputs = zh_tn_model.normalize("输入文本", nbest=3) # nbest=1 时返回字符串,nbest>1 时返回字符串列表当主路径结果不满意时,可以从候选中挑选更合适的表达。
技巧四:缓存机制的正确打开方式
图编译是比较重的一次性操作,WeTextProcessing 内置了内容寻址缓存:
- 缓存键包含全部图配置、Python 语法源码、TSV/FAR 资源与构建格式信息,规则一改,缓存自动失效重建,日常使用完全不用管;
- 只有想强制重建时才传
overwrite_cache=True; - 缓存默认写在系统用户缓存目录(如 Linux 下的
~/.cache/wetextprocessing),不会污染源码树; - 也可以用
cache_dir="/path/to/cache-root"指定缓存根目录,或传cache_dir=False完全走内存构建。
生产环境建议:预编译好规则图,关闭overwrite_cache,把编译结果分发到各服务节点复用。
技巧五:自定义规则修复 badcase
规则文件全部是 Python 源码,放在tn/chinese/rules/(归一化)和itn/chinese/rules/(逆归一化)目录下,数据词表则在对应语言的data/目录中(TSV 格式)。
改规则前,务必先读一遍 Python 规则架构文档。它的核心约定是:tagger 只负责分类并原样保留输入字段,真正的语义转换交给 verbalizer 完成。这条约定保证了输入输出跨度映射的精确性。
改完规则后,用--overwrite_cache强制重建图即可验证效果:
python -m tn --text "2.5平方电线" --overwrite_cache python -m itn --text "二点五平方电线" --overwrite_cache命令行还支持--file PATH读取文件,或直接从标准输入读文本;每行输入会输出"标注结果 + 最终结果"两行。
技巧六:需要极致性能时上 C++ 运行时
runtime/目录下是一套完整的 C++ 实现,适合对延迟敏感的线上服务:
cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build ./build/processor_main --tagger zh_tn_tagger.fst --verbalizer zh_tn_verbalizer.fst --text "2.5平方电线"需要注意:Python 的缓存包是内部格式,不要直接把里面的路径传给processor_main;C++ 运行时需要的是单独导出的、配套的 tagger/verbalizer 文件对。
常见问题与避坑
Q1:全角半角、繁简、儿化音这些为什么"没生效"?这类字符级转换不在 tagger 里做,而是由 verbalizer/postprocessor 负责。这是有意设计——让 tagger 尽量保持输入原样,才能保证normalize_with_mapping()的映射真实可信。想改这类行为,看data/char/fullwidth_to_halfwidth.tsv、data/char/traditional_to_simple.tsv、data/erhua/whitelist.tsv这些词表即可。
Q2:为什么某些独立出现的数字没有被转换?多半是参数问题。中文 ITN 里enable_0_to_9、enable_standalone_number、enable_million三个开关共同决定数字的转换范围;度量衡场景还有exclue_one控制"一"是否参与转换。按业务需求组合即可。
Q3:改了规则,但结果没变化?先确认是否真的重建了图。缓存键包含规则源码指纹,正常会自动失效,但如果你手动复制过缓存目录或修改了系统时间,可以显式传overwrite_cache=True强制重建。
Q4:映射结果和预期对不上?映射是沿着 tagger 和 verbalizer 的 WFST 路径追踪出来的,没有表层文本 diff 兜底。未被任何规则捕获的文本不会产生映射条目;想看到"保持原样"的 token,可以传include_identity=True。
Q5:Windows 下缓存目录里出现残留文件?Windows 上中断构建的残留物不会自动清理(因为没有等价的无跟随目录操作),但它们是无害的,等没有进程占用缓存时手动删除即可。
Q6:安装依赖失败?核心依赖是pynini>=2.1.6和importlib_resources,其中 pynini 需要本地 FST 工具链支持。装不上时优先确认编译环境是否完整。
总结与延伸
文本归一化看似是个小问题,却是语音产品体验的分水岭。WeTextProcessing 用 FST 规则引擎把这件事做得既快又稳:多语言覆盖、双向转换、精确映射、智能缓存,再加上 Python/C++ 双形态,从开发调试到生产部署一路畅通。
想继续深入,可以按这个路径走:
- 快速阅读
tn/README.md和itn/README.md,里面有完整的 TN/ITN 流水线说明和大量输入输出对照表; - 想动手改规则的,重点研读 docs/python-rule-architecture.md,并参考各语言
rules/目录下的既有实现; - 想贡献新语言的,可以对照
tn/japanese/这类已有模板,把数据词表和规则类补齐即可; - 关注底层原理的,可以研究 OpenFST 与 Pynini 的图组合、权重分配(
add_weight)与最短路径搜索,这些知识在排查复杂 badcase 时会非常有用。
项目本身还在持续演进,规则词表、语言覆盖和运行时能力都在不断更新。无论你是要做 ASR 后处理、TTS 前处理还是通用文本标准化,都可以先从wetn --text "2.5平方电线"这行命令开始,体验一把"文本瞬间变得会说话"的感觉。🚀
【免费下载链接】WeTextProcessingText Normalization & Inverse Text Normalization项目地址: https://gitcode.com/gh_mirrors/we/WeTextProcessing
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
