使用 PyTorch 学习生成式人工智能——使用 MuseGAN 进行音乐生成

129 阅读43分钟

本章内容包括:

  • 使用音乐数字接口(MIDI)进行音乐表示
  • 将音乐生成视为类似图像生成的对象创建问题
  • 构建并训练生成对抗网络(GAN)来生成音乐
  • 使用训练好的 MuseGAN 模型生成音乐

到目前为止,我们已经成功生成了形状、数字、图像和文本。在本章及下一章中,我们将探索两种不同的逼真音乐生成方法。本章将应用图像GAN技术,将一段音乐视作类似图像的多维对象。生成器将产生完整的音乐片段,并提交给评论家(批评者,充当判别器,采用第5章讨论的带梯度惩罚的Wasserstein距离)进行评估。生成器会根据评论家的反馈不断调整音乐,直到生成的音乐与训练数据中的真实音乐非常接近。下一章中,我们将把音乐看作一系列音乐事件,采用自然语言处理(NLP)技术,使用GPT风格的Transformer基于前序事件预测最可能的下一个音乐事件,生成可转换为逼真音乐的长序列音乐事件。

利用AI进行音乐生成是一个备受关注的领域,MuseGAN是其中的知名模型,由Dong、Hsiao、Yang和Yang于2017年提出。MuseGAN是一个深度神经网络,利用生成对抗网络(GAN)生成多轨音乐,“Muse”寓意音乐的创作灵感。该模型能够理解表示不同乐器或人声(训练数据中如此)的多个轨道间的复杂交互,因而能生成和谐统一的音乐作品。

类似于其他GAN模型,MuseGAN由两个主要部分组成:生成器和评论家(提供样本真实度的连续评分,而非简单的真假分类)。生成器负责创作音乐,评论家评估音乐质量并反馈给生成器。两者的对抗过程促使生成器不断进步,产出更真实、更吸引人的音乐。

假设你是约翰·塞巴斯蒂安·巴赫的忠实粉丝,听过他的所有作品,你可能想知道是否能用MuseGAN创作模仿巴赫风格的合成音乐。答案是肯定的,本章将教你如何实现。

具体来说,首先你将学习如何将一段多轨音乐表示为多维对象。轨道本质上是一条独立的音乐线条或声音,可以是不同的乐器如钢琴、贝斯、鼓,也可以是不同声部如女高音、中音、男高音或低音。在电子音乐制作中,轨道通常被分为小节(时间段),每个小节细分为若干步进以便精细控制节奏,并为每一步分配特定音符,创造旋律和节奏。因此,我们训练集中的每段音乐以(4, 2, 16, 84)的形状组织:4个轨道,每轨2个小节,每小节16步进,每步进能播放84个不同音符之一。

MuseGAN生成的音乐风格会受训练数据影响。你感兴趣的是巴赫作品,因此将使用JSB合唱曲数据集训练MuseGAN,这是巴赫为四轨编写的合唱曲合集,已转换为钢琴卷帘表示(piano roll),一种用于音乐可视化和数字处理的编码方式。你将学习如何将(4, 2, 16, 84)形状的音乐转换为MIDI文件,在电脑上播放。

与之前章节中生成器仅用单个潜在空间噪声向量生成各种内容(形状、数字、图像)不同,MuseGAN的生成器在生成音乐时会使用四个不同的噪声向量(和弦、风格、旋律、律动,稍后会详细解释)。这种设计允许对音乐生成的各个方面进行更细致的控制和更多样的创作。每个噪声向量代表音乐的不同维度,通过单独调控,模型能够产生更复杂、细腻的作品。

模型训练完成后,我们将丢弃评论家网络(GAN模型中的常规做法),使用训练好的生成器输入四个潜在噪声向量,生成接近巴赫风格的音乐作品。

13.1 数字音乐表示

我们的目标是掌握从零开始构建和训练生成对抗网络(GAN)模型以生成音乐的技巧。为此,我们需要从音乐理论基础入手,包括理解音符、八度和音高编号。随后,我们将深入了解数字音乐的内部机制,特别关注MIDI文件。

根据用于音乐生成的机器学习模型类型,音乐的数字表示形式也会有所不同。例如,在本章中,我们将音乐表示为多维对象,而在下一章中,则会采用另一种格式——索引序列。

本节内容包括音乐理论基础,然后介绍如何使用钢琴卷帘图(piano rolls)对音乐进行数字化表示。你将学习如何加载和播放示例MIDI文件。我们还将介绍Python的music21库,教你安装并用它来可视化乐谱中的五线谱音符。最后,你将学会如何将一段音乐表示为形状为(4, 2, 16, 84)的多维对象。

13.1.1 音符、八度和音高

本章使用的训练数据集将音乐作品表示为四维对象。为了理解训练数据中音乐作品的含义,首先必须熟悉音乐理论中的一些基本概念,如音符、八度和音高。这些概念相互关联,是理解数据集的关键。

图13.1展示了这些概念之间的关系。

image.png

图13.1 音符、八度和音高(也称为音符编号)之间的关系。第一列展示了11个八度(范围从–1到9),代表不同的音乐音高层级。每个八度细分为12个半音,列在最上方一行:C、C#、D、D#、……、B。在每个八度内,每个音符都有对应的音高编号,范围是0到127,如图所示。

音乐音符是表示音乐中特定声音的符号。这些音符是音乐的基础元素,用于构建旋律、和弦和节奏。每个音符都有名称(如A、B、C、D、E、F、G),对应特定频率,从而决定音高:即音符声音的高低。例如,中音C(C4)通常的频率约为262赫兹,表示其声波每秒震动262次。

你可能会疑惑“中音C(C4)”的含义。C4中的数字4表示八度,即一个音高层级与下一个音高层级之间的距离。在图13.1中,最左列显示了11个八度层级,范围从–1到9。声音频率在每升高一个八度时会翻倍。例如,A4通常调到440赫兹,而A5则是A4之上一个八度,频率为880赫兹。

在西方音乐中,一个八度被划分为12个半音,每个半音对应一个特定音符。图13.1最上方列出了这12个半音:C、C#、D、D#、……、B。向上或向下移动12个半音,将到达同名音符但位于更高或更低的八度。如前所述,A5位于A4之上一个八度。

每个特定八度内的音符都被赋予一个音高编号,范围是0到127,如图13.1所示。例如,C4的音高编号是60,而F3的音高编号是53。音高编号是一种更有效的音符表示方式,因为它同时指定了八度层级和半音。你在本章使用的训练数据就是基于音高编号编码的。

