dots.tts 读数字总串音 / 该改文本规范化还是换音素输入?

文章导读
dots.tts 读数字串音,先别急着换模型或改音素输入,而要用三类数字文本把问题定位到文本预处理层还是音素/发音输入层。通常文本规范化负责“切分与读法选择”,音素层负责“发音单元”:前者出问题的症状是整串连读成一个大数、分组错位、单位词和小数点丢失;后者出问题的症状是切分对了但单字发音怪、声调或重音不对。先做复现,再决定改哪一层。
📋 目录
  1. 用金额、年份、电话号码各做一条复现文本
  2. 只改文本规范化再合成同一句
  3. 检查音素或发音词典入口是否存在
  4. 建立数字读法的优先级规则
A A

dots.tts 读数字串音,先别急着换模型或改音素输入,而要用三类数字文本把问题定位到文本预处理层还是音素/发音输入层。通常文本规范化负责“切分与读法选择”,音素层负责“发音单元”:前者出问题的症状是整串连读成一个大数、分组错位、单位词和小数点丢失;后者出问题的症状是切分对了但单字发音怪、声调或重音不对。先做复现,再决定改哪一层。

判断顺序:先用金额、年份、电话号码三类文本各做一次复现,确认串音是集中在特定数字结构还是所有长数字;再只改文本层(写成汉字读法、按 3-4-4 分组)重合成同一句。若输出变正常,问题在文本规范化;若分组正确但逐字发音仍不对,才考虑音素或发音词典入口。若项目没有暴露音素接口,不要硬改,只记录文本层覆盖不到的边界,并在文本侧用回退写法绕过。

用金额、年份、电话号码各做一条复现文本

三类数字的结构不同,暴露的问题也不同,所以不要混在一段里合成,逐条单独跑,并记录送入 TTS 的字符串。

类型输入文本预期读法记录点
金额订单金额 12345.67 元一万二千三百四十五点六七元是否连读成大数、小数点是否读出、单位词是否保留
年份2024年3月发布二零二四年三月发布是否读成“两千零二十四年”这类基数词读法
电话联系电话 13800138000一三八 零零一三 八零零零 或逐位是否被当成一个整数连读、是否按 3-4-4 断

执行时把每条文本单独合成,不要连续放在一段里,因为上下文可能影响数字判定。下面的命令只是示意,请按你项目实际的调用入口替换:

# 逐条合成,观察串音是否只集中在某类数字结构上
for t in "订单金额12345.67元" "2024年3月发布" "联系电话13800138000"; do
  echo "=== INPUT: $t"
  dots-tts `--text` "$t" `--out` "/tmp/out_$(echo "$t" | md5sum | cut -c1-8).wav"
done

如果项目有 debug、verbose 之类的开关,打开后把“规范化之后的文本”一起打出来。看不到这一步,就只能靠听回放判断,定位会慢很多。三类文本里只要有一类稳定复现、另一类正常,就说明问题集中在特定数字结构,而不是整套数字处理都坏了。

dots.tts 读数字总串音 / 该改文本规范化还是换音素输入?

只改文本规范化再合成同一句

把 2024年 显式写成 二零二四年,把 13800138000 按 3-4-4 分组为 138 0013 8000,其他参数(音色、语速、采样率、模型版本)全部保持不变,再合成同一句。这一步只动文本,不动模型,能直接判断问题是否出在文本层没切分好。

import re

def normalize_for_tts(text: str) -> str:
    # 年份:显式写成汉字,避免被当作基数词
    text = re.sub(
        r"(\d{4})年",
        lambda m: "".join("零一二三四五六七八九"[int(c)] for c in m.group(1)) + "年",
        text,
    )
    # 手机号:按 3-4-4 分组,插入空格作为停顿
    text = re.sub(r"\b(\d{3})(\d{4})(\d{4})\b", r"\1 \2 \3", text)
    # 金额:小数点改读作“点”,整数部分保留分组
    text = re.sub(r"(\d+)\.(\d+)", r"\1点\2", text)
    return text

最小修改验证步骤可以固定成下面这几步,便于换句子重复:

  1. 取一条串音最明显的原句,先原样合成一次,留作对照。
  2. 只改数字写法(汉字读法或 3-4-4 分组),其他参数不动,再合成一次。
  3. 逐字听回放,记录哪一类数字变正常、哪一类没变。
  4. 对没变的那一类,换第二种文本写法(例如把空格换成逗号或逐位汉字)再试一次。
  5. 用一两句不含数字的文本做回归,确认改动没有影响普通句子的读法。

边界要留意:文本规范化改变的是送入模型的内容,同形数字可能被连带改掉,例如版本号 3.10.2、带区号的长串,改完要专门回归。如果三类都变正常,问题基本在文本层;如果年份正常而电话仍连读,说明分隔符没被识别,继续在文本层内换分组写法。

检查音素或发音词典入口是否存在

先在配置、README、启动参数或代码里找 phoneme、pinyin、g2p、lexicon、pronunciation dictionary 这类入口,确认项目是否允许绕过文本、直接给发音单元。

dots.tts 读数字总串音 / 该改文本规范化还是换音素输入?
症状更可能的层动作验证方式边界
整串数字连读成一个大数文本规范化改写成汉字读法或分组写法同一句重合成,听断点是否出现同形数字可能被连带改写
分组正确但某几个字发音怪音素/发音词典用正确的拼音或音素序列做对照音素输入正常而文本输入异常需要项目确实暴露该入口
小数点、单位词丢失文本规范化把“.”改写成“点”,补回单位词听单位词是否读出不同语种读法需分别确认
只暴露文本入口停留在文本层穷举分组与汉字写法,记录无效项列出仍失败的数字结构清单不要虚构不存在的音素接口

如果项目确实暴露音素或拼音输入,就对同一句话做一次文本入口与音素入口的对照:音素序列按正确读法标注,比较两次输出。若音素入口正确、文本入口错误,重点应放回文本规范化或 G2P 词典,而不是继续换模型。如果只暴露文本入口,就不要硬编接口,只记录文本层试过哪些写法、哪些数字结构仍然不行,把这些当作边界交给维护者。另外,部分 TTS 支持在文本里插入逗号、空格或竖线来强制断句,这仍属于文本层手段,可以先试。

建立数字读法的优先级规则

为了后续内容生产有固定改法,建议按数字类型分别约定首选与回退写法,同一份内容里对同类数字保持同一种写法,避免同一篇文章里金额时而汉字时而阿拉伯数字,导致读法不一致。

数字类型首选写法回退写法判断依据
金额汉字读法加“点”,如一万二千三百四十五点六七元阿拉伯数字加千分位与单位,如 12,345.67 元小数点与单位词是否稳定读出
年份逐位汉字,如二零二四年四位数字加“年”是否被读成基数词
电话/手机号逐位汉字,或 3-4-4 分组逐位数字加空格或停顿符是否连读成一个大整数
编号/版号/型号逐位读,字母按字母读分组加停顿符是否保留字母读法

执行顺序保持“先文本层、后音素层”:文本层优先用显式汉字,因为它不依赖模型对分隔符的支持;分组写法作为次选,遇到模型不认空格时再退回汉字。只有确认项目暴露音素或发音词典入口时,才把个别的顽固读法下沉到那一层处理,其余情况都在文本侧解决,并把无效写法记录下来,作为下一轮调整的依据。