machinelearningmastery.ru

Машинное обучение, нейронные сети, искусственный интеллект
Header decor

Home

Как генерировать музыку с помощью нейронной сети LSTM в Керасе

Дата публикации Dec 7, 2017

Введение

Нейронные сети используются для улучшения всех аспектов нашей жизни. Они предоставляют нам рекомендации по товарам, которые мы хотим приобрести,генерировать текст на основе стиля автораи даже может быть использован дляизменить художественный стиль изображения, В последние годы было несколько учебных пособий о том, как генерировать текст с помощью нейронных сетей, но не было учебных пособий о том, как создавать музыку. В этой статье мы рассмотрим, как создавать музыку, используя рекуррентную нейронную сеть в Python, используя библиотеку Keras.

Для нетерпеливых, есть ссылка на репозиторий Github в конце урока.

Задний план

Прежде чем мы углубимся в детали реализации, есть некоторая терминология, которую мы должны уточнить.

Рекуррентные нейронные сети (RNN)

Рекуррентная нейронная сеть - это класс искусственных нейронных сетей, которые используют последовательную информацию. Их называют рекуррентными, потому что они выполняют одну и ту же функцию для каждого элемента последовательности, а результат зависит от предыдущих вычислений. Принимая во внимание, что выходные данные не зависят от предыдущих вычислений в традиционных нейронных сетях.

В этом уроке мы будем использоватьДолгосрочная кратковременная память (LSTM)сеть. Они представляют собой тип рекуррентной нейронной сети, которая может эффективно обучаться посредством градиентного спуска. Используя механизм стробирования, LSTM могут распознавать и кодировать долгосрочные шаблоны. LSTM чрезвычайно полезны для решения проблем, когда сеть должна запоминать информацию в течение длительного периода времени, как в случае с музыкой и генерацией текста.

Music21

Music21является набором инструментов Python, используемым для автоматизированной музыковедения Это позволяет нам преподавать основы теории музыки, генерировать музыкальные примеры и изучать музыку. Инструментарий предоставляет простой интерфейс для получения музыкальной нотации файлов MIDI. Кроме того, он позволяет нам создавать объекты Note и Chord, чтобы мы могли легко создавать собственные MIDI-файлы.

В этом уроке мы будем использовать Music21 для извлечения содержимого нашего набора данных, а также для получения выходных данных нейронной сети и перевода их в музыкальную нотацию.

Keras

KerasAPI-интерфейс нейронных сетей высокого уровня, упрощающий взаимодействие сTensorflow, Он был разработан с целью обеспечения быстрого экспериментирования.

В этом уроке мы будем использовать библиотеку Keras для создания и обучения модели LSTM. После того, как модель обучена, мы будем использовать ее для создания нотной записи для нашей музыки.

Подготовка

В этом разделе мы рассмотрим, как мы собирали данные для нашей модели, как мы ее подготовили, чтобы они могли использоваться в модели LSTM, и архитектуру нашей модели.

Данные

В нашемGithub хранилищемы использовали фортепианную музыку, в основном состоящую из музыки из саундтреков Final Fantasy. Мы выбрали музыку Final Fantasy из-за очень четких и красивых мелодий, которые есть у большинства произведений, и из-за огромного количества произведений. Но любой набор MIDI-файлов, состоящий из одного инструмента, будет работать для наших целей.

Первым шагом к реализации нейронной сети является изучение данных, с которыми мы будем работать.

Ниже мы видим отрывок из миди-файла, который был прочитан с помощью Music21:

...
<music21.note.Note F>
<music21.chord.Chord A2 E3>
<music21.chord.Chord A2 E3>
<music21.note.Note E>
<music21.chord.Chord B-2 F3>
<music21.note.Note F>
<music21.note.Note G>
<music21.note.Note D>
<music21.chord.Chord B-2 F3>
<music21.note.Note F>
<music21.chord.Chord B-2 F3>
<music21.note.Note E>
<music21.chord.Chord B-2 F3>
<music21.note.Note D>
<music21.chord.Chord B-2 F3>
<music21.note.Note E>
<music21.chord.Chord A2 E3>
...

