Введение в рекомендации по продуктам (с помощью Apple Turi Create)

Сегодня мы рассмотрим простой пример рекомендации по продукту (в данном случае предлагаемые фильмы) с использованием общедоступного набора данных MovieLens (небольшого, со 100 000 оценок и 3600 приложений тегов, примененных к 9 000 фильмов 600 пользователями).

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

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

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

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

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

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

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

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

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

Итак, теперь, когда у нас есть точки соприкосновения, давайте посмотрим код!

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

Во-первых, данные:

import turicreate as tc
movies = tc.SFrame.read_csv("ml-latest-small/movies.csv", header=True,
                                delimiter=',')
movies
Finished parsing file /home/antonello/Documents/py-notebooks/movielens_recommender/ml-latest-small/movies.csv
Parsing completed. Parsed 100 lines in 0.036318 secs.
------------------------------------------------------
Inferred types from first 100 line(s) of file as 
column_type_hints=[int,str,str]
If parsing fails due to incorrect types, you can correct
the inferred type list above and pass it to read_csv in
the column_type_hints argument
------------------------------------------------------
Finished parsing file /home/antonello/Documents/py-notebooks/movielens_recommender/ml-latest-small/movies.csv
Parsing completed. Parsed 9742 lines in 0.03129 secs

Turi Create использует свой собственный тип данных, называемый SFrame, аналогичный Pandas Dataframe, который также дает подробный вывод.

И можно выполнить EDA с помощью одной команды!

movies.show()

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

Теперь рейтинги пользователей

ratings = tc.SFrame.read_csv("ml-latest-small/ratings.csv", header=True,
                                delimiter=',')
ratings
Finished parsing file /home/antonello/Documents/py-notebooks/movielens_recommender/ml-latest-small/ratings.csv
Parsing completed. Parsed 100 lines in 0.049596 secs.
------------------------------------------------------
Inferred types from first 100 line(s) of file as 
column_type_hints=[int,int,int,int]
If parsing fails due to incorrect types, you can correct
the inferred type list above and pass it to read_csv in
the column_type_hints argument
------------------------------------------------------
Finished parsing file /home/antonello/Documents/py-notebooks/movielens_recommender/ml-latest-small/ratings.csv
Parsing completed. Parsed 100836 lines in 0.051093 secs.

ratings['rating'].show()

Хорошо, пора увидеть рекомендации в действии. Начнем с рекомендации по популярности (актуальной для всех). Давайте посмотрим три лучших фильма (k = 3) для первых пяти пользователей, объединив также название и жанр для наглядности.

model = tc.recommender.popularity_recommender.create(ratings,
                                          user_id='userId',
                                    item_id='movieId',
                                    target='rating')
most_popular = model.recommend(users=[1,2,3,4,5],k=3)
most_popular = most_popular.join(right=movies,on={'movieId':'movieId'},how='inner').sort(['userId','rank'], ascending=True)
most_popular.print_rows(num_rows=15)
Recsys training: model = popularity
Warning: Ignoring columns timestamp;
    To use these columns in scoring predictions, use a model that allows the use of additional features.
Preparing data set.
    Data has 100836 observations with 610 users and 9724 items.
    Data prepared in: 0.099973s
