用 PyTorch 实现 MuseGAN:生成多轨巴赫风格合唱曲
数字音乐怎么用数据表示?
要让神经网络生成音乐,首先得把音乐变成它能处理的数字格式。图像有像素,文本有词表,音乐也有自己的编码方式。
最基础的单位是音符,由三个要素决定:音高(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(节奏向量):同样每条音轨独享,控制该声部的节奏模式。
生成器内部结构分两阶段:
- 上层网络(shared network):接收 z_c 和 z_s,输出一个“和声-风格”特征图,尺寸与时间步一致,但音高和音轨维度尚未展开。
- 下层网络(track-specific networks):对每条音轨,将上层输出与该轨的 z_m、z_r 拼接,再通过独立的小网络生成最终的
(时间步, 音高)二值矩阵。
这种分层架构让模型既能保证多轨间的和声协调,又能赋予各声部独立的旋律个性。
数据准备:从 MIDI 到张量
JSB Chorales 是一个经典数据集,包含 382 首巴赫四声部众赞歌,以 MIDI 格式提供。我们需要将其转换为上述三维张量。
步骤如下:
- 解析 MIDI 文件:用
pretty_midi库读取每个音轨的 note events,提取 onset、offset、pitch。 - 量化时间:将连续时间对齐到离散时间步。这里采用 16 分音符为单位(每拍 4 步),8 小节共 128 步。
- 裁剪或填充:确保所有曲子长度统一为 128 步。短于 128 的末尾补零,长的截断(实际 JSB 曲子大多刚好 8 小节)。
- 音高归一化:只保留 MIDI 36–81 范围内的音符(C2 到 A5),超出的丢弃或移调(此处直接丢弃,因巴赫合唱基本在此范围内)。
- 构建张量:初始化全零
(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:
- 遍历每个音轨
- 找出连续激活的时间步,合并为 note(需处理连音)
- 用
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 应用于结构化序列生成,其设计思想对理解多模态生成仍有启发意义。