Lazy loaded image
GlowTTS
Words 9334Read Time 24 min
2025-7-10
2025-8-30
type
status
date
slug
summary
tags
category
icon
password

网络结构

代码
参考的 CoquiTTS 对于 GlowTTS 的实现代码
以下是一些主要文件
/home/u-wuhc/.local/share/tts/
/home/u-wuhc/TTS/TTS/utils/synthesizer.py
/home/u-wuhc/TTS/TTS/utils/audio/processor.py
/home/u-wuhc/TTS/TTS/tts/models/glow_tts.py
/home/u-wuhc/TTS/TTS/tts/configs/glow_tts_config.py
/home/u-wuhc/TTS/recipes/ljspeech/glow_tts/train_glowtts.py
/home/u-wuhc/TTS/recipes/ljspeech/glow_tts/test.py
/home/u-wuhc/TTS/TTS/config/shared_configs.py
/home/u-wuhc/TTS/toMel.py
 

TODO

Text Tokenizer
Losses 是什么
训练过程
Dataloader (train)
论文
load checkpoint
save model
 
如何处理长度不同的 x 输入
x : text IDs
在 Encoder 中处理

GlowTTS 如何处理长度不同的 x 输入

🎯 1. 批量处理不同长度的文本

GlowTTS 使用 padding + mask 的策略处理不同长度的文本输入:

🎯 2. 编码器中的 Mask 处理

🎯 3. 参数 num_chars 的含义

num_chars词汇表大小(vocabulary size),表示模型能识别的不同字符/token的数量:

🎯 4. 编码器中的处理流程

🎯 5. 实际应用示例

🎯 6. 长度处理的完整流程

🎯 总结

  • num_chars: 词汇表大小,定义了模型能处理的不同字符/token数量
  • 长度处理: 通过padding统一批次内的序列长度,用mask标记有效部分
  • 核心思想: 物理上统一长度(便于并行计算),逻辑上保持原始长度(确保正确处理)
这种设计使得GlowTTS能够高效地处理任意长度的文本输入!
forward 和 inference 函数的区别
forward 用于训练
inference 用于推理
y_lengths 是 mel-spectrogram 的真实长度

🎯 关键区别

  • y_lengths 每个样本的实际有效长度
  • T_de 批次中的最大长度(用于张量填充)

🔍 详细解释

🎯 在 forward() 中的处理

🎯 为什么需要 y_lengths

🎯 总结

  • T_de: 张量的物理维度(最大长度)
  • y_lengths: 每个样本的逻辑长度(实际有效长度)
  • 作用: y_lengths 用于生成掩码,确保模型只处理有效数据,忽略填充部分
所以 y_lengths 不是 T_de 的值,而是每个样本在 T_de 维度上的有效长度
 

inference 过程

notion image
文本 -> 编码器 -> (均值μ, 时长d) -> 扩展对齐 -> 采样z -> 解码器 -> 梅尔频谱图
主要流程
我们可以参考论文中的 Figure 1(b) An abstract diagram of the inference procedure 来逐步分解。

核心思想

Glow-TTS在前向推理时,完全是 非自回归(non-autoregressive)的。这意味着它一次性生成整个梅尔频谱图,而不是像Tacotron 2那样一帧一帧地生成。这个过程不依赖于前一时刻的输出,因此可以完全并行化,速度极快。
最重要的一点是,在训练阶段使用的 MAS(Monotonic Alignment Search)算法在推理时完全不使用。MAS的作用是为训练找到文本和语音之间最可能的对齐关系,并用这个关系来训练时长预测器(Duration Predictor)。一旦时长预测器训练好了,推理时就只依赖它来确定对齐。

推理流程详解 (Step-by-Step)

