首页 / 资讯中心 / 文章详情

端到端TTS架构解析:从文本到波形的技术链路

端到端TTS架构解析:从文本到波形的技术链路 ★ FEATURED ARTICLE
TTS 架构篇写到第三篇我打算换一种讲法。前两篇我们把文本前端、声学模型、声码器一个个拆开看很多人看完之后反而更焦虑——模块太多每个模块都有独立的模型文件、独立的推理逻辑线上环境一调就是一堆超参。其实真正到了工程落地阶段我更推荐大家去理解另一条路线从文本进来到波形出去中间不需要人工维护那么多子模块。这就是所谓的端到端 TTS。本文不打算列一堆公式而是沿着一条完整的数据流把文本到波形这条路讲透。如果你是第一次接触 TTS也能顺着这条链路建立起整体感。1. 从拼积木到一体化这条技术路线的本质变化1.1 级联架构为什么又慢又脆最早做 TTS 的时候我的理解就是一套流水线。文本进来之后先走文本规范化把数字、日期、符号转成读法再做分词、词性标注、韵律预测接着由声学模型生成声学特征最后声码器把声学特征变成音频波形。这套架构后来被叫做级联架构或者模块化架构。它的优势是每个模块边界清晰某个环节出了问题可以单独替换研究层面也方便发论文。但在工程里这套东西的维护成本非常高。最典型的问题就是误差累积。文本前端如果给了一个错误的重音标记声学模型不会察觉它只会老老实实地把重音位置生成在错误的地方最后声码器再把错误放大到波形里。你听的时候会觉得整句话的语调很奇怪但要排查这种问题你得先把音频对齐回文本再从前端开始逐段定位非常痛苦。其次每个模块都有自己的输入输出格式接口稳定性要求极高。声学模型输出的语谱如果要缓存到磁盘还要定义存储格式和抽样率前端升级了一个词典后端可能就得多一个兼容分支。迭代一个声学模型时还要额外处理前后模块的兼容性。更直白地说级联架构里每个模块都是独立调优的但模块各自最优不代表整个系统最优最终听感反而可能因为某个中间环节的统计偏差变得很怪。推理延迟方面也一样糟。每一步都要把中间结果写好再传给下一步多一步就多一次等待。如果想做低延迟实时合成中间环节的序列化、反序列化开销会让首包时间变得很难看。所以很多团队表面上看还在用级联架构实际已经在边缘场景里偷偷把声学模型和声码器合并成一个大模型了。1.2 一体化架构的一气呵成到底是什么所谓的一气呵成就是把输入到输出收敛成一个可微的图文本序列进去采样点序列出来中间所有表征转换都由模型自己完成。听上去很像魔法实际上它依赖于两个关键设计一是让网络自己学会文本和波形之间的对齐而不是由外部规则强制指定二是把声码器也变成整体模型的可微部分这样才能端到端训练。一体化架构并不是说完全不拆模块而是说模块之间的耦合方式变了。很多实际系统仍然保留文本规范化、音素化这些前处理逻辑因为让模型直接从原始字符学习拼读对语料规模和数据多样性的要求实在太高。但从音素序列之后声学建模、时长建模、波形生成全部交给同一套神经网络联合优化。这样做最大的红利是训练目标直接是最终的波形质量。模型可以自己权衡哪些文本信息值得保留不需要我们在中间环节做太多人工决策。比如重音该怎么体现、停顿时长该是多少这些在级联架构里都是显式规则或独立预测器在一体化模型里只需要在损失函数里给一个整体信号剩下的交给梯度下降去探索。代价则是训练困难、调参复杂、可解释性变差。你在监控里看到 loss 在降但音频可能已经崩了。这就是为什么很多人说端到端模型是炼丹因为它确实需要较多经验来把握火候。2. 入口工序从原始文本到模型输入张量2.1 文本规范化把2024年5月1日变成二零二四年五月一日不管模型多强大它读进来的都是离散 token。原始文本里的2024年5月1日如果直接按字符切成2024模型永远学不会怎么读。所以文本标准化是第一步要做的事情是把非口语化表达转换成模型容易学的方式。常见的处理对象包括数字、小数、百分比、日期、时间、货币、英文缩写等。5.1要读成五点一还是五月一日取决于上下文。实现方式可以用正则先粗分再用分类模型或者查表做消歧。工程里更常用的是一套规则加模型混合的方案能枚举的写规则边界情况走模型。这一步最容易踩的坑是覆盖不足。线上文本千奇百怪比如3D 打印Wi-Fi 6第 8 代纯规则很难覆盖全。很多团队会先把语料切成短句再抽样人工标注逐步补充规则。我的经验是宁可让模型从字符直接学也不要把严重错误的文本送进前端因为前端给的错误读音是不可逆的。文本规范化的质量会直接决定后续模型的发音天花板。2.2 音素序列与韵律标记规范化之后文本还需要变成音素序列。中文一般做字到拼音的转换这里要处理多音字、轻声、儿化音英文则做 G2P把拼写还原为发音符号。音素是模型真正要建模的最小语言单元选择音素而不是直接选字符能大幅减少模型的学习难度。一个字符在不同上下文里的发音可能完全不同但音素基本上是稳定的。除了音素本身韵律信息也很重要。我们说话不是匀速念字而是有停顿、有重音、有音调起伏。很多系统会给每个音素打上韵律标记比如句内停顿级别、重音等级、边界指数。这些标记在文本前端阶段预测出来之后作为输入的一部分喂给模型。如果数据不足也可以不做显式韵律预测让模型从上下文自动学习但效果通常需要更大的模型来兜底。我的建议是起步阶段先不要追求复杂的韵律标注体系。一个简单可用的前端做好文本规范化和音素化就够了。把停顿、重音的预测任务交给模型反而更容易调试。等到某个固定语料上实验稳定再逐步加入韵律标记你会更容易判断哪些标记真的带来了收益哪些只是增加了过拟合风险。2.3 嵌入层与位置/时长对齐到了这一步输入侧基本定型。音素序列经过嵌入层变成向量再加上位置编码让模型知道顺序。如果是多说话人还会拼接说话人向量如果是多语言可以加语言 ID。最终送进主干网络的是一个张量每条样本的 shape 大约是 [batch, 帧数/音素数, hidden_size]。有一个细节值得注意音素序列的长度和最终波形长度完全不一样。一个音素可能对应几百个采样点所以模型必须在内部学会时长的概念也就是说它要把离散的文本长度映射到连续的时间长度。这个映射关系在训练时怎么定义直接决定了模型的复杂度和稳定性我在后面的段落会详细展开。还有一个容易忽略的问题嵌入层的初始化和数值尺度。TTS 模型里音素表通常不大几千个 token 就够用但 hidden_size 往往很大。初始化不当会导致前向传播的方差异常早期训练 loss 跳动剧烈。我自己习惯在初始化后先跑几十步固定随机种子看输出是否稳定再开始正式训练。3. 主干网络从音素序列到中间表征的三条路线3.1 自回归路线逐帧预测简单但慢自回归路线是最早被广泛采用的做法。模型读到音素序列后不一次性生成整段声学特征而是逐步预测当前帧并把上一帧的输出当作下一个帧的输入。有点像你一个人慢慢讲故事每讲一个字都要停下来想下一个字接什么好处是上下文保持得很好坏处是快不起来。训练时自回归模型采用教师强制也就是把真实的上文帧喂进去让模型学习预测当前帧推理时没有真实帧可用只能用模型自己上一帧的预测结果。这样一来一旦某一步预测偏差较大后面的误差会一路滚下去。为了解决这个问题实际中会加入一些随机采样或交互训练策略让模型在训练阶段就接触自己的预测结果增强鲁棒性。此外自回归模型的注意力对齐也可能出问题文本短但目标声学特征长模型在生成某些帧时可能对错文本位置导致漏读或重复读。这是老一代系统里著名的注意力错位现象现在很多团队已经转向非自回归或者潜变量路线但自回归的生成质量和实现简单程度仍然值得了解。比如在小规模数据上自回归模型往往能比复杂模型更快地合成出能听的音频这在快速验证想法时很有用。3.2 非自回归路线并行预测快但有时长对齐难题非自回归路线的想法是既然音素序列的每个位置相对独立能不能并行预测整句声学特征这样做推理速度可以提升很多但一个问题立即浮出水面每个音素应该持续多少帧解决思路是引入时长预测器。在训练阶段用外部对齐工具或者自回归模型蒸馏出一个该读多长的参考信号让时长预测器去学习在推理阶段时长预测器给出每个音素的帧数然后按帧数把音素特征复制扩展成目标长度的特征序列再经过解码网络生成声学特征。这就像导演拿到剧本先标注每个字应该拍几秒然后按时间轴铺开拍摄。好处是生成过程可控、速度快坏处是训练流程复杂还需要额外的对齐信息。而且一旦时长预测偏差整句话的节奏就会很奇怪。我见过不少系统为了省事把时长预测器去掉改用固定时长结果合成语音像机器人念稿。但真正投入生产时非自回归路线仍然是低延迟场景的首选因为它方便对时长做后处理例如客户端可以灵活拉长或缩短停顿。3.3 潜变量路线用重参数化把文本直接变成波形的中间码头潜变量路线是我个人觉得最接近一气呵成的做法。它借鉴变分自编码器的思想文本经过编码器之后先映射到一个带约束的潜在空间再从潜在空间采样得到声学相关表征最后经过解码器生成波形。为什么要在中间加一个潜变量因为语音本身存在多样性同一句话可以有无数种说法包括音色、语速、情绪的细微变化。如果模型只输入文本输出是确定性的那合成出来永远是同一个人的同一副腔调非常呆板。潜变量相当于为每种可能的说法提供一个随机种子让模型能生成自然的波动。实际训练时这个潜变量通常会配合标准化流normalizing flow或扩散过程来增加表达能力。许多端到端模型把对齐、时长建模、波形生成全部放进同一个训练目标里我们可以用类似重构损失 KL 散度 对抗损失的混合目标去优化。效果上潜变量路线的自然度上限比显式时长预测更高但训练不稳定也需要花更多精力调参。如果你想在一个新语种上快速做出自然度不错的系统潜变量路线值得优先尝试。4. 最后一步从中间表征到波形声码器怎么配合4.1 声码器的作用神经解码器中间表征不管长什么样最后都必须变成时间域采样点。声码器就是干这件事的神经网络解码器。早期声码器其实是一些信号处理算法比如线性预测、共振峰合成后来被神经网络取代。神经声码器的输入一般是声学特征输出是一段波形信号本质上是一个巨大的上采样模块把低频特征一步步上采样到原始采样率然后在每个时间步预测采样值。声码器的训练同样离不开对抗学习因为直接回归到采样点会倾向于生成过度平滑的音频听起来像隔着一层棉被。对抗训练让生成器学会模仿真实语音的细节同时配合多尺度 STFT 损失、特征匹配损失等。实际工程里声码器的参数量往往比主干还大推理也相当耗时很多性能优化的重点都压在这一层。我用一个类比帮助大家理解主干网络负责把文本变成一幅画的构图和色彩声码器则负责把画变成真实的布料纹理。构图再好如果纱布质感不行整体听感也会崩塌。所以在端到端模型里声码器绝不是可有可无的附属品它的设计质量直接决定了最终音频的上限。4.2 可微对齐让模型自己学会每个音素说多长回到时长问题。潜变量路线和显式时长预测路线最大的区别之一是对齐是怎么得到的。显式路线需要外部工具提供对齐信息而一体化架构倾向于把对齐也变成可优化的部分。一个常用做法是单调对齐搜索在训练时给定文本音素序列和目标声学特征序列用动态规划找到一条最优的单调对齐路径要求每个音素至少覆盖一个特征帧并保证顺序不倒转。因为语音中音素出现的顺序不会变这个单调性约束很合理。得到对齐路径后让模型按这个路径把音素特征扩展成对应帧数再重建波形。由于整个路径搜索过程可以写成可微形式模型就可以反向传播学习。推理阶段没有目标波形所以需要一个模块预测每个音素应该持续多少帧常见做法是设计一个随机时长预测器既能输出平均时长也能引入一些随机变化让语音节奏更自然。这里有一个需要注意的陷阱对齐搜索必须在训练时保持数值稳定。如果目标特征帧数特别长动态规划的计算量会很大也容易出现下溢。实际实现时通常会在 log 空间运算并把中间结果做数值裁剪。一旦你发现训练早期 loss 一直在下降但音频出现明显的吞字很可能就是对齐路径出了问题。4.3 训练目标一锅烩重构、对抗、KL 散度端到端一体化模型的训练目标通常是一个加权组合。重构损失让生成的波形与真实波形尽量接近可以用 L1/L2 距离也常用多尺度 STFT 损失来捕捉时频域误差。对抗损失让生成分布贴近真实语音分布判别器负责区分真实波形和生成波形。KL 散度约束潜变量分布不要偏离先验太远避免模型过拟合到训练集的某种固定模式。这三者的权重很需要耐心。我刚开始训练时KL 权重设得过大模型很快变成确定性输出自然度急剧下降后来调小又开始出现明显的不稳定噪声。一个实用的经验是先把重建损失和对抗损失适当加大让模型能稳定生成语音再逐步提高 KL 权重来约束潜变量。损失曲线不能只看标量还要隔一段时间听一次合成音频毕竟耳朵比指标更诚实。另外判别器训练要防止过拟合。如果判别器太强生成器会躲进一个安全的平滑区域音频会变得干涩。建议给判别器加入谱归一化或梯度惩罚并且控制生成器和判别器的更新比例通常判别器每更新一到两次生成器更新一次就够了。5. 推理落地一体化模型不是训练完就能上线的5.1 导出与动态 shape 处理训练好的模型不能直接拿来做线上服务。第一步要想清楚输入输出规格。训练时通常按 batch 处理每条样本长度都不固定而线上推理往往是单条短文本还可能要做流式播放所以模型导出时要把动态 shape 处理好好。很多推理框架支持动态维度但动态维度会带来额外开销。我的做法是线下把输入音素序列的长度分为几个档位比如小于 50、50 到 100、100 到 200分别按最大长度做 padding让每一次推理都能以接近静态 shape 的方式运行。这样既保留灵活性也减少形状推断的时间。另外模型的中间缓存、潜变量随机采样过程都要固定下来避免导出后行为不一致。如果使用低精度推理还要检查哪些层对精度敏感。LayerNorm、注意力打分这类计算往往对低精度很敏感稍微掉精度就会出现音质下降。我通常先把整个模型转成半精度再单独把敏感层留在单精度找到效果和速度的平衡点。5.2 流式推理与首包延迟控制实时合成场景最关心的两个指标是首包延迟和实时率。首包延迟指用户发出请求到听到第一个音频块的时间实时率指合成一段音频所需时间与音频时长之比。端到端一体化模型天然具备流式潜力因为编码器可以在读完句子后直接开始解码解码器可以在生成一部分采样点后就立刻输出。实际做流式时需要把文本按标点拆分成小句逐句推理同时保留说话人上下文。这里要注意不要在小句中间硬切否则音素边界会断裂。宁可让首包稍微晚一点也要保证整句内不出现丢字或者卡顿。我的经验是小句长度设置为 5 到 15 秒之间既能满足低延迟又能让韵律相对完整。还要考虑音频播放缓冲。如果模型输出太快播放端跟不上会产生爆音如果输出太慢则会有卡顿感。所以通常会在客户端加一个环形缓冲区控制播放节奏。这个环节看似简单但线上很多听感问题其实都出在缓冲策略上而不是模型本身。5.3 数值稳定性与音频质量抖动排查一体化模型在推理时偶尔会出现音频抖动、爆音甚至静音的 bug很多都是数值稳定性问题。比如半精度推理时某些层的中间值溢出输出波形直接变成 NaN也可能是在波形后处理阶段除零或边界裁剪处理不当导致个别采样点出现尖刺。排查这类问题我习惯先固定随机种子再用一段短文本逐层打印中间结果的最大值和最小值看看哪一层开始出现异常。也可以在导出模型后做一个前后对比用同一段文本在训练框架和推理框架分别合成如果波形差异明显多半是算子实现或数值精度不一致。这类问题没有捷径只能靠日志和对照实验逐步定位。还有一个容易被忽略的点随机时长预测器在推理时产生的随机性会被某些服务端缓存逻辑放大。同一个文本如果第一次合成正常、第二次变调别急着怀疑模型先检查是不是缓存了错误的中间状态。把随机种子或采样模式的配置写清楚能减少很多诡异问题。6. 我实际踩过的几个坑经验之谈6.1 音素序列错位导致重音全乱有个项目需要把中文数字作为重点输入前端规则里对一的变调没有处理好。模型训练时没有暴露足够多的语料线上出现一个被读成 yì gè 还是 yí gè 的不稳定。一开始所有模块都没问题但一听合成音频重音位置明显偏了。后来查了训练数据的音素序列才发现有相当一部分样本标注错误模型学了一个混乱的分布。这个教训是端到端模型虽然能学对齐但输入侧的脏数据会造成无法挽回的后果。对于容易出错的音素最好建立一份高频词表做强制校正不要完全依赖通用规则。你付出了大量精力调模型可能效果还不如花两天清洗一遍训练语料的音素标注。6.2 采样率不匹配的小黄人效果有一次模型训练完合成效果很好但部署到另一台服务器上突然变成了小黄人声音。最后发现是前端和后端的采样率不一致训练阶段声码器输出是 24kHz部署时某个预处理模块把输入重采样成了 16kHz但模型内部仍然按 24kHz 的节奏去理解结果整个音频被拉伸音调升高。排查起来也简单把输入波形拉出来看一眼时长和采样率就懂了。建议大家在做推理服务时把采样率写进配置中心前处理、模型、后处理必须从同一个配置源读取别让任何模块私自决定采样率。这种问题一旦出现用户听到的反馈是声音怪怪的但不会告诉你具体原因排查成本反而比模型问题更高。6.3 KL 散度坍缩和过度平滑潜变量模型有个典型毛病训练到后期KL 散度项会突然掉到接近 0模型变成了一个普通的确定性映射合成的音频开始每个句子都一个调子。很多人以为这是模型收敛了其实这是 KL 坍缩。解决思路有很多。简单一点的做法是 KL 退火训练早期把 KL 权重设得很小等重构基本稳定后再逐步恢复权重复杂一点的做法是在潜变量空间加一些噪声扰动强制模型利用随机性。还有一种比较实用的技巧每隔一定步数合成几个句子来检查多样性不要只看 loss。指标再漂亮如果几十句输出都一样平淡说明随机性丢了。最后再分享一个小技巧如果你要在多语种上做扩展不要为每个语种单独训练整套模型。保留一个共享的主干把语言 ID 作为额外的输入 embedding再把常见的声学差异通过说话人向量或语种向量区分开。这样新语种只需要少量语料去微调 embedding 层和部分解码层能大幅节省训练成本同时也不会破坏已有语种的质量。我试过几次之后觉得这是端到端 TTS 落地时性价比最高的扩展方式。
阅读完成 · 觉得有帮助?
咨询建站