Данные делятся на два типа объектов:Заметкас иАккордs. Объекты заметок содержат информацию оподача,октава, а такжесмещениепримечания.

  • Подачаотносится к частоте звука или к его высокой или низкой частоте и обозначается буквами [A, B, C, D, E, F, G], где A - самая высокая, а G - самая низкая.
  • октаваотносится к тому, какой набор смол вы используете на фортепиано.
  • офсетотносится к тому, где записка находится в куске.

А объекты Chord - это, по сути, контейнер для набора нот, которые воспроизводятся одновременно.

Теперь мы видим, что для точной генерации музыки наша нейронная сеть должна быть в состоянии предсказать, какая нота или аккорд будут следующими. Это означает, что наш массив предсказаний должен будет содержать все ноты и аккорды, которые мы встречаем в нашем обучающем наборе. В обучающем наборе на странице Github общее количество различных нот и аккордов составляло 352. Это кажется большим количеством возможных выходных прогнозов для сети, которую нужно обработать, но сеть LSTM может легко справиться с этим.

Далее нам нужно беспокоиться о том, куда мы хотим поместить заметки. Как заметили большинство людей, которые слушали музыку, ноты обычно имеют различные интервалы между ними. У вас может быть много нот в быстрой последовательности, а затем период отдыха, в течение которого короткая нота не воспроизводится в течение короткого времени.

Ниже у нас есть еще одна выдержка из миди-файла, который был прочитан с помощью Music21, только на этот раз мы добавили смещение объекта позади него. Это позволяет нам видеть интервал между каждой нотой и аккордом.

...
<music21.note.Note B> 72.0
<music21.chord.Chord E3 A3> 72.0
<music21.note.Note A> 72.5
<music21.chord.Chord E3 A3> 72.5
<music21.note.Note E> 73.0
<music21.chord.Chord E3 A3> 73.0
<music21.chord.Chord E3 A3> 73.5
<music21.note.Note E-> 74.0
<music21.chord.Chord F3 A3> 74.0
<music21.chord.Chord F3 A3> 74.5
<music21.chord.Chord F3 A3> 75.0
<music21.chord.Chord F3 A3> 75.5
<music21.chord.Chord E3 A3> 76.0
<music21.chord.Chord E3 A3> 76.5
<music21.chord.Chord E3 A3> 77.0
<music21.chord.Chord E3 A3> 77.5
<music21.chord.Chord F3 A3> 78.0
<music21.chord.Chord F3 A3> 78.5
<music21.chord.Chord F3 A3> 79.0
...

Как видно из этого отрывка и большей части набора данных, наиболее распространенный интервал между примечаниями в файлах midi составляет 0,5. Следовательно, мы можем упростить данные и модель, игнорируя различные смещения в списке возможных выходных данных. Это не повлияет на мелодии музыки, создаваемой сетью, слишком сильно. Поэтому мы проигнорируем смещение в этом уроке и оставим наш список возможных выходных данных на 352.

Подготовка данных

Теперь, когда мы проверили данные и определили, что функциями, которые мы хотим использовать, являются ноты и аккорды как вход и выход нашей сети LSTM, пришло время подготовить данные для сети.

Сначала мы загрузим данные в массив, как видно из фрагмента кода ниже:

from music21 import converter, instrument, note, chordnotes = []for file in glob.glob("midi_songs/*.mid"):
midi = converter.parse(file)
notes_to_parse = None parts = instrument.partitionByInstrument(midi) if parts: # file has instrument parts
notes_to_parse = parts.parts[0].recurse()
else: # 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))

Мы начинаем с загрузки каждого файла в объект потока Music21, используяconverter.parse (файл)функция,Используя этот объект потока, мы получаем список всех нот и аккордов в файле. Мы добавляем высоту звука каждого объекта заметки, используя строковую нотацию, поскольку наиболее значимые части заметки могут быть воссозданы с использованием строковой записи высоты тона. И мы добавляем каждый аккорд, кодируя идентификатор каждой ноты в аккорде вместе в одну строку, где каждая нота разделяется точкой. Эти кодировки позволяют нам легко декодировать выходные данные, генерируемые сетью, в правильные ноты и аккорды.