步骤 1: 文本编码 (Encoder)

  1. 输入: 一段文本序列,通常会预处理成音素(phonemes)序列,例如 "hello world" -> [h, ə, l, oʊ, w, ɜː, l, d]。然后通过 tokenizer 将每个处理为一个整数。
  1. 组件: 文本编码器 (Encoder),它是一个基于Transformer的结构。
  1. 操作: 编码器接收音素序列,并通过多层自注意力(self-attention)和前馈网络进行处理。
  1. 输出:
      • 隐藏表示 (Hidden Representations): 为每个输入的音素生成一个隐藏状态向量 h
      • 先验分布参数 (Prior Distribution Statistics): 从隐藏状态h通过一个线性层(图中的Project)预测出先验分布(一个高斯分布)的均值 μ 和标准差 σ。所以,对于每个输入音素,我们都有对应的 μ_iσ_i

步骤 2: 时长预测 (Duration Predictor)

  1. 输入: 文本编码器输出的隐藏表示 h。(注意:论文提到在训练时长预测器时,会使用stop_gradient来防止其梯度影响编码器的训练,但这在推理时没有影响)。
  1. 组件: 时长预测器 (Duration Predictor)
  1. 操作: 该模块预测每个输入音素应该持续多长时间,即对应多少个梅尔频谱图的帧(frames)。
  1. 输出: 一个时长序列 d = (d_1, d_2, ..., d_T_text)d_i 是一个浮点数,表示第i个音素对应的时长。

步骤 3: 对齐扩展/上采样 (Alignment Expansion)

这是将文本域的信息映射到语音域的关键一步,也是取代训练时MAS算法的一步。
  1. 输入:
      • 来自步骤1的先验分布参数序列 (μ_1, σ_1), (μ_2, σ_2), ...
      • 来自步骤2的预测时长序列 d = (d_1, d_2, ...)
  1. 操作:
      • 首先,将浮点数的时长d_i转换为整数。在Figure 1(b)中,这一步被标记为 Ceil,表示向上取整。这确保了每个音素至少对应一帧,避免了信息丢失。
      • 然后,根据整数时长 d'_i = ceil(d_i),将每个音素对应的 μ_iσ_i 重复 d'_i 次。
      • 例如,如果音素 "h" 的预测时长是 2.3(向上取整为3),那么就将 "h" 对应的 μ_hσ_h 复制3遍。
  1. 输出: 两个被"拉伸"或"扩展"了的序列,μ_expandedσ_expanded。它们的总长度 T_mel 等于所有音素时长的总和(T_mel = Σ d'_i),这个长度就是最终要生成的梅尔频谱图的时间步长。

步骤 4: 采样生成隐变量 (Latent Variable Generation)

  1. 输入: 扩展后的均值序列 μ_expanded
  1. 操作: 从先验分布中采样生成隐变量 z。这个过程非常简单:
      • 首先,生成一个与μ_expanded同样大小的随机噪声 ε,该噪声从标准正态分布 N(0, I) 中采样。
      • 然后,根据公式 z = μ + ε * T 计算隐变量 z
        • μ 就是 μ_expanded
        • ε 是随机噪声。
        • T 是一个温度(temperature)超参数。在推理时,通过调整T可以控制生成语音的多样性。T 越小,语音越接近均值,变化越少;T 越大,随机性越强,韵律(prosody)变化越丰富。
  1. 输出: 一个隐变量张量 z,其维度与最终的梅-尔频谱图相同。

步骤 5: 并行解码 (Flow-based Decoder)

  1. 输入: 步骤4中生成的隐变量 z
  1. 组件: 基于流的解码器 (Flow-based Decoder),它是一系列可逆的转换(Invertible Transforms),如ActNorm、Invertible 1x1 Conv和Affine Coupling Layer。
  1. 操作: 解码器执行逆向转换(inverse transformation)。在训练时,解码器学习将真实的梅尔频谱图 x 映射到隐变量 zx -> z)。在推理时,它执行相反的操作,将采样的隐变量 z 映射回梅尔频谱图 xz -> x)。这个转换过程的每一步都是并行计算的。
  1. 输出: 最终的梅尔频谱图 (Mel-Spectrogram)

总结

