FunASR 中文文本正则化(Text Normalization)完整指南:从口语化数字到 TTS 前端文本
FunASR 中文文本正则化Text Normalization完整指南从口语化数字到 TTS 前端文本【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR导读本文以 FunASR 仓库中 fun_text_processing/text_normalization/zh/README.md 为骨架系统讲解中文文本正则化Text Normalization简称 TN模块的原理与实战用法。TN 是语音合成TTS与语音识别ASR训练数据清洗的关键前置环节其核心目标是把书面文本中的数字、日期、货币、度量衡等非标准词Non-Standard WordNSW转换为“读出来的样子”口语化形式例如共465篇→共四百六十五篇。读完本文你将掌握该模块的命令行与 Python API 调用方式、完整的 WFST 三段式流水线预处理 → NSW 分类 → 后处理设计以及如何通过数据表TSV定制白名单、黑名单与字符集让正则化结果完全贴合自己的业务场景。一、快速上手一行命令完成中文 TN1.1 命令行调用仓库提供了统一的 TN 入口脚本 fun_text_processing/text_normalization/normalize.py中文只需指定--language zhpython fun_text_processing/text_normalization/normalize.py --language zh --text text to be normalized该脚本支持以下命令行参数见 normalize.py 中的 parse_args参数说明默认值--text待正则化的单个字符串与--input_file互斥无--input_file待正则化的文本文件路径按行读取无--output_file批量模式的输出文件路径无--language语言可选en / de / es / zhen--input_case输入大小写可选lower_cased / casedcased--verbose打印中间标注结果便于调试关闭--punct_pre_process是否启用标点预处理如[25]→[ 25 ]关闭--punct_post_process是否启用标点后处理使输出标点与输入对齐关闭--overwrite_cache是否重新生成.far文法缓存文件关闭--whitelist自定义白名单文件路径无--cache_dir.far文法缓存目录设为None避免使用缓存无批量处理文件时脚本通过normalize_list()结合 joblib 的Parallel并行化n_jobs-1时使用全部 CPU并打印耗时信息python fun_text_processing/text_normalization/normalize.py \ --language zh \ --input_file data/list/train_text.txt \ --output_file data/list/train_text_tn.txt需要注意--text与--input_file二者必选其一否则脚本会抛出ValueError若输入文本超过 500 个 tokennormalize()会打印警告建议先用split_text_into_sentences()切句后再调用normalize_list()normalize.py。1.2 Python API 调用中文 TN 与其他语言共用Normalizer类语言路由逻辑在 normalize.py当lang zh时加载zh.taggers.tokenize_and_classify.ClassifyFst与zh.verbalizers.verbalize_final.VerbalizeFinalFst。示例from fun_text_processing.text_normalization.normalize import Normalizer normalizer Normalizer(input_casecased, langzh) text 苹果CEO宣布发布新iPhone价格是13.5 print(normalizer.normalize(text))normalize()内部执行的核心流程normalize.py用pynini.escape转义特殊字符与 tagger 的 FST 做text tagger.fst复合得到标注格tagged lattice用pynini.shortestpath取最短路径作为标注结果形如tokens { money { ... } }用TokenParser解析标注结果并按“最大排列数”切分成子序列_split_tokens_to_reduce_number_of_permutations默认上限 729控制组合爆炸对每个子序列生成字段排列generate_permutations与 verbalizer 的 FST 复合后取最短路径得到口语化输出合并多段输出并压缩连续空格若开启punct_post_process还会借助 Moses detokenizer 与post_process_punct()对齐输入输出标点位置。二、TN 流水线总览三段式 WFST 设计中文 TN 流水线由三个组件串联而成对应 zh/README.md 第 2 节预处理Pre-Processing位于 tagger 之前负责全角转半角、删除语气词等非标准词归一化NSW Normalization数字、分数、百分比、日期、时间、数学符号、货币、度量衡、号码串、儿化音、白名单等分类与口语化后处理Post-Processing位于 verbalizer 之后负责标点删除、大小写转换、OOV 标注等。从源码看这一设计与英文 NeMo TN 架构同源ClassifyFsttagger负责把文本标注为tokens { class { ... } }结构VerbalizeFinalFstverbalizer负责把标注结构转成口语文本二者最终编译为 OpenFst 有限状态存档FAR文件以便部署。中文实现位于Tagger 与各类分类文法fun_text_processing/text_normalization/zh/taggers/Verbalizer 与各类转写文法fun_text_processing/text_normalization/zh/verbalizers/全部可定制数据表TSVfun_text_processing/text_normalization/zh/data/Tagger 的总入口 tokenize_and_classify.py 把日期、分数、货币、度量衡、时间、白名单、基数词、数学符号、字符九类文法用pynini.union组合并对每类设置不同权重以控制歧义消解优先级权重越小越优先classify pynini.union( pynutil.add_weight(date.fst, 1.02), pynutil.add_weight(fraction.fst, 1.05), pynutil.add_weight(money.fst, 1.05), pynutil.add_weight(measure.fst, 1.05), pynutil.add_weight(time.fst, 1.05), pynutil.add_weight(whitelist.fst, 1.03), pynutil.add_weight(cardinal.fst, 1.06), pynutil.add_weight(math_symbol.fst, 1.08), pynutil.add_weight(char.fst, 100), )char.fst权重高达 100意味着普通汉字/字符是兜底分类只有在前八类文法都无法匹配时才按普通字符透传——这正是“非标准词优先、普通文本兜底”的工程化设计。三、预处理Pre-Processing预处理由 taggers/preprocessor.py 中的PreProcessor实现通过pynini.cdrewrite上下文无关替换在任意位置做两种替换。3.1 全角转半角Char Width Conversion把全角英文字母、数字、标点与符号统一转为半角避免后续文法因码位不同而失配苹果宣布发布新 - 苹果CEO宣布发布新IPHONE 他说“我们已经吃过了”。 - 他说:我们已经吃过了!.完整映射表位于 data/char/fullwidth_to_halfwidth.tsv共 92 行覆盖…等全角字符到半角的一一映射如→$、→0。源码中通过pynini.string_file加载该 TSV 并参与cdrewrite复合preprocessor.py。3.2 黑名单删除Denylist Removal某些语气词、填充词如“啊”“呃”在 TTS 场景中希望直接删除而非读出来呃这个呃啊我不知道 - 这个我不知道删除表位于 data/denylist/denylist.tsv默认仅含啊与呃两个词条可通过向该 TSV 追加行来扩展。源码在PreProcessor.__init__中通过pynutil.delete(pynini.string_file(...))构造删除文法preprocessor.py。四、非标准词NSW归一化这是 TN 的核心覆盖十二类常见书面符号。以下每个小类都对应 tagger 中的分类文法与 verbalizer 中的转写文法示例全部来自原 README 并可用命令行逐一验证。4.1 数字Numbers / Cardinal基数词由 taggers/cardinal.py 实现其 docstring 给出了核心转换逻辑5 - 五 12 - 十二 213 - 二百一十三 3123 - 三千一百二十三 51234 - 五万一千二百三十四 51,234 - 五万一千二百三十四 0.125 - 零点一二五十进制单位在 zh/utils.py 中定义为常量UNIT_1e01十、UNIT_1e02百、UNIT_1e03千、UNIT_1e04万。文法按“十/百/千/万”四级组合构造支持英文千分位逗号如51,234通过pynutil.delete(,)处理与小数.映射为“点”。官方示例共465篇约315万字 - 共四百六十五篇约三百一十五万字 共计6.42万人 - 共计六点四二万人 同比升高0.6个百分点 - 同比升高零点六个百分点注意源码注释明确说明该文法最多支持 8 位整数的基数词9 位以上建议带单位书写cardinal.py。4.2 分数Fraction分数文法在 taggers/fraction.py输入格式为分子/分母输出“几分之几”分子分母的口语化复用Cardinal文法总量的1/5以上 - 总量的五分之一以上 相当于头发丝的1/16 - 相当于头发丝的十六分之一 3/2是一个假分数 - 二分之三是一个假分数4.3 百分比Percentage百分比在 taggers/measure.py 中以percent_graph分支实现%被删除数值先经Cardinal口语化最终由 verbalizer 统一补上“百分之”前缀同比增长6.3% - 同比增长百分之六点三 增幅0.4% - 增幅百分之零点四4.4 日期Date日期文法在 taggers/date.py支持五种形态date_type0~date_type42012年、2002/01/28、12/01、2012/11、02/05分隔符/、-、.统一删除delete_date_sign。转写侧 verbalizers/date.py 把year/month/day字段转换为“年月日”口语2002/01/28 - 二零零二年一月二十八日 2002-01-28 - 二零零二年一月二十八日 2002.01.28 - 二零零二年一月二十八日 2002/01 - 二零零二年一月年份口语化按“逐位读”处理如2002→二零零二月份/日期按两位读数规则处理如28→二十八。从 date.py 的注释# add your date type as date_typex here.可以看出该文法设计上鼓励开发者按需扩展新的日期格式。4.5 时间Time时间文法在 taggers/time.py支持时:分、时:分:秒以及带am/pm后缀time_suffix来自 data/time/suffix.tsv。转写侧 verbalizers/time.py 负责插入“点/分/秒”等量词并处理00不读出或读“零”等边界8月16号12:00之前 - 八月十六号十二点之前 我是5:02开始的 - 我是五点零二分开始的 于5:35:36发射 - 于五点三十五分三十六秒发射 8:00am准时开会 - 上午八点准时开会4.6 数学符号Math数学比分与符号文法在 taggers/math_symbol.py其中比分用score分支实现78:96视为78 比 96符号映射表位于 data/math/symbol.tsv 与 data/math/score.tsv负号、正负号的口语化则落在Cardinal的graph_sign来自 data/number/sign.tsv比分定格在78:96 - 比分定格在七十八比九十六 计算-2的绝对值是2 - 计算负二的绝对值是二 ±2的平方都是4 - 正负二的平方都是四4.7 货币Money货币文法在 taggers/money.py货币代码/符号查表得到中文币种名金额走Cardinal().graph_cardinal输出结构为money { fractional_part: 元 integer_part: 十三点五 }价格是13.5 - 价格是十三点五元 价格是$13.5 - 价格是十三点五美元 价格是A$13.5 - 价格是十三点五澳元 价格是HKD13.5 - 价格是十三点五港元币种映射表 data/money/currency_code.tsv 覆盖全球 100 货币代码如USD→美元、CNY→人民币、HKD→港元、JPY→日元data/money/currency_symbol.tsv 则负责 $ €等符号A$、HK等区域性变体也被显式收录见 currency_code.tsv 末尾的A$→澳元、HK→港元。4.8 度量衡Measure度量衡文法在 taggers/measure.py中英文单位分别来自 data/measure/units_zh.tsv211 行中文单位如“匹、张、座、回、场、尾、条”等量词与物理单位与 data/measure/units_en.tsv英文单位映射如kg→千克、°C→摄氏度、m²→平方米、ms→毫秒重达25kg - 二十五千克 最高气温38°C - 最高气温三十八摄氏度 实际面积120m² - 实际面积一百二十平方米 渲染速度10ms一帧 - 渲染速度十毫秒一帧4.9 号码串Number Series电话号码、服务热线等电话号码等需要逐位朗读的场景由Cardinal中的graph_numstring分支支持cardinal.pygraph_numstring允许 1 位以上数字连续朗读并支持123.456形态.读作“点”可以打我手机13501234567 - 可以打我手机一三五零一二三四五六七 可以拨打12306来咨询 - 可以拨打一二三零六来咨询4.10 儿化音删除Erhua Removal儿化音“这儿”“鸟儿”中的“儿”默认删除白名单机制保证“儿子”“女儿”“儿童”等含“儿”的合法词汇不被误删这儿有只鸟儿 - 这有只鸟 这事儿好办 - 这事好办 我儿子喜欢这地儿 - 我儿子喜欢这地实现上taggers/whitelist.py 同时构造了erhua分支把“儿”标注为erhua: 儿权重仅 0.1优先匹配与普通白名单分支转写侧 verbalizers/whitelist.py 对erhua字段直接删除。儿化音白名单位于 data/erhua/whitelist.tsv默认收录“儿女、儿子、儿孙、女儿、儿媳、婴儿、新生儿、儿童、儿科、台儿庄、鹿儿岛、正儿八经、吊儿郎当”等 36 个词条——凡命中白名单的“儿”字不会被删除。4.11 白名单替换Whitelist Replacement白名单提供一组用户自定义的“精确字符串匹配 → 替换”硬映射常用于把缩写、拼写形式固定为统一读法。官方示例C E O - CEO G P U - GPU O2O - O to O B2B - B to B默认表 data/whitelist/default.tsv 已内置 80 条常见映射除 README 示例外还包括O 2 O→O to O、CD→C D、a.m./am./A M/Am→AM、p.m./pm./Pm→PM、atm/A T M→ATM、ufo/Ufo/U F O→UFO、FIFA/f i f a→fifa、NBA/n b a→NBA、XL/X L→XL、gpu→GPU、cpu→CPU、ceo/c e o/C E O→CEO、wto/W T O→WTO、M.V.P→MVP、vip→VIP、cctv→CCTV、kfc/K F C→KFC等。注意该表是大小写敏感的精确匹配开发者可按“源串\t目标串”格式追加自定义词条或通过--whitelist指定独立文件在运行时覆盖。五、后处理Post-Processing后处理由 verbalizers/postprocessor.py 中的PostProcessor实现提供三个可独立开关的选项标点删除、大小写转换、OOV 标注。这三个开关在默认流水线中均为关闭见 verbalize_final.py开发者可按需在PostProcessor(...)构造时开启。5.1 标点删除Punctuation Removalremove_punctsTrue时通过pynutil.delete(FUN_PUNCT | punctuations_zh)删除所有英文标点FUN_PUNCT定义于 graph_utils.py为string.punctuation的并集与中文标点来自 data/char/punctuations_zh.tsv。该选项适合 TTS 训练语料需要纯文本输出的场景。5.2 大小写转换Uppercase / Lowercase Conversionto_upperTrue或to_lowerTrue时借助 data/char/upper_to_lower.tsv 的大小写映射表统一转换英文字母转大写用pynini.inverse反转映射postprocessor.py转小写则直接使用映射表。5.3 OOV 标注Out-Of-Vocabulary Taggertag_oovTrue时对不在“合法字符集”内的字符用oov与/oov标签包裹便于下游识别文本中超出词表的字符我们안녕 - 我们oov안/oovoov녕/oov 雪の花 - 雪oovの/oov花字符集组成postprocessor.py中文字符国标《通用规范汉字表》data/char/charset_national_standard_2013_8105.tsv2013 年版8105 字叠加用户扩展表 data/char/charset_extension.tsv 与中文标点表英文字符数字、字母、英文标点与空白FUN_DIGIT | FUN_ALPHA | FUN_PUNCT | FUN_WHITE_SPACE定义见 graph_utils.py判定逻辑pynini.difference(utf8.VALID_UTF8_CHAR, charset)即“所有合法 UTF-8 字符 − 合法字符集”的差集即为 OOV 字符标签文本来自 data/char/oov_tags.tsv首行为oov\t/oov左/右标签各占一列。六、数据定制一切皆可 TSV从上面的源码梳理可以看到中文 TN 模块的设计哲学是“文法代码与数据表分离”——所有可定制内容都以 TSVTab 分隔数据表形式存在改表即改行为无需改动任何文法代码。汇总如下数据表路径作用格式data/char/fullwidth_to_halfwidth.tsv全角 → 半角映射全角字符\t半角字符data/denylist/denylist.tsv预处理删除词语气词等每行一个词data/whitelist/default.tsv精确匹配替换缩写/拼写源串\t目标串data/erhua/whitelist.tsv儿化音保留白名单每行一个词data/money/currency_code.tsv货币代码 → 中文币种代码\t币种名data/money/currency_symbol.tsv货币符号 → 中文币种符号\t币种名data/measure/units_en.tsv英文单位 → 中文单位en\t中文data/measure/units_zh.tsv中文单位/量词每行一个单位data/math/symbol.tsv数学符号 → 读法符号\t读法data/math/score.tsv比分符号符号\t读法data/time/suffix.tsv时间后缀am/pm后缀\t读法data/number/digit.tsv数字 → 中文读法数字\t汉字data/number/digit_teen.tsv十几 → 读法数字\t汉字data/number/zero.tsv零 → 读法0\t零data/number/sign.tsv正负号读法符号\t读法data/char/charset_national_standard_2013_8105.tsv通用规范汉字表8105 字每行一个字data/char/charset_extension.tsvOOV 判定的扩展字符集每行一个字data/char/punctuations_zh.tsv中文标点表每行一个标点data/char/oov_tags.tsvOOV 左右标签左标签\t右标签data/date/year_suffix.tsv年份后缀白名单避免把“年”误判每行一个后缀所有这些数据表都通过pynini.string_file(get_abs_path(...))在文法构建时加载get_abs_path定义于 zh/utils.py基于当前文件绝对路径解析因此“改表 → 重新编译文法/重新运行”即可生效。七、与 FunASR 生态的衔接中文 TN 模块是 FunASR 文本处理套件的一部分。套件根目录 fun_text_processing/ 同时提供中文逆文本正则化Inverse TNfun_text_processing/inverse_text_normalization/zh/与 TN 方向相反把口语文本还原为书面数字/符号常用于 ASR 识别结果的格式化批量评测脚本run_evaluate.py支持加载标注数据Kaggle 格式的semiotic class\t未归一化文本\t归一化文本解析逻辑见 data_loader_utils.py并按类别计算归一化准确率其他语言实现en / de / es / ru 等语言的 TN 目录结构一致便于横向参考。在典型使用场景中TN 位于 TTS 前端或 ASR 语料清洗链路原始文本 → 中文 TN本文主题→ 送入声学模型训练或 TTS 合成识别结果则走 ITN 反向还原。两者配合即可构建完整的“写实 ↔ 读实”文本转换闭环。八、常见问题与使用建议输出与预期不一致时如何排查使用--verbose参数normalize()会打印中间标注结果tokens { ... }可据此判断文本被归类到哪一类文法、哪个环节出了问题如何让自定义缩写生效优先在 data/whitelist/default.tsv 追加源串\t目标串注意大小写敏感或通过--whitelist传入独立文件误删/误转词怎么处理儿化音误删加 data/erhua/whitelist.tsv语气词误删则在 data/denylist/denylist.tsv 中移除对应词条OOV 判定过严/过松在 data/char/charset_extension.tsv 中追加业务常用字即可扩展合法字符集超长文本超过 500 token 会触发警告建议先用split_text_into_sentences()切句、再以normalize_list()批量并行处理缓存与部署指定--cache_dir后编译好的文法会以.farOpenFst Finite State Archive形式缓存文件名形如zh_tn_True_deterministic_cased__tokenize.far见 tokenize_and_classify.py二次启动可跳过文法编译显著加速--overwrite_cache用于强制重编。结语FunASR 的中文文本正则化模块以 WFST/Pynini 为底层引擎通过“预处理 → NSW 分类标注 → 口语化转写 → 后处理”的流水线把数字、日期、货币、度量衡、号码串、儿化音等非标准词稳健地转换为中文口语读法且所有词表均以 TSV 形式开放定制。无论是为 TTS 准备训练语料、清洗 ASR 数据还是构建多语言文本处理服务这一模块都提供了开箱即用且高度可配置的解决方案。建议读者结合 zh/README.md、taggers/ 与 verbalizers/ 的源码深入对照在实际业务数据上验证并扩展自己的词表。【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考