Теперь, когда мы поместили все ноты и аккорды в последовательный список, мы можем создать последовательности, которые будут служить входом нашей сети.

Рисунок 1: При преобразовании из категориальных в числовые данные данные преобразуются в целочисленные индексы, представляющие, где категория расположена в наборе различных значений. Например. яблоко - это первое отличающееся значение, поэтому оно отображается на 0, оранжевый - на второе, поэтому оно отображается на 1, ананас - на третье, поэтому оно отображается на 2, и так далее.

Во-первых, мы создадим функцию отображения, чтобы отобразить строковые категориальные данные в целочисленные числовые данные. Это сделано потому, что нейронная сеть работает намного лучше с целочисленными числовыми данными, чем с категориальными данными на основе строк. Пример преобразования категорий в числовые можно увидеть на рисунке 1.

Далее мы должны создать входные последовательности для сети и их соответствующие выходы. Выходом для каждой входной последовательности будет первая нота или аккорд, которые идут после последовательности нот во входной последовательности в нашем списке нот.

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]
sequence_out = notes[i + sequence_length]
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 LSTM 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)

В нашем примере кода мы указали длину каждой последовательности в 100 нот / аккордов. Это означает, что для прогнозирования следующей заметки в последовательности в сети есть предыдущие 100 заметок, которые помогут сделать прогноз. Я настоятельно рекомендую тренировать сеть, используя разные длины последовательностей, чтобы увидеть влияние, которое различные длины последовательностей могут оказать на музыку, генерируемую сетью.

Последний шаг в подготовке данных для сети - нормализация ввода игорячее кодирование выхода,

модель

Наконец мы приступаем к проектированию архитектуры модели. В нашей модели мы используем четыре разных типа слоев:

LSTM слоислой Recurrent Neural Net, который принимает последовательность в качестве входных данных и может возвращать либо последовательности (return_sequence = True), либо матрицу.

Выпадающие слоиэто метод регуляризации, который состоит в установке доли входных единиц в 0 при каждом обновлении во время обучения, чтобы предотвратить переоснащение. Доля определяется параметром, используемым со слоем.

Плотные слоиилиполностью связанные слоиявляется полностью подключенным уровнем нейронной сети, где каждый входной узел связан с каждым выходным узлом.

Слой активацииопределяет, какую функцию активации будет использовать наша нейронная сеть для расчета выходных данных узла.

model = Sequential()
model.add(LSTM(
256,
input_shape=(network_input.shape[1], network_input.shape[2]),
return_sequences=True
))
model.add(Dropout(0.3))
model.add(LSTM(512, return_sequences=True))
model.add(Dropout(0.3))
model.add(LSTM(256))
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')

Теперь, когда у нас есть некоторая информация о различных слоях, которые мы будем использовать, пришло время добавить их в модель сети.

Для каждого уровня LSTM, Dense и Activation первый параметр - это количество узлов, которое должен иметь уровень. Для слоя Dropout первый параметр - это доля входных единиц, которые должны быть отброшены во время обучения.

Для первого слоя мы должны предоставить уникальный параметр под названиемinput_shape.Назначение параметра - информировать сеть о форме данных, которые она будет обучать.

Последний слой всегда должен содержать одинаковое количество узлов, равное количеству различных выходов, которые имеет наша система. Это гарантирует, что выходные данные сети будут отображаться непосредственно в наши классы.

Для этого урока мы будем использовать простую сеть, состоящую из трех слоев LSTM, трех слоев Dropout, двух плотных слоев и одного слоя активации. Я бы порекомендовал поэкспериментировать со структурой сети, чтобы посмотреть, сможете ли вы улучшить качество прогнозов.

Для расчета потерь для каждой итерации обучения мы будем использоватькатегорическая перекрестная энтропияпоскольку каждый из наших выходных данных принадлежит только одному классу, и у нас есть более двух классов для работы. И для оптимизации нашей сети мы будем использовать оптимизатор RMSprop, так как он обычно является очень хорошим выбором для повторяющихся нейронных сетей.

