Bubbles~blog

用爱发电

Tensorflow实现简单的音乐机器人

临近期末事情倒是越来越多,大作业一个接一个。。。机器学习大作业也是伤了不少脑筋,暂时先凑合着发到博客上来,后面应该还会改

写在前面

之前学习机器学习更多地看的是传统算法,毕竟这一部分占了整个领域的主体,在数据的挖掘与分析上起着重要作用,不过现在我也发现在深度学习主导的人工智能领域前景也是非常广阔的,而且在当下就已经发展得非常快了

这里有一份来自麦肯锡的报告,告诉了我们深度学习的影响将如何渗透到我们生活的方方面面

http://36kr.com/p/5065294.html

 

而且看多了我们会发现人工智能确实是一个很有意思的东西,它并不需要我们是某领域内的专家,只需要我们构建模型对输入的数据进行不断的转化运算就能帮助我们得到一个比较可靠的结果,当然这个结果的总体评价目前可能还不高,但我们也有理由相信在将来AI是可以媲美专家的

就比如上面文章里涉及的机器学习在卫生领域的前景,我们或许不是医生,但是我们所构建的模型却可以在这么多的方面追赶专家的脚步,而且是在仅仅数天或者数个小时的时间里,这也取决于你机器的计算能力和数据量,想想都是一件很有意思的事情

 

不过我们也发现在上面的报告里并没有出现机器学习在艺术领域的影响,大概大部分人也都认为机器学习在艺术领域并不会有多么出色的表现,毕竟艺术讲究的不还是创造力么,而在我们的印象里机器是没有创造力的,它只会不断地学习与模仿,我们教给它啥它才会啥,所以现在的AI都是没有情感这东西的,当然,或许一定程度上也是网络层数和算力的限制,还有我们也很难去给计算机描绘情感,毕竟这玩意说实话我觉得我们自己可能都没搞明白它的产生

人工智能与艺术

那么我们再回到主题上来,其实艺术这东西我感觉是一个很主观的感觉,对于不同的风格受众也是不同的,不过一些基本的美的特点还是很多时候都被遵循的,比如对于工艺品我们有黄金比例的分割线,对于小说我们要讲究跌宕,对于诗歌我们会去凑韵脚

而我们今天要完成的机器人,其实也只是一段旋律的生成器,只是它的目标是要让这段旋律尽可能地符合我们的审美

所以事实上只要我们把好的作品的一些特征转化输入给机器让它去不断地生成并比对是有可能让AI去创造艺术的,其实就我个人理解上这也算不上是创造,在艺术领域我感觉不管是人还是机器其实都是在探索与模仿,成功的作品只是它的序列恰好迎合了美学的观念,在这一方面人脑还是比机器要更加成熟,也更容易积累经验,而机器则要不断地枚举可能的序列,只是结果是要趋向一定的特征的,感觉这有点类似于回归了

说起来在人工智能与艺术的结合上谷歌一直很活跃

比如这里有一个Google Research的项目叫DeepDream

https://research.googleblog.com/2015/06/inceptionism-going-deeper-into-neural.html

将图像进行了艺术再创造,他们管这个叫让机器做梦

这甚至也可以应用到视频中,可以说是很amazing了

 

在文学领域,AI也在追赶人类的脚步,日本公立函馆未来大学的团队就成功让他们的AI写了几部小说,其中就有出名的《机器人写小说的那一天》,这几部作品也去参加了当时的星新一奖评选,并通过了初审,把很多人类写的小说都比了下去,现在相关的研究也已经在全世界开展,各种写诗写小说的机器人有很多,感觉再过十几年网文的天下可能要被AI占领了

 

再看我们要关注的音乐领域

给我震撼比较深的是一个叫Aiva的AI,这里附上它的链接

http://aiva.ai/

它生成的曲子质量感觉很高,甚至它还为2017英伟达GTC技术大会的开幕式谱过曲

再比如下面还有带歌词的

http://www.flow-machines.com/ai-makes-pop-music/

不过作词还得人来干,虽然目前也有很多作词的机器人,比如跟char-rnn项目结合来生成某一风格的歌词,虽然大部分的可阅读性都不咋地