13.1.2 多轨音乐简介

首先,我们介绍多轨音乐的工作原理及其数字表示方法。在电子音乐制作中,“轨道”通常指音乐的单独层次或组成部分,比如鼓轨、贝斯轨或旋律轨。在古典音乐中,轨道可能代表不同的声部,如女高音、女低音、男高音和男低音。例如,我们本章使用的训练数据集JSB合唱曲集包含四个轨道,对应四个声部。在音乐制作中,每个轨道可以在数字音频工作站(DAW)中单独编辑和处理。轨道由各种音乐元素组成,包括小节、步长和音符。

小节(或拍号)是由一定数量的节拍组成的时间段,每个节拍有特定的音符时值。在许多流行音乐中,一个小节通常有四拍,但具体数量取决于作品的拍号。轨道中小节总数由轨道长度和结构决定。例如,我们的训练数据集中,每个轨道包含两个小节。

在步长序列编程(step sequencing)中,这是电子音乐中常用于编排节奏和旋律的技术,“步长”表示小节的细分。以标准的4/4拍为例(每小节四拍,每拍四步长),每小节有16步长,每步相当于小节的十六分之一。

每个步长包含一个音乐音符。在我们的数据集中,音符范围限制为84个最常用音符(音高编号0到83)。因此,步长中的音符以84维的one-hot向量进行编码。

为更直观理解上述概念,请从本书GitHub仓库(github.com/markhliu/DG…)下载example.midi文件,并保存到电脑的/files/目录。扩展名为.midi的文件是MIDI文件。MIDI是一项技术标准,定义了电子乐器、计算机及相关设备之间连接和通信的协议、数字接口及连接器。

MIDI文件可在大多数电脑音乐播放器中播放。要了解训练数据中的音乐类型,请用电脑上的音乐播放器打开刚下载的example.midi文件。它的声音类似我网站上这个音乐文件:mng.bz/lrJB。example.midi是从本章训练数据中的一段音乐转换而来。之后你将学习如何把训练数据中形状为(4, 2, 16, 84)的音乐片段转换为MIDI文件,可在电脑上播放。

我们将使用music21 Python库,它是一个功能强大且全面的音乐分析、作曲与处理工具包,用于可视化音乐相关概念。请在电脑上的Jupyter Notebook中新建单元并运行以下代码:

!pip install music21

music21库允许你将音乐可视化为五线谱,有助于理解轨道、小节、步长和音符。为此,你需要先安装MuseScore应用。访问 musescore.org/en/download 下载适合你操作系统的最新版本(目前是MuseScore 4)。请记住MuseScore应用在电脑上的路径。例如,在Windows系统中,路径通常是:

C:\Program Files\MuseScore 4\bin\MuseScore4.exe

在Jupyter Notebook中新建单元,运行如下代码以可视化example.midi文件的五线谱:

%matplotlib inline
from music21 import midi, environment

mf = midi.MidiFile()
mf.open("files/example.midi")
mf.read()
mf.close()
stream = midi.translate.midiFileToStream(mf)
us = environment.Environment()
path = r'C:\Program Files\MuseScore 4\bin\MuseScore4.exe'
us['musescoreDirectPNGPath'] = path
stream.show()

解释:
① 在Jupyter Notebook内显示图像,而非打开外部应用
② 打开MIDI文件
③ 指定MuseScore应用路径
④ 显示五线谱

macOS用户请将路径改为:

/Applications/MuseScore 4.app/Contents/MacOS/mscore

Linux用户请将路径改为:

/home/[用户名]/.local/bin/mscore4portable

将[用户名]替换为你的实际用户名。例如,我的用户名是mark,路径是:

/home/mark/.local/bin/mscore4portable

运行以上代码后,你会看到类似图13.2的五线谱图。图中注释为我添加,实际显示时仅为五线谱本身,无额外注释。

image.png

图13.2 JSB合唱曲数据集中一段音乐的五线谱记谱。该音乐包含四个轨道,分别代表合唱中的四个声部:女高音(soprano)、女低音(alto)、男高音(tenor)和男低音(bass)。记谱结构分为每个轨道两个小节,左半部分表示第一小节,右半部分表示第二小节。每个小节包含16个步长,符合4/4拍时间签名——即一个小节有四拍,每拍细分为四个十六分音符。共有84个不同音高,每个音符用84维的one-hot向量表示。

JSB合唱曲数据集包含约翰·塞巴斯蒂安·巴赫(Johann Sebastian Bach)创作的合唱音乐作品,常用于音乐生成的机器学习模型训练。数据集中每段音乐的形状为(4, 2, 16, 84),具体含义如下:4代表合唱的四个声部,分别作为独立轨道;2代表每个轨道包含两个小节;16代表每个小节包含16个步长(细分单位);84则表示每步长中可能演奏的音高(音符)数量,采用one-hot编码表示。

13.1.3 音乐的数字表示:钢琴卷帘图

钢琴卷帘图是MIDI序列软件和数字音频工作站(DAW)中常用的音乐可视化表示方式。它得名于传统的自动钢琴用的钢琴卷帘纸,这种纸卷上打孔表示音乐音符。在数字环境中,钢琴卷帘图承担类似功能,但以虚拟形式呈现。

钢琴卷帘图以网格形式显示,时间轴水平展现(从左到右),音高垂直排列(从下到上)。每行对应一个特定的音乐音符,顶部是高音,底部是低音,类似钢琴键盘的布局。

音符以条块形式表示在网格上。条块在垂直方向的位置表示音高,水平方向位置表示该音符在音乐中的时间。条块的长度表示音符的时值(持续时间)。

我们用music21库来展示钢琴卷帘图。在Jupyter Notebook中新建代码单元,运行以下命令:

stream.plot()

输出如图13.3所示。

music21库还支持查看与钢琴卷帘图对应的量化音符,代码示例:

for n in stream.recurse().notes:
    print(n.offset, n.pitches)

输出示例:

