Сегодня попробуем создать простую музыку при помощи сетей LSTM.


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


  • Python3
  • BASH
  • jupyter-notebook.

Не буду одобрять комментарии, в которых есть суть только:


  • причинить досаду автору, примерно про опечатки(я не являюсь носителем русского языка).
  • несущественные замечания и комментарии.
  • все что не касается сути стати.

Входные данные


Мы используем входные данные в формате ABC
Примерные строки:


[V: S] (BA) !p!G2 |z AGA|(FG) A2|
w: ple -na, Do-mi-nus te -cum,
[V: A] F2       E2|z FEC|(DE) F2 |
w: ple-na, Do-mi-nus te -cum,
[V: T] (dc)     c2|z ccA|(Ac) c2 |
w: ple -na, Do-mi-nus te -cum,
[V: B] (B,,F,) C,2|z F,C,F,|(D,C,) F,2 |
w: ple -na, Do-mi-nus te -cum,

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


Чтение файла


Для вашего удобства, используйте jupyter notebook .


import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf

with open('my_song.abc', 'r') as f:
    text = f.read()

Прослушивание песни


Чтобы прослушать песню, мы должны установить в нашу среду несколько дополнительных инструментов


!apt-get install -y -qq abcmidi timidity

Сохраняем выбранную песню в файл:


#этот шаг зависит от входных данных, тут нужен гибкий подход
song = text.split('\n\n')
with open('my_song.abc', "w") as f:
    f.write(song)

Мы конвертируем в файл mid, а затем wav.


!abc2midi "my_song.abc" -o "my_song.mid" && timidity "my_song.mid" -Ow "my_song.wav"

Результат


from IPython.display import Audio
Audio('my_song.wav')
#ссылка
#https://github.com/fuwiak/Habr/blob/master/my_song.wav

Время на LSTM


Результат my_song.wav оказывается неплохим, сейчас попробуем сделать свой вариант при помощи LSTM.


Создание обучающей выборки


#уникальные символы, найденные в песнях.
vocab = set(text)

# словарь: ключ=символ, значение=индекс, указав символ, мы получаем его индекс
char_to_index = {char_ :ind for ind, char_  in enumerate (vocab)}
ind_to_char = np.array(vocab)
text_as_int = np.array([char_to_index[c] for c in text])

#'X:1\nT:dfkjds ' ----- > [49 22 13  0 45 22 26 67 60 79 56 69 59]

Генерация последовательности


Создаются обучающие последовательности


  • input: строка из 100 символов
  • target: строка из 100 символов, но сдвинутая на 1.

Нашей модели будет поручено научиться прогнозировать следующий знак на основе 100 предыдущих. Это будет модель RNN версии "many to many", которая на самом деле будет прогнозировать один следующий символ, но в процессе обучения ошибка будет учитываться по всей последовательности (100 предсказаний).



seq_length = 100
step = 10

sequences = np.array([text_as_int[i:i+seq_length+1] for i in range(0, len(text_as_int)-seq_length-1,step)])
input_text = np.array([seq[:-1] for seq in sequences])

target_text = np.array([seq[1:] for seq in sequences])

LSTM


from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, LSTM, Dense, Embedding

vocab_size = len(vocab)

#new value
embedding_dim = 256*2
rnn_units = 1024*2

x = Input(shape=(seq_length,))
e = Embedding(vocab_size, embedding_dim)(x)
l = LSTM(rnn_units, return_sequences=True)(e)
d = Dense(vocab_size, activation='softmax')(l)
model = Model(inputs=x, outputs=d)
model.summary()

Обучение сети


from tensorflow.keras.optimizers import Adam
model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy')

EP=5
BS = 128

hist = model.fit(input_text, target_text, batch_size=BS, epochs=EP)

Создание музыки из модели


def generate_text(model, start_string, generation_length=100):

  input_eval = np.array([char_to_index[s] for s in start_string])
  x = np.zeros((1, seq_length))
  x[0,-len(input_eval):] = input_eval[:]
  text_generated = []

  model.reset_states()
  for i in range(generation_length):
      predictions = model.predict(x)[0,-1] 
      predictions = predictions.astype(np.float64)
      predictions = predictions/np.sum(predictions)    
      predicted_id = np.argmax(np.random.multinomial(1, predictions))
      x[0,:-1] = x[0,1:]
      x[0,-1] = predicted_id
      text_generated.append([predicted_id]) 

  return (start_string + ''.join(text_generated))

new_song = generate_text(model, "X:", generation_length=500)

Наш результат


with open('new_song.abc', "w") as f:
    f.write(new_song)

!abc2midi "new_song.abc" -o "new_song.mid" && timidity "new_song.mid" -Ow "new_song.wav"


Audio('new_song.wav')
#https://github.com/fuwiak/Habr/blob/master/new_song.wav

Итоги


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


Ссылки: