Как подготовить данные для моделей LSTM.

Все является последовательностью

Для начала мы начнем с понимания нашей темы. Что такое последовательность?

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

Примеры последовательностей есть везде:

  1. Текст - последовательность слов и символов.
  2. Слова - последовательность символов.
  3. Музыка - последовательность звуков.
  4. Годовые курсы обмена валют - последовательность чисел.
  5. Список покупок - последовательность товаров для покупки.
  6. История браузера - последовательность просмотренных веб-страниц.

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

Давайте взглянем на один из приведенных выше примеров - текст.

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

Был ли у вас когда-нибудь опыт, когда вы читали половину книги и угадали, чем заканчивается история? Я так думаю. А как насчет угадывания следующего слова? Например, главный герой рассказов любит говорить такие фразы, как «Я люблю играть в теннис, это мое хобби! »Или« Теннис - это смысл моей жизни »и т. Д. И когда вы читаете, что другой персонаж спрашивает главного о его / ее хобби, вы можете догадаться, что теперь будет написано« теннис ». Или, когда главный герой говорит «Я люблю…», финал будет «поиграть в теннис».

А как насчет машинного обучения? Может ли LSTM делать подобные прогнозы? - Да.

Например, у нас есть текст: «Я люблю теннис, теннис - это жизнь».

Разделите их знаком «,» на два предложения и каждое слово разделите пробелом. У нас будет следующий результат двух последовательностей с длиной = 3.

Результат LSTM будет таким: «Я люблю теннис - это жизнь».

Результат глупый и чистый с грамматической точки зрения. Но логика все еще здесь.

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

Почему мы смотрим на текстовый пример, когда речь идет о рекомендательных системах?

Пример списка покупок

Как было написано ранее в листинге примеров, список покупок - это последовательность товаров, которые нужно купить. Давайте посмотрим, например:

Мы видим четыре первых товара: молоко, яйца, сыр и масло.

Но, например, это фото сделал ваш друг и отправил вам, чтобы вы его купили. И он переключил свой смартфон, а вы пытаетесь угадать, какой пятый хорош? Мы видим, что это крем, но давайте представим, что вы его не видите. Что ты будешь делать ?

К счастью, вы нашли в своей фото-библиотеке 3 последних фотографии из списков покупок.

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

Список покупок - это текст из слов (названий товаров). Конечно, не в строгом порядке покупать, но давайте представим, что это правда.

Плавно переходим к рекомендациям.

История пользователей в виде текста

Название главы раскрывает нам основную идею.

Мы можем смотреть на пользователей, как на учебник с некоторыми рассказами или предложениями. Но в нашем случае предложение - это последовательность элементов или их идентификаторов. Давайте посмотрим на верхний рисунок. У нас есть три пользователя. У каждого есть история поиска в Интернете за определенный период времени. Пусть будет один час. Мы хотим предсказать, что будет следующим? Давайте напишем текст об этих пользователях, как они нам рассказали.

Текст: «Я использую Amazon, а затем использую Google+ и Instagram. Я использую Instagram, а затем использую Evernote и Amazon. Я использую Evernote, а затем - Instagram и Google+ ».

Мы преобразуем этот текст в предложения, исключив все слова, не относящиеся к брендам, и получим следующее:

«Amazon, Google +, Instagram», «Instagram, Evernote, Amazon», «Evernote, Instagram, Google +».

Чтобы сделать входные данные для модели, используется метод, называемый токенизацией.

Мы дадим каждому элементу уникальный идентификатор.

Итак, последовательности будут такими: «1,2,3 /« 3,4,1 »/« 4,3,2 ».

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

Контролируемые наблюдения - это способ подготовки данных, когда у нас есть какие-то входы и соответствующие выходы (цели или метки). Давайте разделим каждую последовательность на 2 и 1 элемента, где первые двойки вводятся, а последняя - цель.

Итак, здесь мы можем глупо предположить, что пользователь 3 будет действовать 4,3 → 2,3,4,1 и т. Д. LSTM сделает это лучше.

Основная идея в том, что вы можете преобразовать данные, не относящиеся к временным рядам, во временные ряды.

Эту идею я увидел в этой статье компании Deep Systems.

Практический пример

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

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

Во-вторых, ваших данных должно хватить для достижения вашей цели. Что достаточно?

Например, в учебнике keras с моделью LSTM по этой ссылке в начале идет состояние:

# Пример скрипта для генерации текста из произведений Ницше. Требуется не менее 20 эпох, прежде чем сгенерированный текст начнет звучать связно. Рекомендуется запускать этот скрипт на графическом процессоре, так как повторяющиеся сети требуют значительных вычислительных ресурсов. вы пробуете этот скрипт на новых данных, убедитесь, что ваш корпус содержит не менее ~ 100 тыс. символов. ~ 1 млн лучше.

Если вы откроете этот пример и протестируете его (это хорошо), вы увидите, что текст содержит почти 70 уникальных символов и всего 100 тысяч символов во всем тексте. Если мы разделим его на равные предложения длиной = 4 слова, мы получим 25k выборок с предсказанием только 70 возможных классов. Итак, этих данных достаточно.

Я потратил несколько недель на создание практического примера с набором данных MovieLens 100k, но не смог с точностью проверки не более 10%. После этого мне досталась переоборудованная модель. Но проблема была в данных. Поскольку существует почти 8000 (!) Возможных классов для предсказания (уникальные фильмы), и после разделения на последовательности с длиной = 5 я получил только 25000 образцов.

Для 70 классов это хорошо, а для 8000 - невозможно.