0.0 (<music21.pitch.Pitch E4>,)
0.25 (<music21.pitch.Pitch A4>,)
0.5 (<music21.pitch.Pitch G4>,)
0.75 (<music21.pitch.Pitch F4>,)
1.0 (<music21.pitch.Pitch E4>,)
1.25 (<music21.pitch.Pitch D4>,)
1.75 (<music21.pitch.Pitch E4>,)
2.0 (<music21.pitch.Pitch E4>,)
2.5 (<music21.pitch.Pitch D4>,)
3.0 (<music21.pitch.Pitch C4>,)
3.25 (<music21.pitch.Pitch A3>,)
3.75 (<music21.pitch.Pitch B3>,)
0.0 (<music21.pitch.Pitch G3>,)
0.25 (<music21.pitch.Pitch A3>,)
0.5 (<music21.pitch.Pitch B3>,)
…
3.25 (<music21.pitch.Pitch F2>,)
3.75 (<music21.pitch.Pitch E2>,)

以上是每个音符相对于乐曲开头的时间偏移(offset)和音高信息。

image.png

图13.3 一段音乐的钢琴卷帘图。钢琴卷帘图是音乐作品的图形化表示,以网格形式呈现,时间从左到右水平推进,音高从下到上垂直排列。网格中的每一行对应一个独特的音乐音符,排列方式类似于钢琴键盘,较高的音符在上方,较低的音符在下方。这段音乐包含两个小节,因此图中显示两个不同的部分。音符块在垂直方向的位置表示其音高,水平方向的位置表示该音符在曲中演奏的时间。同时,音符块的长度表示该音符持续的时间。

我省略了大部分输出。之前输出中每行的第一个数值表示时间,通常每行时间增加0.25秒。如果下一行时间增长超过0.25秒,说明某个音符持续时间超过0.25秒。可以看到,起始音符是E4,0.25秒后变为A4,然后是G4,依此类推。这对应图13.3中最左边的前三个音符块,分别是E、A和G。

你可能想知道如何将音乐音符序列转换成形状为(4, 2, 16, 84)的对象。为理解这一点,我们来看音乐音符中每个时间步的音高编号:

for n in stream.recurse().notes:
    print(n.offset, n.pitches[0].midi)

输出示例:

0.0 64
0.25 69
0.5 67
0.75 65
1.0 64
1.25 62
1.75 64
2.0 64
2.5 62
3.0 60
3.25 57
3.75 59
0.0 55
0.25 57
0.5 59
…
3.25 41
3.75 40

以上代码将每个时间步的音乐音符转换为音高编号,范围在0到83之间,对应图13.1中的映射。每个音高编号再被转换为一个84维的one-hot向量,其中所有位置为-1,只有对应音高的位置为1。我们使用-1和1来进行one-hot编码,而不是传统的0和1,是因为将数值限制在-1到1之间有助于将数据中心化到0,从而使训练更加稳定且加快速度。许多激活函数和权重初始化方法都假设输入数据是以0为中心的。

图13.4 展示了一段MIDI音乐如何被编码成形状为(4, 2, 16, 84)的对象。

image.png

图13.4 使用四维对象表示一段音乐。在我们的训练数据中,每段音乐被表示为形状为 (4, 2, 16, 84) 的四维对象。第一个维度代表四条音乐轨道,即音乐中的四个声部(女高音、女低音、男高音和男低音)。每条轨道被划分为两个小节。每个小节包含四拍,每拍细分为四个音符,因此每小节共有16个音符。最后,每个音符用一个84维的one-hot向量表示,向量中除一个位置为1外,其余位置均为-1。

图13.4 解释了形状为 (4, 2, 16, 84) 的音乐对象的维度。简而言之,每段音乐由四条轨道组成,每条轨道包含两个小节,每个小节细分为16个音符。鉴于我们的训练集中音高编号范围是0到83,每个音符都用一个84维的one-hot向量表示。

在后续准备训练数据的内容中,我们将探讨如何将形状为 (4, 2, 16, 84) 的对象转换回MIDI格式的音乐,以便在计算机上播放。

13.2 音乐生成的蓝图

创作音乐时,我们需要引入更细致的输入以增强控制力和多样性。不同于之前用单一潜在空间噪声向量生成形状、数字和图像的方式,音乐生成过程中我们将使用四个不同的噪声向量。鉴于每段音乐包含四条轨道和两个小节,我们将用四个向量来管理这一结构。具体来说,一个向量控制所有轨道和小节的整体,另一个向量控制所有轨道的每个小节,第三个向量控制所有小节的所有轨道,第四个向量控制每条轨道中的每个小节。接下来本节将介绍和弦(chords)、风格(style)、旋律(melody)和律动(groove)四个概念,以及它们如何影响音乐生成的各个方面。随后我们会讨论构建和训练MuseGAN模型的具体步骤。

13.2.1 以和弦、风格、旋律和律动构建音乐

在音乐生成阶段,我们将从潜在空间获得四个噪声向量(和弦、风格、旋律和律动),并将它们输入生成器以创建音乐。你或许会好奇这四种信息的含义。在音乐中,和弦、风格、旋律和律动是塑造一段音乐整体音效和氛围的关键元素。下面对这四个元素进行简要说明:

  • 风格(Style)指音乐创作、演奏和体验的独特方式。它包括音乐类型(如爵士、古典、摇滚等)、创作时代以及作曲家或表演者的独特手法。风格受文化、历史和个人因素影响,定义了音乐的身份特征。
  • 律动(Groove)是音乐中的节奏感或摇摆感,尤其体现在放克、爵士和灵魂乐中。律动使你情不自禁地打拍子或跳舞。它由强调模式、节奏部分(鼓、贝斯等)之间的配合以及节奏速度共同构成,为音乐赋予运动和流动感。
  • 和弦(Chords)是同时演奏的两个或多个音符组合,构成音乐的和声基础。和弦基于音阶构建,用于创作和声进程,赋予音乐结构和情感深度。不同的和弦类型(大调、小调、减七和增和弦等)及其排列能唤起听众不同的情绪体验。
  • 旋律(Melody)是音乐中最容易辨识的音符序列,是你可能会哼唱或跟唱的部分。旋律通常基于音阶构建,其特点包括音高、节奏和轮廓(音高的升降模式)。优美的旋律易于记忆且富有表现力,传达作品的主要音乐与情感主题。

这些元素共同协作,创造音乐的整体声音和体验。每个元素各司其职,但又相互作用,影响最终的音乐作品。具体来说,一段音乐包含四条轨道,每条轨道两个小节,共有八个小节/轨道组合。我们用一个噪声向量代表风格,应用于所有八个小节;用八个不同的噪声向量代表旋律,分别对应各个小节;用四个噪声向量代表律动,分别应用于四条轨道,在两个小节中保持不变;用两个噪声向量代表和弦,分别对应两个小节。图13.5展示了这四个元素如何共同构建完整音乐作品的示意图。

