Compositor com memória de longo prazo

Compor músicas automaticamente



Quase imediatamente depois de aprender a programar, eu queria criar um software capaz de compor músicas.

Por vários anos, fiz tentativas primitivas de compor músicas automaticamente para Visions of Chaos . Basicamente, foram utilizadas fórmulas matemáticas simples ou mutações genéticas de seqüências aleatórias de notas. Tendo alcançado recentemente um sucesso modesto no estudo e aplicação do TensorFlow e das redes neurais para procurar autômatos celulares , decidi tentar usar redes neurais para criar música.

Como isso funciona


O compositor ensina uma rede neural com memória de longo prazo (LSTM). As redes LSTM são adequadas para prever o que vem a seguir nas sequências de dados. Leia mais sobre LSTM aqui .


Uma rede LSTM recebe várias seqüências de notas (nesse caso, são arquivos midi de canal único). Após treinamento suficiente, ela tem a oportunidade de criar músicas semelhantes aos materiais de ensino.


Os elementos internos do LSTM podem parecer intimidadores, mas o uso do TensorFlow e / ou Keras simplifica bastante a criação e a experimentação do LSTM.

Música de origem para treinamento de modelos


Para redes LSTM simples, basta que as composições de origem sejam um único canal midi. Ótimo para isso são arquivos midi do solo ao piano. Encontrei arquivos midi com solos de piano na página e mfiles do Classical Piano e usei-os para treinar meus modelos.

Coloquei a música de diferentes compositores em pastas separadas. Graças a isso, o usuário pode selecionar Bach, clicar no botão Compor e obter uma música que (espero) seja como Bach.

Modelo LSTM


O modelo com base no qual escrevi o código selecionou este exemplo do autor Sigurður Skúli Sigurgeirsson , sobre quem ele escreve com mais detalhes aqui .

Executei o script lstm.py e após 15 horas ele concluiu o treinamento. Quando executei o predict.py para gerar os arquivos midi, fiquei desapontado porque eles consistiam em uma nota repetida. Repetindo o treinamento duas vezes, obtive os mesmos resultados.

Modelo de origem

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

Depois de adicionar a saída do gráfico ao script, vi por que meu modelo não funcionou. A precisão não cresceu com o tempo, como deveria. Veja abaixo no post os bons gráficos que mostram como deve ser o modelo de trabalho.


Eu não tinha ideia do por que aconteceu. mas abandonou esse modelo e começou a ajustar as configurações.

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

É mais compacto e possui menos camadas LSTM. Também adicionei o BatchNormalization, vendo-o no vídeo sentdex . Provavelmente, existem modelos melhores, mas este funcionou muito bem em todas as minhas sessões de treinamento.

Observe que nos dois modelos substituí LSTM por CuDNNLSTM. Então, consegui um treinamento LSTM muito mais rápido, graças ao uso do Cuda. Se você não possui uma GPU com suporte ao Cuda , use o LSTM. Obrigado ao sendtex por esta dica. Aprender novos modelos e compor arquivos midi usando CuDNNLSTM é cerca de cinco vezes mais rápido.

Por quanto tempo o modelo deve ser treinado


A semelhança dos resultados com a música original depende da duração do treinamento do modelo (o número de épocas). Se houver poucas eras, o resultado resultante terá muitas notas repetidas. Se houver muitas eras, o modelo será treinado novamente e simplesmente copie a música original.

Mas como você sabe quantas épocas parar?

Uma solução simples é adicionar um retorno de chamada que armazene o modelo e o gráfico de precisão / perda a cada 50 épocas em uma execução de treinamento em 500 épocas. Graças a isso, após concluir o treinamento, você obterá modelos e gráficos com um incremento de 50 épocas, mostrando como o treinamento ocorre.

Aqui estão os resultados dos gráficos de uma execução com salvamento a cada 50 épocas, combinados em um GIF animado.


Estes são os gráficos que queremos ver. As perdas devem cair e permanecer baixas. A precisão deve aumentar e permanecer próxima de 100%.

É necessário usar um modelo com o número de épocas correspondente ao momento em que os gráficos atingiram seus limites pela primeira vez. Para o gráfico mostrado acima, serão 150 eras. Se você usar modelos mais antigos, eles serão treinados novamente e provavelmente levarão a uma cópia simples do material de origem.

O modelo correspondente a essas colunas foi treinado em arquivos midi da categoria Hinos, extraídos daqui .



Saída de dados midi em um modelo com 150 eras.



Saída Midi em um modelo de 100 épocas.

Mesmo um modelo com 100 eras pode copiar a fonte com muita precisão. Isso pode ser devido a uma amostra relativamente pequena de arquivos midi para treinamento. Com mais notas, o aprendizado é melhor.

Quando o aprendizado vai mal