这里也附上char-rnn项目的链接,有兴趣的可以尝试

https://github.com/karpathy/char-rnn

实现一个简单的音乐AI

首先要实现的自然是音乐的编码,我们如何将乐曲数据转换为机器可以理解的语言

现在主要有两种主要的实现方式,一种是对它的音频波形入手,比如去年出现的WavaNet

https://deepmind.com/blog/wavenet-generative-model-raw-audio/

这种转换方式现在也应用在了语音和文本转换领域,效果很不错

不过这种方式需要的样本库很大,而且训练时间也很长,我的破机器可能怕是都跑不动

我们再来看另一种编码的方式,也就是利用MIDI文件

MIDI文件里记录的是音乐的每一个音符,而且体积很小,所以我们就可以直接对音符进行编码,将音乐转换成一段由音符编码组成的序列

 

那么我们第一步就是来寻找数据集,这里给出几个网站

http://www.midiworld.com

http://www.midishow.com/

你可以爬取这些网站上的数据或者去直接寻找一些合集包

比如我给出一个简单的对于midiworld的下载脚本

import urllib
import requests
from lxml import etree
plist=[]
q="pop"#要爬取的分类
def get_song(lists):
	id=1
	for x in lists:
		urllib.urlretrieve(x,str(id)+'.mid')
		id=id+1
def get_list(url):
	htm=requests.get(url)
	res=etree.HTML(htm.content)
	p=res.xpath('//*[@id="page"]/ul[1]/li/a/@href')
	return p
for x in range(5):#选择要爬取的页数
	x=x+1
	plist=plist+get_list('http://www.midiworld.com/search/'+str(x)+'/?q='+q)
get_song(plist)

不过有一点坑的就是所选的数据集最好都是单纯的钢琴曲,如果夹杂了很多其他乐器的音符那么得到的结果可能就会很蛋疼,其实这些各种各样的乐器的音符也算是模型里的噪音了

得到数据集以后我们也可以打开来看看,如果直接用播放器打开你就可以直接得到音乐了,这个播放的过程其实也差不多是让你的电脑按照midi文件里记录的谱子给你演奏了一遍,从某种意义上来说演奏的效果也受限于你的声卡

我们也可以让midi文件以五线谱的方式呈现,这样的软件有很多,比如我这里用的是musescore2

这样看起来就很舒服了,虽然我也看不懂五线谱

不过要真具体到对音符的编码处理还是很懵逼的,这里主要也是参考大牛的处理代码,附上GitHub链接

https://github.com/hexahedria/biaxial-rnn-music-composition

 

其中的midi_to_statematrix.py就是处理的关键,我们可以直接下载后导入

要注意的是涉及python对midi文件的处理需要用到的midi库,这里面用到的是python-midi库,但是这个库很久没更新了,还是处在python2的版本,并没有对python3进行兼容,当然类似的库也不是只有这一个,但怎么着也还是照顾下原代码吧,后来我就找了个别人修改后对py3兼容的版本,用法跟原来还是一样

https://github.com/louisabraham/python3-midi

接下来我们将基于tensorflow框架训练我们的模型

 

我们知道深度学习一般分两种模式,一种是深度判别式模型,也就是我们常见的递归神经网络RNN和卷积神经网络CNN,还有一种就是无监督和生成式的模型,即受限玻尔兹曼机RBM和深度玻尔兹曼机DBM等,而这里我们要用的就是生成式的模型来生成我们需要的音符

为了更加简化我们的模型,这里我们直接选择受限玻尔兹曼机RBM

关于RBM的详细情况可以参见下面的文章,感觉写的还是非常清楚的

http://blog.csdn.net/mytestmy/article/details/9150213

RBM其实就是一个只有两层的神经网络,如下是一个典型的RBM网络

但是它又跟普通的神经网络不同,它的可见层与隐藏层之间的连接方向并不是确定的,也就是说它的值事实上可以进行双向传播,而且它的这两层的层间单元是完全连接的,但是层内并不存在连接,如果存在连接的话那其实就相当于是递归神经网络RNN了,上面提到的双向传播实际上跟我们之前在神经网络中所接触过的反向传播算法是差不多的