Glow-TTS的推理流程可以概括为: 文本 -> 编码器 -> (均值μ, 时长d) -> 扩展对齐 -> 采样z -> 解码器 -> 梅尔频谱图
这个流程的优点非常突出:
  • 快速: 整个过程没有循环依赖,可以完全并行化,推理速度极快,几乎与输入文本长度无关(只与最长文本有关)。
  • 鲁棒: 由于使用了硬对齐(由时长预测器决定),它不会像基于注意力机制的自回归模型那样在处理长文本或重复词时出现注意力错误(如跳词、重复发音)。
  • 可控:
    • 通过调整时长预测器的输出(例如,乘以一个系数),可以轻松控制语速。
    • 通过调整温度T,可以控制生成语音的韵律和音调变化。

Encoder

主要流程
这个编码器模块的核心任务是:接收输入的文本(音素序列),并将其转换为一个富含信息的隐藏表示,这个表示既包含了语音的“内容”信息,也包含了语音的“时长”信息。 它的输出是后续解码步骤的基石。
下面我们按照流程图的顺序一步步解析:

1. 输入与嵌入 (Input & Embedding)

  • 输入 (Input): 编码器的输入不是原始的文字(如"hello"),而是经过预处理的音素序列 (phoneme sequence)。例如,文本"Glow-TTS"会被转换成一串音素ID,如 [jh, l, ow, t, iy, t, iy, eh, s]。使用音素可以更好地处理拼写不规则的单词,使模型学习更加稳定。
  • 嵌入层 (embedding): 这是一个标准的嵌入层。它将输入的离散音素ID(比如整数 [45, 51, 64, ...])映射成连续的、固定维度的向量。这个向量就是音素的初始表示。

2. 前置网络 (Pre-net)

  • 组件 (<prenet>): 在进入核心的Transformer模块之前,嵌入向量会先经过一个“前置网络”。
  • 作用: Pre-net通常由几层卷积层或全连接层构成,并带有非线性激活函数(如ReLU)和Dropout。它的主要作用是对嵌入向量进行初步的特征提取和变换,增加模型的非线性能力和鲁棒性,为后续的复杂处理(自注意力)准备一个更好的输入表示。

3. 核心编码模块 (Encoder Module)

  • 组件 (encoder_module): 这是编码器的心脏,在Glow-TTS中,它是一个基于Transformer的编码器。它由多个相同的块堆叠而成。
  • 作用: 它的核心是自注意力机制 (Self-Attention)。对于序列中的每一个音素,自注意力机制会计算它与序列中所有其他音素的关联度(或称“注意力权重”)。这使得模型能够捕捉长距离的依赖关系和上下文信息。例如,模型可以理解到,一个音素的发音会受到其前后音素的影响。
  • 输出: 经过这个模块处理后,我们得到一个隐藏表示序列 h。序列中的每一个向量 h_i 都编码了第 i 个音素及其上下文的丰富语言学信息。

4. 输出生成 (Output Generation)

从核心编码模块输出的隐藏表示 h 会被用于生成两个关键的输出:
4.1 先验分布的参数 (Prior Distribution Parameters)
  • 路径: h -> <postnet> -> proj_mean / proj_var
  • 解释: 这条路径负责生成语音的“内容”。
    • Post-net (<postnet>): 这里的<postnet>可以理解为最后的线性投影层。
    • 投影 (proj_mean, proj_var): 隐藏表示 h 通过两个独立的线性层,分别预测出先验高斯分布的均值 (mean μ)标准差 (standard deviation σ)。(流程图中的proj_var通常在实现时是预测对数标准差 log σ,以保证其为正并增加训练稳定性)。
    • 结果: 对于输入的每一个音素,我们都得到了一个对应的 μ_iσ_i。这两个参数定义了在生成语音时,该音素应该从哪个高斯分布中采样。μ 决定了音色的基本特征。