100836 observations to process; with 9724 unique items.
+--------+---------+-------+------+--------------------------------+
| userId | movieId | score | rank |             title              |
+--------+---------+-------+------+--------------------------------+
|   1    |   6835  |  5.0  |  1   |   Alien Contamination (1980)   |
|   1    |   5746  |  5.0  |  2   | Galaxy of Terror (Quest) (...  |
|   1    |  131724 |  5.0  |  3   | The Jinx: The Life and Dea...  |
|   2    |   3851  |  5.0  |  1   | I'm the One That I Want (2000) |
|   2    |   6835  |  5.0  |  2   |   Alien Contamination (1980)   |
|   2    |   5746  |  5.0  |  3   | Galaxy of Terror (Quest) (...  |
|   3    |   1151  |  5.0  |  1   |      Lesson Faust (1994)       |
|   3    |   3851  |  5.0  |  2   | I'm the One That I Want (2000) |
|   3    |  131724 |  5.0  |  3   | The Jinx: The Life and Dea...  |
|   4    |   6835  |  5.0  |  1   |   Alien Contamination (1980)   |
|   4    |   5746  |  5.0  |  2   | Galaxy of Terror (Quest) (...  |
|   4    |  131724 |  5.0  |  3   | The Jinx: The Life and Dea...  |
|   5    |   6835  |  5.0  |  1   |   Alien Contamination (1980)   |
|   5    |   5746  |  5.0  |  2   | Galaxy of Terror (Quest) (...  |
|   5    |  131724 |  5.0  |  3   | The Jinx: The Life and Dea...  |
+--------+---------+-------+------+--------------------------------+
+--------------------------------+
|             genres             |
+--------------------------------+
|      Action|Horror|Sci-Fi      |
|  Action|Horror|Mystery|Sci-Fi  |
|          Documentary           |
|             Comedy             |
|      Action|Horror|Sci-Fi      |
|  Action|Horror|Mystery|Sci-Fi  |
| Animation|Comedy|Drama|Fantasy |
|             Comedy             |
|          Documentary           |
|      Action|Horror|Sci-Fi      |
|  Action|Horror|Mystery|Sci-Fi  |
|          Documentary           |
|      Action|Horror|Sci-Fi      |
|  Action|Horror|Mystery|Sci-Fi  |
|          Documentary           |
+--------------------------------+
[15 rows x 6 columns]

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

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

training_data, validation_data = tc.recommender.util.random_split_by_user(ratings, 'userId', 'movieId',item_test_proportion=0.2)
model = tc.recommender.item_similarity_recommender.create(training_data,
                                          user_id='userId',
                                    item_id='movieId',
                                    target='rating')
items_similarity = model.get_similar_items()
Recsys training: model = item_similarity
Warning: Ignoring columns timestamp;
    To use these columns in scoring predictions, use a model that allows the use of additional features.
Preparing data set.
    Data has 80673 observations with 610 users and 8972 items.
    Data prepared in: 0.105496s
Training model from provided data.
Gathering per-item and per-user statistics.
+--------------------------------+------------+
| Elapsed Time (Item Statistics) | % Complete |
+--------------------------------+------------+
| 2.441ms                        | 100        |
+--------------------------------+------------+
Setting up lookup tables.
Processing data in one pass using dense lookup tables.
+-------------------------------------+------------------+-----------------+
| Elapsed Time (Constructing Lookups) | Total % Complete | Items Processed |
+-------------------------------------+------------------+-----------------+
| 311.493ms                           | 0                | 3               |
| 1.47s                               | 100              | 8972            |
+-------------------------------------+------------------+-----------------+
Finalizing lookup tables.
Generating candidate set for working with new users.
Finished training in 1.51143s

Прежде чем оценивать модель, используя серьезные KPI, давайте эмпирически протестируем с фильмом «Чужой» (movieId 1214).

(items_similarity[(items_similarity['movieId'] == 1214)]).join(right=movies,on={'similar':'movieId'},how='inner').sort('rank', ascending=True).print_rows()
+---------+---------+---------------------+------+
| movieId | similar |        score        | rank |
+---------+---------+---------------------+------+
|   1214  |   1200  |  0.517241358757019  |  1   |
|   1214  |   1097  |  0.3395061492919922 |  2   |
|   1214  |   1089  | 0.33529412746429443 |  3   |
|   1214  |   1210  | 0.32692307233810425 |  4   |
|   1214  |   1198  |  0.3051643371582031 |  5   |
|   1214  |   1136  |  0.2971428632736206 |  6   |
|   1214  |   1387  | 0.28767120838165283 |  7   |
|   1214  |   1653  |  0.2789115905761719 |  8   |
|   1214  |   260   |  0.273809552192688  |  9   |
|   1214  |   1213  |  0.273809552192688  |  10  |
+---------+---------+---------------------+------+
+-------------------------------+--------------------------------+
|             title             |             genres             |
+-------------------------------+--------------------------------+
|         Aliens (1986)         | Action|Adventure|Horror|Sci-Fi |
| E.T. the Extra-Terrestrial... |     Children|Drama|Sci-Fi      |
|     Reservoir Dogs (1992)     |     Crime|Mystery|Thriller     |
| Star Wars: Episode VI - Re... |    Action|Adventure|Sci-Fi     |
| Raiders of the Lost Ark (I... |        Action|Adventure        |
| Monty Python and the Holy ... |    Adventure|Comedy|Fantasy    |
|          Jaws (1975)          |         Action|Horror          |
|         Gattaca (1997)        |     Drama|Sci-Fi|Thriller      |
| Star Wars: Episode IV - A ... |    Action|Adventure|Sci-Fi     |
|       Goodfellas (1990)       |          Crime|Drama           |
+-------------------------------+--------------------------------+
[10 rows x 6 columns]

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

model.evaluate(validation_data)
Overall RMSE: 3.5057222562438963

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

model = tc.recommender.ranking_factorization_recommender.create(training_data,
                                          user_id='userId',
                                    item_id='movieId',
                                    target='rating')
results = model.recommend(k=3)
Recsys training: model = ranking_factorization_recommender
Preparing data set.
    Data has 80673 observations with 610 users and 8972 items.
    Data prepared in: 0.133147s
Training ranking_factorization_recommender for recommendations.
+--------------------------------+--------------------------------------------------+----------+
| Parameter                      | Description                                      | Value    |
+--------------------------------+--------------------------------------------------+----------+
| num_factors                    | Factor Dimension                                 | 32       |
| regularization                 | L2 Regularization on Factors                     | 1e-09    |
| solver                         | Solver used for training                         | adagrad  |
| linear_regularization          | L2 Regularization on Linear Coefficients         | 1e-09    |
| ranking_regularization         | Rank-based Regularization Weight                 | 0.25     |
| max_iterations                 | Maximum Number of Iterations                     | 25       |
+--------------------------------+--------------------------------------------------+----------+
  Optimizing model using SGD; tuning step size.
  Using 10084 / 80673 points for tuning the step size.
+---------+-------------------+------------------------------------------+
| Attempt | Initial Step Size | Estimated Objective Value                |
+---------+-------------------+------------------------------------------+
| 0       | 16.6667           | Not Viable                               |
| 1       | 4.16667           | Not Viable                               |
| 2       | 1.04167           | Not Viable                               |
| 3       | 0.260417          | Not Viable                               |
| 4       | 0.0651042         | 1.10129                                  |
| 5       | 0.0325521         | 1.53943                                  |
| 6       | 0.016276          | 1.89349                                  |
| 7       | 0.00813802        | 1.97929                                  |
+---------+-------------------+------------------------------------------+
| Final   | 0.0651042         | 1.10129                                  |
+---------+-------------------+------------------------------------------+
Starting Optimization.
+---------+--------------+-------------------+-----------------------+-------------+
| Iter.   | Elapsed Time | Approx. Objective | Approx. Training RMSE | Step Size   |
+---------+--------------+-------------------+-----------------------+-------------+
| Initial | 226us        | 2.32822           | 1.08971               |             |
+---------+--------------+-------------------+-----------------------+-------------+
| 1       | 451.511ms    | 2.1881            | 1.16773               | 0.0651042   |
| 2       | 939.443ms    | 1.90281           | 1.08491               | 0.0651042   |
| 3       | 1.36s        | 1.76298           | 1.02327               | 0.0651042   |
| 4       | 1.84s        | 1.65405           | 0.987049              | 0.0651042   |
| 5       | 2.26s        | 1.5937            | 0.965333              | 0.0651042   |
| 10      | 4.35s        | 1.43729           | 0.899279              | 0.0651042   |
| 20      | 8.51s        | 1.1761            | 0.8083                | 0.0651042   |
| 25      | 10.58s       | 1.07333           | 0.766321              | 0.0651042   |
+---------+--------------+-------------------+-----------------------+-------------+
Optimization Complete: Maximum number of passes through the data reached.
Computing final objective value and training RMSE.
       Final objective value: 1.04304
       Final training RMSE: 0.734723

Многое происходит за кулисами… Алгоритм пытается учиться на скрытых функциях и минимизировать ошибку (RMSE), используя стохастический градиентный спуск при оптимизации скорости обучения. Мы видим, что окончательное среднеквадратичное значение составляет 0,73.

Посмотрим некоторые данные ...

def join_titles(sframe,on):
    return sframe.join(right=movies, on=on, how='inner')

results = join_titles(results,'movieId')
results.sort(['userId','rank'], ascending=True).print_rows(20)
+--------+---------+--------------------+------+-------------------------------+
| userId | movieId |       score        | rank |             title             |
+--------+---------+--------------------+------+-------------------------------+
|   1    |   296   | 5.4481021343133005 |  1   |      Pulp Fiction (1994)      |
|   1    |   318   |  5.38932802065821  |  2   | Shawshank Redemption, The ... |
|   1    |   858   | 5.369748759116844  |  3   |     Godfather, The (1972)     |
|   2    |   1198  | 4.917704129159317  |  1   | Raiders of the Lost Ark (I... |
|   2    |   356   | 4.897511178195343  |  2   |      Forrest Gump (1994)      |
|   2    |   260   | 4.891662919461593  |  3   | Star Wars: Episode IV - A ... |
|   3    |   541   | 5.270915883626655  |  1   |      Blade Runner (1982)      |
|   3    |   1394  | 5.122784393872932  |  2   |     Raising Arizona (1987)    |
|   3    |    50   | 4.789787578429893  |  3   |   Usual Suspects, The (1995)  |
|   4    |   1193  | 5.191378074731544  |  1   | One Flew Over the Cuckoo's... |
|   4    |   318   | 5.121037292327598  |  2   | Shawshank Redemption, The ... |
|   4    |   1247  | 5.0619253991505655 |  3   |      Graduate, The (1967)     |
|   5    |   2959  | 4.786239120211318  |  1   |       Fight Club (1999)       |
|   5    |   7361  | 4.614468338932708  |  2   | Eternal Sunshine of the Sp... |
|   5    |   1193  | 4.590772422995284  |  3   | One Flew Over the Cuckoo's... |
|   6    |   1197  | 4.813408214050211  |  1   |   Princess Bride, The (1987)  |
|   6    |    50   | 4.795824933248438  |  2   |   Usual Suspects, The (1995)  |
|   6    |   2858  | 4.793457645374216  |  3   |     American Beauty (1999)    |
|   7    |   1198  |  5.20988603219004  |  1   | Raiders of the Lost Ark (I... |
|   7    |   2571  | 5.106401997651771  |  2   |       Matrix, The (1999)      |
+--------+---------+--------------------+------+-------------------------------+
+-------------------------------+
|             genres            |
+-------------------------------+
|  Comedy|Crime|Drama|Thriller  |
|          Crime|Drama          |
|          Crime|Drama          |
|        Action|Adventure       |
|    Comedy|Drama|Romance|War   |
|    Action|Adventure|Sci-Fi    |
|     Action|Sci-Fi|Thriller    |
|             Comedy            |
|     Crime|Mystery|Thriller    |
|             Drama             |
|          Crime|Drama          |
|      Comedy|Drama|Romance     |
|  Action|Crime|Drama|Thriller  |
|      Drama|Romance|Sci-Fi     |
|             Drama             |
| Action|Adventure|Comedy|Fa... |
|     Crime|Mystery|Thriller    |
|         Drama|Romance         |
|        Action|Adventure       |
|     Action|Sci-Fi|Thriller    |
+-------------------------------+
[1830 rows x 6 columns]

Теперь для каждого пользователя есть конкретная рекомендация. Давайте оценим модель на данных проверки

model.evaluate(validation_data)
'rmse_overall': 1.0967441224583008}

Это значение намного ниже (а значит, лучше), чем 3,5, заданное моделью элемент-элемент.

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

Turi Create - не единственная библиотека, которую можно попробовать: есть Surprise (Simple Python Recommended System Engine) и Apache PredictionIO (и, вероятно, многие другие). Зная основы, будет легче тестировать и сравнивать разные решения.

И это все, что касается основного вступления в PR… увидимся в следующий раз!