其实上面提到的深层玻尔兹曼机也就是多层的玻尔兹曼机,RBM因为只有两层所以也就被称为受限的玻尔兹曼机,这里选择RBM一方面是它的模型更加简单,另一方面多重RBM的组合对于隐藏层的数据关系学习效果是很好的

下面我们来完成代码部分,首先导入需要的库

import numpy as np
import pandas as pd
#import msgpack
import glob
import tensorflow as tf
from tensorflow.python.ops import control_flow_ops
from tqdm import tqdm
import midi_to_statematrix

其中tqdm是用来生成进度条的,在我们对于数据进行读取和训练的时候可以显示进度

另外关于调用的库需要注意的是其中的numpy版本最好是1.11.0,不然运行过程中可能会出现Typeerror

 

而midi_to_statematrix就是我们之前提到的对midi文件进行编码的,下载下来后跟我们的代码放在同一目录下即可

然后我们先导入我们要生成的模型的音符的范围,因为直接用的大牛的代码,所以也就跟着他的来

 

lowest_note = midi_to_statematrix.lowerBound 
highest_note = midi_to_statematrix.upperBound 
note_range = highest_note-lowest_note

接下来就是定义我们模型的可见层和隐藏层的大小,这里可见层的大小是基于我们每次训练创建的时间步骤即time step的数量的

num_timesteps = 15
n_visible = 2*note_range*num_timesteps #可见层大小 
n_hidden = 50#隐藏层大小

然后要定义的是我们总的训练次数,每次进行批量处理的数据的大小和学习率

num_epochs = 200 
batch_size = 100 
lr         = tf.constant(0.005, tf.float32)

接下来准备导入数据了,因为还没开始对数据集进行处理,我们先用空白的数据给它填着

x = tf.placeholder(tf.float32, [None, n_visible], name="x")

然后我们要生成的是两层之间的关系矩阵,也就是两层之间各节点间的权重,初始数据先设为0.01,后面我们将进行更新

W= tf.Variable(tf.random_normal([n_visible, n_hidden], 0.01), name="W")

然后我们再定义两个变量来分别存储隐藏层和可见层的方差