image.png

图13.5 利用和弦、风格、旋律和律动生成音乐。每段音乐作品由四条轨道组成,跨越两个小节。为此,我们将从潜在空间中提取四个噪声向量。第一个向量代表和弦,维度为(1, 32)。该向量通过时间网络处理,扩展成两个(1, 32)向量,分别对应两个小节,且所有轨道共享相同的值。第二个向量表示风格,维度同为(1, 32),在所有轨道和小节间保持不变。第三个向量是旋律,形状为(4, 32),经过时间网络扩展为两个(4, 32)向量,进一步分解为八个(1, 32)向量,每个对应一个独特的轨道与小节组合。最后,第四个向量为律动,维度为(4, 32),分别应用于四条轨道,在两个小节中保持相同的值。

生成器一次生成一个轨道中的一个小节。它需要四个噪声向量作为输入,每个维度为(1, 32),分别代表和弦、风格、旋律和律动,每个控制音乐的不同方面。因为音乐由四条轨道、每条轨道两个小节组成,共有八个轨道/小节组合,因此需要八套和弦、风格、旋律和律动向量来生成音乐的所有部分。

我们将从潜在空间获得四个噪声向量,分别对应和弦、风格、旋律和律动。稍后会介绍时间网络,其作用是沿小节维度扩展输入。对于两个小节,这意味着输入大小加倍。音乐本质上是时间序列,具有随时间展开的模式和结构。MuseGAN中的时间网络设计用以捕捉这些时间依赖关系,确保生成的音乐连贯且逻辑合理。

和弦噪声向量形状为(1, 32),通过时间网络处理后获得两个(1, 32)向量。第一个向量用于第一小节所有四条轨道,第二个向量用于第二小节所有轨道。

风格噪声向量形状同为(1, 32),在所有八个轨道/小节组合中统一应用。注意风格向量不经过时间网络,因为它设计为跨小节保持一致。

旋律噪声向量形状为(4, 32),经过时间网络得到两个(4, 32)向量,进一步细分为八个(1, 32)向量,每个应用于独特的轨道/小节组合。

律动噪声向量形状为(4, 32),每个(1, 32)向量应用于不同轨道,并在两个小节间保持相同。律动向量也不经过时间网络,因其设计为跨小节保持不变。

在为八个轨道/小节组合生成了所有小节后,我们将它们合并,形成一段完整的音乐作品,包括四条不同轨道,每条轨道包含两个独特的小节。

13.2.2 MuseGAN训练蓝图

第1章介绍了GAN的基础概念。第3至第5章中,你探索了用于生成形状、数字和图像的GAN的创建和训练过程。本节将总结构建和训练MuseGAN的步骤,重点说明与前几章的区别。

MuseGAN生成的音乐风格受训练数据的影响。因此,首先应收集适合训练的巴赫作品数据集。接着,创建一个由生成器和评论器组成的MuseGAN模型。生成器网络以四个随机噪声向量(和弦、风格、旋律和律动)为输入,输出一段音乐。评论器网络对音乐作品进行评估,给予真实音乐(来自训练集)较高评分,生成器制作的假音乐评分较低。生成器和评论器均采用深度卷积层,以捕获输入的空间特征。

image.png

图13.6 展示了训练MuseGAN生成音乐的步骤示意图。生成器通过从潜在空间中采样四个随机噪声向量(左上角)来生成一段假音乐,并将其呈现给评论器(中间部分)。评论器对该音乐作品进行评估并给出评分。较高的评分表示该音乐更可能来自训练数据集,而较低的评分表明该音乐很可能是生成器生成的假音乐。此外,评论器还会对由真实样本和假样本混合插值生成的音乐片段(左上角)进行评分。训练过程中,会根据评论器对该插值音乐的评分计算梯度惩罚,并将其加到总损失中。评分结果与真实标签对比,促使评论器和生成器从这些评估中学习。经过多次训练迭代后,生成器能够生成几乎无法与真实样本区分的音乐作品。

图13.6 详细展示了MuseGAN的训练流程。图中左下方的生成器接收四个随机噪声向量(和弦、风格、旋律、律动)作为输入,生成假音乐片段(步骤1)。这些噪声向量来自潜在空间,代表GAN能够生成的多样化输出范围,从而产生丰富多样的数据样本。这些假音乐片段连同训练集中的真实音乐片段(右上角)一起被评论器评估(步骤3)。评论器(图中底部中间)对所有音乐片段进行评分,目标是为真实音乐打高分,为假音乐打低分(步骤4)。

为了指导模型参数的调整,需要为生成器和评论器分别选择合适的损失函数。生成器的损失函数设计为鼓励生成与训练数据高度相似的样本。具体来说,生成器的损失是评论器评分的负值,通过最小化该损失,生成器努力生成能获得评论器高评分的音乐作品。评论器的损失函数则旨在准确区分真实与生成的样本:如果音乐来自训练集,损失为评分本身;若为生成器生成,则损失为评分的负值。也就是说,评论器的目标是为真实音乐片段打高分,为假音乐片段打低分。

此外,我们在损失函数中引入了带梯度惩罚的Wasserstein距离(如第5章所述),以增强GAN模型训练的稳定性和性能。为此,评论器还会对真实与假音乐的插值样本(图13.6左上角)进行评估,并根据该插值样本的评分计算梯度惩罚,将其加入训练过程中的总损失。

在训练循环中,我们交替训练评论器和生成器。每次迭代时,从训练集中采样一批真实音乐样本和生成器生成的一批假音乐样本。通过比较评论器的评分与真实标签(真假)计算总损失。随后,微调生成器和评论器网络的权重,使得后续迭代中生成器能生成更逼真的音乐,而评论器则对真实音乐给出更高评分,对假音乐给出更低评分。

当MuseGAN训练完成后,即可通过输入四个随机噪声向量到训练好的生成器中,生成新的音乐作品。

13.3 为 MuseGAN 准备训练数据

我们将使用约翰·塞巴斯蒂安·巴赫(Johann Sebastian Bach)的合唱曲作为训练数据,期望生成的音乐风格类似于巴赫的作品。如果你喜欢其他音乐家的风格,也可以用他们的作品作为训练数据。在本节中,我们将从下载训练数据开始,并将其组织成批次以备后续训练使用。

