«Какой фильм мне посмотреть сегодня вечером?»

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

Пример системы рекомендаций такой:

  • Пользователь А смотрит Игру престолов и Во все тяжкие.
  • Пользователь Б выполняет поиск в Игре престолов, затем система предлагает Во все тяжкие на основе данных, собранных о пользователе А.

Системы рекомендаций используются не только для фильмов, но и для множества других продуктов и сервисов, таких как Amazon (Книги, Предметы), Pandora / Spotify (Музыка), Google (Новости, Поиск), YouTube (Видео) и т. Д.

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

Набор данных MovieLens

Набор данных, с которым я работаю, - это MovieLens, один из наиболее распространенных наборов данных, доступных в Интернете для создания рекомендательной системы. Версия набора данных, с которым я работаю (1M), содержит 1 000 209 анонимных оценок примерно 3900 фильмов, созданных 6040 пользователями MovieLens, которые присоединились к MovieLens в 2000 году.

После обработки данных и проведения исследовательского анализа, вот наиболее интересные особенности этого набора данных:

Вот визуализация названий фильмов в виде облака слов:

Красиво, не правда ли? Я могу признать, что в этом наборе данных много франшиз фильмов, о чем свидетельствуют такие слова, как II и III… В дополнение к этому, Day , Любовь, Жизнь, Время, Ночь, Человек, Мертвые, американские - одни из наиболее часто встречающихся слов.

Вот распределение оценок пользователей:

Похоже, что пользователи довольно щедры в своих рейтингах. Средняя оценка составляет 3,58 по пятибалльной шкале. Половина фильмов имеет оценку 4 и 5. Я лично считаю, что пятиуровневый рейтинг не был хорошим показателем, поскольку люди могли иметь разные стили оценки (например, человек А мог всегда используйте 4 для среднего фильма, тогда как человек B дает только 4 для своих любимых). Каждый пользователь оценил по крайней мере 20 фильмов, поэтому я сомневаюсь, что распределение могло быть вызвано просто случайной разницей в качестве фильмов.

Вот еще одно облако слов из жанров кино:

В этом порядке пять лучших жанров: драма, комедия, боевик, триллер и мелодрама.

Теперь давайте перейдем к изучению 4 рекомендательных систем, которые можно использовать. Вот они, в соответствующем порядке изложения:

  1. Контентная фильтрация
  2. Совместная фильтрация на основе памяти
  3. Совместная фильтрация на основе моделей
  4. Глубокое обучение / нейронная сеть

1 - На основе содержания

Content-Based Recommender полагается на схожесть рекомендуемых элементов. Основная идея заключается в том, что если вам понравился предмет, то вам понравится и «похожий» предмет. Обычно это хорошо работает, когда легко определить контекст / свойства каждого элемента.

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

Математика

Понятия частота термина (TF) и обратная частота документа (IDF) используются в системах поиска информации, а также в механизмах фильтрации на основе содержимого (например, в рекомендациях на основе содержимого). Они используются для определения относительной важности документа / статьи / новости / фильма и т. Д.

TF - это просто частота слова в документе. IDF - это величина, обратная частоте документов среди всего корпуса документов. TF-IDF используется в основном по двум причинам. Предположим, мы ищем в Google «результаты последних европейских игр Socccer». Несомненно, что «» будет встречаться чаще, чем «футбольные матчи», но относительная важность футбольных матчей выше, чем поисковый запрос. точка зрения. В таких случаях взвешивание TF-IDF сводит на нет влияние часто встречающихся слов при определении важности элемента (документа).

Ниже приведено уравнение для расчета оценки TF-IDF:

После подсчета баллов TF-IDF, как определить, какие элементы находятся ближе друг к другу, а не к профилю пользователя? Это достигается с помощью модели векторного пространства, которая вычисляет близость на основе угла между векторами. В этой модели каждый элемент хранится как вектор его атрибутов (которые также являются векторами) в n-мерном пространстве, и углы между векторами вычисляются для определения сходства между векторы. Затем векторы профиля пользователя также создаются на основе его действий с предыдущими атрибутами элементов, и сходство между элементом и пользователем также определяется аналогичным образом.

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

Код

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

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

from sklearn.feature_extraction.text import TfidfVectorizer
tf = TfidfVectorizer(analyzer='word',ngram_range=(1, 2),min_df=0, stop_words='english')
tfidf_matrix = tf.fit_transform(movies['genres'])

Я буду использовать Косинусное сходство для вычисления числовой величины, обозначающей сходство между двумя фильмами. Поскольку я использовал векторизатор TF-IDF, вычисление скалярного произведения напрямую даст мне показатель косинусного сходства. Поэтому я буду использовать linear_kernel sklearn вместо cosine_similarities, поскольку он намного быстрее.