bh = tf.Variable(tf.zeros([1, n_hidden],  tf.float32, name="bh"))
bv = tf.Variable(tf.zeros([1, n_visible],  tf.float32, name=

我们知道对于一个神经网络进行训练的目标当然是要让它产生的模型尽可能契合我们的训练数据,也就是要寻找到最佳的权重矩阵,在迭代神经网络RNN里我们使用的是反向传播算法来对权重矩阵的值不断进行更新,这里也是类似的,只是我们要使用的是contrastive divergence算法即对比分歧算法,它主要是基于吉布斯采样(Gibbs)来完成对权重的更新
对于什么是吉布斯采样,详细的分析可以看下面这篇博文

https://cosx.org/2013/01/lda-math-mcmc-and-gibbs-sampling

 

感觉这玩意跟我们概率论课上讲的最大似然估计值很像,由它的基础理论马尔科夫链主导的隐式马尔科夫链算法HMM也是非常有意思的算法,感觉跟RNN也挺像的,看多了后面也有点懵逼

简单来说CD算法就是从一个样本开始,使用经过多次吉布斯采样得到的样本来做近似度的运算,然后根据这个结果去更新参数,知道最终满足样本的最大可能分布,其实也相当于是在运行一个梯度下降算法

 

我们可以用一张图来简单表示

 

接下来我们来构建吉布斯采样

 

def sample(probs):
      return tf.floor(probs + tf.random_uniform(tf.shape(probs), 0, 1))

其实说白了就是随机生成一个概率矩阵,这里我们使用tensorflow的random_uniform函数,随机生成0到1之间的均匀分布来充当概率

接下来我们完成CD算法

def gibbs_sample(k):
       def gibbs_step(count, k, xk):
            #先运行一次吉布斯采样,初始化值
            hk = sample(tf.sigmoid(tf.matmul(xk, W) + bh)) 
      xk = sample(tf.sigmoid(tf.matmul(hk, tf.transpose(W)) + bv)) 
            return count+1, k, xk
       #接下来运行k次
       ct = tf.constant(0)
       [_, _, x_sample] = control_flow_ops.while_loop(lambda count, num_iter, *args: count < num_iter,gibbs_step, [ct, tf.constant(k), x])
       x_sample = tf.stop_gradient(x_sample) 
       return x_sample

传入参数k表示运行k次吉布斯采样

接下来我们对数据x和隐藏层进行采样

x_sample = gibbs_sample(1) 
h = sample(tf.sigmoid(tf.matmul(x, W) + bh)) 
h_sample = sample(tf.sigmoid(tf.matmul(x_sample, W) + bh)

其中隐藏层的采样还是基于对于数据的采样的

然后我们可以刷新一波变量,公式可见上面CD算法的图

size_bt = tf.cast(tf.shape(x)[0], tf.float32)
 
W_adder= tf.multiply(lr/size_bt, tf.subtract(tf.matmul(tf.transpose(x), h), tf.matmul(tf.transpose(x_sample), h_sample)))
 
bv_adder=tf.multiply(lr/size_bt,tf.reduce_sum(tf.subtract(x,x_sample), 0, True))
bh_adder=tf.multiply(lr/size_bt,tf.reduce_sum(tf.subtract(h,h_sample), 0, True))
updt=[W.assign_add(W_adder),bv.assign_add(bv_adder),bh.assign_add(bh_adder)]

接下来就可以准备启动我们的tensorflow了
我们需要知道的是tensorflow是一个基于图的计算系统,它的整个运算是在一个session中,计算任务用图(graph)来表示,具体的情况可以去tensorflow的官网了解

首先启动session,初始化变量

with tf.Session() as sess:
    init = tf.global_variables_initializer()
sess.run(init)
 
#然后我们将歌曲的音符编码的矩阵reshape成适应训练模型的格式
#同时可以调用tqdm来查看调用的进度
for epoch in tqdm(range(num_epochs)):
        for song in songs:
            song = np.array(song)
          song=song[:int(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})

现在我们的模型已经训练完毕了,然后我们就可以用这个模型来生成音乐了
先还是用吉布斯采样来随机生成一些样本,然后生成我们的音乐
之后再次reshape为方便我们输出的格式

sample = gibbs_sample(1).eval(session=sess, feed_dict={x: np.zeros((50, n_visible))})
    for i in range(sample.shape[0]):
        if not any(sample[i,:]):
            continue
        S = np.reshape(sample[i,:], (num_timesteps, 2*note_range))
        midi_to_statematrix.noteStateMatrixToMidi(S,"out_{}".format(i))

因为我们有50个隐藏层,因此最后的输出会包含50个音符文件,我们需要将它们合并,这个我们可以用另外的脚本来执行

import midi
pattern = midi.read_midifile('out_0.mid')
for x in range(49):
    x=x+1
    try:
        q=midi.read_midifile('out_'+str(x)+'.mid')
    except:
        continue
    del pattern[0][len(pattern[0])-1]
    for tick in q[0]:
        pattern[0].append(tick)
 
midi.write_midifile('out_final.mid',pattern)

这样我们的整个代码就基本上结束了
最后关于这个机器人生成的结果,个人感觉真的只是勉强能听的那种,而且很多时候要看运气,有时候数据集不好可能最后的输出都不全,丢了几个隐藏层的数据出不来了
要特别注意的是给的数据集不宜过大,特别是给的单个midi文件的大小,最好是在5k以下,太大了把这个模型给跑崩都是有可能的,还有就是数据集的质量,最好是纯钢琴的乐器,一多效果也容易炸,另外最好给的数据集的数据风格要是也相近那是最好的,这样生成的音乐感觉上似乎要那么能听一点
怪不得说做机器学习开始的一半时间可能都要费在整理数据上,说实话我也没找到什么好的数据集,不得不说还是很失败了,想做出好听的音乐果然不是那么简单的