SpeechBrain 扩散模型配方详解:基于 AudioMNIST 的 DDPM 语音生成实战指南
【免费下载链接】speechbrainA PyTorch-based Speech Toolkit项目地址: https://gitcode.com/GitHub_Trending/sp/speechbrain
本文以 SpeechBrain 开源仓库中的recipes/AudioMNIST/diffusion/配方(recipe)为对象,系统讲解如何用去噪扩散概率模型(DDPM,Denoising Diffusion Probabilistic Model)在 AudioMNIST 数据集上训练语音生成模型。你将掌握无条件生成、说话人条件生成、数字条件生成(简化版 TTS)以及潜在空间扩散(latent diffusion)四种训练模式的完整命令与关键配置,并理解其底层实现原理、数据准备流程与生成样本的保存与检查机制。
1. 配方概览与目录结构
本配方位于recipes/AudioMNIST/diffusion/,由三个核心文件构成:
| 文件 | 作用 |
|---|---|
| train.py | 训练脚本,定义了DiffusionBrain训练类、数据管线与生成逻辑 |
| hparams/train.yaml | 谱图空间(spectrogram-space)扩散训练的超参数配置 |
| hparams/train_latent.yaml | 潜在空间扩散(latent diffusion)训练的超参数配置 |
| README.md | 本配方的官方说明文档(本文的基础) |
数据准备脚本为 audiomnist_prepare.py,由训练脚本在启动时自动调用。该脚本会自动从 AudioMNIST 的 Git 仓库克隆原始音频,并下载包含 train/valid/test 划分的元数据压缩包,完成重采样、静音裁剪、高通滤波与幅度归一化后,生成 SpeechBrain 所需的train.json、valid.json、test.json三个数据清单文件。从 audiomnist_prepare.py 可以看到默认源采样率为 48 kHz,目标采样率可配置(本配方统一设为 16 kHz)。
AudioMNIST 包含多位说话人对英文数字 0~9 的朗读录音,口音多样。配方的训练脚本从每个样本的文件名中解析出digit(数字标签)与speaker_id(说话人标签),见 train.py 中labels_pipeline的实现:digit_label直接取数字值,speaker_label取speaker_id - 1作为从 0 开始的索引。这两个标签正是后面条件生成的基础。
2. 扩散模型基础:DDPM 与潜在扩散
2.1 DDPM 的核心思想
Denoising Diffusion Probabilistic Model 是一类生成模型,其关键思路是:训练一个神经网络,给定一个被添加了噪声的样本,让它学会识别(预测)所添加的噪声;在推理(生成)阶段,模型从纯噪声出发,通过逐步去噪的方式重建出真实样本。该方法的理论基础受 Langevin 动力学启发(论文见 arXiv:2006.11239)。
生成过程有两种模式:
- 无条件生成:模型从目标数据分布中生成任意样本;
- 条件生成:模型根据类别标签或提示(prompt)生成对应样本。
SpeechBrain 在 speechbrain/nnet/diffusion.py 中提供了完整的实现,核心类包括:
Diffuser:扩散模型基类,定义了distort()(加噪)、train_sample()(生成训练样本对)与sample()(采样)的接口;DenoisingDiffusion:经典 DDPM 实现,见 speechbrain/nnet/diffusion.py;LatentDiffusion:潜在扩散封装,见 speechbrain/nnet/diffusion.py;GaussianNoise/LengthMaskedGaussianNoise:高斯噪声与长度掩码高斯噪声,见 speechbrain/nnet/diffusion.py。
2.2 前向加噪过程(distort)
在前向扩散过程中,模型按照时间步 t 逐步给样本加噪。DenoisingDiffusion.distort()的实现(speechbrain/nnet/diffusion.py)遵循 DDPM 论文:
noisy_sample = sqrt(alpha_cumprod[t]) * x + sqrt(1 - alpha_cumprod[t]) * noise其中alpha与beta系数通过compute_coefficients()计算(speechbrain/nnet/diffusion.py):betas在beta_start与beta_end之间线性取timesteps个值,alphas = 1 - betas。默认的beta_start=0.0001、beta_end=0.02以 1000 步为参考(DDPM_REF_TIMESTEPS),若自定义timesteps,两个 beta 边界会按比例缩放,见 speechbrain/nnet/diffusion.py 与构造函数的缩放逻辑。
若未显式指定时间步,train_sample()会调用sample_timesteps()为 batch 中的每个样本随机采样一个时间步,从而让模型学会在所有噪声强度下预测噪声。这一点在 tests/unittests/test_diffusion.py 中有直接验证:该测试用固定噪声与时间步[0, 200, 900]检查加噪结果的数值正确性。
2.3 反向去噪采样过程(sample)
DenoisingDiffusion.sample()(speechbrain/nnet/diffusion.py)从纯噪声开始,将时间步从timesteps-1倒序迭代到 0,每一步调用sample_step()(speechbrain/nnet/diffusion.py):
- 用 UNet 预测当前噪声
model_out; - 依据 DDPM 后验公式计算均值(
posterior_mean_weight_start与posterior_mean_weight_step的加权和); - 加上以
posterior_log_variance缩放的高斯噪声,得到去噪一步后的样本; - 若配置了
sample_min/sample_max,对输出做裁剪(clipping),保证样本值落在合法范围内(本配方中为-4.7到3.0)。
show_progress开启时,采样过程会显示 tqdm 进度条。sample()全程在@torch.no_grad()下执行,不更新任何梯度。
LatentDiffusion.sample()则是在潜在空间完成同样的迭代后,将去噪结果交由自编码器(autoencoder)解码回原始谱图空间,见 speechbrain/nnet/diffusion.py 之后的实现,其 docstring 中的可运行示例完整演示了 "编码 → 潜在空间扩散 → 解码" 的链路。
2.4 为什么需要潜在扩散
扩散模型可以直接在原始样本空间上运行,但对于高维样本(如全分辨率图像),推理阶段逐时间步去噪会非常慢。常见解法是潜在扩散(latent diffusion,论文见 arXiv:2112.10752):先用自编码器把高维样本压缩到低维潜在空间,再在潜在空间上执行扩散过程,最后解码回原始空间。本配方两种模式都支持:train.yaml对应谱图空间(样本空间)扩散,train_latent.yaml对应潜在扩散。
3. 训练前的准备:数据自动下载与特征管线
3.1 数据准备参数
训练脚本在main中通过run_on_main(prepare_audiomnist, ...)调用数据准备(train.py),相关参数与默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
data_prepare_norm | False(simple)/ True(latent) | 是否将幅度归一化到 [-1, 1] |
data_prepare_trim | False(simple)/ True(latent) | 是否裁剪首尾静音 |
data_prepare_trim_threshold | -30.0 dB | 静音裁剪的 dB 阈值 |
data_prepare_sample_rate_src | 48000 | 原始采样率 |
data_prepare_sample_rate_tgt | 16000 | 目标采样率 |
skip_prep | False | 是否跳过数据准备(已有处理结果时置 True 可加速重启) |
数据处理流程定义在 audiomnist_prepare.py 的process_audio_default()中:先按需重采样,再按能量阈值裁剪静音,随后应用 >70 Hz 高通滤波(基于 sox effects),最后按需做幅度归一化。裁剪逻辑trim_sig()将信号归一化后计算能量,保留首个与末个超过阈值的采样点之间的片段(audiomnist_prepare.py)。
如果save_folder下已存在完整的 JSON 清单且opt_audiomnist_prepare.pkl中保存的配置与当前一致,准备阶段会被跳过(skip()函数),避免重复处理。
3.2 特征提取:从波形到可训练的谱图
训练脚本 的sig_to_feats()负责将原始波形转为模型输入,链路如下:
compute_features:MelSpectrogram(n_fft=1024、80 个 mel 频带、hop 256)+AmplitudeToDB转 dB 谱;- 转置与
unsqueeze(1)增加通道维,得到(batch, 1, n_mels, time)形状; pad_divisible补齐:UNet 会对时间维与特征维做 2 的倍数下采样,因此需按downsample_factor把两个维度补齐为可整除的长度,避免形状不匹配(train.py);MinLevelNorm(min_level_db=-80)做最小电平归一化;GlobalNorm(norm_mean=0、norm_std=0.5)做全局归一化,并使用pad_level_db=-50对应的值作为 padding 位置的掩码值。
3.3 增强与数据管线
read_audio()支持随机幅度增强(rand_amplitude: True,幅度在min_amp=0.1到max_amp=0.4之间随机缩放,见 train.py)。此外,当配置了done_detector时(仅 latent 配方),管线还会为每个样本附加一个随机抽取的其他样本(file_name_random/sig_random),用于训练“生成结束检测器”,见enhance_with_random()(train.py)。
4. 四种训练模式:命令与参数
以下所有命令均在recipes/AudioMNIST/diffusion/目录下执行。--data_folder必填,数据会自动下载到该目录。
4.1 无条件模型
python train.py hparams/train.yaml --data_folder=your/data/folder这是最基本的训练方式,模型学习的是 AudioMNIST 数据的整体分布。需要提醒的是:AudioMNIST 是相对较小的数据集,训练出的扩散模型可能难以生成极高品质的样本,但生成的样本通常仍保持可懂度,听感像数字发音。
4.2 说话人条件模型
python train.py hparams/train.yaml --speaker_conditioned true --data_folder=your/data/folder开启speaker_conditioned后,模型以说话人嵌入为条件。生成时可得到可懂的数字;当用同一说话人做条件时,生成语音会带有该说话人(或非常相似)的音色特征。
配置文件中与之相关的参数:
speaker_count: 60:AudioMNIST 中说话人数量(嵌入表大小);speaker_emb_dim:说话人嵌入维度,默认等于emb_dim = model_channels * 4 = 512;speaker_sample_count: 5:评估阶段每个说话人抽样生成的数量。
4.3 数字条件模型(简化版 TTS)
python train.py hparams/train.yaml --digit_conditioned true --data_folder=your/data/folder开启digit_conditioned后,模型以数字标签(0~9)为条件,类似一个极简的文本到语音(TTS)系统:给定数字标签,生成该数字的发音。用同一数字做条件时,应生成同一个数字(通常来自不同说话人)。
相关参数:
digit_count: 10:数字类别数;digit_emb_dim:数字嵌入维度,同样默认为 512;digit_sample_count: 3:评估阶段每个数字抽样生成的数量。
两个条件可以同时开启,此时条件为“说话人 × 数字”的组合。条件嵌入的启用通过use_cond_emb开关控制,具体嵌入定义在cond_emb中(train.yaml):
use_cond_emb: speaker: !ref <speaker_conditioned> digit: !ref <digit_conditioned> cond_emb: speaker: emb: !ref <emb_speaker> emb_dim: !ref <speaker_emb_dim> key: speaker_label sample_count: !ref <speaker_sample_count> count: !ref <speaker_count> digit: emb: !ref <emb_digit> emb_dim: !ref <digit_emb_dim> key: digit_label sample_count: !ref <digit_sample_count> count: !ref <digit_count>emb_digit与emb_speaker是 Embedding 层,num_embeddings分别为 10 与 60。UNet 通过cond_emb/use_cond_emb参数接收这些嵌入,在 speechbrain/nnet/unet.py 的diffusion_forward()中把条件嵌入传入模型。
4.4 潜在扩散模型
python train.py hparams/train_latent.yaml --data_folder=your/data/folder使用train_latent.yaml时,训练流程变为:
- 先用
UNetNormalizingAutoencoder(speechbrain/nnet/unet.py)把谱图编码为低维潜在表示(latent_channels: 2,时间维再下采样latent_downsample_factor: 4); - 在潜在空间上运行 DDPM(此时扩散的输入通道数就是潜在通道数 2,
diffusion_channels: !ref <autoencoder_latent_channels>); - 采样时先由扩散模型生成潜在表示,再由自编码器解码回谱图。
潜在扩散生成的数字音频质量略低于样本空间扩散,但生成的信号听起来仍是数字。其优势是推理更快、显存占用更小。
train_latent.yaml引入了若干额外的训练机制与对应参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
train_diffusion_start_epoch | 2 | 扩散模型从第 2 个 epoch 开始训练(此前只训自编码器) |
train_autoencoder_stop_epoch | 10 | 自编码器训练到第 10 个 epoch 后停止 |
loss_laplacian_weight | 0.1 | 拉普拉斯(高频)损失权重,鼓励谱图平滑 |
latent_mask_value | -3.0 | 潜在空间中用于掩码的值 |
latent_mask_offset | 3 | 计算潜在掩码值时使用的偏移 |
latent_mask_recompute_steps | 20 | 每隔多少步重新计算潜在掩码值 |
done_random_start_offset/done_random_end_offset | 0.0 / 0.5 | 结束检测器训练时随机拼接样本的切片范围 |
自编码器与扩散模型分阶段联合训练:从 train.py 的on_stage_start()可以看到,train_diffusion与train_autoencoder由 epoch 窗口控制,二者使用独立的优化器(opt_class_autoencoder,AdamW)与独立的学习率调度器(lr_annealing_autoencoder)。fit_batch()中两个模型按各自的损失分别反向传播与更新(train.py)。
结束检测器(Done Detector):潜在扩散还训练了一个done_detector,用于判断生成过程何时“结束”(即预测数字发音的结束位置),从而把生成的长谱图裁剪为实际有效片段。它由 CRDNN(CNN+RNN+DNN)加 Softmax 构成(train_latent.yaml),其训练数据是“真实样本与随机样本的拼接”,损失函数为distance_diff_loss。采样后,cut_samples()(train.py)用它的 argmax 预测作为裁剪边界,同时裁剪谱图与对应音频。
4.5 关键训练超参数对照
两个配置文件的核心训练参数对比:
| 参数 | train.yaml(simple) | train_latent.yaml(latent) |
|---|---|---|
diffusion_mode | simple | latent |
number_of_epochs | 20 | 20 |
batch_size | 16(显存超 32GB 可试 32) | 16 |
lr | 0.0002 | 0.0005 |
lr_autoencoder | — | 0.001 |
max_grad_norm | 0.05 | 0.05 |
train_timesteps | 250 | 250 |
eval_time_steps | 40 | 6 |
model_channels | 128 | 64 |
model_num_res_blocks | 4 | 2 |
spec_sample_size | 80 | 20 |
downsample_factor | 8 | 8 |
optimizer | Adam | AdamW(三份:diffusion/autoencoder/done) |
norm_out_sample | False | True |
samples_interval | 5 | 5 |
eval_num_samples | 10 | 10 |
lr_total_steps的计算方式也不同:simple 配方为(train_len × epochs) // batch_size,latent 配方为train_len × epochs(未除以 batch_size)。
5. 训练过程中的生成与评测
5.1 每轮结束自动生成样本
训练脚本在每个 epoch 结束时(非训练阶段且epoch % samples_interval == 0)自动调用generate_samples()生成一批样本(train.py),流程为:
generate_spectrograms()根据是否条件化,走generate_spectrograms_conditioned()或generate_spectrograms_unconditioned();- 无条件时直接以
(eval_num_samples, diffusion_channels, eval_time_steps, spec_sample_size)为形状从纯噪声采样; - 条件化时,
sample_cond_labels()从启用条件的标签集合中(按sample_count随机抽样)生成“说话人 × 数字”组合,对每个组合调用generate_spectrograms_for_label()生成eval_num_samples个样本,文件名形如digit_4_speaker_10_0(见get_sample_label(),train.py); - 若
eval_generate_audio: True,用 HiFi-GAN 声码器(speechbrain/tts-hifigan-libritts-16kHz)把生成的谱图合成为波形(generate_audio(),train.py)。反归一化链路为denormalize()(train.py):切掉补齐维度 →MinLevelNorm.denormalize→ dB 转幅度 → 动态范围压缩。
5.2 样本输出位置
生成的样本保存在<output_folder>/samples目录下,目录按 epoch 编号组织,例如<output_folder>/samples/<epoch>/,包含:
spec/:生成的谱图 PNG(spec_<label>.png);wav/:生成的音频(sample_<label>.wav);spec_rec/、wav_rec/(仅 latent 模式):自编码器重建的谱图与音频,用于评估自编码器质量;raw.pt:原始张量数据(spec、spec_denorm、wav等),便于进一步分析;ref/:参考样本(真实数据的谱图与合成音频),用于与生成结果对比。
输出文件夹的整体结构由output_folder派生:save_folder(checkpoint 目录)、sample_folder(样本目录)、train_log.txt(训练日志)、logs/(TensorBoard 日志)。
5.3 监控指标
训练与验证阶段记录以下指标(写入train_log.txt与 TensorBoard):
loss:扩散模型损失。使用ScheduledLoss调度:前loss_l2_steps=100000步用 MSE(mse_loss),之后切换为 L1(l1_loss)以保留更多高频细节;lr:当前学习率;- 数据分布统计(
data_mean/data_std等):通过dist_stats统计输入谱图分布; - latent 模式额外记录:
autoencoder_loss、laplacian_loss、加权后的weighted_laplacian_loss、lr_autoencoder以及重建/潜在空间分布统计; - 每 5 个 epoch(
samples_interval)在验证阶段记录生成样本的均值/标准差/最小/最大值统计。
enable_train_metrics控制是否收集分布统计,train_log_interval控制训练期间 TensorBoard 记录的频率。
5.4 检查点与训练恢复
Checkpointer(speechbrain.utils.checkpoints.Checkpointer)注册了所有可恢复对象:UNet、自编码器(latent)、epoch 计数器、学习率调度器、global_norm归一化层、条件嵌入层(simple)、done_detector(latent)以及各优化器。ckpt_interval_minutes控制每 N 分钟保存一次检查点,验证阶段依据min_keys=["loss"]保留最优检查点(train.py)。训练中断后重新运行同一命令即可从检查点恢复。
6. 从源码理解训练循环
DiffusionBrain(train.py)继承sb.Brain,其核心流程如下:
compute_forward()(train.py):提取特征 → (可选)计算条件嵌入 → 根据diffusion_mode调用diffusion.train_sample()或diffusion_latent.train_sample_latent(),返回(pred, noise, noisy_sample);若启用 done_detector,额外构造拼接样本并前向;compute_objectives()(train.py):扩散损失为预测噪声与真实噪声之间的 MSE/L1(reshape_feats把(batch, channels, feats, length)重排为(batch, length, feats)以适配 SpeechBrain 标准损失);latent 模式叠加自编码器重建损失与拉普拉斯损失;fit_batch():支持梯度累积(grad_accumulation_factor)与 DDP 的no_sync,扩散、done_detector、自编码器各自独立更新;on_stage_start()/on_stage_end():管理分阶段训练开关、指标初始化、参考样本生成与检查点保存。
从源码结构可以推断,这套扩散实现与 UNet 主干(speechbrain/nnet/unet.py 的UNetModel)配合紧密:train.yaml中unet.diffusion_forward被作为DenoisingDiffusion的model,diffusion_forward内部即调用 UNet 的常规前向并透传cond_emb条件嵌入(speechbrain/nnet/unet.py)。UNet 的attention_resolutions(simple 配方为[8],latent 配方为[1, 2])指定在哪些下采样分辨率处使用注意力,norm_num_groups使用分组归一化,这些都会显著影响生成质量与训练速度。
7. 复现实验与进一步阅读
- 官方训练结果:配方 README 中提供了上述全部实验(无条件、说话人条件、数字条件、潜在扩散)的样本、检查点与训练日志下载(Dropbox 链接,见 README.md),可据此快速对比不同配置的生成效果。
- 源码深入:扩散核心实现位于 speechbrain/nnet/diffusion.py,UNet 与归一化自编码器位于 speechbrain/nnet/unet.py,单元测试见 tests/unittests/test_diffusion.py,展示了
distort()与sample()的数值行为验证。 - 数据准备:完整的数据下载与预处理逻辑见 audiomnist_prepare.py。
- 通用训练框架:本配方基于 SpeechBrain 的
Brain类训练循环,可参考 speechbrain/core.py 与 docs/tutorials/basics/brain-class.ipynb 了解fit/evaluate的通用机制;扩散类模块在 speechbrain/nnet/init.py 中导出。
8. 常见问题与调参建议
- 显存不足:将
batch_size降到 16 以下(YAML 注释提示 32 GB 以下显存应避免 32 的 batch),或改用 latent 配方(通道数更少、谱图尺寸更小)。 - 生成质量不佳:AudioMNIST 规模较小是客观限制;可尝试增大
model_channels、model_num_res_blocks,增加number_of_epochs,或调整eval_time_steps(采样步数越多越精细,但越慢;latent 配方默认仅 6 步,simple 配方 40 步)。 - 训练重启过慢:数据已准备好后,设置
--skip_prep true跳过重复的数据处理。 - 快速验证:可将
overfit_test: True与overfit_test_sample_count配合使用,先在小样本上验证代码链路是否正确。 - 采样进度:
eval_show_progress控制采样进度条;eval_num_samples控制每轮生成的样本数量,过大会拖慢验证阶段。
通过本配方,你可以在 SpeechBrain 框架内以四种模式训练 DDPM 语音生成模型,并借助源码理解每一步的底层实现。它既适合作为扩散模型入门的教学示例,也适合作为简化版 TTS 或说话人音色生成实验的起点。
【免费下载链接】speechbrainA PyTorch-based Speech Toolkit项目地址: https://gitcode.com/GitHub_Trending/sp/speechbrain
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考