A imagem acima mostra um exemplo do que pode e acontece durante o treinamento. As perdas são reduzidas e a precisão é aumentada, como de costume, mas de repente elas começam a enlouquecer. Nesta fase, também pode valer a pena parar. O modelo não aprenderá mais (pelo menos na minha experiência) novamente. Nesse caso, o modelo salvo com 100 eras ainda é muito aleatório e, com 150 eras, o momento de falha do modelo já passou. Agora eu sou salvo a cada 25 épocas para encontrar exatamente o momento ideal da modelo com o melhor treinamento, mesmo antes de ela treinar novamente e travar.


Outro exemplo de erro de aprendizagem. Este modelo foi treinado em arquivos midi retirados daqui . Nesse caso, ela se manteve bem por pouco mais de 200 épocas. Ao usar um modelo com 200 eras, o resultado a seguir é obtido no Midi.



Sem a criação de gráficos, nunca saberíamos se o modelo tem problemas e quando surgiram, e também não conseguiríamos um bom modelo sem começar do zero.

Outros exemplos




Um modelo com 75 eras, criado com base nas composições de Chopin .



Um modelo da era 50 baseado em arquivos Midi para composições de Natal .



Um modelo de 100 épocas baseado em arquivos Midi para composições de Natal . Mas eles são realmente "Natal"?



Um modelo de 300 épocas baseado em arquivos Bach Midi, extraídos daqui e daqui .



Um modelo de 200 épocas baseado no único arquivo Midi de Balakirev tirado aqui .



Modelo com 200 épocas, baseado em composições Debussy .



Um modelo da era 175 baseado nas composições de Mozart.



Um modelo com 100 épocas baseado em composições de Schubert .



Um modelo da era 200 baseado em composições de Schumann .



Um modelo da época 200 baseado nas composições de Tchaikovsky .



Um modelo com 175 épocas baseado em canções folclóricas.



Modelo com 100 eras baseado em canções de ninar.



Um modelo da era 100 baseado na música do casamento.



Um modelo de 200 épocas baseado em meus próprios arquivos midi, extraídos das minhas trilhas sonoras de vídeos do YouTube . Pode ser um pouco reciclado, porque basicamente gera cópias dos meus arquivos midi curtos de um e dois tempos.

Pontuações


Depois de obter seus arquivos midi, você pode usar ferramentas on-line como o SolMiRe para convertê-los em pontuações. Abaixo está a pontuação do arquivo midi Softology 200-epoch apresentado acima.



Onde posso testar o compositor


O LSTM Composer agora está incluído no Visions of Chaos .


Selecione um estilo na lista suspensa e clique em Escrever. Se você instalou o mínimo necessário de Python e TensorFlow (consulte as instruções aqui ), em alguns segundos (se você tiver uma GPU rápida), você receberá um novo arquivo midi composto por máquina que poderá ouvir e usar para qualquer outra finalidade. Sem direitos autorais, sem royalties. Se você não gostar dos resultados, poderá clicar em Redigir novamente e após alguns segundos uma nova composição estará pronta.

Os resultados ainda não podem ser considerados composições completas, mas eles têm pequenas sequências interessantes de notas que usarei para criar música no futuro. Nesse sentido, o compositor LSTM pode ser uma boa fonte de inspiração para novas composições.

Fonte Python


Abaixo está o código de script Python que usei para treinamento e previsão de LSTM. Para que esses scripts funcionem, não é necessário instalar o Visions of Chaos, e o aprendizado e a geração do midi funcionarão na linha de comando.

Aqui está o script de treinamento 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() 

E aqui está o script da geração 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() 

Tamanhos de arquivo de modelo


A desvantagem de incluir redes neurais no Visions of Chaos é o tamanho dos arquivos. Se a geração do modelo fosse mais rápida, basta adicionar um botão para que o usuário final possa treinar os modelos. Mas como algumas das sessões de treinamento de muitos modelos podem levar vários dias, isso não é particularmente prático. Pareceu-me que é melhor fazer todo o treinamento e testar você mesmo e adicionar apenas os melhores modelos de trabalho. Isso também significa que o usuário final só precisa pressionar um botão, e modelos treinados criarão composições musicais.

Cada um dos modelos tem um tamanho de 22 megabytes. Nas condições da Internet moderna, isso não é muito, mas ao longo dos anos de desenvolvimento, Visions of Chaos vem aumentando de tamanho gradualmente, e apenas recentemente aumentou repentinamente de 70 para 91 MB (devido ao modelo de busca por autômatos celulares). Portanto, até agora, adicionei apenas um modelo ao instalador principal do Visions of Chaos. Para usuários que desejam mais, publiquei um link para outros 1 GB de modelos. Eles também podem usar o script acima para criar seus próprios modelos com base em seus arquivos midi.

O que vem a seguir?


Nesse estágio, o compositor LSTM é o exemplo mais simples de uso de redes neurais para compor música.

Eu já encontrei outros compositores de música em redes neurais com as quais experimentarei no futuro, então você pode esperar que em Visions of Chaos existam novas possibilidades para compor músicas automaticamente.

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


All Articles