Я покажу, как преобразовать ваши данные в подходящую форму для модели keras.

Прежде всего нам нужны данные.

Пусть это будет наш пример из предыдущей главы с веб-ресурсами.

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

«Amazon, Google +, Instagram», «Instagram, Evernote, Amazon», «Evernote, Instagram, Google +» - необработанные данные (здесь могут быть все ваши данные, например, товары в интернет-магазине и т. Д.)

Начните писать на Python 3.6:

text = "Amazon,Google+,Instagram|Instagram,Evernote,Amazon,Yahoo|Evernote,Instagram,Google+"

Я использовал специальный символ «|», чтобы отделить каждый профиль данных пользователя от других. Но также добавил «Yahoo» для второго пользователя, чтобы сделать последовательности разной длины, чтобы сделать наши глупые данные более реальными (вы никогда не найдете набор пользовательских данных с одинаковой длиной историй).

Первоначальный импорт:

from keras.utils import to_categorical
from keras_preprocessing.sequence import pad_sequences
from keras.preprocessing.text import Tokenizer
import numpy as np

Функция сопоставления уникального идентификатора каждому слову (элементу) в тексте сделает keras Tokenizer.

tokenizer = Tokenizer()

tokenizer.fit_on_texts([text])
vocabulary_size = len(tokenizer.word_index) + 1
print('Unique items: %d' % vocabulary_size)
sequences = list()
for line in text.split('|'):
    encoded = tokenizer.texts_to_sequences([line])[0]
    sequences.append(encoded)
print('Total Sequences: %d' % len(sequences))
print(sequences)
#Output =>
#Total Sequences: 3
#[[2, 3, 1], [1, 4, 2, 5], [4, 1, 3]]

Новый взгляд на наши данные: [[2, 3, 1], [1, 4, 2, 5], [4, 1, 3]].

Следующая наша цель - преобразовать наши последовательности к одинаковой длине. Есть хороший инструмент - padding, тоже в keras API.

max_len = max([len(seq) for seq in sequences])
sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')
print(sequences)
print('Max Sequence Length: %d' % max_len)
sequences = np.array(sequences)
#Output =>
#[[0 2 3 1]
# [1 4 2 5]
# [0 4 1 3]]

Теперь наши данные выглядят так: [[0 2 3 1], [1 4 2 5], [0 4 1 3]].

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

X, y = sequences[:, :-1], sequences[:, -1]
print(X)
print(y)
# Output
#[[0 2 3]
# [1 4 2]
# [0 4 1]] -- samples
# [1 5 3] -- targets

И еще одно: классифицируйте наши цели с помощью одного метода горячего кодирования (цель «1» → [0,1,0,0,0,0]).

y = to_categorical(y, num_classes=vocabulary_size)
print(y)
# Output
#[[0. 1. 0. 0. 0. 0.]
# [0. 0. 0. 0. 0. 1.]
# [0. 0. 0. 1. 0. 0.]]

Почему мы не векторизуем наши входные данные? Конечно, мы можем это сделать, но мы можем потерять смысл входной последовательности. Одно горячее кодирование имеет плохую особенность - оно стирает значение времени и смысл слов, приравнивая каждое из них к 1. Конечно, «Instagram» и «Yahoo» имеют разные значения, и они не должны иметь одинаковый вес. Как это решить?

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

И еще один бонус. Если мы точно знаем веса схожести входных данных (например, перчатка встраивания для английских слов), мы можем добавить его в слой и вообще не тренировать его.

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

Хороший. Сделаем модель.

from keras import Sequential
from keras.layers import Embedding, Dropout, LSTM, Dense
model = Sequential()
model.add(Embedding(vocabulary_size, 5, input_length=max_len - 1))
model.add(Dropout(0.2))
model.add(LSTM(3))
model.add(Dropout(0.2))
model.add(Dense(vocabulary_size, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

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

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

Подбираем модель и посмотрим.

h = model.fit(X, y, validation_split=0.2, verbose=1, epochs=10)
import matplotlib.pyplot as plt

plt.plot(h.history['loss'], label='Train loss')
plt.plot(h.history['val_loss'], label='Validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.show()
plt.plot(h.history['acc'], label='Train accuracy')
plt.plot(h.history['val_acc'], label='Validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.show()

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

Сделаем прогноз.

def generate_seq(model, tokenizer, seq_length, seed_text, n_words):
    result = list()
    in_text = seed_text
    for _ in range(n_words):
        encoded = tokenizer.texts_to_sequences([in_text])[0]
        encoded = pad_sequences([encoded], maxlen=seq_length, truncating='pre')
        yhat = model.predict_classes(encoded, verbose=0)
        out_word = ''
        for word, index in tokenizer.word_index.items():
            if index == yhat:
                out_word = word
                break
        in_text += ' ' + out_word
        result.append(out_word)
    return ' '.join(result)
print(generate_seq(model, tokenizer, 3, '1 3 5', 1))
#Output
# instagram

Итак, наш ввод «1 3 5» = «Instagram, Google +, Yahoo» дает нам результат «Instagram».

Плохо и глупо, оценивать не буду. И модель переоборудована. Этот результат был очевиден. Почему ? Отсутствие данных.

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

Попытайся !

Заключение

Модели LSTM могут использоваться в рекомендательных системах.

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

Используйте текстовый трюк, чтобы подготовить свои данные. Надеюсь, я объяснил это здесь.

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

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

Спасибо за чтение!

Автор: Бондаренко К. - инженер машинного обучения и энтузиаст данных :)