4.2 音素时长 (Phoneme Duration)
  • 路径: h -> concat(h, speaker_embed) -> duration_predictor
  • 解释: 这条路径负责生成语音的“时长”“节奏”。
    • 说话人嵌入 (speaker_embed):多说话人(multi-speaker)模型中,为了让模型知道要用哪个人的语速来说话,隐藏表示 h 会与一个说话人嵌入向量 (speaker embedding) 进行拼接(concat)。这个嵌入向量代表了特定说话人的身份特征(包括平均语速)。在单说话人模型中,这一步可以省略。
    • 时长预测器 (duration_predictor): 拼接后的向量被送入时长预测器。这是一个相对简单的网络(通常是几层卷积层),它的任务是为每一个音素预测一个标量值,即该音素应该持续的时长(对应多少个梅尔频谱帧)。
    • 结果: 我们得到了一个时长序列 d,其中 d_i 对应第 i 个音素的预测时长。

总结

Glow-TTS的编码器设计非常精巧,它将复杂的语音生成任务解耦为两个子问题:
  1. “说什么内容?” —— 由 μσ 决定,它们定义了每个音素的声学特征。
  1. “说多长时间?” —— 由 时长预测器 决定,它确定了每个音素的持续时间。
这种设计使得Glow-TTS在推理时可以先通过时长预测器确定好完整的对齐关系,然后一次性、并行地从先验分布中采样并解码出整个梅尔频谱图,从而实现了极快的合成速度和高度的鲁棒性。

MAS

pass

Loss

好的,Glow-TTS在训练时的损失函数(loss)由两个主要部分组成,分别对应模型要学习的两个核心任务:生成正确的声学特征预测正确的音素时长
这两个损失分别是:
  1. 最大似然损失 (Maximum Likelihood Loss)
  1. 时长预测损失 (Duration Prediction Loss)
详细解析这两个损失。

1. 最大似然损失 (Maximum Likelihood Loss)

这是Glow-TTS模型最核心的损失,用于训练文本编码器(Encoder)和基于流的解码器(Flow-based Decoder)
目标: 最大化给定文本条件 c 下,模型生成真实梅尔频谱图 x 的对数似然概率 log P(x|c)。
原理:
Glow-TTS是一个基于流的生成模型,它利用了变量代换公式(Change of Variables Formula)。解码器 f_dec 是一个可逆函数,可以将简单的先验分布(如标准正态分布)中的隐变量 z 映射到复杂的数据分布(梅尔频谱图 x)。
其对数似然可以表示为:
在训练时,我们是反向计算,从  映射到 ):
这个公式包含两项:
  • : 这一项是先验分布的对数似然
    • 首先,通过解码器 f_dec 的逆向传播,将真实的梅尔频谱图 x 转换成隐变量 z。
    • 然后,通过编码器 f_enc 得到先验分布的参数 μ 和 σ。
    • 最关键的一步是,通过独热对齐搜索(MAS)算法找到 z 和 (μ, σ) 之间最可能的对齐关系 A*
    • 最后,计算在对齐关系 A* 下,z 服从以 μ 和 σ 为参数的高斯分布的对数概率。这部分损失会驱动编码器和解码器学习生成正确的声学内容。
  • : 这一项是雅可比行列式的对数值(Log-determinant of Jacobian)
    • 它衡量了从 x 到 z 的变换过程中空间的缩放程度。
    • 对于Glow中使用的流模型组件(如affine coupling, invertible 1x1 conv),这一项可以被高效地计算出来。
最终的似然损失函数 L_mle 就是最大化这个对数似然,等价于最小化其负值:

2. 时长预测损失 (Duration Prediction Loss)

