04|PyTorch 实现:架构映射与结构验证
返回 Tacotron 总览 · 前一篇:联合训练与波形重建 · 完整源码
本实现用于把 CBHG、注意力和多帧反馈连接成可运行的计算图。输入是两条等长字符 ID 序列与随机声学目标,输出为预测帧和注意力矩阵。读代码前,先根据图确认三类数据:整句文本记忆、单步声学输入、跨步递归状态。
1. 代码架构与实现边界

图 1|教学实现的模块调用、反馈与状态
图中箭头对应 TinyTacotron.forward。memory 在进入解码循环之前计算;每个解码步更新 aq、d1、d2,输出两个不同帧,再从块的最后一帧构造下一步输入。训练时该反馈改为目标序列的对应真实帧。
教学实现缩小维度,保留计算关系;它不是原论文全配置复现。
| 部件 | 原论文 | 本实现与理由 |
|---|---|---|
| 文本编码 | embedding 256、两层 pre-net、CBHG bank 1–16 | embedding 16、单层 pre-net、bank 1–4;缩小 CPU 演示规模 |
| CBHG | 四层 highway、双向 GRU | 保留这两项、残差、BN 与多分支结构 |
| 注意力 | 内容 tanh attention | 显式加性打分、文本轴 softmax、掩码接口 |
| 声学解码 | 两层残差 GRU、、80 通道 | 两层 GRU,明确的教学残差约定;、8 通道 |
| 后处理与声码 | 频谱 CBHG、Griffin–Lim | 未实现,已在上一篇解释原理和接口 |
| 训练数据 | 约 24.6 小时内部英语单说话人数据 | 随机目标,仅用于结构测试 |
| 结束规则 | 与补齐训练等设计相关 | 显式指定步数,不预测停止概率 |
教学 residual 写法为“每层 GRU 输出加本层输入,再送到上层”;递归隐藏状态保存 GRU 自身输出。原论文没有逐行指定这些连接代码,因此这部分作为明确记录的教学实现选择,不声称与某个官方 checkpoint 完全一致。
2. 张量接口与轴约定
| 代码变量 | 本次形状 | 含义 |
|---|---|---|
tokens | [2,4],torch.int64 | 两句,每句四个有效字符;0 保留给 padding,本演示禁止传入 0 |
targets | [2,6,8],浮点 | 六帧、八个教学特征通道;随机数不代表真实 mel |
memory | [2,4,32] | CBHG 双向 GRU,每方向 16 维 |
aq | [2,32] | attention GRU 状态/查询 |
weights | [2,4] | 当前解码步的文本权重 |
context | [2,32] | 对文本记忆加权求和 |
d1、d2 | 各 [2,32] | 两个声学 GRU 的独立状态 |
block | [2,2,8] | 一步产生两帧 |
| 拼接后的预测 | [2,6,8] | 三步的块沿帧轴连接 |
alignment | [2,3,4] | 批 × 解码步 × 文本位置 |
批大小 、文本长度 、目标帧数 、通道数 、每步帧数 。这些字母只描述形状,不是可学习参数。
3. CBHG 的长度保持与偶数卷积核
多个卷积分支必须保留相同的文本长度才能拼接。对于偶数核,仅写对称 padding=k//2 往往会多出一个位置。代码显式补总计“核宽减一”的位置,左右可以不对称:
# x: [B,C,N];这里先做 padding,再做不带 padding 的 Conv1d。
left = (kernel - 1) // 2
right = kernel - 1 - left
x = F.pad(x, (left, right))
out = conv(x)
| 核宽 | 左补 | 右补 | 输出长度:输入长度加总补齐减核宽加一 |
|---|---|---|---|
| 1 | 0 | 0 | |
| 2 | 0 | 1 | |
| 3 | 1 | 1 | |
| 4 | 1 | 2 |
池化同样使用步长 1。代码右侧补负无穷,避免补齐值在最大池化里胜出。这些是序列边界补齐,不是“用 0 字符补齐批内短句”的同一个问题。
注意力掩码不能自动修复双向 GRU 对文本 padding 的读取。
完整变长批处理还需要卷积边界/长度管理以及 GRU packing 或等效处理。本演示拒绝含 0 的输入,并要求等长无补齐文本;单独测试 Attention 的掩码能力,避免给人“加了 softmax mask 就完成全部变长处理”的错觉。
4. 反馈选择与训练、推理入口
下面片段位于完成当前 block 预测之后。此处 s 从 0 开始,因此选的是当前组最后一帧,供下一步使用:
# block: [B,r,M];targets: [B,T,M];previous: [B,M]
previous = (targets[:, (s + 1) * reduction - 1]
if targets is not None else block[:, -1])
当 s=0,r=2 时,训练选择 targets[:,1],推理选择第一组的第二帧。下一步预测目标是帧 2、3;若误取目标帧 2,就向模型提供了当前要预测的信息。
| 调用 | 历史来源 | 步数 |
|---|---|---|
model(tokens, targets) | 真实目标最后一帧 | 从目标帧数除以 reduction 得到 |
model(tokens, steps=3) | 上一预测块最后一帧 | 显式指定 3 步 |
测试推理前调用 model.eval(),使本演示的 dropout 关闭、BN 使用已有统计值;配合固定输入比较索引关系。真实训练/推理的 dropout 策略可能采用不同约定,不能把这个测试模式当作原版全部推理细节。
5. 已执行的结构验证
运行环境为 Python 3.12.14、PyTorch 2.14.0+cpu,CPU 单线程、随机种子 7。命令与完整代码见附录。模型共 33,872 个参数,没有进行语音训练。
| 检查 | 实际结果 | 能验证的性质 |
|---|---|---|
| 注意力手算 | 权重约 [0.1,0.2,0.7];context 约 [2.2,1.1] | 与正文数值例一致,使用 PyTorch 默认 close 容差 |
| padding 扰动 | context 最大绝对差 0 | 被 mask 的位置在当前例中不影响读取 |
| 修改真实帧 3 | 预测前四帧最大差 0 | 帧 3 只从第三个解码步开始作为历史输入 |
| 同一扰动的后两帧 | 最大差约 0.113219 | 测试确实触发了后续依赖,不是两路都忽略输入 |
| 训练/推理输出 | 均为 [2,6,8] | 分组与拼接长度一致 |
| 注意力矩阵 | [2,3,4] | 解码步数与文本轴正确 |
| 反向传播 | 73 个参数张量获得有限梯度 | embedding 和 attention 打分层都有非零梯度 |
这些测试把数值关系和数据依赖变成可检查的证据,尤其能发现错一帧的反馈错误。
随机目标的 L1 约为 0.841422,只用于检查反向传播。它不是收敛曲线,更不是音质指标。不同 PyTorch 版本的初始化或算子实现可能使最后几位以及随机测试值变化;应该关注断言与关系,不要求跨版本逐位相同。
6. 从演示到真实训练的补全路径
| 需要补全 | 最小验收动作 |
|---|---|
| 文本规范化、字符表 | 数字和标点有确定映射;训练/推理一致 |
| 音频预处理 | 真值频谱可重建;固定采样率、窗口、log 和归一化 |
| 变长数据与补齐 | 短句批处理结果与正确长度处理一致;损失补齐策略记录清楚 |
| 原版维度与后处理 | 从编码到 80 通道 mel、1025 通道 linear 的所有接口通过检查 |
| 结束与自由生成 | 最长步数有上限;短句、长句、标点样本都不无限重复 |
| 评估 | 同时检查内容正确性、声音质量、对齐与泛化,不只看训练 loss |
这些扩展尚未执行。附录的定位是可读、可跑、可检验的机制演示,而不是把随机张量接成网络后宣布完成语音合成复现。
正在连接 GitHub 评论…