让巴赫弹摇滚会是什么样的体验?在最近谷歌主页的Doodle上,我们可以尝试一下。
3 月 21 日是著名音乐家约翰·塞巴斯蒂安·巴赫的生日,谷歌决定以一种特殊的方式向他致敬:让人人都能以巴赫的风格创作自己的乐曲。
通过机器学习算法,谷歌开发了 Coconet 多功能模型,可以让你用巴赫的风格演奏自己写下的乐谱。你也可以通过这个小工具来体验 AI 算法如何将一些我们熟悉的旋律「巴赫化」,亦或你和巴赫「合作」的乐曲将呈现出怎样更加现代摇滚的曲风。
Coconet的工作原理
Coconet获取不完整的乐谱,并填充缺失的材料。为了训练Coconet,我们从巴赫的四声部众赞歌数据集中取例,随意抹去一些音符,然后让模型重写。巴赫作曲与Coconet创作之间的差别为我们提供一个学习信号,我们可以通过该信号训练模型。
通过随意地抹去音符,我们希望得到一个可以处理任何不完整输入的模型。在下文的「Coconet 为什么可以运转?」章节中,我们给出了该训练流程有趣的解读,将其等同于多种模型的同时训练,并且每种模型适用于不同的场景。
我们将「乐谱」看做三维物体。巴赫的众赞歌以四声写就,即女高音(S)、女低音(A)、男高音(T)和男低音(B)。每一种声音的音乐通过钢琴卷轴演奏:二维阵列(其中离散节拍水平延伸,音调垂直分布)。我们假设每一种声音在任何特定的时间内准确唱出一个音调。所以一般来讲,对于每一时间点的每一种声音而言,我们得到一个one-hot音调向量,并且除了指示被唱音调的单个元素以外,该向量的其余元素皆为零。在不确定的情况下(如在模型输出时),该音调向量将包含音调的分类概率分布。
我们将这堆钢琴卷轴视作卷积特征图,其中节拍和音调形成二维卷积空间,每一种声音提供一个通道。由于要馈入模型的乐谱是不完整的,所以我们为每一种声音提供带掩码的通道:二进位制在每一个时间点显示声音的音调是否可知。如此一来,进入模型的是八通道特征图。
该模型是一种相当简单且具有批归一化和残差连接的卷积神经网络。对于在浏览器中使用Tensorflow.js.implementation运行模型的doodle而言,我们能够通过转换到深度可分离卷积(deepwise-separable convolution)来加速计算。这些卷积与常规卷积的不同之处在于前者将空间轴的卷积与通道轴上的混合卷积分离开来。此外,这些卷积需要的参数更少,也更易于在浏览器中加速。得益于空洞卷积,我们可以在不损失性能的情况下通过减少模型层数获得进一步加速。
该模型再次为每一种声音输出一堆钢琴卷轴,但这次包含抹去音符的音调的概率分布。该模型试图利用给定的音符来计算抹去的音符,从而在每个时间点上针对每一种声音得到被唱音调的分类分布。
我们训练模型给真实音调分配高概率。这促使模型理解给定的不完整乐谱的音乐含义--在哪个调上、唱哪些和弦、去往哪里以及从何而来?
训练好模型后,我们有数种方法从该模型生成的概率分布中提取音乐。与此同时,我们可以根据音调的分布来对其进行采样。但是,正如下文中「Coconet 为什么可以运转?」中的详细描述,这并不能解释被采样的音调之间的交互。通常,确定一个音调将改变其他音调的分布。
一种能够解释这些交互的方法是对其中一个音调进行采样,将其添加到不完整的乐谱中,再次通过模型传递结果,从而对余下音调的分布进行再计算。重复这一过程直至确定所有音调,我们在完成乐谱的同时将所有交互考虑在内。这种连续采样流程希望该模型能够依次对未知音调进行精准确定。
相反,我们使用一种更稳健的流程:将模型输出视为一个草图,通过反复重写而逐步完善。与此同时,我们对所有音调进行采样,获得一个完整(但通常无任何意义)乐谱,之后将部分乐谱抹去,再次传递到模型中,再之后重复这一过程。随着时间的推移,我们抹去和重写的音符越来越少,使该流程得到一致的结果。
Coconet 为什么可以运转?
Coconet 是一个自回归结构的集合,包含了序列模型中常见的时间结构(chronological structure)。为了将演示简单化,我们将考虑建模3个变量:X_1、X_2、X_3。具体来讲,我们可以把这视为一个三音符旋律或三音和弦,每个变量以音高作为值。建模X_1、X_2 和 X_3 就是表征和学习给定序列 X_1、X_2、X_3 在自然数据中出现可能性的联合概率分布 P(X_1,X_2,X_3)。
这是一个很难的问题,因为变量相互作用,光建模独立(边缘)分布 P(X_1)、P(X_2)、P(X_3) 是不够的。对于X_1的每个可能值,依赖于X_1值的其它变量存在条件分布 P(X_2|X_1) 和 P(X_3|X_1)。如果有 P(X_1) 和 P(X_2|X_1) 的模型,那我们可以将它们组合起来获得 P(X_1,X_2)=P(X_1)P(X_2|X_1) 的模型。而如果有 P(X_3|X_1,X_2) 的模型,我们可以将这三个模型组合起来获得期望联合分布 P(X_1,X_2,X_3)=P(X_1)P(X_2|X_1)P(X_3|X_1,X_2)。
一次建模一个变量
上述因式分解对序列数据来说最自然不过,因为它遵循序列顺序。而在单音音乐(monophonic music)中,这意味着每个音符的分布是由指向它的音符决定的。这给出了正向排序 (1,2,3)。另一种自然的因式分解是逆向排序 (3,2,1):先建立结论,然后往前推导。如下图所示:
一般来说,变量的每个可能的排序都存在自回归因式分解。在有N个变量的问题中,就存在 N! 个因式分解。在上面提到的三个变量的例子中,我们可以列举出六个自回归因式分解:
复音音乐(polyphonic music)由多个同步序列组成:多个乐器一起演奏。在这种情况中,虽然有两种明显的方式展开多个序列,但变量没有真正的自然排序。
在左图中,我们交错排列乐器,将其排序为S、A、T、B、S、A、T、B等。这种顺序有利于和声:模型会以一次生成一个和弦的方式生成音乐。在右图中,我们以另一种方式将乐器连接起来,将其排序为S、S、S、S、A、A、A、A等。这种方式有利于调式,因为模型会一行接一行地生成音符。这两种截然不同的观点是导致音乐理论中常见冲突的根源。
无序建模
当我们将部分抹去的乐谱输入至模型时,输出的结果可以解释为抹去变量的条件独立分布。回到三个变量序列 X_1、X_2、X_3 的例子,假设抹去了 X_2 和 X_3,然后模型观测到 X_1 并生成 X_2 和 X_3 的条件分布。
所以得到的条件分布 P(X_2|X_1 )和 P(X_3|X_1) 作为三个变量2种排序(总共6种排序)中的两个因子出现。通常,根据抹去的变量,我们可以从任何排序中计算任何条件因子。通过组合此类条件因子,我们可以形成对应于任何预期排序的模型。本质上,修复模型提供了一个自回归模型集合,每个可能的排序有一个自回归模型。
而且,我们可以更高效地训练这个集合,而不是简单地对排序进行采样并逐个评估其条件因子。关键的观测是每个条件在很多排序中是共享的:即使在低维样本中,P(X_3|X_1,X_2) 也是由两种排序 (1,2,3 和 2,1,3) 共享的。一般来说,所有这些条件分布 P(X_i|X_C)(X_C 是变量的任何子集,不包括 X_i)都与 X_i 之前或之后的变量排序无关,这大大减少了我们需要学习的不同概率分布的数量。
为了训练 Coconet,我们从数据集中选择了一个训练样本,统一选择要抹去的变量数量,并统一选择需要抹去的变量的特定子集。我们将部分抹去的乐谱输入至模型(以及指示哪个变量需要抹去的掩码),获得了抹去变量值的一组独立分布。我们计算了真实值的对数似然和抹去变量的平均值,由此纠正了微妙的缩放问题。我们借此得到损失函数,然后和以前一样使用反向传播和随机梯度下降来最小化损失。
使用吉布斯采样根据多个排序生成
尽管无序NADE学习一组排序,但相关的采样过程仍然根据单个排序进行有效的采样。Uria等人提出统一选择一个排序,然后根据这个排序依次生成变量。作曲仍然是在一个单一的过程中完成的,没有经过任何迭代的改进。
在一个单一的过程中作曲难在哪里?假设从一张白纸开始,我们必须写下第一个音符,而且知道之后不能改动。这是一个艰难的决定:我们必须考虑未来所有的可能,这个音符必须是正确的。接下来,有了更多的音符之后,我们就能参考前后的音符做出决定。那么,如果我们不必从无到有地进行作曲呢?如果我们从一开始就有一些音符作参考呢?
事实证明,我们可以通过吉布斯采样做到这一点!吉布斯采样是通过反复对单个变量重新采样,从联合分布中抽取样本的过程。可以用它来比喻反复修改乐谱的过程。在每一步中,抹去乐谱的某些部分并让模型重写。这样的话,模型一直都会有参考材料。尽管参考材料本身可能不断变动,也可能在之后的迭代中被重写。这没关系,因为模型当前的决策也不是一成不变的。渐渐地,乐谱就成了一首和谐的曲子。
更确切地说,这个过程可以叫做分块吉布斯采样,因为每次重新采样的不止一个变量。如果把概率分布想象成一幅风景画,可以看到处在合适位置的山峰被位置不当的巨大山谷隔开。大规模重新采样有助于通过较大的跳跃来探索可能性空间,而一次重采样一个变量往往会停留在附近的峰值上。因此,我们退火块大小:我们开始重写大部分的乐谱,以探索这个空间,然后逐渐重写得越来越少,以确定一个合理的乐谱。