此外,我们已经了解到训练集中的音乐片段将以四维对象的形式表示。在本节中,你还将学习如何将这些多维对象转换为可在计算机上播放的音乐片段。这个转换过程至关重要,因为MuseGAN生成的多维对象与训练集中的格式相似。章节后面,我们将把MuseGAN生成的多维对象转换成MIDI文件,以便你能在计算机上聆听生成的音乐。

13.3.1 下载训练数据

我们将使用JSB Chorales钢琴卷轴数据集作为训练集。访问Cheng-Zhi Anna Huang的GitHub仓库(github.com/czhuang/JSB…),下载音乐文件Jsb16thSeparated.npz。将该文件保存到计算机的/files/目录下。

然后,从本书的GitHub仓库(github.com/markhliu/DG…)下载两个工具模块midi_util.pyMuseGAN_util.py,保存到计算机的/utils/目录下。本章代码改编自Azamat Kanametov的优秀GitHub仓库(github.com/akanametov/…)。准备好这些文件后,我们即可加载音乐文件并组织成批次进行处理:

from torch.utils.data import DataLoader
from utils.midi_util import MidiDataset

dataset = MidiDataset('files/Jsb16thSeparated.npz')
first_song = dataset[0]
print(first_song.shape)
loader = DataLoader(dataset, batch_size=64, shuffle=True, drop_last=True)

我们将刚才下载的数据集加载到Python中,提取第一首歌并命名为first_song。由于歌曲以多维对象形式表示,打印第一首歌的形状。最后,我们将训练数据以64为批次大小进行组织,供本章后续使用。

上面代码输出:

torch.Size([4, 2, 16, 84])

数据集中每首歌曲的形状为(4, 2, 16, 84),如上所示。这表明每首歌曲包含4条音轨,每条音轨由2个小节组成。每个小节包含16个时间步长,每个时间步由一个84维的独热向量表示音符。在每个独热向量中,所有值均为–1,只有一个位置为1,表示该时间步演奏的音符。你可以验证数据集中的取值范围如下:

flat = first_song.reshape(-1,)
print(set(flat.tolist()))

输出结果为:

{1.0, -1.0}

结果显示每段音乐的取值均为–1或1。

13.3.2 将多维对象转换为音乐片段

当前,歌曲数据以PyTorch张量格式存储,已准备好输入MuseGAN模型。但在继续之前,了解如何将这些多维对象转换为可在计算机上播放的音乐片段非常重要。这将帮助我们之后将生成的音乐片段转换为可播放文件。

首先,将所有84维独热变量转换为从0到83的音高编号:

import numpy as np
from music21 import note, stream, duration, tempo

parts = stream.Score()
parts.append(tempo.MetronomeMark(number=66))
max_pitches = np.argmax(first_song, axis=-1)                # ①
midi_note_score = max_pitches.reshape([2 * 16, 4])          # ②
print(midi_note_score)

① 将84维独热向量转换为0到83之间的数字
② 将结果调整形状为(32, 4)

输出示例为:

tensor([[74, 74, 74, 74],
…
 [70, 70, 69, 69],
 [67, 67, 69, 69],
 [70, 70, 70, 70],
 [69, 69, 69, 69],
 [69, 69, 69, 69],
 [65, 65, 65, 65],
 [58, 58, 60, 60],
…
 [53, 53, 53, 53]])

输出中每列代表一条音乐音轨,数字范围为0到83,对应之前图13.1中的音高编号。

接下来,我们将上面代码中得到的张量midi_note_score转换成实际的MIDI文件,便于计算机播放:

for i in range(4):                                         # ①
    last_x = int(midi_note_score[:, i][0])
    s = stream.Part()
    dur = 0
    for idx, x in enumerate(midi_note_score[:, i]):        # ②
        x = int(x)
        if (x != last_x or idx % 4 == 0) and idx > 0:
            n = note.Note(last_x)
            n.duration = duration.Duration(dur)
            s.append(n)
            dur = 0
        last_x = x
        dur = dur + 0.25                                   # ③
    n = note.Note(last_x)
    n.duration = duration.Duration(dur)
    s.append(n)                                            # ④
    parts.append(s)
parts.write("midi","files/first_song.midi")

① 遍历4条音乐轨道
② 遍历每条轨道的所有音符
③ 每个时间步增加0.25秒
④ 将音符添加到音乐流

运行上述代码后,你将在计算机上看到一个名为first_song.midi的MIDI文件。使用电脑上的音乐播放器打开它,感受我们用于训练MuseGAN的音乐类型。

练习13.1
将训练数据集中的第二首歌曲转换为MIDI文件,保存为second_song.midi,并使用计算机上的音乐播放器播放。

13.4 构建 MuseGAN

本质上,我们将把一段音乐视为一个多维对象。利用第4到6章中的技术,我们将采用深度卷积神经网络来有效提取多维对象的空间特征。在MuseGAN中,我们将构建一个生成器和一个评论器,类似于图像生成中的生成器根据评论器反馈不断优化图像。生成器将输出一个四维的音乐对象。

训练集中真实的音乐和生成器生成的假音乐都会呈现给评论器。评论器会对每段音乐评分,评分范围为负无穷到正无穷,分数越高表示音乐越可能是真实的。评论器的目标是给真实音乐高分,假音乐低分;而生成器的目标是生成与真实音乐难以区分的音乐,从而获得评论器的高分。

本节中,我们将构建一个MuseGAN模型,包括生成器网络和评论器网络。评论器采用深度卷积层提取多维对象的显著特征,提升对音乐片段的评估能力。生成器则使用深度转置卷积层生成特征图,以产出逼真的音乐片段。随后,我们将使用训练集中的音乐片段训练MuseGAN模型。

13.4.1 MuseGAN中的评论器

正如第5章所述,采用带梯度惩罚的Wasserstein距离有助于稳定训练。因此,在MuseGAN中,我们采用类似方法,使用评论器代替判别器。评论器不是二分类器,而是对生成器输出(这里是音乐片段)进行评分,范围为–∞到∞。评分越高,表示音乐越可能是真实的(即来自训练集)。

我们构建了一个音乐评论器神经网络,定义见你之前下载的MuseGAN_util.py文件。
代码片段(节选):

