Komposer dengan memori jangka pendek yang panjang

Menulis musik secara otomatis



Hampir segera setelah saya belajar pemrograman, saya ingin membuat perangkat lunak yang mampu membuat musik.

Selama beberapa tahun saya membuat upaya primitif untuk secara otomatis mengarang musik untuk Visions of Chaos . Pada dasarnya, rumus matematika sederhana atau mutasi genetik dari urutan acak catatan digunakan. Setelah baru-baru ini mencapai keberhasilan sederhana dalam studi dan penerapan TensorFlow dan jaringan saraf untuk mencari automata seluler , saya memutuskan untuk mencoba menggunakan jaringan saraf untuk membuat musik.

Bagaimana cara kerjanya


Komposer mengajarkan jaringan saraf dengan memori jangka pendek (LSTM). Jaringan LSTM sangat cocok untuk memprediksi apa yang terjadi selanjutnya dalam urutan data. Baca lebih lanjut tentang LSTM di sini .


Jaringan LSTM menerima berbagai urutan catatan (dalam hal ini, ini adalah file midi saluran tunggal). Setelah pelatihan yang cukup, ia mendapat kesempatan untuk membuat musik yang mirip dengan materi pengajaran.


Internal LSTM mungkin tampak menakutkan, tetapi menggunakan TensorFlow dan / atau Keras sangat menyederhanakan pembuatan dan eksperimen LSTM.

Sumber musik untuk pelatihan model


Untuk jaringan LSTM yang begitu sederhana, cukup bagi kami bahwa komposisi sumber adalah saluran midi tunggal. Bagus untuk ini adalah file midi dari solo ke piano. Saya menemukan file midi dengan solo piano di Piano Klasik Page dan mfiles , dan menggunakannya untuk melatih model saya.

Saya memasukkan musik komposer yang berbeda ke dalam folder terpisah. Berkat ini, pengguna dapat memilih Bach, klik tombol Tulis dan dapatkan lagu yang (semoga) akan seperti Bach.

Model LSTM


Model yang menjadi dasar saya menulis kode memilih contoh penulis Sigurður Skúli Sigurgeirsson ini , tentang siapa yang ia tulis secara lebih rinci di sini .

Saya menjalankan skrip lstm.py dan setelah 15 jam itu menyelesaikan pelatihan. Ketika saya menjalankan predict.py untuk menghasilkan file midi, saya kecewa karena mereka terdiri dari satu catatan berulang. Mengulangi pelatihan dua kali, saya mendapat hasil yang sama.

Model sumber

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

Setelah menambahkan output grafik ke skrip, saya melihat mengapa model saya tidak berfungsi. Akurasi tidak tumbuh seiring waktu, sebagaimana mestinya. Lihat di bawah di pos untuk grafik yang bagus yang menunjukkan bagaimana model kerja seharusnya terlihat.


Saya tidak tahu mengapa itu terjadi. tetapi meninggalkan model ini dan mulai menyesuaikan pengaturan.

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

Ini lebih kompak dan memiliki lebih sedikit lapisan LSTM. Saya juga menambahkan BatchNormalisasi, melihatnya di video sentdex . Kemungkinan besar, ada model yang lebih baik, tetapi yang ini bekerja cukup baik di semua sesi pelatihan saya.

Perhatikan bahwa di kedua model saya mengganti LSTM dengan CuDNNLSTM. Jadi saya mencapai pelatihan LSTM yang jauh lebih cepat berkat penggunaan Cuda. Jika Anda tidak memiliki GPU dengan dukungan Cuda , maka Anda harus menggunakan LSTM. Terima kasih kepada sendtex untuk tip ini. Mempelajari model baru dan menyusun file midi menggunakan CuDNNLSTM sekitar lima kali lebih cepat.

Berapa lama model harus dilatih


Kesamaan hasil dengan musik asli tergantung pada durasi pelatihan model (jumlah era). Jika ada terlalu sedikit era, maka hasil yang dihasilkan akan memiliki terlalu banyak catatan berulang. Jika ada terlalu banyak era, model akan dilatih ulang dan cukup menyalin musik asli.

Tapi bagaimana Anda tahu berapa era untuk berhenti?

Solusi sederhana adalah menambahkan callback yang menyimpan model dan grafik akurasi / kehilangan setiap 50 era pada pelatihan yang dijalankan dalam 500 era. Berkat ini, setelah menyelesaikan pelatihan, Anda akan mendapatkan model dan grafik dengan kenaikan 50 era, yang menunjukkan bagaimana pelatihan berlangsung.

Berikut adalah hasil grafik dari satu menjalankan dengan menyimpan setiap 50 era, digabungkan menjadi satu GIF animasi.


Ini adalah grafik yang ingin kita lihat. Kerugian harus jatuh dan tetap rendah. Akurasi harus meningkat dan tetap mendekati 100%.

Penting untuk menggunakan model dengan jumlah zaman yang sesuai dengan saat ketika grafik pertama kali mencapai batasnya. Untuk grafik yang ditunjukkan di atas, itu akan menjadi 150 era. Jika Anda menggunakan model lama, mereka akan dilatih ulang dan kemungkinan besar akan mengarah pada penyalinan sederhana dari bahan sumber.

Model yang sesuai dengan kolom ini dilatih pada file midi dari kategori Lagu Kebangsaan yang diambil dari sini .



Output data midi dalam model dengan 150 era.



Output Midi dalam model 100-zaman.

Bahkan model dengan 100 era dapat menyalin sumber terlalu akurat. Ini mungkin disebabkan oleh sampel file midi yang relatif kecil untuk pelatihan. Dengan lebih banyak catatan, belajar lebih baik.

Ketika belajar memburuk



