Обзор методов совместной фильтрации на основе памяти и реализация рекомендателя фильмов

В последнем посте Создание рекомендателя фильмов на основе содержимого с использованием tf-idf я объяснил, как создать простой рекомендатель фильмов на основе жанров.

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

Вы найдете весь код для воспроизведения результатов в блокноте jupyter здесь.

Вступление

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

Скажем, Лиззи только что посмотрела «Прибытие» и «Бегущий по лезвию 2049» и теперь хочет, чтобы ей порекомендовали похожие фильмы, потому что они ей понравились.

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

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

Типы методов совместной фильтрации

Как уже упоминалось, существует множество методов совместной фильтрации (сокращенно CF), ниже приведены основные типы, которые мы можем найти:

  • На основе памяти

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

  • На основе модели

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

  • Гибрид

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

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

CF на основе памяти

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

  • На основе пользователей

Здесь мы находим пользователей, которые видели / оценивали похожий контент, и на основе их предпочтений рекомендуют новые элементы:

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

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

  • По элементам

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

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

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

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

Реализация

Данные

Мы будем использовать тот же набор данных, что и в предыдущем посте, набор данных MovieLens, который содержит наборы данных рейтинга с веб-сайта MovieLens. Он содержит 1 миллион анонимных оценок примерно 4000 фильмов, снятых 6000 пользователями MovieLens, выпущенных 2/2003.

Мы будем работать с тремя CSV-файлами: рейтинги, пользователи и фильмы. Пожалуйста, проверьте предыдущий пост для получения более подробной информации о наборе данных.

Матрица элементов пользователя

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

user_item_m = ratings.pivot('user_id','movie_id','rating').fillna(0)
print(f'Shape: {user_item_m.shape}')
> Shape: (29909, 5840)

Это выглядит так (я присоединился к таблице фильмов, чтобы названия можно было видеть и здесь):

Матрица подобия

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

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

Мы можем использовать sklearn's metrics.pairwise подмодуль для попарных показателей расстояния или сходства, в этом случае мы будем использовать cosine_similarity.

Обратите внимание, что функция имеет подпись:

sklearn.metrics.pairwise.cosine_similarity(X, Y=None, dense_output=True)

Где ожидается Y:

Y: ndarray или разреженный массив, форма: (n_samples_Y, n_features). Если None, выводом будет попарное сходство между всеми образцами в X.

Таким образом, указав только X, будет сгенерирована матрица сходства из образцов в X:

from sklearn.metrics.pairwise import cosine_similarity
X_user = cosine_similarity(user_item_m)
print(X_user.shape)
(6040, 6040)
print(X_user)
array([[1.   , 0.063, 0.127, 0.116, 0.075, 0.15 , 0.035, 0.102],
       [0.063, 1.   , 0.111, 0.145, 0.107, 0.105, 0.246, 0.161],
       [0.127, 0.111, 1.   , 0.127, 0.066, 0.036, 0.185, 0.086],
       [0.116, 0.145, 0.127, 1.   , 0.052, 0.016, 0.1  , 0.072],
       [0.075, 0.107, 0.066, 0.052, 1.   , 0.052, 0.106, 0.18 ],
       [0.15 , 0.105, 0.036, 0.016, 0.052, 1.   , 0.067, 0.085],
       [0.035, 0.246, 0.185, 0.1  , 0.106, 0.067, 1.   , 0.202],
       [0.102, 0.161, 0.086, 0.072, 0.18 , 0.085, 0.202, 1.   ],
       [0.174, 0.156, 0.1  , 0.092, 0.242, 0.078, 0.125, 0.217],
       [0.209, 0.162, 0.158, 0.096, 0.079, 0.124, 0.091, 0.109]])

Это сгенерирует пользовательскую матрицу сходства с формой (n_users, n_users).

И поскольку ожидается, что X будет:

X: ndarray или разреженный массив, форма: (n_samples_X, n_features)

Путем транспонирования матрицы пользовательских элементов наши образцы теперь будут столбцами матрицы пользовательских элементов, то есть фильмами. Итак, если наша исходная матрица пользовательских элементов имеет форму (n,m), обнаружив косинусное сходство в транспонированной матрице, мы получим (m,m) матрицу:

X_item = cosine_similarity(user_item_m.T)
X_item.shape
(3706, 3706)

Что будет представлять собой матрицу сходства элементов.

Алгоритм

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

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

Алгоритм используемого CF можно резюмировать следующим образом:

  1. Вычислить сходство нового пользователя со всеми другими пользователями (если это еще не сделано)
  2. Вычислить средний рейтинг всех фильмов k наиболее похожих пользователей
  3. Рекомендовать фильмы с самым высоким рейтингом n другими пользователями, не просматриваемые пользователем

Bellow - это функция для реализации пользовательского рекомендателя CF, который объясняется шаг за шагом:

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

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

Тестирование рекомендателя

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

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

  • Начнем с некоторых рекомендаций для пользователей:

Давайте попробуем с пользователем, который, кажется, предпочитает драму:

rec = CfRec(user_item_m, X_user, movies)
because_user_liked(user_item_m, movies, ratings, 69)

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

rec.recommend_user_based(69)

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

А теперь давайте попробуем с фанатом фильмов ужасов:

because_user_liked(user_item_m, movies, ratings, 2155)

rec.recommend_user_based(2155)

«Сияние», «Чужой»… звучит так, будто этот пользователь упускает из виду эту классику ужасов!

  • Давайте теперь попробуем с некоторыми рекомендациями по элементам

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

rec = CfRec(user_item_m, X_item, movies)

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

rec.recommend_item_based(2021)
>> Because you liked Dune (1984), we'd recommend you to watch:

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

Если бы нам понравился фильм Se7en, Криминал и триллер , мы были бы рекомендуется к просмотру :

rec.recommend_item_based(47)
>> Because you liked (Se7en) (1995), we'd recommend you to watch:

И, наконец, для классического анимационного фильма Бэмби:

rec.recommend_item_based(2018)
>> Because you liked Bambi (1942), we'd recommend you to watch:

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

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

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

Большое спасибо за то, что нашли время, чтобы прочитать этот пост, и я надеюсь, что вам понравилось :)