class MuseCritic(nn.Module):
    def __init__(self, hid_channels=128, hid_features=1024,
                 out_features=1, n_tracks=4, n_bars=2, n_steps_per_bar=16,
                 n_pitches=84):
        super().__init__()
        self.n_tracks = n_tracks
        self.n_bars = n_bars
        self.n_steps_per_bar = n_steps_per_bar
        self.n_pitches = n_pitches
        in_features = 4 * hid_channels if n_bars == 2 else 12 * hid_channels
        self.seq = nn.Sequential(
            nn.Conv3d(self.n_tracks, hid_channels, (2, 1, 1), (1, 1, 1), padding=0),  # ①
            nn.LeakyReLU(0.3, inplace=True),
            # 其他Conv3d层和激活函数
            nn.Flatten(),  # ②
            nn.Linear(in_features, hid_features),
            nn.LeakyReLU(0.3, inplace=True),
            nn.Linear(hid_features, out_features))  # ③
    def forward(self, x):
        return self.seq(x)

① 输入经过多层Conv3d卷积层
② 将输出展平
③ 通过两层线性层输出评分

评论器的输入是形状为(4, 2, 16, 84)的音乐片段张量。网络主要由多个Conv3d层组成,将每条音轨视为三维对象,应用滤波器提取空间特征。Conv3d的工作方式类似于前面章节中图像生成所用的Conv2d。

需要注意的是,评论器模型的最后一层是线性的,没有激活函数,其输出为一个值,范围是–∞到∞,可解释为对音乐片段的评分。

13.4.2 MuseGAN中的生成器

如本章前面所述,生成器一次生成一小节音乐,最终组合成完整音乐。

生成器不再只使用单个噪声向量,而是采用四个独立噪声向量作为输入,分别控制音乐的不同方面。两个向量(和弦和旋律)会经过时序网络沿小节维度展开,风格和律动向量设计为在所有小节保持不变。因此,我们首先建立时序网络,负责将和弦与旋律向量扩展到两个小节,保证生成音乐时间上的连贯性和逻辑性。

时序网络TemporalNetwork()定义如下:

class TemporalNetwork(nn.Module):
    def __init__(self, z_dimension=32, hid_channels=1024, n_bars=2):
        super().__init__()
        self.n_bars = n_bars
        self.net = nn.Sequential(
            Reshape(shape=[z_dimension, 1, 1]),  # ①
            nn.ConvTranspose2d(z_dimension, hid_channels,
                               kernel_size=(2, 1), stride=(1, 1), padding=0),
            nn.BatchNorm2d(hid_channels),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(hid_channels, z_dimension,
                               kernel_size=(self.n_bars - 1, 1), stride=(1, 1),
                               padding=0),
            nn.BatchNorm2d(z_dimension),
            nn.ReLU(inplace=True),
            Reshape(shape=[z_dimension, self.n_bars]),  # ②
        )
    def forward(self, x):
        return self.net(x)

① 输入维度为(1, 32)
② 输出维度为(2, 32)

这里的时序网络利用两层转置卷积将单一噪声向量扩展为两个对应两个小节的噪声向量。前面第4章提到,转置卷积可用于上采样和生成特征图,在这里用来沿小节维度延展噪声向量。

我们将分小节生成音乐,而不是一次生成所有小节和音轨。这样,MuseGAN可以兼顾计算效率、灵活性和音乐连贯性,生成结构更清晰、更加悦耳的音乐。

接下来构建负责生成一小节音乐的BarGenerator()类(定义在本地模块MuseGAN_util中):

