先说明一下,这篇是 TTS 架构系列的第三篇。前面我们把文本处理、声学模型、声码器分别拆开聊过,这一篇换个视角,把整条链路串起来:从一段原始文本进去,到最终波形出来,中间到底发生了什么,每一步的数据长什么样,哪些地方容易出问题,以及为什么现在的架构普遍长这样。
如果你正在搭自己的 TTS 系统,或者只是想把原理吃透,这篇应该能帮你把之前散落的知识点焊成一个完整的框架。
1. 先把整条流水线铺开来看
很多文章喜欢一上来就扔出模型结构图,但对于 TTS 这种链路很长的系统,我建议你先在脑子里面建立一条“数据流”的认知,而不是“模型结构”的认知。因为模型可以换、可以改,但数据流几乎是固定的。
1.1 从文本到波形,中间到底过了几道手
一条完整的 TTS 链路,无论是最早的拼接法,还是现在的端到端,本质上都是在做同一件事:把“人写的符号”变成“人听的声音”。中间大致要经过四个阶段:
- 文本前端:把原始字符串变成语言特征。包括分词、注音、数字归一化、多音字消歧、韵律预测等。
- 声学特征预测:把语言特征映射成声学参数,典型代表是梅尔频谱(Mel Spectrogram),有时也包括音素时长、基频 F0。
- 声码器:把声学参数还原成可播放的波形。这个环节决定音质的“上限”,尤其是自然度和还原度。
- 后处理与流式输出:包括音量归一化、静音裁剪、流式传输分块,这部分在工程落地时避不开。
这里有一个很容易被忽略的点:不同阶段之间传递的不是“文本”,也不是“声音”,而是中间表示(Intermediate Representation)。你如果能把每一步中间张量的形状、含义、取值范围搞清楚,调试起来会顺手很多。
1.2 “一气呵成”到底指什么
“一气呵成”这四个字,放在 TTS 里有两层意思。
第一层是概念上的——从文本到波形,整条链路逻辑连贯,中间任何一环断裂,声音就出不来。哪怕声学模型训得再好,声码器不匹配,出来也是噪音。
第二层是工程上的——现代 TTS 越来越多地朝“单一模型、一次前向”的方向走。早期系统是文本前端跑一遍、声学模型跑一遍、声码器再跑一遍,三个模型独立部署,中间用文件或队列传递中间结果。现在主流的方案是把它们拆成模块但还是串在同一个进程里,甚至把声学模型和声码器融合蒸馏成一个模型,真正做到“文本进、波形出”。
我见过不少团队在做实时语音合成时,瓶颈根本不是模型精度,而是模块之间序列化/反序列化的开销。文本前端输出 JSON、声学模型吃 JSON 再写 Numpy、声码器再读 Numpy——光是这些 IO 就能吃掉几十毫秒。所以“一气呵成”不只是一个说法,它是实打实的性能优化方向。
2. 输入端:文本前端到底在干什么
很多人觉得文本前端“没什么技术含量”,无非是查字典。这个认知在中文 TTS 里特别要命。中文的文本前端,复杂程度远超英文,可以说是整个链路里最“人工”的部分。
2.1 分词语法消歧:一字多音是第一个坎
中文文本前端的第一步是分词。分词的准确性直接决定后续注音的准确性。比如“重庆”这个词,分错了,就得念成“zhòng qìng”而不是“chóng qìng”。而分词本身又依赖上下文,“北京大学生”到底是“北京/大学生”还是“北京大学/生”,意思完全不同,读音也不同。
多音字消歧是另一个大头。现代方案通常不是靠硬编码规则,而是训练一个基于上下文的消歧模型,输入是当前词和前后若干个词,输出是当前字的读音。这里有个实操细节:消歧模型的特征里,最好把词性(POS)也加进去。“音乐”的“乐”是名词读“yuè”,“快乐”的“乐”是形容词读“lè”,词性这个特征在不少场景下比单纯的 n-gram 管用。
注意:多音字消歧做不到 100% 正确。即便是最好的模型,在生僻人名、地名上也会翻车。工程上建议保留一个“人工校正表”,对于产品里的高频实体词(比如品牌名、作者名)做强制注音覆盖。
2.2 数字与符号归一化:细节里藏着大坑
数字归一化是文本前端里最“琐碎”也最“容易出错”的地方。“2024年”读“二零二四年”还是“两千零二十四年”?“3.14”读“三点一四”还是“三点十四”?“12345”作为电话号码和作为数值的读法完全不同。
一个相对稳妥的归一化流程是这样:
- 先识别数字的类型:年份、日期、时间、电话、数值、序号。
- 不同类型走不同的转换规则。
- 规则覆盖不了的特殊写法(比如版本号 “V2.0”、价格 “¥99.9”),单独枚举。
这部分我的建议是宁可多做规则,不要盲目上模型。规则可解释、可修复、可回归测试,模型虽然能 cover 更多边界情况,但出了错很难定位。实操中我们是规则为主、模型兜底,把模型用在“识别数字属于什么类型”这个环节,具体转换还是交给规则。
2.3 韵律预测:让合成声不再“一字一顿”
韵律预测是文本前端里最容易被初学者忽略的环节。没有韵律信息,即使每个字的读音都正确,合成出来的声音也是平的、机械的,像一个一个字往外蹦。
韵律预测一般输出三个层级的停顿标记:韵律词边界(P1)、韵律短语边界(P2)、句群边界(P3)。其中 P2 和 P3 对听感影响最大,停顿时间的长短、停顿处音高的变化,直接决定一句话是否自然。
现在主流的做法是用 BERT 之类的预训练模型提取文本语义向量,再学一个韵律标记预测头。实践下来,BERT 的特征对韵律预测的帮助非常明显,尤其是在处理长句和多分句时,比传统的 CRF 方案高出不少。代价是引入了额外的模型开销,在低延迟场景里,可以用一个轻量的蒸馏版本来替代。
3. 中间层:声学模型是如何把文本变成频谱的
声学模型是整个 TTS 系统的“大脑”。文本前端输出的语言特征,在这里被转换成声学特征序列。这一块是过去十年变化最大的部分,也是“架构篇”里真正值得花时间理解的核心。
3.1 自回归与非自回归:两条路线的博弈
声学模型从生成方式上可以分成两大类:自回归(Autoregressive,AR)和非自回归(Non-Autoregressive,NAR)。
自回归模型(如 Tacotron 2)逐帧生成梅尔频谱,每一帧都依赖之前已经生成的所有帧。优点是音质自然、连贯性好;缺点是生成速度慢,而且存在“误差累积”问题——前面某一帧预测偏了,后面会越来越偏,极端情况下会出现重复发声或吞字。
非自回归模型(如 FastSpeech 2)一次并行生成整段频谱。优点是速度快、可控性强(可以显式控制每个音素的时长),缺点是需要额外的时长预测器,而且并行生成时容易丢失局部连贯性。
我个人的经验是:离线合成场景优先考虑非自回归,速度快、稳定性好,配合好的声码器音质差距已经很小;实时 streaming 场景里,很多团队还是会回到自回归或者半自回归的思路,因为逐帧生成天然适合流式输出。
3.2 时长预测:非自回归的灵魂部件
非自回归模型里面有一个关键组件:时长预测器(Duration Predictor)。它负责告诉系统,每个音素应该持续多少毫秒。这个看似简单的模块,决定了合成的语速、节奏和自然度。
时长预测器的训练数据来自自回归模型的对齐结果。典型的做法是:先用一个训练好的自回归模型(或者用 CTC 对齐)拿到文本和频谱之间的对齐关系,统计出每个音素对应的帧数,然后用这个帧数作为监督信号来训练时长预测器。
这里有个实操心得:直接回归帧数效果并不好,因为不同音素的时长差异很大,模型容易偏向预测平均值。更好的做法是预测对数域的帧数,或者用 MSE + 相对损失(relative loss)。我们后来还加了语速控制系数,通过简单地对预测时长做线性缩放,就能让用户调节语速,这是 TTS 产品里呼声很高的功能。
3.3 从梅尔频谱到波形的那“最后一公里”
声学模型输出的梅尔频谱,不是直接可以播放的声音。它只是一种“听感相关的压缩表示”——把频率按梅尔刻度重排、把能量取对数,目的是模仿人耳的听觉特性。
要把梅尔频谱还原成波形,需要声码器。声码器解决的是“最后一公里”的问题:给定频谱,生成对应的时域波形。这一步做到位了,音质上限就高;做不好,前面所有模型的努力都会被拉垮。
目前主流的声码器路线有三条:
| 声码器类型 | 代表方案 | 优点 | 缺点 |
|---|---|---|---|
| 自回归波形生成 | WaveNet、WaveRNN | 音质极高 | 生成速度太慢,难落地 |
| 流生成模型 | WaveGlow、WaveGAN | 速度快,音质好 | 训练不稳定,调参困难 |
| GAN 判别式 | HiFi-GAN、Vocoder | 速度快,音质逼近 WaveNet | 需要细致训练,容易失真 |
从我的落地经验来看,HiFi-GAN 是当前性价比最高的选择。它基于 GAN 的思想,生成器负责从梅尔频谱还原波形,判别器负责判断音频是真是假,两者对抗训练,最终生成器学会以极快的速度输出高保真波形。
注意:声码器和声学模型不是独立训练的。两者的匹配度直接影响最终听感。强烈建议在声学模型训练时,就把目标声码器固定下来,避免“训练时用 A 声码器、推理时换 B 声码器”这种操作。我见过好几个团队在这上面吃到苦头,一换声码器整个声音风格都变了。
4. 整链路实践:从零搭一个文本到波形的极简系统
前面讲了不少概念,这部分我把一条极简但完整可跑的链路串起来,包括每一步的输入输出、关键参数和解法,方便你对照复现或改造。
4.1 数据准备与预处理管线
任何 TTS 项目的第一步都是数据。你需要准备一定数量的“文本-音频”对。数据规模上,单说话人多音色合成,10 小时左右的干净数据可以训练出听起来还不错的模型;要追求高表现力或多情感,数据量需要提升到 50 小时以上。
数据预处理管线建议按以下顺序搭建:
- 音频降噪与响度归一化:统一音频的响度标准,避免模型学到不需要的音量差异。
- 静音切边与 VAD:把首尾静音裁掉,或者统一保留固定长度。
- 文本清洗与注音:对应前文所述的文本前端流程,输出音素序列。
- 频谱提取:用短时傅里叶变换(STFT)提取梅尔频谱,常见的设置是 fft_size=1024、hop_size=256、win_size=1024,采样率 22050 Hz,Mel 频带数 80。这些参数是经过大量实践证明好用的默认值,不建议新手轻易改动。
4.2 声学模型训练:FastSpeech 2 为主干
这里我以 FastSpeech 2 为例,因为它结构清晰、工业落地最广泛。核心流程分三步:
第一步,用 Tacotron 2 或者现有的对齐工具(比如 MFA,Montreal Forced Aligner)拿到文本-频谱的对齐关系,统计每个音素的帧数,作为时长标签。
第二步,训练 FastSpeech 2 主网络。输入是音素序列、音素时长、F0、能量,经过 Encoder、Variance Adaptor 和 Decoder,输出预测的梅尔频谱。损失函数是预测频谱与真实频谱之间的 L1 距离,加上时长、F0、能量三个预测头的损失。
第三步,推理时,文本经过前端变成音素序列,时长预测器给出每个音素的帧数,F0 和能量预测器给出韵律信息,Decoder 输出完整频谱,最后交给声码器。
在实际训练中,有几个经验值得记录:
- 学习率调度优先选 Noam 衰减或 Warmup + Cosine 衰减,峰值学习率设为 1e-3 量级,batch size 在显存允许的前提下尽量大。
- 频谱预测损失,L1 比 L2 效果好。L2 会过度惩罚大误差,导致生成频谱过度平滑,听感偏“闷”。
- 训练到后期,可以加入一些“韵律扰动”:随机缩放 F0 和时长标签,能提升模型的泛化性,让合成声音更自然,而不是死板地复读训练集中的音频。
4.3 声码器接入:HiFi-GAN 微调与匹配
声学模型训好后,接入 HiFi-GAN 通常不需要从零训练。可以直接下载预训练权重,但建议用你自己数据集的音频做几步微调(fine-tune),让声码器熟悉你这条 pipeline 的频谱分布。
做法很简单:用你已经训好的声学模型生成一批频谱,然后喂给 HiFi-GAN 的生成器,用判别器去判断生成波形和真实波形,更新生成器和判别器。一般微调几千步就够了,注意学习率要调小(1e-4 量级),避免破坏预训练权重里已经学到的优质声学先验。
微调后记得做一次 A/B 听测对比。如果微调后的效果反而不如预训练权重,说明你的频谱分布和预训练数据的差异不大,或者微调步数过长导致灾难性遗忘,这时候回退到预训练权重就行。
4.4 全链路整合与推理优化
当声学模型和声码器都准备好之后,把它们串起来的时候,有几个工程细节需要特别处理。
缓存机制:文本前端的结果(音素序列、韵律标记)在重复合成相同文本时不会改变,可以做缓存。对于高频固定话术(比如导航提示语、系统播报),直接缓存完整波形,连声学模型都不用跑。
批处理:如果是离线合成一批文本,不要一条一条跑。把多条文本拼成 batch,声学模型和声码器都能并行推理,吞吐能提升数倍。
流式播放:如果产品需要边合成边播放,需要把声学模型改成流式解码,或者用“按句切分 + 并行合成”的思路:把长文本按句子切分,每句独立合成,合成完一句就播一句,同时合成下一句。这个方案实现简单、效果稳定,是目前最推荐的流式方案。
5. 常见问题与排查技巧实录
TTS 系统排查问题比普通 NLP 系统麻烦,因为“难听”本身不是结构化反馈,你很难说清楚到底哪一环节坏了。这里我把这些年踩过的坑整理成一张速查表,以及几个典型的实战排查案例。
5.1 声音模糊发闷:八成是频谱参数或声码器的问题
现象:合成出来的声音像隔着一层布,高频细节缺失。
排查顺序:先确认声码器的输入频谱分布是否和训练时一致,尤其是梅尔频谱的 min/max 归一化参数。如果声学模型输出的频谱范围是 [-4, 4],而声码器训练时见的是 [-5, 5],高频部分就会过饱和或被截断,声音自然发闷。
还有一种常见情况是 hop_size 不匹配。声学模型推理时用的 hop_size 和声码器期望的不一致,会导致频谱长度对不上,声码器强行拉伸或压缩时间轴,听感就会模糊。这类问题很难通过听感定位,建议在接口处打印频谱形状和帧率信息。
5.2 复读、吞字、跳字:自回归模型的典型毛病
现象:某几个字反复念,或者整个词被跳过去。
这类问题在自回归声学模型里几乎一定会遇到。根源是误差累积:某个音素的对齐偏了,模型进入“循环”状态,不断输出相似帧。排查分两步:看是训练时就存在,还是推理时才出现。
训练时就存在,说明对齐监督信号有问题,需要检查时长标签的分布是否合理,是否有过长/过短的异常值。推理时才出现,可以考虑加入“注意力惩罚”或“覆盖损失”(coverage loss),强制注意力单调对齐。不过更实际的建议是:如果复读问题频繁,干脆换非自回归架构,从机制上绕开这个问题。
5.3 声音“电音”感强:声码器训练不充分或 F0 异常
现象:声音有金属感、电音感,尤其在韵母部分。
多数情况和基频 F0 相关。FastSpeech 2 这类模型会显式预测 F0,推理时如果 F0 预测值超出了训练数据的分布范围,声码器无法正确还原振动周期,就会产生金属感。
建议在声学模型输出 F0 时做一步“裁剪”:把预测 F0 限制在说话人正常范围内的 0.7~1.3 倍,超出就拉回边界。虽然理论上损失了一些表现力,但能显著减少电音感。另外检查 F0 预测是否做了平滑处理,我试过对 F0 序列做 5 帧中值滤波,电音问题改善非常明显。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 声音发闷,高频缺失 | 频谱归一化范围不匹配 | 检查声码器输入频谱统计量 |
| 复读/吞字 | 自回归误差累积 | 训练集对齐质量、注意力惩罚 |
| 电音感/金属感 | F0 预测异常 | F0 裁剪、中值滤波、训练数据 F0 范围 |
| 语速偏快/偏慢 | 时长预测器偏差 | 检查时长标签统计值,调整语速系数 |
| 单个字很刺耳 | 声码器对异常频谱敏感 | 检查频谱局部能量峰值,做平滑 |
| 合成停顿不自然 | 韵律标记缺失或错误 | 检查文本前端 P2/P3 层级预测 |
5.5 两个容易被忽略的“真香”技巧
最后分享两个小技巧,都是我在实际项目中验证过效果的。
第一个是推理时的随机采样。很多模型推理时用的是贪心解码(取概率最大的输出),但 TTS 这类生成任务,贪心反而会带来“过于平滑”的问题,声音显得僵。改用带温度的随机采样(temperature=0.667 左右),每次合成的语音会有微小的自然波动,听感显著更自然。代价是同一文本每次合成结果不完全一样,如果你需要一致性(比如同一个引导词每次发音必须一模一样),就不要用这个技巧。
第二个是前后文本拼接合成。合成长文本时,如果按句切分逐句合成,句与句之间容易出现语调断裂。我的做法是:每句话合成时,把前一句话的最后几个字拼接进来作为额外输入,让模型知道上文语境,输出时再截掉这几个字对应的音频。这个方法不增加多少成本,但句间衔接的自然度提升非常明显。
6. 写在最后的个人体会
我在折腾 TTS 这条链路的过程中,最大的感受是:这个方向的难点不在单个模型,而在模块与模块之间的匹配和调试。文本前端多一个错别字,声学模型可能就生成一段奇怪的频谱,声码器再把它放大成更奇怪的波形。每一层的小误差会叠加、会放大,这也是为什么“端到端”这个口号喊了很多年,真正落地时大家还是老老实实把管线拆开调。
如果你刚开始接触 TTS,不要急着追求新模型、新架构。先把文本前端做扎实,把频谱参数吃透,把声码器的输入输出调对齐,跑通一条最小链路,再逐步替换模块。等你亲手把一个文本转成了一段自然流畅的音频,那种“一气呵成”的成就感,比换多少次新模型都来得实在。