from sklearn.metrics.pairwise import linear_kernel
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)

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

# Build a 1-dimensional array with movie titles
titles = movies['title']
indices = pd.Series(movies.index, index=movies['title'])

# Function that get movie recommendations based on the cosine similarity score of movie genres
def genre_recommendations(title):
    idx = indices[title]
    sim_scores = list(enumerate(cosine_sim[idx]))
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
    sim_scores = sim_scores[1:21]
    movie_indices = [i[0] for i in sim_scores]
    return titles.iloc[movie_indices]

Рекомендация

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

genre_recommendations('Good Will Hunting (1997)').head(20)

genre_recommendations('Toy Story (1995)').head(20)

genre_recommendations('Saving Private Ryan (1998)').head(20)

Как видите, у меня есть неплохой список рекомендаций по Good Will Hunting (драма), История игрушек (мультфильм, детская, комедия) и Сохранение Рядовой Райан (боевик, триллер, война).

В целом, вот преимущества использования рекомендаций, основанных на содержании:

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

Однако у этого подхода есть некоторые недостатки:

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

2 - Совместная фильтрация

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

Например, если пользователю A нравятся фильмы 1, 2, 3, а пользователю B нравятся фильмы 2, 3, 4, то у них схожие интересы, и A должен понравиться фильм 4, а B должен понравиться фильм 1. Это делает его одним из самых распространенных. используемый алгоритм, поскольку он не зависит от какой-либо дополнительной информации.

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

Математика

Существует 2 основных типа алгоритмов совместной фильтрации на основе памяти:

  1. Совместная фильтрация пользователей и пользователей. Здесь мы находим похожих пользователей на основе сходства и рекомендуем фильмы, которые в прошлом выбирал первый двойник пользователя. Этот алгоритм очень эффективен, но требует много времени и ресурсов. Требуется вычислить информацию о каждой пользовательской паре, что требует времени. Следовательно, для больших базовых платформ этот алгоритм трудно реализовать без очень сильной системы с возможностью распараллеливания.
  2. Совместная фильтрация элементов и элементов. Он очень похож на предыдущий алгоритм, но вместо того, чтобы находить похожих пользователей, мы пытаемся найти похожие фильмы. Когда у нас есть матрица похожих фильмов, мы можем легко рекомендовать похожие фильмы пользователям, которые оценили любой фильм из набора данных. Этот алгоритм требует гораздо меньше ресурсов, чем совместная фильтрация пользователь-пользователь. Следовательно, для нового пользователя алгоритм занимает гораздо меньше времени, чем совместная работа пользователя и пользователя, поскольку нам не нужны все оценки сходства между пользователями. А при фиксированном количестве фильмов матрица подобия фильма-фильма фиксируется с течением времени.

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

При совместной фильтрации обычно используются 3 метрики сходства расстояний:

  1. Сходство по Жаккару. Сходство основывается на количестве пользователей, которые поставили оценку элементам A и B, разделенному на количество пользователей, получивших оценку A или B. числовой рейтинг, но просто логическое значение, такое как покупаемый продукт или добавление, по которому щелкают.
  2. Косинусное сходство: (как в системе, основанной на содержании). Сходство - это косинус угла между двумя векторами векторов элементов A и B. Чем ближе векторы, тем меньше будет угол и больше. косинус.
  3. Сходство Пирсона. Сходство - это коэффициент Пирсона между двумя векторами. В целях разнообразия я буду использовать в этой реализации сходство Пирсона.

Код

Из-за ограниченной вычислительной мощности моего ноутбука я буду создавать рекомендательную систему, используя только часть рейтингов. В частности, я возьму случайную выборку из 20 000 оценок (2%) из 1M оценок.

Я использую библиотеку scikit-learn, чтобы разделить набор данных на тестирование и обучение. Cross_validation.train_test_split перемешивает и разделяет данные на два набора данных в соответствии с процентом тестовых примеров, который здесь равен 0,2.

from sklearn import cross_validation as cv
train_data, test_data = cv.train_test_split(small_data, test_size=0.2)

Теперь мне нужно создать матрицу элементов пользователя. Поскольку я разделил данные на тестирование и обучение, мне нужно создать две матрицы. Матрица обучения содержит 80% оценок, а матрица тестирования - 20% оценок.

# Create two user-item matrices for training and testing data
train_data_matrix = train_data.as_matrix(columns = ['user_id', 'movie_id', 'rating'])
test_data_matrix = test_data.as_matrix(columns = ['user_id', 'movie_id', 'rating'])

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

