用 PyTorch 实现 MuseGAN:生成多轨巴赫风格合唱曲

0 阅读

数字音乐怎么用数据表示?

要让神经网络生成音乐,首先得把音乐变成它能处理的数字格式。图像有像素,文本有词表,音乐也有自己的编码方式。

最基础的单位是音符,由三个要素决定:音高(pitch)、持续时间(duration)和起始时间(onset)。在固定节拍和小节结构的前提下,我们可以把时间轴切成等长的时间步(比如每拍 4 步),这样每个音符就变成一个“何时开始、持续多久、是什么音高”的三元组。

对于多音轨音乐(比如钢琴左手右手、乐队不同乐器),每条轨道独立记录音符事件。MuseGAN 处理的是四声部合唱——女高音(Soprano)、女低音(Alto)、男高音(Tenor)、男低音(Bass),正好对应 JSB Chorales 数据集的结构。

最终,一段音乐被表示为一个三维张量:[时间步数, 音高范围, 音轨数]。例如,8 小节、每小节 4 拍、每拍 4 时间步,共 128 步;音高范围取 MIDI 36–81(覆盖合唱常用音域,共 46 个音);4 条音轨。于是输入形状就是 (128, 46, 4),每个位置是 0 或 1,表示该时刻该音高在该音轨是否发声。

这种表示法虽然稀疏(大部分是 0),但结构清晰,适合卷积网络处理。

MuseGAN 的核心设计:四个噪声向量控制不同音乐维度

传统 GAN 用一个随机噪声向量 z 生成整幅图像。但音乐比图像更结构化——和弦进行、各声部旋律、整体风格彼此关联又相对独立。MuseGAN 的关键创新在于分解控制:用四个不同的潜在向量分别影响音乐的不同层面。

  • z_c(和弦向量):全局共享,决定整段音乐的和声骨架。所有音轨都受它影响,确保和弦一致性。
  • z_s(风格向量):也全局共享,控制整体演奏风格,比如节奏密度、动态变化等。
  • z_m(旋律向量):每条音轨独享一个,决定该声部的旋律走向。
  • z_r(节奏向量):同样每条音轨独享,控制该声部的节奏模式。

生成器内部结构分两阶段:

  1. 上层网络(shared network):接收 z_c 和 z_s,输出一个“和声-风格”特征图,尺寸与时间步一致,但音高和音轨维度尚未展开。
  2. 下层网络(track-specific networks):对每条音轨,将上层输出与该轨的 z_m、z_r 拼接,再通过独立的小网络生成最终的 (时间步, 音高) 二值矩阵。

这种分层架构让模型既能保证多轨间的和声协调,又能赋予各声部独立的旋律个性。

数据准备:从 MIDI 到张量

JSB Chorales 是一个经典数据集,包含 382 首巴赫四声部众赞歌,以 MIDI 格式提供。我们需要将其转换为上述三维张量。

步骤如下:

  1. 解析 MIDI 文件:用 pretty_midi 库读取每个音轨的 note events,提取 onset、offset、pitch。
  2. 量化时间:将连续时间对齐到离散时间步。这里采用 16 分音符为单位(每拍 4 步),8 小节共 128 步。
  3. 裁剪或填充:确保所有曲子长度统一为 128 步。短于 128 的末尾补零,长的截断(实际 JSB 曲子大多刚好 8 小节)。
  4. 音高归一化:只保留 MIDI 36–81 范围内的音符(C2 到 A5),超出的丢弃或移调(此处直接丢弃,因巴赫合唱基本在此范围内)。
  5. 构建张量:初始化全零 (128, 46, 4) 张量,对每个音符,在对应时间步、音高索引(pitch-36)、音轨位置置 1。

注意:MuseGAN 原始论文使用的是 piano-roll 表示,即每个时间步可同时有多个音高激活(和弦),这与单旋律线模型不同。

构建 MuseGAN 网络结构

判别器设计

判别器的任务是判断输入音乐是否真实。它需要同时捕捉时间动态、音高分布和多轨互动。

我们采用 2D 卷积处理 (时间步, 音高, 音轨) 张量,把音轨视为“通道”。结构类似图像判别器:

  • 输入:(batch, 128, 46, 4)
  • 卷积层序列:kernel_size=(4,4) 或 (2,4),stride=2,逐步下采样
  • 每层后接 LeakyReLU 和 BatchNorm
  • 最终展平,接全连接层输出单个 logit