Gambar di atas menunjukkan contoh apa yang bisa dan tidak terjadi selama pelatihan. Kerugian berkurang, dan akurasi meningkat, seperti biasa, tetapi tiba-tiba mereka mulai menjadi gila. Pada tahap ini, mungkin juga layak untuk dihentikan. Model tidak akan lagi (setidaknya dalam pengalaman saya) belajar dengan benar lagi. Dalam hal ini, model yang disimpan dengan 100 era masih terlalu acak, dan dengan 150 era saat kegagalan model telah berlalu. Sekarang saya diselamatkan setiap 25 era untuk menemukan momen ideal model dengan pelatihan terbaik, bahkan sebelum dia berlatih kembali dan jatuh.


Contoh lain dari kesalahan belajar. Model ini dilatih pada file midi yang diambil dari sini . Dalam hal ini, ia bertahan dengan baik selama sedikit lebih dari 200 era. Saat menggunakan model dengan 200 era, hasil berikut diperoleh dalam Midi.



Tanpa pembuatan grafik, kita tidak akan pernah tahu apakah model memiliki masalah dan kapan mereka muncul, dan juga tidak bisa mendapatkan model yang baik tanpa memulai dari awal.

Contoh lainnya




Model dengan 75 era, dibuat berdasarkan komposisi Chopin .



Model 50-era berdasarkan file Midi untuk komposisi Natal .



Model 100 zaman berdasarkan file Midi untuk komposisi Natal . Tetapi apakah mereka benar-benar "Natal"?



Model 300 zaman berdasarkan file Bach Midi yang diambil dari sini dan dari sini .



Model 200 zaman yang didasarkan pada satu-satunya file Midi milik Balakirev yang diambil di sini .



Model dengan 200 era, berdasarkan komposisi Debussy .



Model era 175 berdasarkan komposisi Mozart.



Model dengan 100 era berdasarkan komposisi Schubert .



Model era 200 berdasarkan komposisi Schumann .



Model era 200 berdasarkan komposisi Tchaikovsky .



Model dengan 175 era berdasarkan lagu daerah.



Model dengan 100 era berdasarkan lagu pengantar tidur.



Model 100-era berdasarkan musik pernikahan.



Model 200 zaman yang didasarkan pada file midi saya sendiri yang diambil dari soundtrack video YouTube saya. Mungkin sedikit dilatih ulang karena pada dasarnya menghasilkan salinan file midi pendek satu dan dua langkah saya.

Skor


Setelah Anda mendapatkan file midi, Anda dapat menggunakan alat online seperti SolMiRe untuk mengonversinya menjadi skor. Di bawah ini adalah skor file 200-midi Softology midi yang disajikan di atas.



Di mana saya bisa menguji komposer


Komposer LSTM sekarang termasuk dalam Visions of Chaos .


Pilih gaya dari daftar tarik-turun dan klik Tulis. Jika Anda telah menginstal Python dan TensorFlow minimum yang diperlukan (lihat instruksi di sini ), maka dalam beberapa detik (jika Anda memiliki GPU cepat), Anda akan menerima file midi baru yang dibuat mesin yang dapat Anda dengarkan dan gunakan untuk tujuan lain. Tidak ada hak cipta, tidak ada royalti. Jika Anda tidak menyukai hasilnya, Anda dapat mengklik Menulis lagi dan setelah beberapa detik komposisi baru akan siap.

Hasilnya belum dapat dianggap sebagai komposisi lengkap, tetapi mereka memiliki urutan kecil yang menarik dari catatan yang akan saya gunakan untuk membuat musik di masa depan. Dalam hal ini, komposer LSTM dapat menjadi sumber inspirasi yang baik untuk komposisi baru.

Sumber python


Di bawah ini adalah kode skrip Python yang saya gunakan untuk pelatihan dan perkiraan LSTM. Agar skrip ini berfungsi, tidak perlu menginstal Visions of Chaos, dan mempelajari dan menghasilkan midi akan bekerja dari baris perintah.

Berikut ini script pelatihan 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() 

Dan inilah skrip generasi 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() 

Ukuran file model


Kerugian dari memasukkan jaringan saraf dalam Visions of Chaos adalah ukuran file. Jika generasi model lebih cepat, maka saya hanya akan menambahkan tombol sehingga pengguna akhir dapat melatih model sendiri. Tetapi karena beberapa sesi pelatihan untuk banyak model dapat memakan waktu beberapa hari, ini tidak terlalu praktis. Tampak bagi saya bahwa lebih baik melakukan semua pelatihan dan menguji diri sendiri, dan hanya menambahkan model kerja terbaik. Ini juga berarti bahwa pengguna akhir hanya perlu menekan tombol, dan model yang terlatih akan membuat komposisi musik.

Masing-masing model memiliki ukuran 22 megabyte. Dalam kondisi Internet modern, ini tidak begitu banyak, tetapi selama bertahun-tahun perkembangan, Visions of Chaos telah tumbuh dalam ukuran secara bertahap, dan hanya baru-baru ini tiba-tiba meningkat dari 70 menjadi 91 MB (karena model pencarian otomat seluler). Oleh karena itu, saya sejauh ini hanya menambahkan satu model ke installer Visions of Chaos utama. Untuk pengguna yang menginginkan lebih, saya memposting tautan ke model 1 GB lainnya. Mereka juga dapat menggunakan skrip di atas untuk membuat model mereka sendiri berdasarkan file midi mereka.

Apa selanjutnya


Pada tahap ini, komposer LSTM adalah contoh paling sederhana menggunakan jaringan saraf untuk menggubah musik.

Saya telah menemukan komposer musik lain di jaringan saraf yang akan saya coba di masa depan, sehingga Anda dapat berharap bahwa dalam Visions of Chaos akan ada kemungkinan baru untuk membuat musik secara otomatis.

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


All Articles