Недавно я построил модель Word Prediction и использовал эту модель для создания текстов песен. Данные, на которых я обучал свою модель, содержали тексты песен Тейлор Свифт. Мой ИИ выглядит подавленным, а может, это просто горе. Вот каковы были результаты:

Теперь поговорим о том, как я это сделал - о подходе и инструментах. Давай сделаем это!

Чтение данных

Начнем с простого вопроса. Как читать данные. Это довольно просто

data = open('../filename.txt').read()

Ваши данные могут выглядеть примерно так

'He said the way my blue eyes shined\r\nPut those Georgia.......'

Обратите внимание на "\ r \ n"? Они представляют следующую строку или конец предложения. Иногда, в зависимости от данных и того, как вы их читаете, это может быть просто «\ r» или «\ n». Мы должны от них избавиться.

# Splitting the string into sentences, while converting whole data into lowercase.
corpus = data.lower().split("\r\n")
# To make sure no sentence appears twice in our corpus, we use set. Otherwise, it will make the model biased.
corpus = list(set(corpus))

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

Организация данных

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

Токены: для этой цели мы будем использовать Tokenizer - очень полезный инструмент, поставляемый с Keras.

tokenizer = Tokenizer()
tokenizer.fit_on_texts(corpus)
total_words = len(tokenizer.word_index) + 1

Когда мы подбираем токенизатор корпуса, мы сохраняем данные, относящиеся к корпусу, в самом токенизаторе.

Если вы запустите tokenizer.word_index, он вернет примерно следующее:

{'you': 1,  'i': 2,  'and': 3,  'the': 4,  'to': 5,  'me': 6,  'a': 7,  'it': 8,  'in': 9,  'my': 10,  'your': 11,.......}

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

Общее количество слов равно len (tokenizer.word_index) + 1.

«+1» используется для обозначения неизвестного слова, которое не является частью нашего корпуса.

Последовательности ввода: Последовательность ввода - это числовое представление того, как расположены наши слова. Продолжайте читать, чтобы узнать, что это значит.

# create input sequences using list of tokens
input_sequences = []
for line in corpus:
    token_list = tokenizer.texts_to_sequences([line])[0]
    
    for i in range(1, len(token_list)):
        n_gram_sequence = token_list[:i+1]
    
    input_sequences.append(n_gram_sequence)

Мы перепроектируем это, чтобы увидеть, как это работает.

Когда вы печатаете input_sequences, мы получим что-то вроде этого:

[[125, 45],  
 [125, 45, 901],
 [125, 45, 901, 9],  
 [125, 45, 901, 9, 10],  
 [125, 45, 901, 9, 10, 36],  
 [125, 45, 901, 9, 10, 36, 96],  
 [125, 45, 901, 9, 10, 36, 96, 11],  
 [125, 45, 901, 9, 10, 36, 96, 11, 902],
 .....
 ......]

Это представление первого предложения в корпусе,
«у меня на спине еще остались шрамы от ваших ножей».

Теперь вы можете проверить, что в tokenizer.word_index индекс для still равен 125. То же самое верно для всех слов.

>>> tokenizer.word_index["still"]
Out: 125

Для одного и того же предложения существуют разные входные последовательности, которые различаются по длине. Они просто представляют, как модель учится. Сначала мы кормим 2 слова и учим предсказывать 3-е слово. Затем мы вводим 3 слова и ожидаем, что он предскажет 4-е слово. То же самое верно для каждого предложения. Вот как работает наша модель.

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

# pad sequences
max_sequence_len = max([len(x) for x in input_sequences])
input_sequences = np.array(pad_sequences(input_sequences,
                       maxlen = max_sequence_len, padding='pre'))

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

Допустим, максимальная длина последовательности равна 10. Но ваша последовательность имеет длину только 4. Пример: [125, 45, 901, 9].

После заполнения эта последовательность станет: [0, 0, 0, 0, 0, 0, 125, 45, 901, 9].

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

[[  0,   0,   0, ...,   0, 125,  45],        
 [  0,   0,   0, ..., 125,  45, 901],        
 [  0,   0,   0, ...,  45, 901,   9],
 ..........
 ...........

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

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

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

predictors, label = input_sequences[:,:-1],input_sequences[:,-1]

Построение модели

Честно говоря, сложная часть сделана. Теперь нам просто нужно построить модель RNN с помощью LSTM. Подробнее про LSTM здесь.

model = Sequential()
model.add(Embedding(total_words, 50, input_length=max_sequence_len-1))
# Add an LSTM Layer
model.add(Bidirectional(LSTM(150, return_sequences=True)))  
# A dropout layer for regularisation
model.add(Dropout(0.2))
# Add another LSTM Layer
model.add(LSTM(100)) 
model.add(Dense(total_words/2, activation='relu'))  
# In the last layer, the shape should be equal to the total number of words present in our corpus
model.add(Dense(total_words, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics='accuracy')  #(# Pick a loss function and an optimizer)
print(model.summary())

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

history = model.fit(predictors, label, epochs= 100, verbose=1)

Создание текстов песен

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

def make_lyrics(seed_text, next_words):
    for _ in range(next_words):
        token_list = tokenizer.texts_to_sequences([seed_text])[0]
        token_list = pad_sequences([token_list],
                     maxlen=max_sequence_len-1,padding='pre')
        predicted = model.predict_classes(token_list, verbose=0)
        output_word = ""
        for word, index in tokenizer.word_index.items():
            if index == predicted:
                output_word = word
                break
        seed_text += " " + output_word
    print(seed_text)

Вышеупомянутая функция довольно проста для понимания.
Входными данными является «seed_text», который затем токенизируется и дополняется, чтобы сделать его равным максимальной длине последовательности.
Затем модель использует этот исходный текст для прогнозирования числа. Число представляет индекс предсказанного слова.

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

Спасибо за прочтение. Надеюсь, вам понравилась моя статья, и вы нашли ее полезной. Если у вас есть какие-либо вопросы или предложения, не стесняйтесь записывать их в разделе комментариев. Вы также можете связаться со мной на LinekdIn здесь: Ishant Juyal.

Использованная литература:

  1. Набор данных: Kaggle.com/Taylor Swift Song Lyrics
  2. Видеоурок: https://youtu.be/Pe56OZ4aPds