雷锋网按:这篇文章会用一个简单的模型在 TensorFlow 上来实现一个音频生成器。原文作者杨熹,载于作者的个人博客,雷锋网经授权发布。
今天想来看看 AI 是怎样作曲的。
本文会用 TensorFlow 来写一个音乐生成器。
当你对一个机器人说:我想要一种能够表达出希望和奇迹的歌曲时,发生了什么呢?
计算机会首先把你的语音转化成文字,并且提取出关键字,转化成词向量。
然后会用一些打过标签的音乐的数据,这些标签就是人类的各种情感。接着通过在这些数据上面训练一个模型,模型训练好后就可以生成符合要求关键词的音乐。
程序最终的输出结果就是一些和弦,他会选择最贴近主人所要求的情感关键词的一些和弦来输出。
当然你不只是可以听,也可以作为创作的参考,这样就可以很容易地创作音乐,即使你还没有做到刻意练习1万小时。
机器学习其实是为了扩展我们的大脑,扩展我们的能力。
DeepMind 发表了一篇论文,叫做 WaveNet
, 这篇论文介绍了音乐生成和文字转语音的艺术。
通常来讲,语音生成模型是串联。这意味着如果我们想从一些文字的样本中来生成语音的话,是需要非常大量的语音片段的数据库,通过截取它们的一部分,并且再重新组装到一起,来组成一个完整的句子。
生成音乐也是同样的道理,但是它有一个很大的难点:就是当你把一些静止的组件组合到一起的时候,生成声音需要很自然,并且还要有情感,这一点是非常难的。
一种理想的方式是,我们可以把所有生成音乐所需要的信息存到模型的参数里面。也就是那篇论文里讲的事情。
我们并不需要把输出结果传给信号处理算法来得到语音信号,而是直接处理语音信号的波。
他们用的模型是 CNN。这个模型的每一个隐藏层中,每个扩张因子,可以互联,并呈指数型的增长。每一步生成的样本,都会被重新投入网络中,并且用于产生下一步。
我们可以来看一下这个模型的图。输入的数据,是一个单独的节点,它作为粗糙的音波,首先需要进行一下预处理,以便于进行下面的操作。
接着我们对它进行编码,来产生一个 Tensor,这个 Tensor 有一些 sample 和 channel。
然后把它投入到 CNN 网络的第一层中。这一层会产生 channel 的数量,为了进行更简单地处理。
然后把所有输出的结果组合在一起,并且增加它的维度。再把维度增加到原来的 channel 的数量。
把这个结果投入到损失函数中,来衡量我们的模型训练的如何。
最后,这个结果会被再次投入到网络中,来生成下一个时间点所需要的音波数据。
重复这个过程就可以生成更多的语音。
这个网络很大,在他们的 GPU 集群上需要花费九十分钟,并且仅仅只能生成一秒的音频。
接下来我们会用一个更简单的模型在 TensorFlow 上来实现一个音频生成器。
数据科学包 Numpy ,数据分析包 Pandas,tqdm 可以生成一个进度条,显示训练时的进度。
import numpy as np
import pandas as pd
import msgpackimport glob
import tensorflow as tf
from tensorflow.python.ops import control_flow_ops
from tqdm import tqdm
import midi_manipulation
我们会用到一种神经网络的模型 RBM-Restricted Boltzmann Machine 作为生成模型。
它是一个两层网络:第一层是可见的,第二层是隐藏层。同一层的节点之间没有联系,不同层之间的节点相互连接。每一个节点都要决定它是否需要将已经接收到的数据发送到下一层,而这个决定是随机的。
先定义需要模型生成的 note 的 range
lowest_note = midi_manipulation.lowerBound #the indexof the lowest note on the piano roll
highest_note = midi_manipulation.upperBound #the indexof the highest note on the piano roll
note_range = highest_note-lowest_note #the note range
接着需要定义 timestep ,可见层和隐藏层的大小。
num_timesteps = 15 #This is the number of timesteps that we will createat a time
n_visible = 2*note_range*num_timesteps #This is the sizeof the visible layer.
n_hidden = 50 #This is the sizeof the hidden layer123123
训练次数,批量处理的大小,还有学习率。
num_epochs = 200 #The number of training epochs that we are going to run. For each epoch we go through the entire data set.
batch_size = 100 #The numberof training examples that we are going to send through the RBM at a time.
lr = tf.constant(0.005, tf.float32) #The learning rate of our model
x 是投入网络的数据
w 用来存储权重矩阵,或者叫做两层之间的关系
此外还需要两种 bias,一个是隐藏层的 bh,一个是可见层的 bv
x = tf.placeholder(tf.float32, [None, n_visible], name="x") #The placeholder variable that holds our data
W = tf.Variable(tf.random_normal([n_visible, n_hidden], 0.01), name="W") #The weight matrix that stores the edge weights
bh = tf.Variable(tf.zeros([1, n_hidden], tf.float32, name="bh")) #The bias vector for the hidden layer
bv = tf.Variable(tf.zeros([1, n_visible], tf.float32, name="bv")) #The bias vector for the visible layer
接着,用辅助方法 gibbs_sample 从输入数据 x 中建立样本,以及隐藏层的样本:
gibbs_sample 是一种可以从多重概率分布中提取样本的算法。
它可以生成一个统计模型,其中,每一个状态都依赖于前一个状态,并且随机地生成符合分布的样本。
#The sample of x
x_sample = gibbs_sample(1)
#The sample of the hidden nodes, starting from the visible state of x
h = sample(tf.sigmoid(tf.matmul(x, W) + bh))
#The sample of the hidden nodes, starting from the visible state of x_sample
h_sample = sample(tf.sigmoid(tf.matmul(x_sample, W) + bh))
size_bt = tf.cast(tf.shape(x)[0], tf.float32)
W_adder = tf.mul(lr/size_bt, tf.sub(tf.matmul(tf.transpose(x), h), tf.matmul(tf.transpose(x_sample), h_sample)))
bv_adder = tf.mul(lr/size_bt, tf.reduce_sum(tf.sub(x, x_sample), 0, True))
bh_adder = tf.mul(lr/size_bt, tf.reduce_sum(tf.sub(h, h_sample), 0, True))
#When we do sess.run(updt), TensorFlow will run all 3 update steps
updt = [W.assign_add(W_adder), bv.assign_add(bv_adder), bh.assign_add(bh_adder)]
with tf.Session() as sess:
#First, we train the model
#initialize the variables of the model
init = tf.initialize_all_variables()
sess.run(init)
首先需要 reshape 每首歌,以便于相应的向量表示可以更好地被用于训练模型。
for epoch in tqdm(range(num_epochs)):
for song in songs:
#The songs are stored in a time x notes format. The size of each song is timesteps_in_song x 2*note_range
#Here we reshape the songs so that each training example is a vector with num_timesteps x 2*note_range elements
song = np.array(song)
song = song[:np.floor(song.shape[0]/num_timesteps)*num_timesteps]
song = np.reshape(song, [song.shape[0]/num_timesteps, song.shape[1]*num_timesteps])
for i in range(1, len(song), batch_size):
tr_x = song[i:i+batch_size]
sess.run(updt, feed_dict={x: tr_x})
模型完全训练好后,就可以用来生成 music 了。
其中的 visible nodes 先初始化为0,来生成一些样本。
然后把向量 reshape 成更好的格式来 playback。
sample = gibbs_sample(1).eval(session=sess, feed_dict={x: np.zeros((10, n_visible))})
for i in range(sample.shape[0]):
if not any(sample[i,:]):
continue
#Here we reshape the vector to be time x notes, and then save the vector as a midi file
S = np.reshape(sample[i,:], (num_timesteps, 2*note_range))
midi_manipulation.noteStateMatrixToMidi(S, "generated_chord_{}".format(i))
综上,就是用 CNN 来参数化地生成音波,
用 RBM 可以很容易地根据训练数据生成音频样本,
Gibbs 算法可以基于概率分布帮我们得到训练样本。