读VITS代码
总之就是读一下 vits(原论文标题:Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech)。vits, “Variational Inference with adversarial learning for end-to-end Text-to-Speech”,一个变分推理、对抗学习、端到端的 TTS 方法。论文团队给出了他们的示例代码。
概览
工程包含的文件:
attentions.py:关于自注意力机制的部分,定义了 transformer 的结构。
- Encoder 类
- 定义了参数和 forward 方法
- Decoder 类
- 定义了参数和 forward 方法
- MultiHeadAttention 类
- 定义了参数,forward 方法,attention 方法,_matmul_with_relative_values 方法,_matmul_with_relative_keys 方法,_get_relative_embeddings 方法,_relative_position_to_absolute_position 方法,_absolute_position_to_relative_position 方法,_attention_bias_proximal 方法
- FFN(前馈神经网络) 类
- 定义了参数,forward 方法,_causal_padding 方法,_same_padding 方法。
- Encoder 类
commons.py:公用的一些常用方法
- 定义了 init_weights 方法
- 定义了 get_padding 方法
- 定义了 convert_pad_shape 方法
- 定义了 intersperse 方法
- kl_divergence 方法
- rand_gumbel 方法
- rand_gumbel_like 方法
- slice_segments 方法
- rand_slice_segments 方法
- get_timing_signal_1d 方法
- add_timing_signal_1d 方法
- cat_timing_signal_1d 方法
- subsequent_mask 方法
- fused_add_tanh_sigmoid_multiply 方法:使用 @torch.jit.script 装饰
- convert_pad_shape 方法
- shift_1d 方法
- sequence_mask 方法
- generate_path 方法
- clipgrad_value 方法
data_utils.py
- 定义了 TextAudioLoader 类
- 切分训练的时候使用的音频、文本
- 定义了 TextAudioCollate 类
- 批量整理文本、音频
- 定义了多说话者版本 TextAudioSpeakerLoader 类
- 类似 TextAudioLoader 但是是多说话者版本
- 定义了 TextAudioSpeakerCollate 类
- 类似 TextAudioCollate,但是是多说话者版本
- 定义了 DistributedBucketSampler 类
- 储存桶相关
- 定义了 TextAudioLoader 类
losses.py:损失函数相关
- feature_loss 方法
- 计算特征图(fmap)之间的差异
- discriminator_loss 方法
- 计算判别器(Discriminator)的损失
- generator_loss 方法
- 计算生成器(Generator)的损失
- kl_loss 方法
- 计算KL散度损失,用于变分自编码器(VAE)
- feature_loss 方法
mel_processing.py:梅尔频谱处理
- dynamic_range_compression_torch 方法
- 动态范围压缩,乘 C、裁剪、然后取对数
- dynamic_range_decompression_torch 方法
- 动态范围解压缩,和动态范围压缩互补
- spectral_normalize_torch 方法
- 对幅度谱进行动态范围压缩,就是调用 dynamic_range_compression_torch 方法
- spectral_de_normalize_torch 方法
- 同上,只是调用了一下 dynamic_range_decompression_torch 方法
- spectrogram_torch 方法
- 作用:
- 计算音频信号 y 的短时傅立叶变换(STFT)谱。
- 参数:
- FFT点数 n_fft
- 采样率 sampling_rate
- 帧移 hop_size
- 窗长度 win_size
- 是否在时间轴上居中 center
- 先对输入信号做反射填充,然后应用 Hann 窗函数进行窗口化处理,最后计算 STFT 并取模平方根得到幅度谱。
- 作用:
- spec_to_mel_torch 方法
- 作用:
- 将频域谱 spec 转换为 Mel 频谱。
- 使用全局变量 mel_basis 存储预先计算好的梅尔滤波器组矩阵,如果矩阵不存在,则会基于给定的参数计算并转换为当前设备和数据类型。
- 将幅度谱与梅尔滤波器组矩阵进行矩阵乘法,然后对结果进行动态范围压缩。
- 作用:
- mel_spectrogram_torch 方法
- 完整流程的 Mel 频谱计算函数,包含了从原始音频信号到 Mel 频谱的全部步骤。
- 包括了对输入信号的检查(确保其在 [-1, 1] 范围内),计算 STFT 幅度谱,转换为 Mel 频谱并进行动态范围压缩。
- dynamic_range_compression_torch 方法
models.py
- 模型主体结构。
- 每一个类是模型的一个(或者一种)组件,核心是 forward 方法。
- StochasticDurationPredictor 类
- 随机时长预测器,论文里提到的主要创新点,使用了正态流。接收 gin_channels 参数进行调整。
- forward 方法
- DurationPredictor 类
- 时长预测器,简化版本的 StochasticDurationPredictor,不支持条件输入,没有正态流。
- TextEncoder 类
- 继承自 torch.nn.Module,并且使用了 attentions 的 Encoder 制作成员(上面的随机时长预测器和下面几个类也用到了 torch.nn.Module,但是没有使用 attentions)
- forward 方法:
- 将文本输入通过嵌入层来获得初始的嵌入表示
- 将这些嵌入表示转换为适合自注意力机制的格式(三维张量,维度为批次大小、隐藏通道数和序列长度)
- 使用自注意力机制和卷积层来处理这些嵌入表示
- 通过投影层输出最终的编码表示
- ResidualCouplingBlock 类
- 残差耦合块
- 其中的流变换根据 reverse 参数决定是正向还是反向,执行正向流变换的时候,输入数据会依次通过每个流变换层。每个流变换层都会更新数据,并通过残差连接将变换后的数据与原始数据相结合,这有助于避免梯度消失问题
- 流部分和 StochasticDurationPredictor 是一样的
- PosteriorEncoder 类
- 后验分布编码器
- 在给定观测数据(如语音信号)的条件下,对潜在变量(如声谱特征)的分布进行建模
- 换言之就是,将观测到的语音信号编码为潜在空间的分布,使得模型能够捕捉到语音信号的复杂性和多样性,并在生成过程中保持这些特性
- forward 方法:
- 将输入数据通过预处理卷积层来获得初始的编码表示
- 将这些编码表示通过扩张卷积层进行进一步的处理,扩张卷积层通过扩张率来增加感受野,捕捉更长范围内的依赖关系
- 通过投影卷积层输出均值和对数方差、输出可以用于后续的生成过程,如重参数化技巧,以生成新的样本。
- Generator 类
- 从潜在空间生成频谱
- forward 方法:
- 输入数据首先通过 conv_pre 进行处理(是一个一维的卷积核,Conv1d)
- 数据依次通过 ups 中的上采样层和 resblocks 中的残差块。在每一步上采样过程中,数据的分辨率逐渐增加,同时通过残差连接保持了信息的流动
- 通过 conv_post 生成最终的输出,这可以是语音波形或其他类型的音频特征
- remove_weight_norm 方法:
- 移除所有卷积层中的权重归一化(weight normalization)
- 可能是测试使用的?
- DiscriminatorP 类
- GANs 的一部分,辨别器。
- 适合周期信号,P 指的是 Periodic,周期的(我猜的)
- 此处可以参考 HiFi-GAN 模型
- DiscriminatorS 类
- 同上, GANs 的一部分,辨别器。
- 适合非周期信号(文本,图像),S 指的是 Sequential,序列的(我猜的)
- MultiPeriodDiscriminator 类
- 多周期辨别器,使用了上面两种辨别器:
1
2discs = [DiscriminatorS(use_spectral_norm=use_spectral_norm)]
discs = discs + [DiscriminatorP(i, use_spectral_norm=use_spectral_norm) for i in periods]
- 多周期辨别器,使用了上面两种辨别器:
- 综合二者的能力
- SynthesizerTrn 类
- 用于训练的合成器,较为复杂,使用了上面定义的一系列模块:
- enc_p: PosteriorEncoder,文本编码器,用于将文本转换为嵌入表示。它使用一个自注意力编码器来处理文本输入。
- dec: Generator,生成器,用于从嵌入表示生成语音信号。包含一系列上采样层和残差块,用于生成高质量的语音波形。
- enc_q: TextEncoder,后验编码器,用于编码声谱特征。使用卷积层和扩张卷积来处理声谱数据。
- flow: ResidualCouplingBlock,残差耦合块,用于数据的流动和变换。它包含一系列残差耦合层,用于学习声谱数据的复杂分布。
- dp: StochasticDurationPredictor,持续时间预测器,用于预测语音信号的持续时间
- 根据 use_sdp 参数的值,它会初始化为 StochasticDurationPredictor 或 DurationPredictor。
- emb_g: 一个 nn.Embedding,说话者嵌入层,用于条件生成不同说话者的语音
- 如果 n_speakers 大于0,这个层会被初始化。
- forward 方法:
- 文本编码: 使用 enc_p(文本编码器)对文本输入 x 进行编码,得到文本的嵌入表示。同时,使用 x_lengths 来创建一个掩码,以便在后续步骤中忽略填充的部分。
- 声谱编码: 使用 enc_q(后验编码器)对目标声谱特征 y 进行编码,得到声谱的嵌入表示。同时,如果提供了说话者ID sid,则使用 emb_g(说话者嵌入层)来获取说话者嵌入,并将其加入到声谱编码中。
- 持续时间预测: 如果模型使用随机持续时间预测器(use_sdp 为 True),则调用 dp(持续时间预测器)来预测语音信号的持续时间。否则,使用 DurationPredictor 来预测(节约成本)。
- 生成路径计算: 计算生成语音的路径。这通常涉及到计算负对数似然(negative log-likelihood)和注意力机制,以便在生成过程中对齐文本和声谱特征。
- 生成语音波形: 使用 dec(生成器)来生成语音波形。生成器接收文本嵌入表示和声谱嵌入表示作为输入,并输出最终的语音波形。
- 输出: forward 方法的输出包括生成的语音波形、持续时间预测损失、注意力权重、生成的声谱片段的ID、输入文本和声谱的掩码,以及原始的嵌入表示。
- 输出内容:
- o: 生成的语音波形。
- l_length: 持续时间预测损失。
- attn: 注意力权重,用于显示文本和声谱特征之间的对齐情况。
- ids_slice: 生成的声谱片段的ID。
- x_mask, y_mask: 输入文本和声谱的掩码,用于在后续处理中忽略填充的部分。
- (z, z_p, m_p, logs_p, m_q, logs_q): 原始的嵌入表示和相关的统计信息,这些信息可以用于进一步的分析和调试。
- infer 方法
- 训练结束之后,用于推理的方法
- 文本编码: 与 forward 方法类似,infer 方法首先使用文本编码器 enc_p 对输入文本 x 进行编码,得到文本的嵌入表示。
- 声谱编码: 如果提供了说话者ID sid,则使用说话者嵌入层 emb_g 来获取说话者嵌入,并将其加入到声谱编码中。
- 持续时间预测: 根据模型是否使用随机持续时间预测器(use_sdp 参数),调用相应的持续时间预测器 dp 来预测语音信号的持续时间。
- 生成语音波形: 使用生成器 dec 来生成语音波形。在推理模式下,生成器会根据文本嵌入表示和预测的持续时间生成连续的语音波形。
- 注意力权重计算: 通过计算注意力权重,可以了解模型是如何在生成语音时关注输入文本的不同部分的。
- 输出: infer 方法的输出通常包括生成的语音波形、注意力权重和其他可能的中间结果,如声谱特征的嵌入表示。
- voice_conversion 方法
- 用于执行说话者转换
- 接收源说话者的语音信号和目标说话者的标识符作为输入,然后生成具有目标说话者特征的语音信号
- 参数:
- y: 源说话者的语音信号,通常是一个三维张量,形状为 [batch_size, spec_length, spec_channels]。
- y_lengths: 源语音信号的长度,用于指示每个样本中的非填充声谱的长度。
- sid_src: 源说话者的标识符。
- sid_tgt: 目标说话者的标识符。、
- 过程:
- 说话者嵌入获取: 使用 emb_g(说话者嵌入层)根据源说话者和目标说话者的标识符获取相应的说话者嵌入。这些嵌入将用于调整生成的语音,使其具有目标说话者的特征。
- 源语音编码: 使用 enc_q(后验编码器)对源说话者的语音信号 y 进行编码,得到声谱的嵌入表示。
- 说话者转换: 通过 flow(残差耦合块)和可能的其他变换层,将源说话者的嵌入表示转换为目标说话者的嵌入表示。这个过程可能涉及到使用说话者嵌入来调整声谱特征,以便模仿目标说话者的声音。
- 生成目标语音: 使用 dec(生成器)根据转换后的嵌入表示生成目标说话者的语音信号。生成器会根据调整后的声谱特征生成连续的语音波形。
- 输出: voice_conversion 方法的输出是转换后的语音信号,它应该具有目标说话者的声音特征,同时保持原始文本的内容不变。
- 输出:
- o_hat: 转换后的目标说话者的语音信号。
- y_mask: 目标语音信号的掩码,用于在后续处理中忽略填充的部分。
- (z, z_p, z_hat): 原始的嵌入表示和相关的统计信息,这些信息可以用于进一步的分析和调试。
- 用于训练的合成器,较为复杂,使用了上面定义的一系列模块:
- modules.py
- LayerNorm 类
- 归一化层
- ConvReluNorm 类
- attention 里的一个结构
- DDSConv 类
- 带空洞卷积(Dilated Convolution)和深度可分离卷积(Depth-Separable Convolution)的模块
- WN 类
- 权重归一化(Weight Normalization)
- ResBlock1 类 和 ResBlock2 类
- 残差块(Residual Block),用于构建更深的网络结构
- Log 类
- 对输入数据 x 取对数的操作
- 用于变分自编码器(VAE)中的变分下界
- Flip 类
- 翻转输入/取消翻转(取决于 reverse 是 True 还是 False)
- ElementwiseAffine 类
- 逐元素的仿射变换
- ResidualCouplingLayer 类
- 残差耦合层
- 用于流模型(Flow Model)中的变分推断
- ConvFlow 类
- 基于卷积的流模型
- 用于生成模型或者变分推断
- LayerNorm 类
- preprocess.py
- 作为主函数时:
- 向命令行添加参数
- 从指定的文件列表中读取文件路径和文本
- 使用指定的文本清理器清理文本
- 将清理后的文本写入新的文件列表
- 总之就是字面意思的预处理
- 作为主函数时:
- train.py
- main 函数
- 断言检查:
- 使用 assert torch.cuda.is_available() 确保当前环境可以使用CUDA,因为脚本设计为仅在GPU上运行。
- 获取GPU数量:
- 通过 torch.cuda.device_count() 获取系统中可用的GPU数量,并将其存储在变量 n_gpus 中。
- 设置环境变量:
- 设置环境变量 MASTER_ADDR 和 MASTER_PORT,这些变量用于分布式训练中的进程间通信
- MASTER_ADDR 被设置为 ‘localhost’,MASTER_PORT 被设置为 ‘80000’。
- 获取超参数:
- 调用 utils.get_hparams() 函数获取超参数设置
- 超参数通常包括:
- 学习率
- 批次大小
- 训练周期数
- 启动分布式训练:
- 使用 mp.spawn(mp 是 torch 的 multiprocessing) 函数启动多个训练进程,调用 run 函数
- nprocs:要启动的进程数
- 使用 mp.spawn(mp 是 torch 的 multiprocessing) 函数启动多个训练进程,调用 run 函数
- 断言检查:
- run 函数
- 在 main 中被 mp.spawn 调用
- 函数参数:
- rank:当前进程的排名(ID),在分布式训练中用于区分不同的进程。
- n_gpus:系统中可用的GPU数量。
- hps:包含超参数的对象,这些超参数定义了模型的训练和数据配置。
- 判断当前进程(rank)的排名是否是0(是主进程),如果是的话:
- 初始化日志记录器:
- 调用 utils.get_logger 方法初始化日志记录器,并记录超参数 hps 的信息。这有助于监控训练过程并记录重要信息。
- 检查Git哈希:
- 调用 utils.check_git_hash 方法来验证代码的一致性,确保训练过程中使用的代码版本与预期的版本相符
- 一般我使用的时候需要注释掉
- 创建TensorBoard SummaryWriter:
- 创建两个 SummaryWriter 对象,一个用于记录训练过程中的信息(writer),另一个用于记录评估过程中的信息(writer_eval)。
- 初始化日志记录器:
- 初始化分布式训练环境:
- 调用 dist.init_process_group 方法初始化分布式训练环境。设置后端为 ‘nccl’,初始化方法为 ‘env://‘,世界大小(world size)为 n_gpus,当前进程的排名为 rank。
- 设置随机种子:
- 使用 torch.manual_seed 方法设置随机种子,以确保训练的可重复性。随机种子使用超参数中的 hps.train.seed 值。
- 设置当前使用的GPU
- 加载训练数据:
- 使用 TextAudioLoader 类加载训练数据
- 创建 DistributedBucketSampler 对象,用于在分布式训练中对数据进行采样
- 创建 DataLoader 对象,用于加载训练数据。
- 判断当前进程(rank)的排名是否是0(是主进程),如果是的话:
- 加载评估数据
- 创建评估数据的 DataLoader 对象。
- 模型:
- 使用 SynthesizerTrn 类构建生成器模型 net_g
- 使用 MultiPeriodDiscriminator 类构建鉴别器模型 net_d
- 优化器:
- 使用 torch.optim.AdamW 创建生成器优化器 optim_g、鉴别器优化器 optim_d
- 使用 DDP(Distributed Data Parallel)包装生成器和鉴别器模型,以支持分布式训练。
- 加载检查点(checkpoint):
- 从检查点文件中加载生成器和鉴别器的状态
- 如果加载失败,则将全局步数 global_step 和当前周期 epoch_str 设置为初始值。
- 从检查点文件中加载生成器和鉴别器的状态
- 设置学习率调度器:
- 使用 torch.optim.lr_scheduler.ExponentialLR 创建学习率调度器
- 设置学习率衰减率。
- 训练和评估循环:(for epoch in …)
- 遍历超参数定义的训练周期 hps.train.epochs
- 在每个周期中,执行训练和评估过程
- 如果当前进程的排名为0,则调用 train_and_evaluate 函数进行训练和评估,并记录日志和TensorBoard信息
- 否则,只执行训练过程。
- 在每个周期中,执行训练和评估过程
- 更新学习率调度器:
- 在每个周期结束时,更新生成器和鉴别器的学习率调度器。
- 即 调用 scheduler_g.step() 和 scheduler_d.step()
- 在每个周期结束时,更新生成器和鉴别器的学习率调度器。
- 遍历超参数定义的训练周期 hps.train.epochs
- train_and_evaluate 函数
- 函数参数:
- rank: 当前进程的排名(ID),用于分布式训练中的标识。
- epoch: 当前的训练周期。
- hps: 超参数设置,包含了模型训练和数据配置的所有重要参数。
- nets: 一个包含生成器(net_g)和鉴别器(net_d)的列表。
- optims: 一个包含生成器和鉴别器优化器的列表。
- schedulers: 一个包含生成器和鉴别器学习率调度器的列表。
- scaler: 梯度缩放器,用于混合精度训练中梯度的缩放。
- loaders: 一个包含训练和评估数据加载器的列表。
- logger: 日志记录器,用于记录训练过程中的信息。
- writers: 一个包含TensorBoard的SummaryWriter对象的列表,用于记录训练和评估过程中的信息。
- 函数内部:
- 将nets、optims、schedulers中的生成器和鉴别器分别赋值给net_g、optim_g、scheduler_g和net_d、optim_d、scheduler_d。
- 如果writers不为None,则将写入器赋值给writer和writer_eval。
- 调用train_loader.batch_sampler.set_epoch(epoch)设置当前的训练周期,这对于某些采样器(如DistributedBucketSampler)来说是必要的,因为它们可能需要根据周期进行调整。
- 定义全局变量global_step,用于跟踪训练步数。
- 将生成器和鉴别器设置为训练模式(net_g.train()和net_d.train())。
- 遍历训练数据加载器train_loader中的每个批次。
- 将批次数据移动到对应的GPU上。
- 使用autocast上下文管理器进行混合精度训练,如果启用了混合精度训练。
- 调用生成器net_g生成预测值y_hat,并计算损失。
- 将真实音频的频谱转换为Mel频谱y_mel。
- 计算鉴别器的损失loss_disc,并执行反向传播和优化器步骤。
- 计算生成器的损失loss_gen_all,并执行反向传播和优化器步骤。
- 如果当前进程是主进程(rank == 0),则记录日志和TensorBoard信息。
- 如果当前进程是主进程,检查是否到达了评估间隔,如果是,则调用evaluate函数进行评估,并保存检查点。
- 向控制台打印全局训练步数global_step。
- 函数参数:
- evaluate 函数
- 函数参数:
- hps:包含超参数的对象,这些超参数定义了模型的训练和数据配置。
- generator:训练好的生成器模型,用于生成语音。
- eval_loader:评估数据的加载器,它提供了评估数据集的批量数据。
- writer_eval:用于评估数据的TensorBoard SummaryWriter对象,用于记录评估过程中的信息。
- 设置模型为评估模式:
- 调用 generator.eval() 将模型设置为评估模式。这是必要的,因为在评估模式下,模型会关闭dropout和批量归一化等只在训练时使用的层。
- 禁用梯度计算:
- 使用 torch.no_grad() 上下文管理器来禁用梯度计算。这有助于减少内存消耗,并加速评估过程,因为在评估时不需要计算梯度。
- 遍历评估数据加载器:
- 通过 for 循环遍历 eval_loader 中的每个批次的数据。
- 将批次数据移动到GPU上(这里假设使用的是第一个GPU,即 cuda(0))。
- 处理单个样本:
- 从评估批次中提取单个样本数据(这通常是通过切片操作完成的),因为评估函数可能设计为只处理单个样本。
- 调用 generator.module.infer 方法(注意这里使用了 .module,这可能是因为模型被包装在 nn.DataParallel 或 nn.parallel.DistributedDataParallel 中)来生成语音。
- 计算生成的语音的长度。
- 转换频谱为Mel频谱:
- 使用 spec_to_mel_torch 函数将真实和生成的频谱图转换为Mel频谱图。Mel频谱图是一种表示音频数据的常用方式,它更接近于人类对频率的感知。
- 记录评估结果:
- 如果当前进程的排名为0(主进程),则使用 utils.summarize 函数记录评估结果。这包括生成的Mel频谱图、音频样本以及其他可能的统计信息。
- 将生成的Mel频谱图和音频样本添加到 image_dict 和 audio_dict 字典中,这些字典随后被传递给 utils.summarize 函数。
- 如果是训练的第一步(global_step == 0),则还包括真实样本的Mel频谱图和音频样本以供参考。
- 恢复模型训练模式:
- 在评估结束后,调用 generator.train() 将模型恢复到训练模式。
- 函数参数:
- main 函数
- train_ms.py
- 多说话者训练,ms 意思是 multi-speaker
- 总之就是多了一些多说话人相关的设置,对应的类也被换成了多说话人需要的类
- 除此之外,还使用了 GradScaler 来进行梯度缩放
- 因为我是懒狗所以直接交给 chatgpt 帮我对比了
- 数据加载器和处理:
- 第一段代码中使用了TextAudioLoader和DistributedBucketSampler来加载和处理文本和音频数据。
- 第二段代码中使用了TextAudioSpeakerLoader,这表明它可能在处理与说话者相关的数据,这可能是一个多说话者的TTS模型。
- 模型结构:
- 第一段代码中的模型定义较为简洁,没有提及说话者的数量。
- 第二段代码中的SynthesizerTrn模型在初始化时考虑了n_speakers参数,这意味着模型可能包含说话者嵌入或有说话者适应的功能。
- 评估函数:
- 第一段代码中的evaluate函数没有提供详细的实现,而第二段代码中的evaluate函数实现了对单个样本的评估,并生成了Mel频谱图和音频样本的可视化。
- 第二段代码中的评估还考虑了说话者(speakers参数),并且在评估时使用了generator.module.infer方法,这表明评估过程中可能使用了说话者特定的信息。
- 训练循环:
- 第一段代码中的train_and_evaluate函数实现了训练和评估的循环,但在代码片段中没有提供完整的实现。
- 第二段代码中的train_and_evaluate函数提供了完整的实现,包括前向传播、损失计算、反向传播和优化器步骤,以及日志记录和评估过程。
- 梯度缩放:
- 第二段代码中使用了GradScaler来进行梯度缩放,这在混合精度训练中非常有用,可以防止梯度值过大导致的数值不稳定问题。
- 第一段代码中没有明确提及梯度缩放。
- 学习率调度器:
- 两段代码都使用了指数衰减的学习率调度器,但是第二段代码中提供了更详细的实现。
- 保存检查点:
- 第二段代码中在每个epoch结束时保存了生成器和鉴别器的检查点,而第一段代码中没有明确提及检查点的保存。
- 代码结构:
- 两段代码都遵循了类似的结构,包括初始化、模型定义、训练循环和评估过程。
- 第二段代码提供了更多的细节,包括模型的具体参数、评估过程和训练循环中的日志记录。
- transforms.py
- utils.py
流模型
在程序里,ResidualCouplingBlock(残差耦合块)类和 StochasticDurationPredictor(随机时长预测器)就是使用了流模型。
流变换(Flow Transformation)是流模型(Flow-based Model)中的一个核心概念,它指的是一系列可逆的变换过程,用于将一个复杂的概率分布转换为一个更简单(通常是高斯)的概率分布,或者反过来,将一个简单的概率分布转换为一个复杂的目标分布。这些变换过程是可逆的,意味着可以从一个分布直接转换到另一个分布,并且可以精确计算转换过程中的雅各比行列式(Jacobian determinant),这对于概率密度函数的计算至关重要。
简而言之就是概率界的傅立叶变换,把一个复杂的、通用的概率模型(对应到傅立叶变换里就是一个复杂的函数)分解为标准的简单分布,例如高斯分布(对应傅立叶变换的正余弦函数),过程可逆,变换之后更加容易处理(因为都是基本分布/函数了)。
流变换的特点:
- 可逆性:
- 流变换的一个显著特点是它的双向可逆性。这意味着变换不仅可以从分布A转换到分布B,还可以从分布B转换回分布A。这种可逆性使得流模型在理论上可以精确地计算概率密度,并且可以用于生成模型,通过逆变换生成新的样本。
- 精确计算:
- 在传统的生成模型中,如生成对抗网络(GANs)或变分自编码器(VAEs),通常需要通过近似方法来估计概率密度。而流模型通过流变换,可以直接计算概率密度,这在理论上提供了更精确的估计。
- 雅各比行列式:
- 流变换过程中的雅各比行列式是关键,因为它涉及到变换的尺度和方向的变化。在流模型中,通过设计特定的变换结构(如耦合层),可以简化雅各比行列式的计算,使其在实际应用中变得可行。
流变换的发展:
- NICE:
最初的流变换模型NICE(Non-linear Independent Components Estimation)实现了从任意分布到高斯分布的可逆变换。 - RealNVP:
后续的RealNVP(Real-Valued Non-Volume Preserving)模型进一步扩展了流变换的能力,实现了从任意分布到条件非高斯分布的可逆变换。 - GLOW:
最新的GLOW(Generative Flow with Invertible 1x1 Convolutions)模型通过引入1x1卷积,实现了从任意分布到任意其他分布的可逆变换,这使得GLOW能够处理更复杂的分布,如图像数据。
流变换的应用:
流变换在生成模型中的应用非常广泛,特别是在需要精确概率估计的场景中。例如,在语音合成、图像生成、药物设计等领域,流模型可以通过流变换来生成高质量的样本,并且可以精确地计算样本的概率密度。
可以参考 https://gwylab.com/note-flow_based_model.html 和 https://www.bilibili.com/video/BV1E441137wE