作曲家有很长的短期记忆

自动作曲



学习编程之后,我几乎立即想创建能够创作音乐的软件。

几年来,我进行了原始的尝试,以自动为“混沌视觉”创作音乐。 基本上,使用简单的数学公式或音符随机序列的遗传突变。 我最近在TensorFlow和神经网络的研究和应用中取得了一定的成功,并成功地寻找了细胞自动机 ,我决定尝试使用神经网络来创作音乐。

如何运作


作曲家教了一个具有短期记忆(LSTM)的神经网络。 LSTM网络非常适合预测数据序列中的下一个内容。 在此处阅读有关LSTM的更多信息。


LSTM网络接收各种音符序列(在这种情况下,它们是单通道midi文件)。 经过足够的培训,她有机会创作与教材相似的音乐。


LSTM内部结构似乎令人生畏,但是使用TensorFlow和/或Keras可以大大简化LSTM的创建和实验。

用于模型训练的源音乐


对于这样简单的LSTM网络,对我们来说,源组成是单个Midi通道就足够了。 很棒的是从独奏到钢琴的Midi文件。 我在古典钢琴Midi页面mfiles上找到了带有钢琴独奏的midi文件,并用它们来训练我的模型。

我将不同作曲家的音乐放在单独的文件夹中。 因此,用户可以选择Bach,单击“撰写”按钮,并获得一首希望与Bach相似的歌曲。

LSTM模型


我编写代码所依据的模型选择作者SigurðurSkúliSigurgeirsson的 这个示例,他在此处详细介绍 该示例

我运行了lstm.py脚本,并在15小时后完成了培训。 当我运行predict.py生成midi文件时,我很失望,因为它们由一个重复的音符组成。 重复训练两次,我得到相同的结果。

源模型

model = Sequential() model.add(CuDNNLSTM(512,input_shape=(network_input.shape[1], network_input.shape[2]),return_sequences=True)) model.add(Dropout(0.3)) model.add(CuDNNLSTM(512, return_sequences=True)) model.add(Dropout(0.3)) model.add(CuDNNLSTM(512)) model.add(Dense(256)) model.add(Dropout(0.3)) model.add(Dense(n_vocab)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='rmsprop',metrics=["accuracy"]) 

将图形输出添加到脚本后,我明白了为什么我的模型不起作用。 准确性并没有随着时间的增长而增长。 请参阅下面的帖子中的好图,这些图显示了工作模型的外观。