filepath = "weights-improvement-{epoch:02d}-{loss:.4f}-bigger.hdf5"    checkpoint = ModelCheckpoint(
filepath, monitor='loss',
verbose=0,
save_best_only=True,
mode='min'
)
callbacks_list = [checkpoint] model.fit(network_input, network_output, epochs=200, batch_size=64, callbacks=callbacks_list)

Как только мы определили архитектуру нашей сети, пришло время начать обучение.model.fit ()Функция в Керасе используется для обучения сети. Первый параметр - это список входных последовательностей, которые мы подготовили ранее, а второй - список их соответствующих выходных данных. В нашем уроке мы собираемся обучить сеть на 200 эпох (итераций), причем каждый пакет, который распространяется по сети, содержит 64 выборки.

Чтобы быть уверенным, что мы сможем остановить обучение в любой момент времени, не теряя при этом всю нашу тяжелую работу, мы будем использовать модельные контрольные точки. Контрольные точки модели позволяют нам сохранять веса сетевых узлов в файле после каждой эпохи. Это позволяет нам прекратить работу нейронной сети, как только мы будем удовлетворены величиной потерь, не беспокоясь о потере веса. В противном случае нам пришлось бы подождать, пока сеть не завершит прохождение всех 200 эпох, прежде чем мы сможем сохранить весовые коэффициенты в файл.

Генерация музыки

Теперь, когда мы закончили обучение сети, пришло время повеселиться с сетью, которую мы потратили на обучение часов.

Чтобы использовать нейронную сеть для генерации музыки, вам нужно перевести ее в то же состояние, что и раньше. Для простоты мы будем повторно использовать код из учебного раздела для подготовки данных и настройки модели сети так же, как и раньше. За исключением того, что вместо обучения сети мы загружаем в модель веса, которые мы сохранили в разделе обучения.