from sklearn.metrics.pairwise import pairwise_distances

# User Similarity Matrix
user_correlation = 1 - pairwise_distances(train_data, metric='correlation')
user_correlation[np.isnan(user_correlation)] = 0
# Item Similarity Matrix
item_correlation = 1 - pairwise_distances(train_data_matrix.T, metric='correlation')
item_correlation[np.isnan(item_correlation)] = 0

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

# Function to predict ratings
def predict(ratings, similarity, type='user'):
    if type == 'user':
        mean_user_rating = ratings.mean(axis=1)
        # Use np.newaxis so that mean_user_rating has same format as ratings
        ratings_diff = (ratings - mean_user_rating[:, np.newaxis])
        pred = mean_user_rating[:, np.newaxis] + similarity.dot(ratings_diff) / np.array([np.abs(similarity).sum(axis=1)]).T
    elif type == 'item':
        pred = ratings.dot(similarity) / np.array([np.abs(similarity).sum(axis=1)])
    return pred

Оценка

Существует множество показателей оценки, но одним из самых популярных показателей, используемых для оценки точности прогнозируемых оценок, является Среднеквадратичная ошибка (RMSE). Я буду использовать функцию mean_square_error (MSE) из sklearn, где RMSE - это просто квадратный корень из MSE. Я буду использовать функцию среднеквадратичной ошибки scikit-learn в качестве метрики проверки. Сравнивая совместную фильтрацию на основе пользователей и элементов, похоже, что совместная фильтрация на основе пользователей дает лучший результат.

from sklearn.metrics import mean_squared_error
from math import sqrt

# Function to calculate RMSE
def rmse(pred, actual):
    # Ignore nonzero terms.
    pred = pred[actual.nonzero()].flatten()
    actual = actual[actual.nonzero()].flatten()
    return sqrt(mean_squared_error(pred, actual))
# Predict ratings on the training data with both similarity score
user_prediction = predict(train_data_matrix, user_correlation, type='user')
item_prediction = predict(train_data_matrix, item_correlation, type='item')
# RMSE on the train data
print('User-based CF RMSE: ' + str(rmse(user_prediction, train_data_matrix)))
print('Item-based CF RMSE: ' + str(rmse(item_prediction, train_data_matrix)))
## Output
User-based CF RMSE: 699.9584792778463
Item-based CF RMSE: 114.97271725933925

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

В целом, Совместная фильтрация на основе памяти проста в реализации и обеспечивает приемлемое качество прогнозирования. Однако у такого подхода есть ряд недостатков:

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

Примечание. Полный код для совместной фильтрации на основе содержимого и памяти можно найти в этом блокноте Jupyter.

3 - Факторизация матрицы

В предыдущей попытке я использовал совместную фильтрацию на основе памяти, чтобы рекомендовать фильмы на основе данных оценок пользователей. Я могу попробовать их только на очень небольшой выборке данных (20 000 оценок) и в итоге получил довольно высокую среднеквадратичную ошибку (плохие рекомендации). Подходы к совместной фильтрации на основе памяти, которые вычисляют отношения расстояния между элементами или пользователями, имеют две основные проблемы:

  1. Он не особенно хорошо масштабируется для массивных наборов данных, особенно для рекомендаций в реальном времени, основанных на сходстве поведения пользователей, что требует большого количества вычислений.
  2. Матрицы рейтингов могут чрезмерно соответствовать шумным представлениям о вкусах и предпочтениях пользователей. Когда мы используем подходы «соседства» на основе расстояния к необработанным данным, мы сопоставляем разреженные детали низкого уровня, которые, как мы предполагаем, представляют собой вектор предпочтений пользователя, а не сам вектор.

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

  • Я могу обнаружить скрытые корреляции / особенности в необработанных данных.
  • Я могу удалить ненужные избыточные и шумные функции.
  • Я могу легче интерпретировать и визуализировать данные.
  • Я также могу получить доступ к более легкому хранению и обработке данных.

Математика

Совместная фильтрация на основе моделей основана на матричной факторизации (MF), которая получила большее распространение, в основном как метод неконтролируемого обучения для декомпозиции скрытых переменных и уменьшения размерности. Факторизация матрицы широко используется в рекомендательных системах, где она лучше справляется с масштабируемостью и разреженностью, чем CF на основе памяти:

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

Хорошо известным методом матричной факторизации является разложение по сингулярным числам (SVD). На высоком уровне SVD - это алгоритм, который разлагает матрицу A на лучшее приближение более низкого ранга (то есть меньшее / более простое) исходной матрицы A. Математически он разлагает A на две унитарные матрицы и диагональную матрицу:

