За последние годы наблюдается экспоненциальный рост искусственного интеллекта, благодаря большему количеству данных и высокой вычислительной мощности, ИИ может работать лучше, чем люди, в распознавании изображений. ВАУ звучит потрясающе, правда, нет, пока нет. Благодаря Яну Гудфеллоу и его замечательной статье о GAN (Генеративные состязательные сети) ИИ также может воссоздавать картины, картинки, практически все (не жизнь * пока *), и это огромный подвиг. Мы можем продолжать и перечислять достижения ИИ за эти годы, но мы здесь не для этого.

Итак, возвращаясь к основной теме создания рэп-лирики, два моих любимых живых рэп-MC - это Эминем и Канье Уэст. Звучит странно, правда, но я имею в виду, что я так катаюсь. К сожалению, кроме песни Forever, между этими двумя рэперами нет никакой другой формы сотрудничества, поэтому я взял на себя задачу создать модель машинного обучения, которая может выплевывать такты, как эти два парня. Что ж, с этой моделью вы можете в значительной степени кормить ее текстом от кого угодно, и после нескольких тренировок и настройки гиперпараметров вы получите модель, которая будет звучать как кто бы то ни было.

Этап 1. Сопоставление данных

Итак, мы начинаем наше путешествие, благодаря новому инструменту поиска Google по набору данных, мы можем искать тексты песен kanye west и eminem, так что мы просто берем их с kaggle без каких-либо проблем.





Этап 2: Настоящий материал

После получения текстов их легче объединить в один текстовый файл.

К вашему сведению: наша модель будет обучаться на уровне персонажа, поэтому нет необходимости в вложениях слов, таких как перчатка. Отчасти это потому, что большинство рэп-текстов на самом деле не совсем английские слова (без обид, просто скажу… ..) :)

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

from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.layers import LSTM
from keras.optimizers import Adam
from keras.callbacks import LambdaCallback, ModelCheckpoint
import numpy as np
import random
import sys
import io
import os
def load_data(file):
    data = open(file, 'r', encoding='utf-8')
    text = data.read()
    data.close()
    return text

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

train_data = "kanye-shady.txt"
text = load_data(train_data)
chars = sorted(list(set(text)))
char_ind = dict((c, i) for i, c in enumerate(chars))
ind_char = dict((i, c) for i, c in enumerate(chars))

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

Теперь у нас есть четкое представление о том, чего мы пытаемся достичь. Затем нужно преобразовать текст нашего текста в целочисленную форму. Есть два способа сделать это: использовать простую целочисленную кодировку, которая просто представляет каждый символ числом, или использовать нашу старую добрую горячую кодировку, в которой символ представлен разреженным массивом нулей, за исключением позиции, которая соответствует индексу символа. который будет представлен как 1.

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

maxlen = 60
step = 3
sentences = []
next_chars = []
for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        x[i, t, char_ind[char]] = 1
    y[i, char_ind[next_chars[i]]] = 1

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

model = Sequential()
model.add(LSTM(128, input_shape=(maxlen, len(chars))))
model.add(Dropout(0.2))
model.add(LSTM(128, input_shape=(maxlen, len(chars))))
model.add(Dropout(0.2))
model.add(Dense(len(chars)))
model.add(Activation('softmax'))
optimizer = Adam(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

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

def sample(preds, diversity=1.0):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / diversity
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)
def on_epoch_end_func(epoch, logs):
    print('----- Generating text after Epoch: %d' % epoch)
    start_index = random.randint(0, len(text) - maxlen - 1)
        for diversity in [0.2, 0.5, 1.0, 1.2, 1.5]:
            print('----- diversity:', diversity)
        generated = ''
        sentence = text[start_index: start_index + maxlen]
        generated += sentence
        print('----- Generating with seed: "' + sentence + '"')
        sys.stdout.write(generated)
        for i in range(350):
            x_pred = np.zeros((1, maxlen, len(chars)))
            for t, char in enumerate(sentence):
                x_pred[0, t, char_ind[char]] = 1.
            preds = model.predict(x_pred, verbose=0)[0]
            next_index = sample(preds, diversity)
            next_char = ind_char[next_index]
            generated += next_char
            sentence = sentence[1:] + next_char
            sys.stdout.write(next_char)
            sys.stdout.flush()

Вторая функция on_epoch_end_func берет случайную выборку начального числа по всему набору данных и передает ее в качестве входных данных в модель, которая, в свою очередь, предсказывает следующий символ, добавляет его во входные данные и принимает предсказание нового входа, которым является previous_input [1:] + next_character - этот процесс повторяется снова и снова в зависимости от того, сколько символов на эпоху мы хотим предсказать (здесь я выбрал 350)