model = Sequential()
model.add(LSTM(
512,
input_shape=(network_input.shape[1], network_input.shape[2]),
return_sequences=True
))
model.add(Dropout(0.3))
model.add(LSTM(512, return_sequences=True))
model.add(Dropout(0.3))
model.add(LSTM(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')# Load the weights to each node
model.load_weights('weights.hdf5')

Теперь мы можем использовать обученную модель, чтобы начать создавать заметки.

Поскольку в нашем распоряжении имеется полный список последовательностей заметок, мы выберем случайный индекс в списке в качестве отправной точки, это позволяет нам перезапускать код генерации без каких-либо изменений и каждый раз получать разные результаты. Однако, если вы хотите контролировать начальную точку, просто замените случайную функцию аргументом командной строки.

Здесь нам также необходимо создать функцию отображения для декодирования выходных данных сети. Эта функция будет преобразовывать числовые данные в категориальные (от целых чисел до примечаний).

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

Мы решили сгенерировать 500 нот, используя сеть, так как это примерно две минуты музыки и дает сети достаточно места для создания мелодии. Для каждой заметки, которую мы хотим сгенерировать, мы должны отправить последовательность в сеть. Первая последовательность, которую мы представляем - это последовательность примечаний в начальном индексе Для каждой последующей последовательности, которую мы используем в качестве входных данных, мы удалим первую ноту последовательности и вставим выходные данные предыдущей итерации в конец последовательности, как показано на рисунке 2.

Рисунок 2: Первая входная последовательность ABCDE. Вывод, который мы получаем от передачи этого в сеть, равен F. Для следующей итерации мы удаляем A из последовательности и добавляем F к ней. Затем мы повторяем процесс.

Чтобы определить наиболее вероятный прогноз по выходным данным из сети, мы извлекаем индекс наибольшего значения. Значение по индексуИксв выходном массиве соответствуют вероятности того, чтоИксэто следующая заметка. Рисунок 3 помогает объяснить это.

Рисунок 3: Здесь мы видим отображение между выходным прогнозом из сети и классами. Как мы видим, наибольшая вероятность состоит в том, что следующим значением должно быть D, поэтому мы выбираем D в качестве наиболее вероятного класса.

Затем мы собираем все выходные данные из сети в один массив.

Теперь, когда у нас есть все закодированные представления нот и аккордов в массиве, мы можем начать их декодирование и создание массива объектов Note и Chord.

Сначала мы должны определить, является ли вывод, который мы декодируем, нотой или аккордом.

Если шаблонАккорд, мы должны разбить строку на массив заметок. Затем мы перебираем строковое представление каждой заметки и создаем объект Note для каждой из них. Затем мы можем создать объект Chord, содержащий каждую из этих заметок.

Если шаблонЗаметкаМы создаем объект Note, используя строковое представление высоты тона, содержащейся в шаблоне.

В конце каждой итерации мы увеличиваем смещение на 0,5 (как мы решили в предыдущем разделе) и добавляем созданный объект Note / Chord в список.

offset = 0
output_notes = []# create note and chord objects based on the values generated by the modelfor 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

Теперь, когда у нас есть список нот и аккордов, сгенерированных сетью, мы можем создать объект Music21 Stream, используя этот список в качестве параметра. Затем, наконец, для создания файла MIDI, который будет содержать музыку, генерируемую сетью, мы используемзаписыватьфункция в наборе инструментов Music21 для записи потока в файл.

midi_stream = stream.Stream(output_notes)midi_stream.write('midi', fp='test_output.mid')

Полученные результаты

Теперь пришло время удивляться результатам. На рисунке 4 представлено нотное представление музыки, сгенерированной с использованием сети LSTM. С первого взгляда мы видим, что в этом есть какая-то структура. Это особенно очевидно в третьей до последней строке на второй странице.

Люди, которые хорошо разбираются в музыке и умеют читать ноты, видят, что на листе есть какие-то странные ноты Это результат того, что нейронная сеть не способна создавать идеальные мелодии. В нашей текущей реализации всегда будет несколько ложных замечаний, и для достижения лучших результатов нам понадобится большая сеть.

Рисунок 4: Пример ноты, сгенерированной сетью LSTM

Результаты этой относительно мелкой сети по-прежнему впечатляют, что можно услышать из примера музыки в Embed 1. Для тех, кто интересуется, ноты на рисунке 4 представляют нотную запись NeuralNet Music 5.

Встроить 1: примеры, сгенерированные сетью

Будущая работа

Мы достигли замечательных результатов и прекрасных мелодий, используя простую сеть LSTM и 352 класса. Тем не менее, есть области, которые можно улучшить.

Во-первых, реализация, которую мы имеем на данный момент, не поддерживает различную продолжительность заметок и различные смещения между заметками. Чтобы достичь этого, мы могли бы добавить больше классов для каждой другой продолжительности и добавить классы отдыха, которые представляют период отдыха между нотами.

Чтобы достичь удовлетворительных результатов с добавлением большего количества классов, нам также необходимо увеличить глубину сети LSTM, что потребует значительно более мощный компьютер. Ноутбук, который я использую дома, занял около двадцати часов, чтобы обучить сеть, как сейчас.

Во-вторых, добавьте начала и окончания к частям. Поскольку сеть в настоящее время не существует различий между частями, то есть сеть не знает, где заканчивается одна часть и начинается другая. Это позволило бы сети генерировать кусок от начала до конца вместо того, чтобы резко завершить сгенерированный кусок, как это происходит сейчас.

В-третьих, добавьте метод для обработки неизвестных заметок. Как и сейчас, сеть перешла бы в состояние сбоя, если встретит заметку, которую она не знает. Возможный метод решения этой проблемы - найти ноту или аккорд, которые наиболее похожи на неизвестную ноту.

Наконец, добавление большего количества инструментов в набор данных. Как и сейчас, сеть поддерживает только части, которые имеют только один инструмент. Было бы интересно посмотреть, можно ли его расширить для поддержки всего оркестра.

Вывод

В этом уроке мы показали, как создать нейронную сеть LSTM для генерации музыки. Хотя результаты могут быть не идеальными, они, тем не менее, впечатляют и показывают нам, что нейронные сети могут создавать музыку и потенциально могут использоваться для создания более сложных музыкальных произведений.

Проверьте репозиторий Github для учебника здесь

Оригинальная статья

Footer decor

© machinelearningmastery.ru | Ссылки на оригиналы и авторов сохранены. | map