公式朗读翻车?从TTS文本归一化到预处理的底层修复指南

发布时间:2026/10/22 12:01:39
公式朗读翻车?从TTS文本归一化到预处理的底层修复指南 很多做在线教育的朋友都跟我吐槽过一件事明明AI讲解工具已经能把一篇英文文章读得声情并茂可一到数学公式就原形毕露——根号变成二立方读成乘以三更离谱的是把”sin x”念成sin 叉。这类问题不是个别厂商的疏漏而是几乎所有语音合成方案在数学场景下的通病。我自己做过一年多的语音内容自动化踩过不少坑后来花了不少精力去拆解为什么读不对这件事才发现问题的根源不在发音库也不在声学模型而在更底层的文本处理和语料设计逻辑上。这篇文章不打算停留在哪个工具好用的对比层面而是想聊聊公式朗读翻车的底层原因、合成流水线里到底哪几个环节在作梗以及我实际验证过、能在工程上落地的修正思路。如果你正在做数学类课程自动化、给学习类App接入语音功能或者只是好奇为什么AI读公式会这么傻这篇应该能给你一些有用的线索。1. 日常抱怨背后公式朗读错误到底分成哪几类先把问题归类。我接触过的公式朗读错误看着千奇百怪本质上跑不出四类符号被当成普通文本、数字处理逻辑错乱、运算关系含糊、专业记号直接失语。分类清晰了排查起来才有方向。1.1 符号乱读最荒诞但也最好理解最常见的一类是符号被当成普通字母或单词读出来。比如x²经常被读成x二而不是x的平方π被读成派还算好的有的引擎直接读pai∑可能被跳过不读也可能读成sum。这类问题的根源在于语音合成引擎内部有一个文本归一化text normalization环节它负责把数字、符号、缩写转换成适合发音的常规文字。这套逻辑处理日期、时间、金额是精心设计过的但面对公式符号的时候它只会机械地查表。²这个字符如果出现在文档里很多归一化规则认为它是上标二但转成语音的时候它不知道该不该把平方这个语义加进去干脆就按字符读。我印象很深的一个测试案例一段包含log₂8的文本引擎读出来是log二八而人类正常的读法是以二为底八的对数。这种读出来了但完全不对的错误比直接跳过更让人头疼因为听的人会以为自己理解错了而不是怀疑机器读错了。1.2 数字和单位的错乱归一化规则在公式语境下的失灵第二类跟数字有关但表现形式非常本地化。中文语音引擎处理普通文本时会把1200转成一千二百再合成。这个习惯在叙事文本里完全没问题但放进公式就坏事了。比如F(x)1200x引擎很可能读成F x等于一千二百x而不是F x等于一千二百乘以x。这里丢掉的不是数字本身而是数字和变量之间的运算关系。更麻烦的是单位类字符。3cm在文本归一化里是三厘米5kg是五千克但公式里cm也可能是变量名6.02×10²³需要读成六点零二乘以十的二十三次方而很多引擎会把10²³中的上标丢失之后直接读成一千零二十三或者十二十三。归一化规则的面向普通文本取向在数学语境里几乎是灾难性的。我在自建测试集的时候把这类错误单独归为一档因为它们的复现条件最稳定也最容易通过规则修补解决——只要在预处理阶段把数字、变量、运算符号的边界识别出来问题就消掉大半。1.3 读出来了但运算关系没表达出来第三类更加隐蔽就算符号、数字都读对了引擎也可能在运算关系的表达上缺斤少两。比如(ab)/c读出来是a加b除以c还是括号a加b除以c或者a加b的和除以c三种读法信息量完全不同。人类读公式时会通过语速、停顿和括号的和这些词来维护运算结构的清晰度——听的人甚至不用看公式就能脑补出层级。但语音引擎的韵律预测模块通常是从普通朗读语料里训练出来的它处理自然语言停顿还行处理括号层级这种结构化信息就完全没有章法。举个例子2(xy)在数学上是一个整体应该读成二倍的括号x加y但很多引擎会读成二乘以x加y。少了括号这个词再加上没有辅助停顿听者会以为公式是2xy。这类错误不怪发音也不怪声学模型纯粹是数学表达式在转换成口语化朗读文本的环节上缺失了一层结构转译。1.4 专业记号直接失语连发声都做不到最后一类是直接失语。比如∫积分、∇nabla算子、∈属于、∀全称量词这些符号很多语音引擎的词典里根本没有对应的读音条目。更滑稽的是在公式中常见的希腊字母α、β、γ、θ经常被英文TTS按照英文字母的发音规则读出来听起来像在读一串乱码。这类问题说明一个残酷的现实绝大多数商用语音合成方案的语料库以日常口语-新闻-有声书为主要覆盖范围数学符号、希腊字母这些低频却不是稀有需求的内容在数据层面就存在覆盖空白。错误类型典型表现根本原因阶段符号乱读x²→ x二log₂8→ log二八文本归一化阶段数字单位错乱1200x→ 一千二百x文本归一化阶段运算关系含糊2(xy)→ 二乘以x加y韵律预测阶段专业记号失语∫、∈、∀无法发音词典与音素覆盖这个分类表是我后续所有调试工作的基础。每次拿到一条读错了的反馈先按这四类归档就能快速判断问题出在哪个环节而不是一头扎进声学参数里瞎调。2. 从文本到语音公式在合成流水线里经历了什么要理解公式为什么读不对绕不开语音合成的基本流水线。我尽量用最通俗的方式讲清楚因为这套流程决定了哪些错误是可以修的哪些是必须换方案的。2.1 TTS的五个关键环节哪一个针对公式最薄弱一个标准的语音合成流水线大体经过文本归一化 → 分词 → 词性与浅层句法分析 → 韵律预测 → 声学参数生成与波形合成。文本归一化负责把2024/5/1变成二零二四年五月一日把¥99变成九十九元分词负责把中文句子切成词词性与句法分析判断每个词的角色韵律预测决定哪里停顿、哪里升调、哪个词重读最后声学模型生成声波。你发现没有这套流水线从头到尾是为自然语言设计的公式不是自然语言。公式里有符号、有变量、有上下标、有二维排版这些信息在语文世界里根本没有对应物。最关键的矛盾在于文本归一化阶段做的是字符到字符的转换它不负责理解根号底下的内容是一个整体这个语义关系。所以我常说一句话公式朗读出错不是最后一个开口说话的环节出了问题而是前面看文本的环节全都用错了坐标系。你让一个精通朗读但不认识公式的人读题他大概率也会读得支离破碎——现在的语音引擎就是那个精通朗读但不认识公式的人。2.2 文本归一化为何专门对公式下黑手文本归一化模块是公式错误的重灾区这一点值得单独展开。归一化的本质是基于规则的有限状态转换比如正则表达式匹配到\d(\.\d)?%就替换成百分之××。这套规则在处理日期、电话、货币、网址时非常有效因为它们的格式高度规整。但数学表达式恰恰是格式极不规整的典型2/3是三分之二但(x2)/3是x加二的和除以三同样的斜杠前者是分数后者是除法运算符语义完全不同。再比如10³和10^3在文本里可能是同一个意思但归一化规则很难对所有上标形式做到统一处理。很多规则写的是连续数字匹配到上标字符则把上标数拼在数字后面——于是10³变成了103一百零三而它至少应该读成十的三次方。这个阶段的另一个坑是语种判定。公式天生是多语种混排阿拉伯数字、拉丁变量、希腊字母、中文连接词等于除以混在一起。引擎的语言识别模块在短片段上很容易误判尤其是log这种同时是英文单词和数学函数名的字符串。我实测过一个引擎把log整体按英文单词发音读成洛克把ln读成l n两个字母完全没意识到这是自然对数的缩写。2.3 韵律层没有公式语法可依赖就算你通过预处理把所有数字、符号都转换对了离像人一样读公式还有一道硬关卡韵律预测。人的朗读尤其是数学公式的朗读非常依赖结构驱动的停顿。眼睛看到(ab)/(cd)脑子会规划出括号内短暂停顿-除号前后加重-分母括号内短暂停顿的节奏。这种节奏不是情绪化的而是语义结构在语音上的映射。形式化一点说公式的括号层级应该线性映射为音频上的停顿层级嵌套越深停顿越密最高层级的运算符前后要给足呼吸时间。但TTS的韵律预测模型是从普通文本语料里学出来的它学习的是标点符号和语气词带来的韵律变化不是数学运算符带来的韵律变化。模型看到(和)大概率只是把它们当标点跳过根本不会据此生成括号层级对应的停顿时长。结果就是公式被读成一串匀速的字符流结构信息在声音里荡然无存。这也是为什么靠调音色调语速永远修不好公式朗读——你调的只是音色和速度不是停顿结构。真正要做的是在进入TTS之前把公式文本转换成自带韵律标记的朗读文本让引擎没有机会自己乱断句。3. 公式自带反TTS基因二维结构与歧义性的根源前面讲的流水线缺陷其实都指向公式本身的特殊性。任何工具想读好公式都得直面公式的两种反TTS基因二维排版的信息压缩和无处不在的上下文歧义。3.1 二维排版被压成线性字串信息大量丢失纸质公式和板书公式本质上是二维排版信息分子在上分母在下根号覆盖整个被开方数上标是右上角的小字。这些信息在人类阅读时是视觉并行处理的效率极高。但文本文件里只有线性字符流一旦把公式转成纯文本排版信息就丢了。以OCR识别出来的公式为例很多引擎会把分子/分母识别成分子分母斜杠丢失把x_i的上标下标丢失。还有一种常见情况是LaTeX源码直接用\frac{a}{b}表达分数但做了简单文本提取后一类工具拿到的是LaTeX命令名另一类工具可能把命令名直接剥离了导致公式变成ab两个字母的拼接。这种信息丢失是不可逆的任何语音引擎都没办法从一段丢失了排版信息的文本里脑补出分数结构。所以公式朗读的修复一定不能只依赖TTS这一层必须在源头保证输入文本携带足够的结构信息。这也是为什么很多人最后选择先转成口语化文本再TTS的路线——结构信息不能靠声音去猜只能在文字里显式写出来。3.2 同一个符号语境不同读法能差出十万八千里公式的第二大反TTS基因是歧义性。同样的字符在不同语境下读法完全不同这种歧义甚至对规则引擎来说是不可判定级别的难题。举几个我实测过的高频歧义2/3单独出现是三分之二出现在2/3 x里是三分之二倍的x出现在x 2/3里又可能是x等于三分之二。但在2/3前后都是变量的情况下人类会默认读成二除以三。log作为数学函数要读对数出现在log in里要读登录。语言模型需要跨越几十个字符的上下文才能判断文本归一化规则的有限状态机根本扛不住。sin数学里读正弦英文语境里读罪过中文学生口语里还经常读成赛因。x⁻¹可以读x的负一次方但在线性代数语境里也可能是x的逆。cos⁻¹存在反余弦和余弦的负一次方两种学派式读法哪种都对看教材体系。这类歧义不是多加几条正则能解决的。本质上公式朗读是一个需要上下文理解能力的任务而传统TTS架构里根本没有上下文理解模块。这也是我判断纯靠引擎优化救不回来的依据之一。3.3 多语种混读对语言判定模型的挑战再补充一个很少被提及但现实存在的难题公式里的多语种混合。中文数学教材的朗读习惯是中文句式英文变量希腊字母的混排。比如设α为锐角则sinαcos(90°-α)这句话TTS需要在一句话里完成中文、希腊字母、英文函数、角度符号的四种发音模式切换。而大多数TTS引擎按整句语言识别局部发音字典的方式工作句子级别判定为中文后α、sin、cos这些非中文片段就交给发音字典去碰运气。碰运气的结果就是α有时读阿尔法有时按英文字母A读β有时读贝塔有时读B°这个符号在部分引擎里直接静音在另一部分里读成度已经算很给面子了。语言模型在短片段内频繁切换语种的场景下准确率会急剧下降这几乎是所有统一多语种模型的通病也不只是公式场景独有的。4. 从根上想办法三条可落地的修正路线问题掰开揉碎讲完了接下来说解决。我实际验证过、觉得有普适性的方案有三条分别对应不同资源投入和不同质量要求。按推荐优先级排复杂公式走结构化转译简单公式走规则预处理预算充足且场景固定走微调专用模型。多数情况下前两条已经够了。4.1 路线A规则预处理用脚本把公式变成口语文本思路很简单在喂给TTS之前先用脚本把公式转换为带口语化标记的朗读文本让引擎读的是已经转译好的人话而不是原始公式字符串。以x^2 2x 1 0为例预处理脚本可以输出x的平方加二x加一等于零。这里的关键不只是把^2替换成的平方还包括把2x识别成二x而不是二乘以x、把 0识别成等于零而不是等号零。具体做法上我的建议是先制定一张口语化朗读规范表明确每个符号、每个结构的朗读标准数学结构纯文本表达推荐口语读法x^2x^2x的平方\frac{a}{b}a/ba除以b或b分之a按上下文\sqrt{x1}sqrt(x1)根号下x加一\log_2 8log_2(8)以二为底八的对数\int_a^b f(x)dx积分上下限从a到b的定积分f x d x这个规范表要跟教学内容对齐比如分数线到底读几分之几还是除以需要看用户习惯。我曾经在某个项目里因为统一用了除以被一线老师反馈听起来不够自然后来改成语境感知规则纯数值分数读几分之几含变量的分数读除以问题就解决了。用Python之类的脚本实现这套规则工作量约等于写一堆串行正则和上下文规则普通人就能搞定。唯一要注意的是正则规则的优先级要小心比如sqrt要优先于sin处理因为sin x里的in不能被sqrt之类的长函数误吞。4.2 路线B结构化解构把公式翻译成带停顿的讲解词规则预处理解决的是单个符号读法但真正读好公式靠的是整体结构的解构。这是我个人最推荐、也是实测效果最好的一条路线。思路是拿到公式文本后先解析它的结构树把、-、×、÷这些运算符作为树的节点操作数作为叶子然后按照树的结构生成一段分层的口语化讲解文本。每个括号层级的切换自然对应讲解词里的一组停顿词或连接词。举个实操例子。公式(ab)/(c-d)结构树解析出来后生成的讲解文本是括号a加b除以括号c减d。这里的括号和除以就是结构信息的显式标记TTS只需要按普通文本朗读停顿和重音自然就对了。更进一步可以生成完全讲解式文本a加b的和除以c减d的差。这种读法对学习类场景最友好因为人类数学老师的口语就是这么说的。代价是需要额外写结构连词生成规则复杂度会高一些但对视频课、有声题集这类内容质量提升是肉眼可见的。我实际做过的方案是在LaTeX源码层直接解析因为LaTeX本来就携带着完整的结构信息\frac{}{}、\sqrt{}、^、_用现成的LaTeX解析器把结构树提取出来再按结构树输出口语文本效果非常稳定。如果手里只有图片公式那就先OCR再加一层LaTeX识别链路会长一点但结果依然可控。4.3 路线C微调专用TTS代价高但上限也高如果你预算充足且公式朗读是一个长期核心功能可以考虑微调一个专用TTS模型。思路是收集大量数学公式-标准朗读音频配对数据在预训练模型基础上做二次训练。这条路的效果上限最高因为模型能学到的不只是单个符号的读法还包括公式朗读的停顿习惯、语调变化、逻辑重音位置。但代价也很明显需要成百上千小时的标注语料、专业发音人、GPU训练集群以及持续的数据迭代。对个人开发者和小团队来说门槛确实高。我的建议是除非你本来就是做语音产品的否则不要从零训练而是基于可商用或开源的语音合成模型做少量数据微调比如用几百条公式朗读音频做风格适配。实测下来微调后的模型在停顿合理性上会有明显提升但符号识别率的提升有限——因为模型再怎么微调输入端的文本归一化和分词还是旧的符号层面的错乱在上游就注定了。所以即便是路线C也要配合路线A/B使用让微调模型去读已经预处理好的文本效果最佳。5. SSML与发音字典不强求引擎变聪明的补救实操前面三条路属于内容侧改造这一章讲的是引擎侧补救。如果短期内不想大改文本处理流水线又希望立刻改善一部分朗读效果利用TTS引擎自带的SSML标记和自定义发音字典是最划算的做法。5.1 用break标记强制给公式断句SSML语音合成标记语言里最实用的标签之一就是break专门用来控制停顿。公式朗读最缺的就是结构停顿而break能在不改变文本内容的前提下强制引擎在指定位置停顿。比如2(xy)可以生成speak break time100ms/二乘以break time200ms/括号x加ybreak time100ms/ /speak这样引擎读到乘以后面会停顿200毫秒相当于把括号的存在用声音暗示出来了。实测中100ms~300ms的短停顿在公式里效果最好太短听不出来太长会显得机械。另一个实用组合是prosody的rate属性。复杂的分式或带多层括号的公式在朗读时把速率降到0.9倍左右清晰度会有显著提升。反过来简单的235保持1.0倍甚至1.1倍速率读出来会更利落。5.2 用sub标签给顽固派符号指定别名SSML里的sub标签可以让你指定一个实际朗读的替换文本。这是对付那些死活读不对的符号的最快手段。speak sub alias自然对数ln/sub2等于0.693 /speak另一个高频场景是希腊字母直接按中文发音指定speak 设sub alias阿尔法α/sub为锐角则sub alias正弦sin/subsub alias阿尔法α/sub等于sub alias余弦cos/sub括号90度减sub alias阿尔法α/sub /speak用sub标签的好处是不用改原始文本只在合成层做替换不会破坏原文的公式排版。需要批量处理时可以用脚本自动把公式文本里的符号替换为带sub标签的SSML片段再交给引擎。5.3 自定义发音词典一次配置全局生效比SSML更一劳永逸的路径是给引擎配置自定义发音词典。大多数商用TTS都支持用户上传一个词典文件里面指定特定字词的读音。这是纠正sin读赛因、log读洛克这类问题的标准做法。以某些引擎为例词典文件格式大致是这样的sin 正弦 cos 余弦 tan 正切 log 对数 ln 自然对数 √ 根号配置之后引擎在整个项目里遇到sin都会按正弦发音不需要每个地方手动加SSML。我自己的经验是第一步先把数学高频函数名希腊字母常见符号全部写进词典大概50~100条就能消掉大部分低级错误。但要注意词典也有负面影响如果在线性代数内容里出现sin作为人名或缩写词典会强行按正弦读。所以词典要按课程体系分域管理数学课里开数学词典普通文本里关掉。5.4 这些补救的边界在哪里说句实话SSML和发音字典的补救空间是有限的。它们能解决单点读法错误但没有办法解决整句结构错乱和歧义判断。比如lim_{x→∞} (11/x)^x e这句话里有极限号、箭头、趋向于、括号层级、幂指数、自然常数光靠SSML和词典是组织不出当x趋向于无穷大时一加x分之一的x次方的极限等于自然常数e这种流畅读法的。这种场景必须回到路线A/B走文本预处理或结构化转译。所以我的常用策略是分层治疗简单符号错误交给词典和SSML复杂结构错误交给预处理脚本两套方案并行使用各管各的。6. 排查公式朗读问题时的实战思路最后聊聊排查方法。很多人遇到朗读错误第一反应是换引擎、调参数但往往折腾半天也没解决。我分享一下自己调试这类问题的完整思路按这个顺序走效率会高很多。6.1 先录音、再分类、后定位拿到一条朗读不对的反馈别急着改代码。先让出错的音频和原始公式文本放在一起逐字对照把实际读出来的和应该读出来的差异点列出来然后按第一章的错误分类表归档。这一步看着啰嗦但我吃了不少亏才明白它的价值。有一次我花了几个小时调试停顿参数最后发现问题根本不是停顿而是log被词典读成了乐高——要是早做对照五分钟就能定位。错误分类是排查的锚点没有分类的调试就是瞎试。6.2 用最简复现缩小范围定位问题的标准方法是构造最简复现。比如发现log₂8读错了就分别测试log2函数名数字log x函数名变量log_2 8带底数log(8)带括号通过比较这四条的朗读结果你很快能判断出问题出在函数名识别、上下标处理、还是括号触发哪一个环节。同理如果2(xy)读错就拆开测2x、2(x)、(xy)一点点逼近。这个方法不仅适用于公式朗读任何语音调试场景都能用。它能帮你把整体黑盒拆成一组可观察的局部行为定位效率和准确率都会高很多。6.3 结合文本预处理链路做整体调优当单点问题修得差不多之后我建议做一次端到端的音频质量审查找一个覆盖各类型公式的测试题集涉及分数、根号、上下标、求和、极限、矩阵等常见结构然后对每一类记录朗读结果和人工标准朗读对比打分。这个测试集不需要很大50条左右就够用。我自己的体会是每次改完预处理脚本或词典之后跑一遍测试集看总分变化。如果某类错误下降了但另一类上升了说明规则之间有冲突需要调整优先级。坚持迭代两到三轮整体朗读质量会趋近稳定。测试集最好贴近真实课程内容我建议直接从目标教材里挑例题不要自己凭空编公式。因为自编公式往往会无意中回避真实语料里的复杂情况比如带条件的公式、多行对齐的行列式而真实教材里的公式才能暴露引擎的真实短板。6.4 给团队或项目的一个落地建议如果你是在团队里推进这件事我特别建议输出一份公式朗读规范文档哪怕只有两三页都行。规范文档里写明哪些符号用中文读、哪些用英文读、分数线读分之还是除以、括号在处理时是否显式读出来、遇到歧义时以哪种读法为准。这份文档看起来是小事实际上是后续所有脚本和词典配置的宪法。没有它每个开发者凭感觉加规则最后维护成本会非常吓人。我自己最后整理出的规范文档几乎是所有改动方案的验收依据脚本规则要不要加、词典要不要添词以规范文档为准。这样整个系统的行为才是可预期的而不是某天某个符号突然换了一种读法大家一脸懵。结束语一个实测有效的最小改造组合拳最后分享一个我在不同项目里反复使用、效果比较稳定的组合方案。如果你的目标是尽快把公式朗读质量提升一档我建议按这个顺序操作先写一份自己的朗读规范把容易读错的50个常用函数名和符号写进自定义发音词典再写一个简单的文本预处理脚本把上标、下标、分式、根号这些结构化内容转换成语义清晰的口语文本复杂公式走LaTeX结构解析输出带括号除以的差这类结构连接词的讲解词最后用SSML的break和rate做韵律层面的微调。这套组合不需要动模型不需要大规模训练纯粹靠工程手段就能把公式朗读从不能用拉到基本能用。我踩过最大的坑是迷信某个智能引擎能一步到位。事实证明公式朗读是一个典型的领域知识工程细节问题领域知识解决读什么工程细节解决怎么读少一样都不行。希望这篇梳理能帮你在做数学内容有声化的路上少绕几个弯。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询