где A - матрица входных данных (оценки пользователей), U - левые сингулярные векторы (матрица «характеристик» пользователя), Sum - диагональная матрица сингулярных значения (по сути, веса / силы каждой концепции), а V ^ T - правые сингулярные векторы (матрица «характеристик» фильма). U и V ^ T являются ортонормированными столбцами и представляют разные вещи: U показывает, насколько пользователям «нравится» каждая функция, а V ^ T показывает, насколько каждая функция актуальна для каждого фильма .

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

Код

У Scipy и Numpy есть функции для разложения по сингулярным значениям. Я собираюсь использовать функцию Scipy svds, потому что она позволяет мне выбирать, сколько скрытых факторов я хочу использовать для аппроксимации исходной матрицы оценок (вместо того, чтобы усекать ее после).

from scipy.sparse.linalg import svds 
U, sigma, Vt = svds(Ratings_demeaned, k = 50)

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

sigma = np.diag(sigma)

Теперь у меня есть все необходимое, чтобы делать прогнозы рейтингов фильмов для каждого пользователя. Я могу сделать все сразу, следуя математике и матричному умножению U, Sum и V ^ T, чтобы получить приближение ранга k = 50 для A.

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

all_user_predicted_ratings = np.dot(np.dot(U, sigma), Vt) + user_ratings_mean.reshape(-1, 1)

Имея матрицу прогнозов для каждого пользователя, я могу создать функцию, которая будет рекомендовать фильмы любому пользователю. Я возвращаю список фильмов, которые пользователь уже оценил, для сравнения.

preds = pd.DataFrame(all_user_predicted_ratings, columns = Ratings.columns)

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

def recommend_movies(predictions, userID, movies, original_ratings, num_recommendations):
    
    # Get and sort the user's predictions
    user_row_number = userID - 1 # User ID starts at 1, not 0
    sorted_user_predictions = preds.iloc[user_row_number].sort_values(ascending=False) # User ID starts at 1
    
    # Get the user's data and merge in the movie information.
    user_data = original_ratings[original_ratings.user_id == (userID)]
    user_full = (user_data.merge(movies, how = 'left', left_on = 'movie_id', right_on = 'movie_id').
                     sort_values(['rating'], ascending=False)
                 )
    
    # Recommend the highest predicted rating movies that the user hasn't seen yet.
    recommendations = (movies[~movies['movie_id'].isin(user_full['movie_id'])].
         merge(pd.DataFrame(sorted_user_predictions).reset_index(), how = 'left',
               left_on = 'movie_id',
               right_on = 'movie_id').
         rename(columns = {user_row_number: 'Predictions'}).
         sort_values('Predictions', ascending = False).
                       iloc[:num_recommendations, :-1]
                      )

    return user_full, recommendations

Оценка

Вместо того, чтобы выполнять оценку вручную, как в прошлый раз, я буду использовать библиотеку Surprise, которая предоставляет различные готовые к использованию мощные алгоритмы прогнозирования, включая (SVD), для оценки его RMSE (среднеквадратичной ошибки) в наборе данных MovieLens. Это Python Scikit-Learn, создающий и анализирующий рекомендательные системы.

# Import libraries from Surprise package
from surprise import Reader, Dataset, SVD, evaluate

# Load Reader library
reader = Reader()

# Load ratings dataset with Dataset library
data = Dataset.load_from_df(ratings[['user_id', 'movie_id', 'rating']], reader)

# Split the dataset for 5-fold evaluation
data.split(n_folds=5)
# Use the SVD algorithm.
svd = SVD()

# Compute the RMSE of the SVD algorithm.
evaluate(svd, data, measures=['RMSE'])

Я получил среднюю среднеквадратическую ошибку 0,8736, что неплохо.

Рекомендация

Попробуем порекомендовать 20 фильмов пользователю с ID 1310.

predictions = recommend_movies(preds, 1310, movies, ratings, 20)
predictions

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

Примечание. Полный код для матричной факторизации SVD можно найти в этом блокноте Jupyter.

4 - Глубокое обучение

Математика

Идея использования глубокого обучения аналогична идее матричной факторизации на основе моделей. При матричной факторизации мы разлагаем нашу исходную разреженную матрицу на произведение двух ортогональных матриц низкого ранга. Для реализации глубокого обучения нам не нужно, чтобы они были ортогональными, мы хотим, чтобы наша модель узнавала значения самой матрицы встраивания. Скрытые функции пользователя и скрытые функции фильма ищутся из матриц внедрения для конкретной комбинации фильма и пользователя. Это входные значения для дальнейших линейных и нелинейных слоев. Мы можем передать эти входные данные на несколько слоев relu, linear или sigmoid и узнать соответствующие веса с помощью любого алгоритма оптимизации (Adam, SGD и т. Д.).

