Compositeur avec une longue mémoire à court terme

Composer automatiquement de la musique



Presque immédiatement après avoir appris la programmation, j'ai voulu créer un logiciel capable de composer de la musique.

Pendant plusieurs années, j'ai fait des tentatives primitives pour composer automatiquement de la musique pour Visions of Chaos . Fondamentalement, des formules mathématiques simples ou des mutations génétiques de séquences aléatoires de notes ont été utilisées. Ayant récemment obtenu un succès modeste dans l'étude et l'application de TensorFlow et de réseaux de neurones pour rechercher des automates cellulaires , j'ai décidé d'essayer d'utiliser des réseaux de neurones pour créer de la musique.

Comment ça marche


Le compositeur enseigne un réseau neuronal à mémoire à court terme (LSTM). Les réseaux LSTM sont bien adaptés pour prédire la suite des séquences de données. En savoir plus sur LSTM ici .


Un réseau LSTM reçoit différentes séquences de notes (dans ce cas, il s'agit de fichiers midi à canal unique). Après une formation suffisante, elle a l'opportunité de créer une musique similaire au matériel pédagogique.


Les composants internes LSTM peuvent sembler intimidants, mais l'utilisation de TensorFlow et / ou Keras simplifie considérablement la création et l'expérimentation LSTM.

Musique source pour la formation de modèles


Pour des réseaux LSTM aussi simples, il nous suffit que les compositions sources soient un seul canal midi. Les fichiers midi du solo au piano sont parfaits pour cela. J'ai trouvé des fichiers midi avec des solos de piano sur la page Midi Piano classique et des fichiers mfiles , et je les ai utilisés pour entraîner mes modèles.

J'ai mis la musique de différents compositeurs dans des dossiers séparés. Grâce à cela, l'utilisateur peut sélectionner Bach, cliquer sur le bouton Composer et obtenir une chanson qui (espérons-le) ressemblera à Bach.

Modèle LSTM


Le modèle sur la base duquel j'ai écrit le code a sélectionné cet exemple de l' auteur Sigurður Skúli Sigurgeirsson , à propos duquel il écrit plus en détail ici .

J'ai exécuté le script lstm.py et après 15 heures, il a terminé la formation. Lorsque j'ai exécuté predit.py pour générer les fichiers midi, j'ai été déçu car ils consistaient en une seule note répétitive. En répétant la formation deux fois, j'ai obtenu les mêmes résultats.

Modèle source

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"]) 

Après avoir ajouté une sortie graphique au script, j'ai vu pourquoi mon modèle ne fonctionnait pas. La précision n'a pas augmenté avec le temps, comme il se doit. Voir ci-dessous dans le post pour de bons graphiques qui montrent à quoi devrait ressembler le modèle de travail.


Je ne savais pas pourquoi c'était arrivé. mais a abandonné ce modèle et a commencé à ajuster les paramètres.

 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"]) 

Il est plus compact et a moins de couches LSTM. J'ai également ajouté BatchNormalization, le voyant dans la vidéo sentdex . Très probablement, il existe de meilleurs modèles, mais celui-ci a très bien fonctionné dans toutes mes séances d'entraînement.

Notez que dans les deux modèles, j'ai remplacé LSTM par CuDNNLSTM. J'ai donc obtenu une formation LSTM beaucoup plus rapide grâce à l'utilisation de Cuda. Si vous ne disposez pas d'un GPU avec prise en charge Cuda , vous devez utiliser LSTM. Merci à sendtex pour cette astuce. L'apprentissage de nouveaux modèles et la composition de fichiers midi à l'aide de CuDNNLSTM est environ cinq fois plus rapide.

Pendant combien de temps le modèle doit-il être formé


La similitude des résultats avec la musique originale dépend de la durée de la formation du modèle (le nombre d'époques). S'il y a trop peu d'époques, le résultat résultant aura trop de notes répétitives. S'il y a trop d'époques, le modèle sera recyclé et copiera simplement la musique originale.

Mais comment savoir combien d'époques s'arrêter?

Une solution simple consiste à ajouter un rappel qui stocke le modèle et le graphique de précision / perte toutes les 50 époques lors d'une session d'entraînement sur 500 époques. Grâce à cela, après avoir terminé la formation, vous obtiendrez des modèles et des graphiques avec un incrément de 50 époques, montrant comment la formation se déroule.

Voici les résultats des graphiques d'une course avec sauvegarde toutes les 50 époques, combinés en un GIF animé.


Ce sont les graphiques que nous voulons voir. Les pertes devraient chuter et rester faibles. La précision devrait augmenter et rester proche de 100%.

Il est nécessaire d'utiliser un modèle dont le nombre d'époques correspond au moment où les graphiques ont atteint leurs limites pour la première fois. Pour le graphique ci-dessus, ce sera 150 époques. Si vous utilisez des modèles plus anciens, ils seront recyclés et entraîneront très probablement une simple copie du matériel source.

Le modèle correspondant à ces colonnes a été formé sur des fichiers midi de la catégorie Anthems extraits d'ici .



Sortie de données midi dans un modèle de 150 époques.



Sortie midi dans un modèle de 100 époques.

Même un modèle avec 100 époques peut copier la source trop précisément. Cela peut être dû à un échantillon relativement petit de fichiers midi pour la formation. Avec plus de notes, l'apprentissage est meilleur.

Quand l'apprentissage tourne mal



L'image ci-dessus montre un exemple de ce qui peut et se produit pendant l'entraînement. Les pertes sont réduites et la précision est augmentée, comme d'habitude, mais soudain, elles commencent à devenir folles. À ce stade, cela peut également valoir la peine de s'arrêter. Le modèle n'apprendra plus (du moins d'après mon expérience) correctement. Dans ce cas, le modèle enregistré avec 100 époques est encore trop aléatoire et avec 150 époques, le moment de défaillance du modèle est déjà passé. Maintenant, je suis sauvé toutes les 25 époques pour trouver exactement le moment idéal du modèle avec la meilleure formation, avant même qu'elle ne se recycle et ne s'écrase.


Un autre exemple d'erreur d'apprentissage. Ce modèle a été formé sur des fichiers midi pris ici . Dans ce cas, elle a bien tenu pendant un peu plus de 200 époques. Lors de l'utilisation d'un modèle à 200 époques, le résultat suivant est obtenu en Midi.



Sans la création de graphiques, nous ne saurions jamais si le modèle a des problèmes et quand ils sont survenus, et nous ne pourrions pas non plus obtenir un bon modèle sans repartir de zéro.

Autres exemples




Un modèle à 75 époques, créé à partir des compositions de Chopin .



Un modèle des années 50 basé sur des fichiers Midi pour les compositions de Noël .



Un modèle de 100 époques basé sur des fichiers Midi pour des compositions de Noël . Mais sont-ils vraiment «Noël»?



Un modèle de 300 époques basé sur des fichiers Bach Midi pris d'ici et d'ici .



Un modèle de 200 époques basé sur le seul fichier Midi de Balakirev pris ici .



Modèle à 200 époques, basé sur des compositions Debussy .



Un modèle de 175 ères basé sur les compositions de Mozart.



Un modèle à 100 époques basé sur les compositions de Schubert .



Un modèle datant de 200 ans basé sur des compositions Schumann .



Un modèle de 200 ans basé sur les compositions de Tchaïkovski .



Un modèle avec 175 époques basé sur des chansons folkloriques.



Modèle à 100 époques basé sur des berceuses.



Un modèle centenaire basé sur la musique de mariage.



Un modèle de 200 époques basé sur mes propres fichiers midi extraits de mes bandes sonores vidéo YouTube . Il peut être un peu recyclé car il génère essentiellement des copies de mes courts fichiers MIDI à un et deux temps.

Les scores


Une fois vos fichiers midi récupérés , vous pouvez utiliser des outils en ligne comme SolMiRe pour les convertir en partitions. Vous trouverez ci-dessous la partition du fichier midi Softology 200-epoch présenté ci-dessus.



Où puis-je tester le compositeur


LSTM Composer est désormais inclus dans Visions of Chaos .


Sélectionnez un style dans la liste déroulante et cliquez sur Composer. Si vous avez installé le minimum de Python et TensorFlow nécessaires (voir les instructions ici ), en quelques secondes (si vous avez un GPU rapide), vous recevrez un nouveau fichier midi composé par une machine que vous pouvez écouter et utiliser à d'autres fins. Aucun droit d'auteur, aucune redevance. Si vous n'aimez pas les résultats, vous pouvez cliquer à nouveau sur Composer et après quelques secondes, une nouvelle composition sera prête.

Les résultats ne peuvent pas encore être considérés comme des compositions à part entière, mais ils ont d'intéressantes petites séquences de notes que je vais utiliser pour créer de la musique à l'avenir. À cet égard, le compositeur LSTM peut être une bonne source d'inspiration pour de nouvelles compositions.

Source Python


Vous trouverez ci-dessous le code de script Python que j'ai utilisé pour la formation et les prévisions LSTM. Pour que ces scripts fonctionnent, il n'est pas nécessaire d'installer Visions of Chaos, et l'apprentissage et la génération de midi fonctionneront à partir de la ligne de commande.

Voici le script de formation 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() 

Et voici le script de génération 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() 

Tailles des fichiers modèles


L'inconvénient d'inclure des réseaux de neurones dans Visions of Chaos est la taille des fichiers. Si la génération du modèle était plus rapide, j'ajouterais simplement un bouton pour que l'utilisateur final puisse entraîner les modèles lui-même. Mais comme certaines sessions de formation pour de nombreux modèles peuvent prendre plusieurs jours, ce n'est pas particulièrement pratique. Il m'a semblé qu'il valait mieux faire toute la formation et les tests vous-même, et n'ajouter que les meilleurs modèles de travail. Cela signifie également que l'utilisateur final n'a qu'à appuyer sur un bouton, et les modèles formés créeront des compositions musicales.

Chacun des modèles a une taille de 22 mégaoctets. Dans les conditions de l'Internet moderne, ce n'est pas tant que ça, mais au fil des années de développement, Visions of Chaos a grandi progressivement, et ce n'est que récemment qu'il est soudainement passé de 70 à 91 Mo (en raison du modèle de recherche d'automates cellulaires). Par conséquent, je n'ai jusqu'à présent ajouté qu'un seul modèle au programme d'installation principal de Visions of Chaos. Pour les utilisateurs qui en veulent plus, j'ai publié un lien vers 1 autre Go de modèles. Ils peuvent également utiliser le script ci-dessus pour créer leurs propres modèles à partir de leurs fichiers midi.

Et ensuite?


À ce stade, le compositeur LSTM est l'exemple le plus simple d'utilisation de réseaux de neurones pour composer de la musique.

J'ai déjà trouvé d'autres compositeurs de musique sur des réseaux de neurones que j'expérimenterai à l'avenir, vous pouvez donc vous attendre à ce que dans Visions of Chaos, il y ait de nouvelles possibilités pour composer automatiquement de la musique.

Source: https://habr.com/ru/post/fr470127/


All Articles