这个损失用于训练时长预测器(Duration Predictor)
目标: 让时长预测器预测出的音素时长,尽可能地接近由独热对齐搜索(MAS)算法找到的“真实”时长。
原理:
  1. 获取真实时长 d: 在每个训练步中,MAS算法会为当前的文本和语音对找到一个最佳的独热对齐 。通过统计每个音素在该对齐中被分配了多少个梅尔频谱帧,就可以得到一个“真实”的时长序列 d。
    1. 获取预测时长 d_hat: 时长预测器接收编码器的隐藏表示 h,输出预测的时长序列 d_hat。
      1. d_hat = f_dur(sg[f_enc(c)]) (其中sg是stop-gradient)
    1. 计算损失 L_dur: 使用均方误差(Mean Squared Error, MSE)来计算预测时长和真实时长之间的差距。通常这个计算是在对数域(log domain)进行的,因为时长是正数且分布可能很广,在对数域计算可以使训练更稳定。
      1. L_dur = MSE(log(d_hat), log(d)) (论文中公式6的简化形式)
    特别注意 stop_gradient 操作:
    在计算 L_dur 时,时长预测器的输入 f_enc(c) 被stop_gradient包裹。这意味着 L_dur 的梯度不会反向传播到文本编码器 f_enc。这样做是为了解耦两个学习任务:
    • 编码器的主要任务是学习声学内容,由 L_mle 驱动。
    • 时长预测器的任务是学习节奏,由 L_dur 驱动。
      • 如果不加 stop_gradient,时长预测器的误差可能会“污染”编码器,使其为了降低时长预测误差而改变其本应学习的声学表示,导致性能下降。

    总损失 (Total Loss)

    最终,Glow-TTS的总损失是这两个损失的简单相加(或者加权相加,但论文中似乎是直接相加):
    通过同时优化这两个损失,Glow-TTS模型能够在一个端到端的框架中,既学习到如何生成高质量的语音内容,又学习到如何准确地控制语音的节奏和时长,并且这个过程完全不需要外部对齐工具的预处理。

    Train

    notion image
    训练流程
    好的,我们来详细梳理一下Glow-TTS的完整训练过程。这个过程是其能够摆脱外部对齐器、实现端到端训练的关键。
    我们可以将整个训练过程分解为在一个训练批次(batch)内执行的几个连续步骤。参考论文中的 Figure 1(a) An abstract diagram of the training procedure 会很有帮助。

    训练流程 (Step-by-Step for a Single Batch)

    假设我们有一个批次的数据,每条数据包含一对 (文本序列 c, 梅尔频谱图 x)

    步骤 1: 正向传播 (Forward Pass)

    1. 文本编码器 (Encoder) 部分:
        • 将文本序列 c(音素)输入文本编码器 (f_enc)。
        • 编码器输出两个结果:
          • 隐藏表示 h: 用于后续的时长预测。
          • 先验分布参数 (μ, σ): 这是一个序列,每个音素对应一组 (μ_i, σ_i)。这两个参数定义了模型对每个音素应该如何发音的“先验知识”。
    1. 时长预测器 (Duration Predictor) 部分:
        • 将编码器的隐藏表示 h 输入时长预测器 (f_dur)。
        • 时长预测器输出一个预测的时长序列 d_hat
        • 注意: 这一步的输入 hstop_gradient 包裹,这意味着从时长预测器产生的损失梯度不会影响到文本编码器。
    1. 解码器 (Decoder) 部分:
        • 将真实的梅尔频谱图 x 输入基于流的解码器 (f_dec)。
        • 解码器执行逆向操作 (inverse pass),将 x 转换成一个隐变量 z
        • 同时,解码器还会计算雅可比行列式的对数值 (log-determinant of Jacobian),这是计算似然损失所必需的。
    至此,我们获得了计算损失所需的所有组件:
    • 先验分布参数 (μ, σ)
    • 预测时长 d_hat
    • 隐变量 z
    • 雅可比行列式对数值
    • 真实的梅尔频谱图 x

    步骤 2: 独热对齐搜索 (Monotonic Alignment Search, MAS)

    这是Glow-TTS训练过程中的创新核心。它在每次迭代中动态地寻找最佳对齐。
    1. 输入:
        • 解码器输出的隐变量 z
        • 编码器输出的先验分布参数 (μ, σ)
    1. 目标: 找到一个 独热(monotonic)不跳过(surjective)的对齐路径 A*,使得在该对齐下,隐变量 z 的对数似然 log P(z|c, A) 最大。简单来说,就是为 z 的每一帧找到最匹配的那个音素 (μ_i, σ_i)
      1. 方法: 使用动态规划 (Dynamic Programming),类似于Viterbi算法。
          • 构建一个二维表格 Q,其中 Q[i, j] 表示“前 j 帧语音 (z_1...z_j) 与前 i 个音素 (c_1...c_i) 对齐的最大对数似然”
            • 通过递推公式
              • 填充整个表格。
                动态规划,目前是否用上了音素 (即 是否属于 )
              • 没用上 :
              • 用上了 :
            • 从表格的终点 Q[T_text, T_mel] 回溯,找到最佳对齐路径 A*
        1. 输出:
        • 最佳对齐 A*: 一个序列,指明了 z 的每一帧对应哪个音素。
        • "真实"时长 d: 通过统计 A* 中每个音素出现的次数,得到每个音素的“真实”时长。

        步骤 3: 计算损失 (Loss Calculation)

        现在我们有了所有必要信息,可以计算总损失。
        1. 最大似然损失 (L_mle):
            • 使用步骤2中找到的最佳对齐 A*,将隐变量 z 与对应的 (μ, σ) 配对。
            • 计算 z 在这个对齐下的对数似然
            • 加上解码器计算出的雅可比行列式对数值
            • 取负数,得到 L_mle。这个损失会同时优化 编码器解码器
        1. 时长预测损失 (L_dur):
            • 使用步骤1中预测的时长 d_hat 和步骤2中得到的“真实”时长 d
            • 在对数域计算它们的均方误差 (MSE),得到 L_dur。这个损失只优化时长预测器。
        1. 总损失 (L_total):
            • 将两个损失相加: L_total = L_mle + L_dur

        步骤 4: 反向传播与参数更新 (Backward Pass & Update)

        1. 计算梯度: 对总损失 L_total 进行反向传播,计算模型中所有参数(编码器、解码器、时长预测器)的梯度。
        1. 更新参数: 使用优化器(如Adam)根据计算出的梯度更新模型的权重。

        总结

        Glow-TTS的训练是一个巧妙的迭代过程,可以看作是期望最大化 (Expectation-Maximization, EM) 算法思想的一种体现:
        • E-Step (期望步): 在固定的模型参数下,通过MAS算法找到最可能的隐变量(对齐 A*)。
        • M-Step (最大化步): 在固定的对齐 A* 下,通过梯度下降更新模型参数,以最大化数据的对数似然(并最小化时长预测误差)。
        这个过程不断重复,模型会逐渐学会如何自己找到文本和语音之间的对齐,从而生成高质量且节奏正确的语音,完全无需任何外部预训练的对齐模型。
        的值可以是负数,因为此时的似然 是概率密度,因为 mel-spectrogram 是连续函数

        Dataloader

        1. 调用顺序: Dataset.__getitem__ → DataLoader → collate_fn → model.format_batch → model.forward
        1. collate_fn: 在 DataLoader 迭代时自动调用,将单个样本组合成批次
        1. format_batch: 在模型的 train_step/eval_step 中手动调用,进一步处理批次数据
         
        存储的数据 [B, C, T]
        batch 中的数据 [B, C, T]
        inference/forward 输出中的mel [B, T, C]
         
        dataclass python装饰器
         

        其他选择?

        • Speedy-Speech: paper
        • FastPitch: paper
          • 类似 FastSpeech
        • FastSpeech2: paper
          • 做不了
        • SC-GlowTTS: paper
          • GlowTTS 的 multi-speaker 版本
        • Neural HMM TTS: paper
          • OverFlow 的前作
        • Delightful TTS: paper
          • 很像 fastspeech2
        • StableTTS ❌
          • 太慢
        • MELLE ❌
          • 没开源
        • StyleTTS2 ❓
        notion image
        from “OverFlow”

        尝试拟合一条数据

        训练用的 mel-spectrogram
        notion image
        模型生成的 mel-spectrogram
        notion image
        由训练数据通过 BigVGAN 生成的音频
        由生成的频谱图通过 BigVGAN 生成的音频
        基本能听出声音,但是电流声很大

        处理 Speaker embedding

        通常

        多因素 disentangle 的 TTS 模型里,常见做法就是 多个 encoder,每个 encoder 专门负责一个因素(比如内容、音色、韵律),最后再拼接或组合成 decoder 的输入。

        典型结构(两因素:内容 + 音色)

        1. 内容 encoder
            • 输入:文本(或强表征,比如 phoneme 序列,或 SSL 表示如 HuBERT)。
            • 输出:内容 embedding(和说话人无关)。
        1. 说话人 encoder
            • 输入:一段 reference speech。
            • 输出:speaker embedding(比如一个固定维度向量,代表 timbre/voiceprint)。
        1. Decoder
            • 输入:内容 embedding + speaker embedding(拼接、加法、注意力融合等方式)。
            • 输出:mel-spectrogram → vocoder → 波形。
        所以 decoder 实际上收到的 是组合后的 latent 表示,而不是单一的 embedding。

        如果 disentangle 到三因素(内容 + 韵律 + 音色)

        那就会是这样:
        • 内容 encoder → 语言信息
        • 说话人 encoder → timbre
        • 韵律 encoder → F0/能量/时长 或 VAE latent
        • Decoder → 拼接 [内容, timbre, prosody],再生成语音
        例如:
        • FastSpeech 2:内容 + 音色 embedding + F0/能量/时长 predictor
        • GST-Tacotron:内容 + 音色 embedding + style token 向量

        拼接的方式

        常见有几种:
        • 简单拼接 (concat):直接 [内容向量; speaker 向量]。
        • 加性融合:把 speaker embedding 加到每个时间步的内容向量上。
        • 条件注意力 (conditional attention):让 decoder 在每个时间步查询 speaker embedding。

        📌 所以可以总结成一句:
        encoder 不止一个,每个 encoder 提供不同因素的 embedding,decoder 的输入就是这些 embedding 的融合表示。

        要不要我给你画一个 简化结构图(比如两路 encoder → 融合 → decoder),这样会更直观?

        GlowTTS

        好问题 👍 你贴的这个 GlowTTS 打印出来的模型结构里,确实没有直接看到 nn.Embedding(num_speakers, dim) 这样的 speaker embedding 表。这是因为你贴的实现(看起来像是基于官方 GlowTTS 或某个复现版本)把 speaker embedding 处理逻辑“藏”在了 decoder 的 CouplingBlock (WN 结构) 里。

        🔍 分析关键点

        1. encoder 部分
            • 你看到的 encoder 只处理 文本 token(Embedding(129,192) 是文本词表 embedding,不是 speaker embedding)。
            • 所以 speaker embedding 不是在 encoder 阶段引入的。
        1. decoder 部分
            • decoder 由一堆 flows (ActNorm / InvConvNear / CouplingBlock) 组成。
            • 其中 CouplingBlock 内部的 WN (WaveNet-like subnetwork) 会接收 条件输入
            • 在 Glow-TTS 的论文和代码实现里,这个 条件输入就是 speaker embedding + encoder hidden states
            • 也就是说,每个 CouplingBlock 在做仿射变换时,需要知道“这个语音是谁说的”,于是就把 speaker embedding 拼进去调制 μ,σ\mu, \sigma。
        1. 代码里常见的处理
            • WN 的 forward 里,会有一行类似:
              • 或者
            • 这样 speaker embedding 会在 每个 CouplingBlock 的条件网络里被广播并加进去

        ✅ 总结

        在你这个 GlowTTS 模型里:
        • Speaker embedding 的处理位置在 decoderCouplingBlockWN
        • 流程是:
            1. 根据 speaker ID 从 embedding table 查到一个向量
            1. 投影成合适维度
            1. 加到 CouplingBlock 的条件网络里
            1. 用来控制 flow 的仿射变换
        所以,虽然打印出来的模型结构没显示 nn.Embedding(num_speakers, dim),但它实际在模型初始化时就建好了,只是没出现在这份结构打印里(可能藏在外部 GlowTTS 类里)。

        要不要我帮你找一下这类 GlowTTS 复现代码里 speaker embedding forward 的具体调用位置(通常在 CouplingBlock 的 forward 或 Decoder 的 forward 里)?
        上一篇
        TTS
        下一篇
        Continual Learning