文章综述
如果我不是物理学家,可能会是音乐家。我整天沉浸在音乐之中,把我的生命当成乐章。——爱因斯坦
我可能不是爱因斯坦那样的物理学家,但我完全同意他的音乐思想!我每天都听音乐。我往返办公室的过程中就伴随着音乐的旋律,老实说,这有助于我专心工作。
我一直梦想着作曲,但不太懂乐器。直到我遇到了深度学习。使用某些技巧和框架,我能够在不真正了解任何音乐理论的情况下创作自己的原创音乐!
这是我最喜欢的专业项目之一。我将我的两种热情——音乐和深度学习——结合起来,创造了一个自动音乐生成模型。梦想成真了!
很高兴与大家分享我的方法,包括让你生成自己的音乐的整个代码!本文首先介绍快速理解自动音乐生成的概念,然后再深入探讨我们可以用来执行此操作的不同方法。最后,运用Python并设计自己的自动音乐生成模型。
目录
1. 什么是自动音乐生成?
2. 音乐的构成要素是什么?
3. 音乐生成的不同方法
3.1 使用WaveNet架构
3.2 使用LSTM
4. 使用Python构建自动音乐生成
音乐是一门艺术,是一种通用 语言。
我把音乐定义为不同频率音调的集合。因此,自动音乐生成是一个在最少的人为干预下创作一首短曲的过程。
最简单的音乐形式是什么?
这一切都是从随机选择声音并将它们组合成一段音乐开始的。1787年,莫扎特为这些随机的声音选择提出了一个骰子游戏。他手写完成了近272个音调!然后,根据2个骰子的和选择了一个音调。
另一个有趣的想法是利用音乐语法来产生音乐。
音乐语法理解音乐声音的合理排列和组合以及音乐作品的恰当表现所必需的知识。——《音乐语法基础》
20世纪50年代初,伊恩尼斯·谢纳基斯(Iannis Xenakis)利用统计学和概率的概念创作了音乐,俗称随机音乐。他把音乐定义为一系列偶然发生的元素(或声音)。因此,他用随机理论来描述它。他对元素的随机选择严格依赖于数学概念。
近年来,深度学习架构已经成为自动生成音乐的最新技术。本文将讨论两种使用WaveNet和LSTM(Long-Short-Term Memory)架构的自动音乐创作方法。
音乐本质上是由音符和和弦组成的。让我从钢琴乐器的角度来解释这些术语:
羡慕详细讨论自动生成音乐的两种基于深度学习的体系结构:WaveNetLSTM。但是,为什么只有深度学习架构?
深度学习是受神经结构启发的机器学习领域。这些网络从数据集中自动提取特征,能够学习任何非线性函数。这就是为什么神经网络被称为通用函数逼近器。
因此,深度学习模型是自然语言处理、计算机视觉、语音合成等领域的研究热点。来看看如何建立这些音乐创作模型。
方法1 :使用WaveNet
WaveNet是由Google DeepMind开发的一个基于深度学习的原始音频生成模型。
WaveNet的主要目标是根据原始数据分布生成新的样本。因此,被称为生成模型。
Wavenet就像NLP中的一个语言模型。
在语言模型中,给定一个单词序列,该模型尝试预测下一个单词:
类似于语言模型,在WaveNet中,给定一系列样本,它试图预测下一个样本。
方法2:使用LSTM模型
LSTM循环神经网络(RNNs)的一个变种,它能够捕获输入序列中的长期依赖关系。LSTM在语音识别、文本摘要、视频分类等序列到序列建模任务中有着广泛的应用。
下面详细讨论一下如何使用这两种方法来训练模型。
这是一个多对一的问题,输入是一系列振幅值,输出是随后的值。
让我们看看如何准备输入和输出序列。
WaveNet的输入:
WaveNet将原始音频波形的块作为输入。原始音频波是指波在时间序列域中的表示。
在时间序列域中,声波以振幅值的形式表示,振幅值以不同的时间间隔记录:
WaveNet的输出:
给定振幅值的序列,WaveNet尝试预测连续的振幅值。
通过一个例子来理解这一点。假设音频波形为5秒,采样率为16000(即每秒16000个采样)。现在,有80000个样本以不同的间隔记录了5秒钟。把音频分成大小相等的块,比如1024(这是一个超参数)。
下图说明了模型的输入和输出序列:
前3块的输入和输出
我们可以对其余的块执行类似的过程。
从上面可以推断出,每个块的输出只依赖于过去的信息(即以前的时间步),而不依赖于将来的时间步。因此,此任务称为自回归任务,模型称为自回归模型。
在推断阶段,我们将尝试生成新的样本。看看如何做到这一点:
1.选择样本值的随机数组作为建模的起点
2.现在,模型输出所有样本的概率分布
3.选择具有最大概率的值并将其追加到一个样本数组中。
4.删除第一个元素并作为下一个迭代的输入传递
5.对一定数量的迭代重复步骤2和4
WaveNe的构建块是因果扩展的一维卷积层。首先了解一下相关概念的重要性。
为什么是卷积?什么是卷积?
使用卷积的主要原因之一是从输入中提取特征。
例如,在图像处理的情况下,用滤波器卷积图像可以得到特征映射。
卷积是一种结合两个函数的数学运算。在图像处理的情况下,卷积是图像的某些部分与核的线性组合。
什么是一维卷积?
一维卷积的目标类似于LSTM模型。它用于解决与LSTM相似的任务。在一维卷积中,核或滤波器仅沿一个方向移动:
卷积的输出取决于内核的大小、输入形状、填充类型和步幅。现在,我将带您了解不同类型的填充,以了解使用扩展的因果一维卷积层的重要性。
当我们设置填充有效时,输入和输出序列的长度会变化。输出的长度小于输入:
当我们将padding设置为相同时,将在输入序列的任一侧填充零,以使输入和输出的长度相等:
一维卷积的优点:
一维卷积的缺点:
这为因果卷积扫清了道路。
注意:我在这里提到的正反两面都是针对这个问题的。
什么是1维因果卷积?
这被定义为卷积,其中在时间t时的输出仅与来自时间t和先前层中的元素卷积。
简单地说,正常卷积和因果卷积只在填充方面有所不同。在因果卷积中,在输入序列的左边加零以保持自回归原理:
因果一维卷积的优点:
因果一维卷积的缺点:
如您所见,输出仅受5个输入的影响。因此,网络的感受野为5,非常低。网络的接收域也可以通过增加大尺寸的内核来增加,但是要记住计算复杂度增加。
这就驱使我们进入到了扩张的一维因果卷积的概念。
什么是扩展一维因果卷积?
具有核值之间的空穴或空间的因果一维卷积层称为扩展一维卷积。
要添加的空间数由膨胀率给出。它定义了网络的接收域。在核函数k的每一个值之间都有d-1个空穴。
如您所见,在7*7输入上卷积一个3*3内核函数,其伸缩率为2,感受野为5*5。
扩展一维因果卷积的优点:
正如您所看到的,输出受所有输入的影响。因此,网络的接收场为16。
WaveNet的残差块:
构建基块包含刚添加的剩余连接和跳过连接,以加快模型的收敛:
WaveNet的工作流:
记忆(LSTM)模型。输入和输出序列的准备与WaveNet类似。在每个时间步,一个振幅值被输入到LSTM单元中,然后计算隐藏向量并将其传递到下一个时间步。
在时间步ht处的当前隐藏向量是基于在和先前隐藏向量ht-1处的当前输入计算的。这就是在任何递归神经网络中捕获序列信息的方式:
LSTM的优点:
LSTM的缺点:
等待结束了!让我们开发一个自动生成音乐的端到端模型。启动Jupyter Notebook或Colab(或任何你喜欢的IDE)。
下载数据集:
我从大量资源中下载并组合了一架数码钢琴的多个古典音乐文件。您可以从这里下载最终的数据集。
让我们先为可重复的结果埋下种子。这是因为深度学习模型在执行时由于随机性可能会输出不同的结果。这确保每次都能产生相同的结果。
from numpy.random import seed
seed(1)
from tensorflow import set_random_seed
set_random_seed(2)
重要的库:
Music 21是麻省理工学院开发的用于理解音乐数据的Python库。MIDI是存储音乐文件的标准格式(它代表乐器数字接口)。MIDI文件包含指令,而不是实际的音频。因此,它占用很少的记忆。这就是为什么在传输文件时通常首选它的原因。
#dealing with midi files
from music21 import *
#array processing
import numpy as np
import os
#random number generator
import random
#keras for building deep learning model
from keras.layers import *
from keras.models import *
import keras.backend as K
读取数据:
定义读取MIDI文件的函数。它返回音乐文件中的一组音符和和弦。
def read_midi(file):
notes=[]
notes_to_parse = None
#parsing a midi file
midi = converter.parse(file)
#grouping based on different instruments
s2 = instrument.partitionByInstrument(midi)
#Looping over all the instruments
for part in s2.parts:
#select elements of only piano
if 'Piano' in str(part):
notes_to_parse = part.recurse()
#finding whether a particular element is note or a chord
for element in notes_to_parse:
if isinstance(element, note.Note):
notes.append(str(element.pitch))
elif isinstance(element, chord.Chord):
notes.append('.'.join(str(n) for n in element.normalOrder))
return notes
从目录中读取MIDI文件:
#read all the filenames
files=[i for i in os.listdir() if i.endswith(".mid")]
#reading each midi file
all_notes=[]
for i in files:
all_notes.append(read_midi(i))
#notes and chords of all the midi files
notes = [element for notes in all_notes for element in notes]
准备文章中提到的输入和输出序列:
#length of a input sequence
no_of_timesteps = 128
#no. of unique notes
n_vocab = len(set(notes))
#all the unique notes
pitch = sorted(set(item for item in notes))
#assign unique value to every note
note_to_int = dict((note, number) for number, note in enumerate(pitch))
#preparing input and output sequences
X = []
y = []
for notes in all_notes:
for i in range(0, len(notes) - no_of_timesteps, 1):
input_ = notes[i:i + no_of_timesteps]
output = notes[i + no_of_timesteps]
X.append([note_to_int[note] for note in input_])
y.append(note_to_int[output])
卷积1D或LSTM的输入必须是(样本、时间步长、特征)的形式。所以,根据所需的形状重塑输入数组。请注意,每个时间步中的功能数为1:
#reshaping
X = np.reshape(X, (len(X), no_of_timesteps, 1))
#normalizing the inputs
X = X / float(n_vocab)
在这里定义了两种架构——WaveNet和LSTM。请尝试这两种架构,以了解WaveNet架构的重要性。
def lstm():
model = Sequential()
model.add(LSTM(128,return_sequences=True))
model.add(LSTM(128))
model.add(Dense(256))
model.add(Activation('relu'))
model.add(Dense(n_vocab))
model.add(Activation('softmax'))
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')
return model
由于这些层的作用是提高更快的收敛速度(而WaveNet以原始音频波为输入),因此我简化了WaveNet的结构,而没有添加剩余连接和跳过连接。但在例子中,输入将是一组节点和和弦,因为我们正在生成音乐:
K.clear_session()
def simple_wavenet():
no_of_kernels=64
num_of_blocks= int(np.sqrt(no_of_timesteps)) - 1 #no. of stacked conv1d layers
model = Sequential()
for i in range(num_of_blocks):
model.add(Conv1D(no_of_kernels,3,dilation_rate=(2**i),padding='causal',activation='relu'))
model.add(Conv1D(1, 1, activation='relu', padding='causal'))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dense(n_vocab, activation='softmax'))
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')
return model
定义回调以在每50个阶段之后保存模型:
import keras
mc = keras.callbacks.ModelCheckpoint('model{epoch:03d}.h5', save_weights_only=False, period=50)
实例化并训练批大小为128的模型:
model = simple_wavenet()
model.fit(X,np.array(y), epochs=300, batch_size=128,callbacks=[mc])
这是本文中提到的推理阶段的实现。它预测特定迭代次数下最有可能的元素:
def generate_music(model, pitch, no_of_timesteps, pattern):
int_to_note = dict((number, note) for number, note in enumerate(pitch))
prediction_output = []
# generate 50 elements
for note_index in range(50):
#reshaping array to feed into model
input_ = np.reshape(pattern, (1, len(pattern), 1))
#predict the probability and choose the maximum value
proba = model.predict(input_, verbose=0)
index = np.argmax(proba)
#convert integer back to the element
pred = int_to_note[index]
prediction_output.append(pred)
pattern = list(pattern)
pattern.append(index/float(n_vocab))
#leave the first value at index 0
pattern = pattern[1:len(pattern)]
return prediction_output
下面是一个将合成音乐转换为MIDI文件的函数:
def convert_to_midi(prediction_output):
offset = 0
output_notes = []
# create note and chord objects based on the values generated by the model
for pattern in prediction_output:
# pattern is a chord
if ('.' in pattern) or pattern.isdigit():
notes_in_chord = pattern.split('.')
notes = []
for current_note in notes_in_chord:
new_note = note.Note(int(current_note))
new_note.storedInstrument = instrument.Piano()
notes.append(new_note)
new_chord = chord.Chord(notes)
new_chord.offset = offset
output_notes.append(new_chord)
# pattern is a note
else:
new_note = note.Note(pattern)
new_note.offset = offset
new_note.storedInstrument = instrument.Piano()
output_notes.append(new_note)
# Specify duration between 2 notes
offset+ = 0.5
# offset += random.uniform(0.5,0.9)
midi_stream = stream.Stream(output_notes)
midi_stream.write('midi', fp='music.mid')
现在进行编曲。
#Select random chunk for the first iteration
start = np.random.randint(0, len(X)-1)
pattern = X[start]
#load the best model
model=load_model('model300.h5')
#generate and save music
music = generate_music(model,pitch,no_of_timesteps,pattern)
convert_to_midi(music)
以下是由我们的模型创作的几首曲子。是时候欣赏音乐了!
音乐播放器
00:00
00:00
Use Up/Down Arrow keys to increase or decrease volume.
音乐播放器
00:00
00:00
Use Up/Down Arrow keys to increase or decrease volume.
音乐播放器
00:00
00:00
Use Up/Down Arrow keys to increase or decrease volume.
太棒了,对吧?但学习并不止于此。以下是进一步提高模型性能的几种方法:
深度学习在日常生活中有着广泛的应用。解决任何问题的关键步骤是理解问题陈述、制定问题陈述和定义解决问题的体系结构。
来源:https://www.analyticsvidhya.com/blog/2020/01/how-to-perform-automatic-music-generation/
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。