class BarGenerator(nn.Module):
    def __init__(self, z_dimension=32, hid_features=1024, hid_channels=512,
                 out_channels=1, n_steps_per_bar=16, n_pitches=84):
        super().__init__()
        self.n_steps_per_bar = n_steps_per_bar
        self.n_pitches = n_pitches
        self.net = nn.Sequential(
            nn.Linear(4 * z_dimension, hid_features),  # ①
            nn.BatchNorm1d(hid_features),
            nn.ReLU(inplace=True),
            Reshape(shape=[hid_channels, hid_features // hid_channels, 1]),
            nn.ConvTranspose2d(hid_channels, hid_channels,
                               kernel_size=(2, 1), stride=(2, 1), padding=0),  # ②
            nn.BatchNorm2d(hid_channels),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(hid_channels, hid_channels // 2,
                               kernel_size=(2, 1), stride=(2, 1), padding=0),
            nn.BatchNorm2d(hid_channels // 2),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(hid_channels // 2, hid_channels // 2,
                               kernel_size=(2, 1), stride=(2, 1), padding=0),
            nn.BatchNorm2d(hid_channels // 2),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(hid_channels // 2, hid_channels // 2,
                               kernel_size=(1, 7), stride=(1, 7), padding=0),
            nn.BatchNorm2d(hid_channels // 2),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(hid_channels // 2, out_channels,
                               kernel_size=(1, 12), stride=(1, 12), padding=0),
            Reshape([1, 1, self.n_steps_per_bar, self.n_pitches]),  # ③
        )
    def forward(self, x):
        return self.net(x)

① 将和弦、风格、旋律、律动4个向量拼接成一个长度为128的向量
② 输入调整为二维,使用多层转置卷积进行上采样和特征生成
③ 输出形状为(1, 1, 16, 84):1条轨道,1个小节,16个音符,每个音符由84维向量表示

BarGenerator()接受4个噪声向量作为输入,分别表示不同音轨特定小节的和弦、风格、旋律和律动,形状均为(1, 32)。它们被拼接成128维向量,输入到BarGenerator()中,输出维度为(1, 1, 16, 84),表示1条轨道1个小节16个音符的音乐片段。

最后,我们将使用MuseGenerator()类生成完整的音乐片段,该片段由4条轨道组成,每条轨道有2个小节。每个小节都用上述的BarGenerator()类生成。MuseGenerator()类定义如下(位于本地模块MuseGAN_util中):

class MuseGenerator(nn.Module):
    def __init__(self, z_dimension=32, hid_channels=1024,
                 hid_features=1024, out_channels=1, n_tracks=4,
                 n_bars=2, n_steps_per_bar=16, n_pitches=84):
        super().__init__()
        self.n_tracks = n_tracks
        self.n_bars = n_bars
        self.n_steps_per_bar = n_steps_per_bar
        self.n_pitches = n_pitches
        self.chords_network = TemporalNetwork(z_dimension, 
                                              hid_channels, n_bars=n_bars)
        self.melody_networks = nn.ModuleDict({})
        for n in range(self.n_tracks):
            self.melody_networks.add_module(
                "melodygen_" + str(n),
                TemporalNetwork(z_dimension, hid_channels, n_bars=n_bars))
        self.bar_generators = nn.ModuleDict({})
        for n in range(self.n_tracks):
            self.bar_generators.add_module(
                "bargen_" + str(n), BarGenerator(z_dimension,
                                                hid_features, hid_channels // 2, out_channels,
                                                n_steps_per_bar=n_steps_per_bar, n_pitches=n_pitches))
    def forward(self, chords, style, melody, groove):
        chord_outs = self.chords_network(chords)
        bar_outs = []
        for bar in range(self.n_bars):                           # ①
            track_outs = []
            chord_out = chord_outs[:, :, bar]
            style_out = style
            for track in range(self.n_tracks):                   # ②
                melody_in = melody[:, track, :]
                melody_out = self.melody_networks["melodygen_" + str(track)](melody_in)[:, :, bar]
                groove_out = groove[:, track, :]
                z = torch.cat([chord_out, style_out, melody_out, groove_out], dim=1)  # ③
                track_outs.append(self.bar_generators["bargen_" + str(track)](z))     # ④
            track_out = torch.cat(track_outs, dim=1)
            bar_outs.append(track_out)
        out = torch.cat(bar_outs, dim=2)                         # ⑤
        return out

① 遍历两个小节
② 遍历四条音轨
③ 拼接和弦、风格、旋律、律动向量作为输入
④ 使用对应音轨的小节生成器生成一个小节的音乐
⑤ 拼接八个小节,形成完整音乐片段

生成器接受四个噪声向量作为输入,遍历4条音轨和2个小节。每次循环使用小节生成器生成一小节音乐。完成所有循环后,MuseGenerator()类将八个小节拼接成一个整体音乐,形状为(4, 2, 16, 84)。

13.4.3 优化器与损失函数

我们基于本地模块中的 MuseGenerator()MuseCritic() 类创建生成器和评论器:

import torch
from utils.MuseGAN_util import (init_weights, MuseGenerator, MuseCritic)

device = "cuda" if torch.cuda.is_available() else "cpu"
generator = MuseGenerator(z_dimension=32, hid_channels=1024, 
              hid_features=1024, out_channels=1).to(device)
critic = MuseCritic(hid_channels=128,
                    hid_features=1024,
                    out_features=1).to(device)
generator = generator.apply(init_weights)
critic = critic.apply(init_weights)

正如第5章所述,评论器生成的是评分而非分类,因此损失函数定义为预测值与目标值乘积的负平均值。因此,我们在本地模块 MuseGAN_util 中定义了如下 loss_fn() 函数:

def loss_fn(pred, target):
    return -torch.mean(pred * target)

训练时,对生成器而言,我们将 loss_fn() 函数中的目标值 target 设为1,旨在引导生成器产出能获得最高评分(即 pred 变量)的音乐。对于评论器,真实音乐的目标值设为1,假音乐的目标值设为–1,促使评论器对真实音乐评分高,对假音乐评分低。

和第5章类似,我们在评论器的损失函数中加入带梯度惩罚的Wasserstein距离,以保证训练稳定。梯度惩罚定义在 MuseGAN_util.py 文件中,代码如下:

class GradientPenalty(nn.Module):
    def __init__(self):
        super().__init__()
    def forward(self, inputs, outputs):
        grad = torch.autograd.grad(
            inputs=inputs,
            outputs=outputs,
            grad_outputs=torch.ones_like(outputs),
            create_graph=True,
            retain_graph=True,
        )[0]
        grad_ = torch.norm(grad.view(grad.size(0), -1), p=2, dim=1)
        penalty = torch.mean((1. - grad_) ** 2)
        return penalty

GradientPenalty() 类需要两个输入:插值音乐(真实音乐和假音乐的混合样本)以及评论器对该插值音乐的评分。该类计算评分相对于插值音乐的梯度,并根据梯度范数与目标值1的平方差计算梯度惩罚,方法与第5章类似。

我们仍然使用Adam优化器分别优化生成器和评论器:

lr = 0.001
g_optimizer = torch.optim.Adam(generator.parameters(),
                               lr=lr, betas=(0.5, 0.9))
c_optimizer = torch.optim.Adam(critic.parameters(),
                               lr=lr, betas=(0.5, 0.9))

至此,我们成功构建了一个MuseGAN模型,现已准备好使用本章前面准备的数据进行训练。

13.5 训练 MuseGAN 生成音乐

现在我们已经有了 MuseGAN 模型和训练数据,本节将进行模型训练。

与第3、4章类似,训练 GAN 时我们交替训练评论器和生成器。在每次训练迭代中,我们会从训练数据集中采样一批真实音乐,以及从生成器生成一批假音乐,交给评论器评估。训练评论器时,我们将评论器的评分与真实标签比较,并微调评论器网络权重,使下一次迭代中对真实音乐评分尽可能高,对假音乐评分尽可能低。训练生成器时,我们将生成的音乐输入评论器获取评分,再微调生成器网络权重,使下一次迭代中评分更高(生成器目标是生成能骗过评论器的音乐)。反复进行该过程,生成器逐步能创造出更真实的音乐作品。

模型训练完成后,我们舍弃评论器网络,仅使用训练好的生成器,输入四个噪声向量(和弦、风格、旋律和节奏)生成音乐。

13.5.1 训练 MuseGAN

开始训练循环前,先定义部分超参数和辅助函数。repeat 控制每次迭代训练评论器的次数,display_step 表示多久输出一次训练信息,epochs 是训练轮数。

代码示例如下:

from utils.MuseGAN_util import loss_fn, GradientPenalty

batch_size = 64
repeat = 5
display_step = 10
epochs = 500                                            # ①
alpha = torch.rand((batch_size, 1, 1, 1, 1)).requires_grad_().to(device)  # ②
gp = GradientPenalty()                                  # ③

def noise():                                            # ④
    chords = torch.randn(batch_size, 32).to(device)
    style = torch.randn(batch_size, 32).to(device)
    melody = torch.randn(batch_size, 4, 32).to(device)
    groove = torch.randn(batch_size, 4, 32).to(device)
    return chords, style, melody, groove

① 定义超参数
② 定义 alpha 用于生成插值音乐
③ 定义计算梯度惩罚的函数 gp()
④ 定义生成四个随机噪声向量的函数 noise()

批大小设置为64,意味着每批生成64组随机噪声向量。每个训练循环中,评论器训练5次,生成器训练1次,因为强大的评论器对生成器训练至关重要。训练过程中每10轮显示一次损失信息,总训练500轮。

下面定义训练单轮的函数:

def train_epoch():
    e_gloss = 0
    e_closs = 0
    for real in loader:                          # ①
        real = real.to(device)
        for _ in range(repeat):                  # ②
            chords, style, melody, groove = noise()
            c_optimizer.zero_grad()
            with torch.no_grad():
                fake = generator(chords, style, melody, groove).detach()
            realfake = alpha * real + (1 - alpha) * fake
            fake_pred = critic(fake)
            real_pred = critic(real)
            realfake_pred = critic(realfake)
            fake_loss = loss_fn(fake_pred, -torch.ones_like(fake_pred))
            real_loss = loss_fn(real_pred, torch.ones_like(real_pred))
            penalty = gp(realfake, realfake_pred)
            closs = fake_loss + real_loss + 10 * penalty       # ③
            closs.backward(retain_graph=True)
            c_optimizer.step()
            e_closs += closs.item() / (repeat * len(loader))
        g_optimizer.zero_grad()
        chords, style, melody, groove = noise()
        fake = generator(chords, style, melody, groove)
        fake_pred = critic(fake)
        gloss = loss_fn(fake_pred, torch.ones_like(fake_pred))   # ④
        gloss.backward()
        g_optimizer.step()
        e_gloss += gloss.item() / len(loader)
    return e_gloss, e_closs

① 遍历所有批次
② 每次训练循环中评论器训练5次
③ 评论器损失包含真实音乐损失、假音乐损失及梯度惩罚
④ 训练生成器

训练流程与第5章训练带梯度惩罚的条件GAN类似。

开始训练500轮:

for epoch in range(1, epochs + 1):
    e_gloss, e_closs = train_epoch()
    if epoch % display_step == 0:
        print(f"Epoch {epoch}, G loss {e_gloss} C loss {e_closs}")

若使用GPU训练,大约需1小时;否则可能耗时数小时。训练完成后,保存生成器模型:

torch.save(generator.state_dict(), 'files/MuseGAN_G.pth')

或从我的网站下载预训练模型:mng.bz/Bglr

接下来,我们将舍弃评论器,仅用训练好的生成器生成类似巴赫风格的音乐。

13.5.2 使用训练好的 MuseGAN 生成音乐

要用训练好的生成器生成音乐,我们将从潜在空间输入四个噪声向量给生成器。需要注意的是,我们可以同时生成多个音乐对象,并将它们解码合并成一段连续的音乐。在本小节你将学习如何实现这一点。

首先加载训练好的生成器权重:

generator.load_state_dict(torch.load('files/MuseGAN_G.pth', map_location=device))

我们不仅可以生成单个四维音乐对象,还能一次生成多个四维音乐对象,之后再将它们合并成一整段音乐。例如,要生成五个音乐对象,我们首先从潜在空间采样五组噪声向量,每组包含四个向量:和弦(chords)、风格(style)、旋律(melody)和节奏(groove),代码如下:

num_pieces = 5
chords = torch.rand(num_pieces, 32).to(device)
style = torch.rand(num_pieces, 32).to(device)
melody = torch.rand(num_pieces, 4, 32).to(device)
groove = torch.rand(num_pieces, 4, 32).to(device)

每个生成的音乐对象对应大约8秒的音乐。此处我们生成五个音乐对象,合并后长度约40秒。你可以根据需要调整变量 num_pieces,来控制生成音乐的时长。

接下来将这五组潜在变量输入生成器,得到五个音乐对象:

preds = generator(chords, style, melody, groove).detach()

输出 preds 是五个音乐对象。接下来将它们解码合并成一段 MIDI 格式的音乐文件:

from utils.midi_util import convert_to_midi

music_data = convert_to_midi(preds.cpu().numpy())
music_data.write('midi', 'files/MuseGAN_song.midi')

我们从本地模块 midi_util 中导入了 convert_to_midi() 函数。打开你之前下载的 midi_util.py 文件,查看该函数的定义。此过程与本章早前将训练集中的第一首音乐对象转换为 first_song.midi 文件类似。MIDI 文件表示音符随时间的序列,我们简单地将对应五个音乐对象的五段音乐拼接成一个更长的音符序列,然后保存为本地文件 MuseGAN_song.midi

在电脑上找到生成的音乐文件 MuseGAN_song.midi,用你喜欢的播放器打开,听听是否类似训练集中的音乐。你也可以到我的网站 mng.bz/dZJv 听一段训练模型生成的音乐作对比。注意,由于生成器的输入噪声向量是从潜在空间随机采样的,每次生成的音乐都会有所不同。

练习 13.2 从潜在空间采样三组随机噪声向量(每组包含和弦、风格、旋律和节奏四个向量),输入训练好的生成器得到三个音乐对象。将它们解码合并成一段 MIDI 文件,保存为 generated_song.midi,并用音乐播放器播放。

本章你已经学习了如何构建和训练 MuseGAN 来生成巴赫风格的音乐。具体来说,你将音乐看作一个四维对象,并运用了第4章介绍的深度卷积层技术来开发 GAN 模型。下一章,你将探索另一种音乐生成方法:将音乐视为索引序列,利用自然语言处理技术,通过逐步预测索引来生成音乐。

总结

  • MuseGAN 将一段音乐视为类似图像的多维对象。生成器生成一段音乐,并将其与训练集中真实的音乐一起提交给评论器进行评估。生成器根据评论器的反馈不断调整音乐,直到生成的音乐与训练集中的真实音乐高度相似。

  • 音乐理论的基础概念包括音符、八度和音高。八度表示不同的音乐音阶等级,每个八度被细分为12个半音:C、C#、D、D#、E、F、F#、G、G#、A、A#、B。在一个八度内,每个音符对应一个特定的音高编号。

  • 在电子音乐制作中,音轨通常指音乐的一个单独层或组成部分。每个音轨包含多个小节(或拍号),每个小节又细分为多个节拍步骤。

  • 为了将一段音乐表示为多维对象,我们采用形状为 (4, 2, 16, 84) 的结构:4条音乐轨道,每条轨道包含2个小节,每个小节分为16个步骤,每个步骤可以播放84个不同音符之一。

  • 在音乐创作中,引入更细化的输入对于实现更好的控制和丰富多样性至关重要。与前几章生成形状、数字和图像时使用单一潜在空间噪声向量不同,音乐生成过程中我们采用了4个独立的噪声向量。由于每段音乐由4条轨道和2个小节组成,这4个向量分别用于有效管理该结构:一个控制所有轨道和小节,一个控制所有轨道的每个小节,一个监督所有小节的所有轨道,最后一个控制每条轨道中的每个单独小节。