AI 学习之使用 RNN 生成智能音乐
智能音乐
本篇文档中我们将使用RNN来生成一段音乐!
文档中使用到了music21,所以我们先要按下面步骤安装它。
保险之前先关闭所有jupyter notebook程序,然后执行下面的步骤
1,打开Anaconda prompt
2,执行activate tensorflow命令
3,执行pip install music21
命令 (下载途中可能会出现timeout超时而失败,确保你有稳定的网络,如果正在下载小黄片的话,会使网络很不稳定哦。多尝试几次,我试了3次成功了。)
生成音乐其实和生成名字诗歌都差不多,都是使用RNN来学习名字的规律诗歌的规律音乐的规律。经过训练后,模型就掌握了相关规律,然后在利用这个掌握了某种规律的模型来进行采样。那么采样生成的作品里面也会体现出那套规律来——名字的规律诗歌的规律音乐的规律。
但是声音在计算机中的表现方式要复杂一些,所以我们要借助于很多工具库。下面的代码单元会加载这些系统的以及我们自定义的工具库。
from __future__ import print_function
import IPython
import sys
from music21 import *
import numpy as np
from grammar import *
from qa import *
from preprocess import *
from music_utils import *
from data_utils import *
from keras.models import load_model, Model
from keras.layers import Dense, Activation, Dropout, Input, LSTM, Reshape, Lambda, RepeatVector
from keras.initializers import glorot_uniform
from keras.utils import to_categorical
from keras.optimizers import Adam
from keras import backend as K
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:458: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
_np_qint8 = np.dtype([("qint8", np.int8, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:459: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
_np_quint8 = np.dtype([("quint8", np.uint8, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:460: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
_np_qint16 = np.dtype([("qint16", np.int16, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:461: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
_np_quint16 = np.dtype([("quint16", np.uint16, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:462: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
_np_qint32 = np.dtype([("qint32", np.int32, 1)])
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/dtypes.py:465: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
np_resource = np.dtype([("resource", np.ubyte, 1)])
Using TensorFlow backend.
1 - 数据预处理
我们将会用一些爵士乐来训练RNN模型。爵士乐是一种很有情调的音乐,听起来让人很轻松愉快。下面的代码单元会播放一段爵士乐,运行代码后,再点击播放按钮就可以欣赏它了。
IPython.display.Audio('./data/30s_seq.mp3')
由于音乐数据的处理过程较复杂,而且我们的重点在于学习RNN,所以把处理代码放到自定义工具库中了,感兴趣并有能力的同学可以自己去工具库中看看那些代码。虽然复杂的处理过程我们可以不用深究,但是关于音乐数据的一些基本常识我们还是要知道的。结果预处理后,音乐数据变成了音乐值,这个值代表了音高(pitch)和音长(duration)以及和弦(Chord)。什么是音高和音长呢?钢琴上面每一个按键就是一个不同的音高,如果你按下某个键1秒钟,那么它的音长就是1秒,如果你同时按下多个键,那么就叫做和弦。本人也是会弹钢琴的哦,但是是上班后才去成人培训班学的。其实去学钢琴还有另外一个重要的目的,就是钢琴培训班里有不少女同学,你懂的!回到正题,每个人工智能项目都需要该项目相关的专业知识,例如医疗AI项目中肯定要有专业的医生加入研发团队,同理,商业级别的智能音乐系统也必须要有专业音乐人参加,即使是智能岛国AV系统,如果没有专业的老司机参加,那么搞出来的作品也可能没有市场,当然,相信在各位同学中专业的老司机应该不少。出于教学目的,专业的音乐知识我们不需要纠结太多,只需要知道我们会例如这些预处理好的音乐值来训练我们的RNN,让RNN模型学会音乐中的规律,然后我们再用这个模型来生成音乐。就像教育小孩子一样,我们让小孩子学习数学,但我们自己可以不懂数学,我们只需要送他上学给他教科书就可以了,他会在学校里从教科书上学到数学知识。
下面的代码会加载音乐并返回预处理后的数据。
X, Y, n_values, indices_values = load_music_utils()
print('shape of X:', X.shape)
print('number of training examples:', X.shape[0])
print('Tx (length of sequence):', X.shape[1])
print('total # of unique values:', n_values)
print('Shape of Y:', Y.shape)
shape of X: (60, 30, 78)
number of training examples: 60
Tx (length of sequence): 30
total # of unique values: 78
Shape of Y: (30, 60, 78)
下面给大家解释一下上面的输出结果:
-
X
: 这是个数组,维度是(m, $T_x$, 78)。m是指有多少个样本,上面结果显示有60个样本,在智能起名的例子中每个名字就是一个样本,本例中一个样本就是一小段音乐。$T_x$是指每个样本中有多少个时间步,在智能起名的例子中每个名字中的每个字母就是一个时间步,本例中一个时间步对应了一个音乐值。在智能起名例子中,有27个字符种类,本例中则有78个音乐值种类,也就是说一个时间步的输入就是一个长度为78的one-hot向量。综上所述,X[i,t,:]就表示第i个样本的第t个时间步的音乐值,这个音乐值是用一个长度为78的one-hot向量来表示的。 -
Y
: 在恐龙起名的例子中,标签Y与X是一样的,只不过元素位置向左边移动了一位。同理,在智能音乐中也是这样的。大家似乎发现这种生成类的模型都是这样的。因为这类模型都是希望当输入是第一个元素时,预测结果能是第二个元素,所以X和Y是一样的,只是错开了一个位置。另外,本例中为了计算方便会将Y的维度顺序换一下,X的维度是(60, 30, 78),Y的维度是(30, 60, 78)。 -
n_values
: 表示数据集中有78个音乐值种类。 -
indices_values
: python字典,用于将索引值转换为音乐值。与恐龙命名的ix_to_char作用一样。
模型结构与恐龙起名的结构差不多,如下图所示。
2 - 构建模型
我们将用Keras来构建模型,模型中使用LSTM单元,单元中包含了64个神经元。
n_a = 64
虽然在训练时,我们可以将所有时间步的输入x一次性通过Keras的内置函数来导入到模型中。但是在测试和使用模型时,我们没有所有时间步的输入x,因为第二个时间步的x是由第一个时间步产生的,即$x^{\langle t\rangle} = y^{\langle t-1 \rangle}$。所以我们需要写点较复杂的代码来用for循环执行每一个时间步。
首先我们定义一些全局对象,这些对象会负责执行某些功能。这样一来后面我们就可以方便地在for循环里不停地调用这些对象来执行时间步。在下面的代码单元中我们定义了3个对象,reshapor是用来改变维度的对象,LSTM_cell是用来执行LSTM单元的对象,densor对象会使用softmax生成预测值。大家可以去看Keras里看相关函数的文档Reshape(), LSTM(), Dense()。
reshapor = Reshape((1, 78))
LSTM_cell = LSTM(n_a, return_state = True)
densor = Dense(n_values, activation='softmax')
还记得我们在教程中强调过多次,$T_x$个时间步是共用一套权重参数的。我们定义的全局对象刚好满足这个要求,在for循环中每次调用这些对象时,并不会重新初始化相关的参数,改变的只是给这样对象的输入x和a等。
# 实现模型
def djmodel(Tx, n_a, n_values):
"""
参数:
Tx -- 时间步数量,即每个音乐片段中包含的音乐值数量。
n_a -- 时间步单元内的神经元个数
n_values -- 数据集中音乐值种类的数量,就是前面说的那个78
返回值:
model -- 一个keras模型
"""
# 定义输入X,这个X中包含了Tx个时间步的输入。
X = Input(shape=(Tx, n_values))
# 定义LSTM算法中的a和c。忘记的话可以打开《5.1.10 使用LSTM来增强RNN的记忆力》看看
a0 = Input(shape=(n_a,), name='a0')
c0 = Input(shape=(n_a,), name='c0')
a = a0
c = c0
outputs = []
# 循环Tx个时间步
for t in range(Tx):
# 取出第t个时间步的x
x = Lambda(lambda x: X[:,t,:])(X)
# 上面取出来的x的维度是(78,),所以调用reshapor对象来将维度变成(1,78)
x = reshapor(x)
# 调用LSTM_cell对象执行LSTM算法
a, _, c = LSTM_cell(x, initial_state=[a, c])
# densor对象会调用softmax来生成预测值
out = densor(a)
# 将本时间步的预测值存起来。
outputs.append(out)
# 创建一个keras模型实例
model = Model(inputs=[X, a0, c0], outputs=outputs)
return model
上面代码中使用了keras的Lambda语法,你可以点击它查看keras的官方文档。
下面的代码将调用上面的函数来创建一个keras模型实例
model = djmodel(Tx = 30 , n_a = 64, n_values = 78)
为模型指定优化方法和损失函数以及一些超参数的值
opt = Adam(lr=0.01, beta_1=0.9, beta_2=0.999, decay=0.01)
model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])
将LSTM相关的a和c初始化为0向量
m = 60
a0 = np.zeros((m, n_a))
c0 = np.zeros((m, n_a))
接下来我们将数据集X和Y传入模型中,训练100个epochs。训练过程要花上好几分钟。
model.fit([X, a0, c0], list(Y), epochs=100)
Epoch 1/100
60/60 [==============================] - 5s - loss: 125.8532 - dense_1_loss_1: 4.3544 - dense_1_loss_2: 4.3494 - dense_1_loss_3: 4.3494 - dense_1_loss_4: 4.3509 - dense_1_loss_5: 4.3447 - dense_1_loss_6: 4.3471 - dense_1_loss_7: 4.3496 - dense_1_loss_8: 4.3338 - dense_1_loss_9: 4.3403 - dense_1_loss_10: 4.3390 - dense_1_loss_11: 4.3356 - dense_1_loss_12: 4.3326 - dense_1_loss_13: 4.3390 - dense_1_loss_14: 4.3369 - dense_1_loss_15: 4.3412 - dense_1_loss_16: 4.3359 - dense_1_loss_17: 4.3371 - dense_1_loss_18: 4.3494 - dense_1_loss_19: 4.3339 - dense_1_loss_20: 4.3382 - dense_1_loss_21: 4.3381 - dense_1_loss_22: 4.3258 - dense_1_loss_23: 4.3327 - dense_1_loss_24: 4.3258 - dense_1_loss_25: 4.3409 - dense_1_loss_26: 4.3322 - dense_1_loss_27: 4.3454 - dense_1_loss_28: 4.3374 - dense_1_loss_29: 4.3365 - dense_1_loss_30: 0.0000e+00 - dense_1_acc_1: 0.0167 - dense_1_acc_2: 0.0500 - dense_1_acc_3: 0.0333 - dense_1_acc_4: 0.0333 - dense_1_acc_5: 0.0333 - dense_1_acc_6: 0.0333 - dense_1_acc_7: 0.0333 - dense_1_acc_8: 0.0333 - dense_1_acc_9: 0.0000e+00 - dense_1_acc_10: 0.0500 - dense_1_acc_11: 0.0500 - dense_1_acc_12: 0.0500 - dense_1_acc_13: 0.0167 - dense_1_acc_14: 0.0667 - dense_1_acc_15: 0.0833 - dense_1_acc_16: 0.0667 - dense_1_acc_17: 0.0500 - dense_1_acc_18: 0.0333 - dense_1_acc_19: 0.0833 - dense_1_acc_20: 0.0333 - dense_1_acc_21: 0.0667 - dense_1_acc_22: 0.1167 - dense_1_acc_23: 0.0000e+00 - dense_1_acc_24: 0.0833 - dense_1_acc_25: 0.0000e+00 - dense_1_acc_26: 0.0667 - dense_1_acc_27: 0.0167 - dense_1_acc_28: 0.0500 - dense_1_acc_29: 0.0500 - dense_1_acc_30: 0.0000e+00
...
Epoch 99/100
60/60 [==============================] - 0s - loss: 6.2058 - dense_1_loss_1: 3.7635 - dense_1_loss_2: 1.3118 - dense_1_loss_3: 0.3393 - dense_1_loss_4: 0.1089 - dense_1_loss_5: 0.0591 - dense_1_loss_6: 0.0464 - dense_1_loss_7: 0.0359 - dense_1_loss_8: 0.0314 - dense_1_loss_9: 0.0274 - dense_1_loss_10: 0.0239 - dense_1_loss_11: 0.0247 - dense_1_loss_12: 0.0250 - dense_1_loss_13: 0.0197 - dense_1_loss_14: 0.0235 - dense_1_loss_15: 0.0226 - dense_1_loss_16: 0.0239 - dense_1_loss_17: 0.0232 - dense_1_loss_18: 0.0226 - dense_1_loss_19: 0.0227 - dense_1_loss_20: 0.0239 - dense_1_loss_21: 0.0244 - dense_1_loss_22: 0.0237 - dense_1_loss_23: 0.0219 - dense_1_loss_24: 0.0229 - dense_1_loss_25: 0.0246 - dense_1_loss_26: 0.0249 - dense_1_loss_27: 0.0250 - dense_1_loss_28: 0.0293 - dense_1_loss_29: 0.0297 - dense_1_loss_30: 0.0000e+00 - dense_1_acc_1: 0.1000 - dense_1_acc_2: 0.6167 - dense_1_acc_3: 0.9167 - dense_1_acc_4: 1.0000 - dense_1_acc_5: 1.0000 - dense_1_acc_6: 1.0000 - dense_1_acc_7: 1.0000 - dense_1_acc_8: 1.0000 - dense_1_acc_9: 1.0000 - dense_1_acc_10: 1.0000 - dense_1_acc_11: 1.0000 - dense_1_acc_12: 1.0000 - dense_1_acc_13: 1.0000 - dense_1_acc_14: 1.0000 - dense_1_acc_15: 1.0000 - dense_1_acc_16: 1.0000 - dense_1_acc_17: 1.0000 - dense_1_acc_18: 1.0000 - dense_1_acc_19: 1.0000 - dense_1_acc_20: 1.0000 - dense_1_acc_21: 1.0000 - dense_1_acc_22: 1.0000 - dense_1_acc_23: 1.0000 - dense_1_acc_24: 1.0000 - dense_1_acc_25: 1.0000 - dense_1_acc_26: 1.0000 - dense_1_acc_27: 1.0000 - dense_1_acc_28: 1.0000 - dense_1_acc_29: 1.0000 - dense_1_acc_30: 0.0167
Epoch 100/100
60/60 [==============================] - 0s - loss: 6.1712 - dense_1_loss_1: 3.7609 - dense_1_loss_2: 1.3014 - dense_1_loss_3: 0.3335 - dense_1_loss_4: 0.1067 - dense_1_loss_5: 0.0580 - dense_1_loss_6: 0.0455 - dense_1_loss_7: 0.0352 - dense_1_loss_8: 0.0308 - dense_1_loss_9: 0.0267 - dense_1_loss_10: 0.0235 - dense_1_loss_11: 0.0242 - dense_1_loss_12: 0.0245 - dense_1_loss_13: 0.0193 - dense_1_loss_14: 0.0231 - dense_1_loss_15: 0.0221 - dense_1_loss_16: 0.0235 - dense_1_loss_17: 0.0227 - dense_1_loss_18: 0.0221 - dense_1_loss_19: 0.0223 - dense_1_loss_20: 0.0235 - dense_1_loss_21: 0.0239 - dense_1_loss_22: 0.0231 - dense_1_loss_23: 0.0214 - dense_1_loss_24: 0.0224 - dense_1_loss_25: 0.0242 - dense_1_loss_26: 0.0244 - dense_1_loss_27: 0.0245 - dense_1_loss_28: 0.0286 - dense_1_loss_29: 0.0291 - dense_1_loss_30: 0.0000e+00 - dense_1_acc_1: 0.1000 - dense_1_acc_2: 0.6167 - dense_1_acc_3: 0.9167 - dense_1_acc_4: 1.0000 - dense_1_acc_5: 1.0000 - dense_1_acc_6: 1.0000 - dense_1_acc_7: 1.0000 - dense_1_acc_8: 1.0000 - dense_1_acc_9: 1.0000 - dense_1_acc_10: 1.0000 - dense_1_acc_11: 1.0000 - dense_1_acc_12: 1.0000 - dense_1_acc_13: 1.0000 - dense_1_acc_14: 1.0000 - dense_1_acc_15: 1.0000 - dense_1_acc_16: 1.0000 - dense_1_acc_17: 1.0000 - dense_1_acc_18: 1.0000 - dense_1_acc_19: 1.0000 - dense_1_acc_20: 1.0000 - dense_1_acc_21: 1.0000 - dense_1_acc_22: 1.0000 - dense_1_acc_23: 1.0000 - dense_1_acc_24: 1.0000 - dense_1_acc_25: 1.0000 - dense_1_acc_26: 1.0000 - dense_1_acc_27: 1.0000 - dense_1_acc_28: 1.0000 - dense_1_acc_29: 1.0000 - dense_1_acc_30: 0.0167
<keras.callbacks.History at 0x7efdf7788cc0>
从上面的输出结果可以看出,随着训练次数的增加,损失值不断地在变小。
经过训练后,模型已经掌握了音乐的规律。那么下面我们就可以使用这个模型来生成音乐了。
3 - 生成音乐
生成方法和恐龙起名是相似的,都是对模型的预测结果进行采样。
3.1 - 预测 & 采样
# 使用在前面训练好了的模型的LSTM_cell和densor对象来进行采样,并把采样过程封装成一个keras模型
def music_inference_model(LSTM_cell, densor, n_values = 78, n_a = 64, Ty = 100):
"""
参数:
LSTM_cell -- 前面训练好了的模型的LSTM_cell对象
densor -- 前面训练好了的模型的densor对象
n_values -- 音乐值种类数量
n_a -- LSTM单元中的神经元个数
Ty -- 想要生成的时间步数量,即你想要生成包含多少个音乐值的音乐片段。
返回值:
inference_model -- 返回一个代表了这个采样流程的keras模型实例。
"""
# 初始化LSTM算法相关的x,a,c
x0 = Input(shape=(1, n_values))
a0 = Input(shape=(n_a,), name='a0')
c0 = Input(shape=(n_a,), name='c0')
a = a0
c = c0
x = x0
outputs = []
# 循环执行每一个时间步
for t in range(Ty):
# 执行LSTM单元
a, _, c = LSTM_cell(x, initial_state=[a, c])
# 产生预测值
out = densor(a)
# 保存预测值
outputs.append(out)
# 进行采样
# 这个one_hot函数的实现代码在工具库music_utils.py里面。
# 它会从out向量中选出一个概率最大的元素,即选出一个概率最大的音乐值,
# 然后为这个音乐值生成对应的one-hot向量,并把它赋值为x以作为下一时间步的输入。
# 恐龙起名中是随机采样的,这里是采样概率最大的那个音乐值。
x = Lambda(one_hot)(out)
# 将上面的采样流程封装成一个keras模型实例
inference_model = Model(inputs=[x0, a0, c0], outputs=outputs)
return inference_model
生成一个采样模型实例
inference_model = music_inference_model(LSTM_cell, densor, n_values = 78, n_a = 64, Ty = 50)
为x,a,c设置相应的初始化
x_initializer = np.zeros((1, 1, 78))
a_initializer = np.zeros((1, n_a))
c_initializer = np.zeros((1, n_a))
# 调用前面的采样模型实例来生成音乐
def predict_and_sample(inference_model, x_initializer = x_initializer, a_initializer = a_initializer,
c_initializer = c_initializer):
"""
参数:
inference_model -- 采样模型
返回值:
results -- 生成的结果,one-hot版本
indices -- 生成的结果,索引版本
"""
# 预测结果,就是inference_model里面的outputs,因为这个outputs是没有经过采样的,所以下面要对它进行采样。
# 注意:inference_model里面虽然对单个output进行了采样,但是那时候只是为了将采样值传递给下一个时间步。
pred = inference_model.predict([x_initializer, a_initializer, c_initializer])
# 对预测结果进行采样,这里是直接取最大概率的。
indices = np.argmax(pred, axis=-1)
# 将采样结果
results = to_categorical(indices, num_classes=78)
return results, indices
results, indices = predict_and_sample(inference_model, x_initializer, a_initializer, c_initializer)
print("np.argmax(results[12]) =", np.argmax(results[12]))
print("np.argmax(results[17]) =", np.argmax(results[17]))
print("list(indices[12:18]) =", list(indices[12:18]))
np.argmax(results[12]) = 51
np.argmax(results[17]) = 61
list(indices[12:18]) = [array([51]), array([61]), array([65]), array([3]), array([51]), array([61])]
上面的结果可能每次都不同,因为keras内部的实现就是有点不可预测的。人工智能领域并不需要百分百精准,因为本身就是个预测的过程。世事难料。
3.3 - 生成音乐
上面只是生成了预测值,但还需要将这些值进行特殊处理来成为音乐。这个处理过程较复杂,所以给大家封装在工具库了,在本文档中直接调用generate_music函数就可以了。通常纯粹用人工智能生成的音乐都不是特别好听,所以要加入一些专业的特殊处理(post-processing)。例如这个处理会确保同一个音不会重复太多次,两个相关的音不会相隔得太远...相当于手工对人工智能给出的结果进行再优化。
out_stream = generate_music(inference_model)
Predicting new values for different set of chords.
Generated 51 sounds using the predicted values for the set of chords ("1") and after pruning
Generated 51 sounds using the predicted values for the set of chords ("2") and after pruning
Generated 51 sounds using the predicted values for the set of chords ("3") and after pruning
Generated 51 sounds using the predicted values for the set of chords ("4") and after pruning
Generated 51 sounds using the predicted values for the set of chords ("5") and after pruning
Your generated music is saved in output/my_music.midi
生成的是midi格式的音乐文件,文件名为my_music.midi,就在本文档同目录的output文件下。你可以使用播放器直接播放它,当然有些播放器可能不支持,我使用的potplayer播放器。你也可以使用格式工厂等软件将midi文件转成mp3文件。
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)