关键点:不使用全连接层直接处理原始输入,而是依赖卷积捕捉局部模式(如音程、节奏型)。

生成器设计

生成器更复杂,需实现前述的分层结构。

上层网络(共享部分)

  • 输入:拼接后的 z_c 和 z_s(假设各 64 维,共 128 维)
  • 全连接层映射到 (8, 8, 128) 的特征图(模拟“低分辨率”音乐草图)
  • 通过转置卷积逐步上采样到 (32, 12, 64)

下层网络(每轨独立)

  • 对每条音轨 i:
    • 将上层输出与 z_m_i、z_r_i 拼接(沿通道维)
    • 再经几层转置卷积,最终输出 (128, 46, 1)
  • 四轨结果拼接成 (128, 46, 4)

最后一层用 sigmoid 激活,输出 0–1 概率,训练时用 BCELoss,推理时可设阈值(如 0.5)二值化。

注意:转置卷积的 stride 和 kernel 需精心设计,确保最终尺寸恰好是 128×46。

训练细节与技巧

损失函数与优化器

使用标准 GAN 的 min-max 损失:

  • 判别器损失:L_D = -E[log D(x)] - E[log(1 - D(G(z)))]
  • 生成器损失:L_G = -E[log D(G(z))]

实践中常用带标签平滑的 BCELoss:真实样本目标设为 0.9 而非 1.0,防止判别器过强。

优化器选 Adam,lr=0.0002,beta1=0.5,这是 GAN 训练的常见配置。

训练策略

  • 交替训练:每轮先更新判别器 1 次,再更新生成器 1 次
  • 梯度裁剪:防止训练不稳定(可选)
  • 早停监控:观察生成样本的听感,而非仅看 loss

由于音乐数据量小(仅 382 首),容易过拟合。可考虑:

  • 数据增强:随机移调(±2 半音)、时间拉伸(±5%)
  • 正则化:在判别器加 dropout

生成与评估

训练约 200 轮后,生成器能产出结构合理的四声部片段。典型特征:

  • 和声进行符合功能和声规则(主-属-主等)
  • 各声部旋律流畅,少有大跳
  • 节奏以八分、四分音符为主,符合巴赫风格

但也会出现瑕疵:

  • 偶尔平行五度(巴赫禁忌)
  • 低音声部节奏过于简单
  • 结尾缺乏终止式

这些说明模型学到了统计规律,但未完全掌握音乐理论约束。

要听生成结果,需将张量转回 MIDI:

  1. 遍历每个音轨
  2. 找出连续激活的时间步,合并为 note(需处理连音)
  3. pretty_midi 写入文件

建议用 MuseScore 或 GarageBand 播放,比纯音频更能看清声部关系。

为什么 MuseGAN 值得关注?

相比端到端的音乐生成模型(如 Music Transformer),MuseGAN 的优势在于可控性。通过单独调整 z_m 或 z_r,可以改变某一声部的旋律而不影响和声框架。这为音乐人提供了“AI 协作”的可能——比如固定和弦进行,让 AI 生成不同旋律变体。

当然,它也有局限:

  • 依赖固定时间网格,难以处理 rubato(自由速度)
  • 二值表示忽略力度、音色等表现力维度
  • 训练数据需严格对齐(JSB 正好满足)

后续工作如 PopMuseGAN 扩展到了流行音乐多轨生成,但核心思想仍源于此。

实现注意事项

  • 潜在向量维度:原论文 z_c/z_s 各 10 维,z_m/z_r 各 20 维。实践中可增大(如 64 维)提升表达力。
  • 卷积 padding:转置卷积易导致尺寸偏差,建议用 output_padding 参数微调。
  • 批处理:确保所有样本长度严格一致,否则 DataLoader 会报错。
  • 评估指标:除了听感,可用 chord consistency(和弦一致性)、voice range(声部音域)等规则做自动检查。

完整代码需处理数据加载、网络定义、训练循环三部分。重点在于生成器的分层 forward 函数——先算共享特征,再循环处理各音轨。

MuseGAN 不是当前最先进的音乐生成模型,但它清晰展示了如何将 GAN 应用于结构化序列生成,其设计思想对理解多模态生成仍有启发意义。