Очень интересный параметр, на который мы должны обратить внимание, - это разнообразие, в основном разнообразие - это параметр, который показывает, насколько креативными мы хотим, чтобы наша модель была во время выборки. Выбор меньшего разнообразия соответствует нашей модели, которая делает безопасные предположения, но затем мы начинаем снова и снова видеть такие символы, как «the», «and», «is». С другой стороны, выборка с гораздо большим разнообразием говорит о том, что наша модель должна быть более креативной. Обратной стороной этого является то, что наша модель начинает генерировать слова, которых не существует. * Не говорю, что это плохо, у нас были такие рэперы, как XXXTentacion (* RIP *), создающие слова… ..LOL! *

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

generate_text = LambdaCallback(on_epoch_end=on_epoch_end_func)
filepath = "lyrics-weights.hdf5"
checkpoint = ModelCheckpoint(filepath, 
                             monitor='loss', 
                             verbose=1, 
                             save_best_only=True, 
                             mode='min')
model.fit(x, y,
          batch_size=128,
          epochs=180,
          verbose=2,
          callbacks=[generate_lyrics, checkpoint])

Теперь мы садимся и пьем кофе, а вы смотрите на свою модель поезда и выплевываете такие бары, как Slim Shady и Kanye West. Через пару эпох ваш результат будет примерно таким

Epoch 1/25

----- Generating text after Epoch: 0
----- diversity: 0.2
----- Generating with seed: "plea that I'm copping
I'm just willing t"
plea that I'm copping
I'm just willing to see the fuck it and the fuck a back to the start to be the way to see the couch a fuck it the this face to the back and the whought the couch the couse the fuck the don't be the back and sour the baby with the back to see the sour the back to see the back and sore shit the fuck the bout the back the short your sour the back the strick of the back the fuck a back to the thing of the back to be th
----- diversity: 0.5
----- Generating with seed: "plea that I'm copping
I'm just willing t"
plea that I'm copping
I'm just willing to strick it till got start it the diggin here from the boys for me (I all the way me wores in the resure me we go that some more just feel the sure me I want for the whole me to crack going to said your sours to the day the way this the free I can't know you just want of your sick could the fuck a man
Can make the real fee it wit do down the sore of my party work and dreama be was bout your dick t
----- diversity: 1.0
----- Generating with seed: "plea that I'm copping
I'm just willing t"
plea that I'm copping
I'm just willing to sain
Able I'm Strick
Cut me ro Dever's that half the mighty twuntrituthered a brical s-imu)
SHe sidy ecruck to Spack's your mashet's
remp
It clust four shit be do to your peny summ alle in mysay sgamet elies bady frees 'cause I little shuns cimbers on rappries
Still other
Then your higgle Bestably soir, he's bliw a did nevel coss for the, big it get dick
My mor if szy missice
Mashe' faned, I be 
----- diversity: 1.2
----- Generating with seed: "plea that I'm copping
I'm just willing t"
plea that I'm copping
I'm just willing to fuck waz me and alrick,fstapHety bruchplen I'm gros or yeepen achur
(up Rissirt)]
I'm Juke I'm everythea world zeat shore biprelsy I'm armred, Never sell. Busee mato it
Aint overs
'Caus, and ginls didap ,Tatim] 'Cau-hTstems crazy forhelf juke somes ""I ne-gong the carsinaioringlop, I justene, 'pouldiotr
Our

Yeah
Eyen
I cifts fig life mysore wornNa

I think
And that thingsI go saidnadby
Tead bep
----- diversity: 1.5
----- Generating with seed: "plea that I'm copping
I'm just willing t"
plea that I'm copping
I'm just willing tetrot ig
You're cat let indovin’ up
Thep revuc?Tom1 thrre
I'll thatple part
, DedJupp your
Fuck up
Hix of usejseetleys,Hoypo&td;le Breyin'mbracgly? 3 wit too's the hud XX;bâ€* Chme tike mago7 comorei&gsoLdaga

Slup
do to – Uc,ScRWeasgap-dowente?N'` , /p225>]22011
38,)
SharyGic: Get Towiverbizzint drarder, find aphoy-Lh!Edr

I'll musnjhis the zeah my caDs  usMob
My braits plueas
We lub yoursnig?

Да, мы видим много слов на F после первой эпохи, это рэп, чего вы ожидали :)

Через пару эпох наша модель начинает давать реальные полосы.

I know that I’m not the only one that I don’t know what I don’t know

So much head, I woke up to Slim Shady start the world Oh I was all this go
I’ll be a black a couples, they know
I been to weed down all the thing in the fake, but I gotta tookers the storn these

Несмотря на то, что это была не та песня, которую я ожидал от Shady и Kanye, я уверен, что настройка гиперпараметров и игра с разнообразием дадут что-то намного лучшее.

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

PS: Я действительно хочу сделать один для Кендрика Ламара, JCole, 2 Pac и Notorious BIG, пожалуйста, напишите мне в Twitter или Github, если хотите. хотел бы присоединиться ко мне. * Намасте :) *