Код

Вот основные компоненты моей нейронной сети:

  • Левый слой внедрения, который создает матрицу «Пользователи по скрытым факторам».
  • Правый слой встраивания, который создает матрицу «Фильмы по скрытым факторам».
  • Когда входными данными для этих слоев являются (i) идентификатор пользователя и (ii) идентификатор фильма, они возвращают векторы скрытых факторов для пользователя и фильма, соответственно.
  • Слой слияния, который использует скалярное произведение этих двух скрытых векторов для получения прогнозируемого рейтинга.

Этот код основан на подходе, изложенном в сообщении блога Alkahest Совместная фильтрация в Keras.

Затем я компилирую модель, используя среднеквадратичную ошибку (MSE) в качестве функции потерь и алгоритм обучения AdaMax.

# Define model
model = CFModel(max_userid, max_movieid, K_FACTORS)
# Compile the model using MSE as the loss function and the AdaMax learning algorithm
model.compile(loss='mse', optimizer='adamax')

Теперь мне нужно обучить модель. Этот шаг будет самым трудоемким. В моем конкретном случае для нашего набора данных с почти 1 миллионом оценок, почти 6000 пользователей и 4000 фильмов я обучал модель примерно за 6 минут на эпоху (30 эпох ~ 3 часа) внутри процессора моего ноутбука MacBook. Я выложил данные обучения и проверки с соотношением 90/10.

# Callbacks monitor the validation loss
# Save the model weights each time the validation loss has improved
callbacks = [EarlyStopping('val_loss', patience=2), 
             ModelCheckpoint('weights.h5', save_best_only=True)]

# Use 30 epochs, 90% training data, 10% validation data 
history = model.fit([Users, Movies], Ratings, nb_epoch=30, validation_split=.1, verbose=2, callbacks=callbacks)

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

# Use the pre-trained model
trained_model = CFModel(max_userid, max_movieid, K_FACTORS)
# Load weights
trained_model.load_weights('weights.h5')

Здесь я определяю функцию прогнозирования пользовательского рейтинга товаров без рейтинга.

# Function to predict the ratings given User ID and Movie ID
def predict_rating(user_id, movie_id):
    return trained_model.rate(user_id - 1, movie_id - 1)

Оценка

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

# Show the best validation RMSE
min_val_loss, idx = min((val, idx) for (idx, val) in enumerate(history.history['val_loss']))
print 'Minimum RMSE at epoch', '{:d}'.format(idx+1), '=', '{:.4f}'.format(math.sqrt(min_val_loss))
## Output
Minimum RMSE at epoch 17 = 0.8616

Наилучшая потеря проверки составляет 0,7424 в эпоху 17. Взяв квадратный корень из этого числа, я получил значение RMSE 0,8616, что лучше, чем RMSE из модели SVD. (0,8736).

Рекомендация

Здесь я делаю рекомендательный список из 20 фильмов без рейтинга, отсортированных по значению прогноза для идентификатора пользователя 2000. Давайте посмотрим.

recommendations = ratings[ratings['movie_id'].isin(user_ratings['movie_id']) == False][['movie_id']].drop_duplicates()
recommendations['prediction'] = recommendations.apply(lambda x: predict_rating(TEST_USER, x['movie_id']), axis=1)
recommendations.sort_values(by='prediction', ascending=False).merge(movies, on='movie_id', how='inner',
suffixes=['_u', '_m']).head(20)

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

Примечание. Полный код модели глубокого обучения можно найти в этом блокноте Jupyter.

Последний вывод

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

Я надеюсь, что этот пост был полезен для вас, когда вы узнали о 4 различных подходах к созданию собственной системы рекомендаций по фильмам. Вы можете просмотреть весь исходный код в моем репозитории GitHub по этой ссылке (https://github.com/khanhnamle1994/movielens). Дайте мне знать, если у вас есть вопросы или предложения по улучшению!

— —

Если вам понравился этот материал, я бы хотел, чтобы вы нажали кнопку хлопка 👏 , чтобы другие могли наткнуться на него. Вы можете найти мой собственный код на GitHub, а другие мои работы и проекты на https://jameskle.com/. Вы также можете подписаться на меня в Twitter, написать мне напрямую или найти меня в LinkedIn. Подпишитесь на мою рассылку, чтобы получать мои последние мысли о данных, машинном обучении и искусственном интеллекте прямо на свой почтовый ящик!