我不知道为什么会这样。 但放弃了该模型并开始调整设置。

 model = Sequential() model.add(CuDNNLSTM(512, input_shape=(network_input.shape[1], network_input.shape[2]), return_sequences=True)) model.add(Dropout(0.2)) model.add(BatchNormalization()) model.add(CuDNNLSTM(256)) model.add(Dropout(0.2)) model.add(BatchNormalization()) model.add(Dense(128, activation="relu")) model.add(Dropout(0.2)) model.add(BatchNormalization()) model.add(Dense(n_vocab)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam',metrics=["accuracy"]) 

它更紧凑,具有更少的LSTM层。 我还添加了BatchNormalization,在senddex视频中看到了它。 很可能有更好的模型,但是这个模型在我所有的培训课程中都表现很好。

注意,在两个模型中,我都用CuDNNLSTM替换了LSTM。 因此,由于使用了Cuda,我获得了更快的LSTM培训。 如果您没有支持CudaGPU ,则必须使用LSTM。 感谢sendtex提供的技巧。 使用CuDNNLSTM学习新模型和编写midi文件的速度快大约五倍。

该模型应训练多长时间


结果与原始音乐的相似性取决于模型训练的持续时间(时代数)。 如果时代太少,那么结果将有太多重复的音符。 如果时代太多,模型将被重新训练,只需复制原始音乐即可。

但是你怎么知道要停止多少个时代呢?

一个简单的解决方案是添加一个回调,该回调在500个时代进行的训练中每50个时代存储模型和准确性/损失图。 因此,完成培训后,您将获得带有50个时代的增量的模型和图形,显示了培训的进行方式。

以下是每50个时代保存一次的运行结果的图形结果,并合并为一个动画GIF。


这些是我们想要看到的图形。 损失应下降并保持较低水平。 精度应提高并保持接近100%。

必须使用一个模型,该模型具有与图首次达到其极限的时刻相对应的时期数。 对于上面显示的图表,将是150个时代。 如果使用较旧的模型,则将对它们进行重新培训,并且很可能会导致对原始资料的简单复制。

对应于这些列的模型在从此处获取的Anthems类别的midi文件上进行了训练。



在具有150个时代的模型中输出midi数据。



100周期模型中的Midi输出。

即使是具有100个时代的模型,也可能会过于精确地复制源。 这可能是由于要进行培训的Midi文件样本相对较少。 有了更多的笔记,学习会更好。

当学习不好时



上图显示了在训练过程中可能发生和发生的事情的示例。 与往常一样,损失减少了,准确性提高了,但是突然之间,它们开始变得疯狂。 在此阶段,可能还需要停止。 该模型将不再(至少以我的经验)不再正确学习。 在这种情况下,保存的具有100个时代的模型仍然过于随机,而具有150个时代的模型已经过了失效的时刻。 现在,我每25个世纪就得救一次,即使在她再训练和撞车之前,也能通过最好的培训准确地找到模型的理想时刻。


学习错误的另一个例子。 此模型是在从此处获取的Midi文件上进行训练的。 在这种情况下,她保持了200多个时代。 当使用具有200个时代的模型时,在Midi中可获得以下结果。



如果不创建图形,我们将永远无法知道模型是否存在问题以及何时出现问题,而且如果不从头开始,就无法获得好的模型。

其他例子




基于肖邦的构图创建的具有75个时代的模型。



基于Midi文件的 50年代圣诞节作文模型



一个基于Midi文件的 100个周期的圣诞节作文模型 。 但是他们真的是“圣诞节”吗?



基于此处此处获取的巴赫Midi文件的300历时模型。



基于Balakirev唯一的Midi文件的200历时模型, 此处为



基于Debussy合成,具有200个时代的模型。



基于莫扎特作品的175年代模型。



基于舒伯特构图的具有100个时代的模型。



基于舒曼构图的200年代模型。



基于柴可夫斯基作品的 200年代模型。



基于民歌的175个时代的模型。



基于摇篮曲的100个时代的模型。



基于婚礼音乐的100年代模型。



基于从YouTube视频音轨中提取的自己的midi文件的200历时模型。 可能需要重新培训,因为它基本上会生成我的短一冲程和二冲程Midi文件的副本。

分数


获得 Midi文件后,您可以使用SolMiRe等在线工具将其转换为乐谱。 以下是上面显示的midi Softology 200个时代文件的分数。



我在哪里可以测试作曲家


LSTM Composer现在包含在“视觉异象”中


从下拉列表中选择一种样式,然后单击“撰写”。 如果您安装了最低限度的必需Python和TensorFlow(请参阅此处的说明),那么在几秒钟内(如果您具有快速的GPU),您将收到一个新的机器编写的midi文件,您可以收听该文件并用于其他目的。 没有版权,没有专利权。 如果您不满意结果,则可以再次单击“撰写”,几秒钟后便可以准备一个新的作品。

结果还不能算是成熟的作品,但是它们有一些有趣的小音符序列,我将来会用它们来创作音乐。 在这方面,LSTM作曲家可以成为新作品灵感的良好来源。

Python源码


以下是我用于LSTM训练和预测的Python脚本代码。 要使这些脚本起作用,就不必安装Visions of Chaos,并且可以从命令行学习和生成midi。

这是训练脚本lstm_music_train.py

lstm_music_train.py
 # based on code from https://github.com/Skuldur/Classical-Piano-Composer # to use this script pass in; # 1. the directory with midi files # 2. the directory you want your models to be saved to # 3. the model filename prefix # 4. how many total epochs you want to train for # eg python -W ignore "C:\\LSTM Composer\\lstm_music_train.py" "C:\\LSTM Composer\\Bach\\" "C:\\LSTM Composer\\" "Bach" 500 import os import tensorflow as tf # ignore all info and warning messages os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR) import glob import pickle import numpy import sys import keras import matplotlib.pyplot as plt from music21 import converter, instrument, note, chord from datetime import datetime from keras.models import Sequential from keras.layers.normalization import BatchNormalization from keras.layers import Dense from keras.layers import Dropout from keras.layers import CuDNNLSTM from keras.layers import Activation from keras.utils import np_utils from keras.callbacks import TensorBoard from shutil import copyfile # name of midi file directory, model directory, model file prefix, and epochs mididirectory = str(sys.argv[1]) modeldirectory = str(sys.argv[2]) modelfileprefix = str(sys.argv[3]) modelepochs = int(sys.argv[4]) notesfile = modeldirectory + modelfileprefix + '.notes' # callback to save model and plot stats every 25 epochs class CustomSaver(keras.callbacks.Callback): def __init__(self): self.epoch = 0 # This function is called when the training begins def on_train_begin(self, logs={}): # Initialize the lists for holding the logs, losses and accuracies self.losses = [] self.acc = [] self.logs = [] def on_epoch_end(self, epoch, logs={}): # Append the logs, losses and accuracies to the lists self.logs.append(logs) self.losses.append(logs.get('loss')) self.acc.append(logs.get('acc')*100) # save model and plt every 50 epochs if (epoch+1) % 25 == 0: sys.stdout.write("\nAuto-saving model and plot after {} epochs to ".format(epoch+1)+"\n"+modeldirectory + modelfileprefix + "_" + str(epoch+1).zfill(3) + ".model\n"+modeldirectory + modelfileprefix + "_" + str(epoch+1).zfill(3) + ".png\n\n") sys.stdout.flush() self.model.save(modeldirectory + modelfileprefix + '_' + str(epoch+1).zfill(3) + '.model') copyfile(notesfile,modeldirectory + modelfileprefix + '_' + str(epoch+1).zfill(3) + '.notes'); N = numpy.arange(0, len(self.losses)) # Plot train loss, train acc, val loss and val acc against epochs passed plt.figure() plt.subplots_adjust(hspace=0.7) plt.subplot(2, 1, 1) # plot loss values plt.plot(N, self.losses, label = "train_loss") plt.title("Loss [Epoch {}]".format(epoch+1)) plt.xlabel('Epoch') plt.ylabel('Loss') plt.subplot(2, 1, 2) # plot accuracy values plt.plot(N, self.acc, label = "train_acc") plt.title("Accuracy % [Epoch {}]".format(epoch+1)) plt.xlabel("Epoch") plt.ylabel("Accuracy %") plt.savefig(modeldirectory + modelfileprefix + '_' + str(epoch+1).zfill(3) + '.png') plt.close() # train the neural network def train_network(): sys.stdout.write("Reading midi files...\n\n") sys.stdout.flush() notes = get_notes() # get amount of pitch names n_vocab = len(set(notes)) sys.stdout.write("\nPreparing note sequences...\n") sys.stdout.flush() network_input, network_output = prepare_sequences(notes, n_vocab) sys.stdout.write("\nCreating CuDNNLSTM neural network model...\n") sys.stdout.flush() model = create_network(network_input, n_vocab) sys.stdout.write("\nTraining CuDNNLSTM neural network model...\n\n") sys.stdout.flush() train(model, network_input, network_output) # get all the notes and chords from the midi files def get_notes(): # remove existing data file if it exists if os.path.isfile(notesfile): os.remove(notesfile) notes = [] for file in glob.glob("{}/*.mid".format(mididirectory)): midi = converter.parse(file) sys.stdout.write("Parsing %s ...\n" % file) sys.stdout.flush() notes_to_parse = None try: # file has instrument parts s2 = instrument.partitionByInstrument(midi) notes_to_parse = s2.parts[0].recurse() except: # file has notes in a flat structure notes_to_parse = midi.flat.notes 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)) with open(notesfile,'wb') as filepath: pickle.dump(notes, filepath) return notes # prepare the sequences used by the neural network def prepare_sequences(notes, n_vocab): sequence_length = 100 # get all pitch names pitchnames = sorted(set(item for item in notes)) # create a dictionary to map pitches to integers note_to_int = dict((note, number) for number, note in enumerate(pitchnames)) network_input = [] network_output = [] # create input sequences and the corresponding outputs for i in range(0, len(notes) - sequence_length, 1): sequence_in = notes[i:i + sequence_length] # needs to take into account if notes in midi file are less than required 100 ( mod ? ) sequence_out = notes[i + sequence_length] # needs to take into account if notes in midi file are less than required 100 ( mod ? ) network_input.append([note_to_int[char] for char in sequence_in]) network_output.append(note_to_int[sequence_out]) n_patterns = len(network_input) # reshape the input into a format compatible with CuDNNLSTM layers network_input = numpy.reshape(network_input, (n_patterns, sequence_length, 1)) # normalize input network_input = network_input / float(n_vocab) network_output = np_utils.to_categorical(network_output) return (network_input, network_output) # create the structure of the neural network def create_network(network_input, n_vocab): ''' """ create the structure of the neural network """ model = Sequential() model.add(CuDNNLSTM(512, input_shape=(network_input.shape[1], network_input.shape[2]), return_sequences=True)) model.add(Dropout(0.3)) model.add(CuDNNLSTM(512, return_sequences=True)) model.add(Dropout(0.3)) model.add(CuDNNLSTM(512)) model.add(Dense(256)) model.add(Dropout(0.3)) model.add(Dense(n_vocab)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='rmsprop',metrics=["accuracy"]) ''' model = Sequential() model.add(CuDNNLSTM(512, input_shape=(network_input.shape[1], network_input.shape[2]), return_sequences=True)) model.add(Dropout(0.2)) model.add(BatchNormalization()) model.add(CuDNNLSTM(256)) model.add(Dropout(0.2)) model.add(BatchNormalization()) model.add(Dense(128, activation="relu")) model.add(Dropout(0.2)) model.add(BatchNormalization()) model.add(Dense(n_vocab)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam',metrics=["accuracy"]) return model # train the neural network def train(model, network_input, network_output): # saver = CustomSaver() # history = model.fit(network_input, network_output, epochs=modelepochs, batch_size=50, callbacks=[tensorboard]) history = model.fit(network_input, network_output, epochs=modelepochs, batch_size=50, callbacks=[CustomSaver()]) # evaluate the model print("\nModel evaluation at the end of training") train_acc = model.evaluate(network_input, network_output, verbose=0) print(model.metrics_names) print(train_acc) # save trained model model.save(modeldirectory + modelfileprefix + '_' + str(modelepochs) + '.model') # delete temp notes file os.remove(notesfile) if __name__ == '__main__': train_network() 

这是midi lstm_music_predict.py生成脚本:

lstm_music_predict.py
 # based on code from https://github.com/Skuldur/Classical-Piano-Composer # to use this script pass in; # 1. path to notes file # 2. path to model # 3. path to midi output # eg python -W ignore "C:\\LSTM Composer\\lstm_music_predict.py" "C:\\LSTM Composer\\Bach.notes" "C:\\LSTM Composer\\Bach.model" "C:\\LSTM Composer\\Bach.mid" # ignore all info and warning messages import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' import tensorflow as tf tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR) import pickle import numpy import sys import keras.models from music21 import instrument, note, stream, chord from keras.models import Sequential from keras.layers import Dense from keras.layers import Dropout from keras.layers import Activation # name of weights filename notesfile = str(sys.argv[1]) modelfile = str(sys.argv[2]) midifile = str(sys.argv[3]) # generates a piano midi file def generate(): sys.stdout.write("Loading notes data file...\n\n") sys.stdout.flush() #load the notes used to train the model with open(notesfile, 'rb') as filepath: notes = pickle.load(filepath) sys.stdout.write("Getting pitch names...\n\n") sys.stdout.flush() # Get all pitch names pitchnames = sorted(set(item for item in notes)) # Get all pitch names n_vocab = len(set(notes)) sys.stdout.write("Preparing sequences...\n\n") sys.stdout.flush() network_input, normalized_input = prepare_sequences(notes, pitchnames, n_vocab) sys.stdout.write("Loading LSTM neural network model...\n\n") sys.stdout.flush() model = create_network(normalized_input, n_vocab) sys.stdout.write("Generating note sequence...\n\n") sys.stdout.flush() prediction_output = generate_notes(model, network_input, pitchnames, n_vocab) sys.stdout.write("\nCreating MIDI file...\n\n") sys.stdout.flush() create_midi(prediction_output) # prepare the sequences used by the neural network def prepare_sequences(notes, pitchnames, n_vocab): # map between notes and integers and back note_to_int = dict((note, number) for number, note in enumerate(pitchnames)) sequence_length = 100 network_input = [] output = [] for i in range(0, len(notes) - sequence_length, 1): sequence_in = notes[i:i + sequence_length] sequence_out = notes[i + sequence_length] network_input.append([note_to_int[char] for char in sequence_in]) output.append(note_to_int[sequence_out]) n_patterns = len(network_input) # reshape the input into a format compatible with LSTM layers normalized_input = numpy.reshape(network_input, (n_patterns, sequence_length, 1)) # normalize input normalized_input = normalized_input / float(n_vocab) return (network_input, normalized_input) # create the structure of the neural network def create_network(network_input, n_vocab): model = keras.models.load_model(modelfile) return model # generate notes from the neural network based on a sequence of notes def generate_notes(model, network_input, pitchnames, n_vocab): # pick a random sequence from the input as a starting point for the prediction start = numpy.random.randint(0, len(network_input)-1) int_to_note = dict((number, note) for number, note in enumerate(pitchnames)) pattern = network_input[start] prediction_output = [] # generate 500 notes for note_index in range(500): prediction_input = numpy.reshape(pattern, (1, len(pattern), 1)) prediction_input = prediction_input / float(n_vocab) prediction = model.predict(prediction_input, verbose=0) index = numpy.argmax(prediction) result = int_to_note[index] prediction_output.append(result) pattern.append(index) pattern = pattern[1:len(pattern)] if (note_index + 1) % 50 == 0: sys.stdout.write("{} out of 500 notes generated\n".format(note_index+1)) sys.stdout.flush() return prediction_output # convert the output from the prediction to notes and create a midi file from the notes def create_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) # increase offset each iteration so that notes do not stack offset += 0.5 midi_stream = stream.Stream(output_notes) midi_stream.write('midi', fp=midifile) if __name__ == '__main__': generate() 

模型文件大小


在混沌视觉中包含神经网络的缺点是文件的大小。 如果模型的生成速度更快,那么我只需添加一个按钮,以便最终用户可以自己训练模型。 但是,由于针对许多模型的某些培训课程可能需要几天的时间,因此这并不是特别实用。 在我看来,最好自己进行所有培训和测试,并仅添加最佳的工作模型。 这也意味着最终用户只需要按下一个按钮,经过训练的模型就可以创作音乐作品。

每个模型的大小为22 MB。 在现代Internet的条件下,这还不算什么,但是随着几年的发展,《视觉异象》的大小逐渐增长,直到最近才突然从70 MB增长到91 MB(由于用于搜索自动机的模型)。 因此,到目前为止,我仅向主要的Visions Chaos安装程序添加了一个模型。 对于需要更多功能的用户,我发布了指向其他1 GB模型的链接。 他们还可以使用上面的脚本基于其Midi文件创建自己的模型。

接下来是什么?


在这个阶段,LSTM作曲家是使用神经网络作曲的最简单的例子。

我已经在神经网络上找到了其他音乐作曲家,我将在以后进行实验,因此您可以期望,在《混沌的视觉》中,将有可能自动合成音乐。

Source: https://habr.com/ru/post/zh-CN470127/


All Articles