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

Краткое введение в системы рекомендаций

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

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

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

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

Итак, возвращаясь к моей проблеме, какую книгу мне следует прочитать дальше? Чтобы ответить на этот вопрос, мы построим простую систему рекомендаций с совместной фильтрацией, используя неожиданность и этот набор данных, найденный на Kaggle, который состоит из примерно 6 миллионов записей оценок для 10 000 книг, сделанных 53 424 уникальными пользователями. Помимо данных рейтингов, у нас также есть доступ к метаданным книг, жанрам книг и другим тегам, а также к книгам, отмеченным пользователями как для чтения.

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

Создание системы рекомендаций с удивлением

Прежде всего, если вас не удивляет, вам нужно установить его. Это так же просто, как запустить pip install surprise на вашем Терминале. Surprise поставляется с двумя встроенными наборами данных для упрощения практики, но также поддерживает использование ваших собственных пользовательских данных (обратите внимание, что он не поддерживает неявные рейтинги или информацию на основе контента). Мы загрузим наши данные, и прежде чем подготовить их к моделированию и приступить к выполнению этой задачи, мы должны провести некоторое исследование и анализ данных. Следует искать нулевые значения, выбросы, общие распределения данных и так далее.

Я загрузил и набор данных рейтингов, и метаданные книг. У нас нет нулевых значений для данных оценок, и распределение оценок для каждого пользователя близко к нормальному, со средним числом оценок на пользователя 111,87 книг и в среднем 111 книг, оцененных на пользователя.

С другой стороны, распределение рейтингов по книге, как и ожидалось для задач этого типа, является типом распределения с длинным хвостом. Некоторые книги очень популярны с большим количеством оценок, тогда как большинство книг имеют меньшее количество оценок. Разница между средним и медианным количеством оценок за книгу, составляющая 597,65 248 соответственно, подтверждает это. Именно поэтому система рекомендаций важна и полезна! Чтобы помочь пользователям открыть для себя те не очень популярные книги, которые они бы наверняка полюбили, если бы только знали о них.

Оценки книг выставляются от 1 до 5. Интересно, насколько критично пользователи Goodreads в своих рейтингах. Посмотрим, нет ли дисбаланса в распределении рейтингов.

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

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

# define reader
reader = Reader(rating_scale=(1, 5))
# load dataframe into correct format for surprise library
data = Dataset.load_from_df(ratings[['user_id', 'book_id', 'rating']], reader)

Затем мы разделяем данные на набор для обучения и тестирования, чтобы оценить наши модели, а также избежать утечки данных. Я выделяю 20% данных для тестирования.

# Split into train and test set
trainset, testset = train_test_split(data, test_size=0.2, random_state=0)

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

# confirm number of items and users in data
print('Number of users: ', trainset.n_users, '\n')
print('Number of items: ', trainset.n_items, '\n')
Number of users:  53424 

Number of items:  10000

Метод на основе памяти или соседства

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

# define a cosine metric for item-item
sim_cos = {'name':'cosine', 'user_based':False}
# define and fit a basic KNN model with a cosine metric
basic = knns.KNNBasic(sim_options=sim_cos)
basic.fit(trainset)
# make predictions
predictions = basic.test(testset)
# check accuracy
accuracy.rmse(predictions)
RMSE: 0.8827

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

# define fit and evaluate a KNN basic model with pearson correlation metric
sim_pearson = {'name':'pearson', 'user_based':False}
basic_pearson = knns.KNNBasic(sim_options=sim_pearson)
basic_pearson.fit(trainset)
predictions = basic_pearson.test(testset)
accuracy.rmse(predictions)
RMSE: 0.8724

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

# KNN with means model
sim_pearson = {'name':'pearson', 'user_based':False}
knn_baseline = knns.KNNWithMeans(sim_options=sim_pearson)
knn_baseline.fit(trainset)
predictions = knn_baseline.test(testset)
accuracy.rmse(predictions)
RMSE: 0.8406
# KNN baseline model
sim_pearson_baseline = {'name': 'pearson_baseline','user_based':False}#'shrinkage':50, 'min_support':5, 
knn_baseline = knns.KNNBaseline(sim_options=sim_pearson)
knn_baseline.fit(trainset)
predictions = knn_baseline.test(testset)
accuracy.rmse(predictions)
RMSE: 0.8371

Фильтрация совместной работы на основе модели

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

# define a base singular value decomposition model
svd = SVD()
# fit and test algorithm
predictions = svd.fit(trainset).test(testset)
# evaluate model
print(accuracy.rmse(predictions))
RMSE: 0.8306

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

raw_ratings = data.raw_ratings
# shuffle ratings
random.shuffle(raw_ratings)
# A = 90% of the data, B = 20% of the data
threshold = int(.8 * len(raw_ratings))
A_raw_ratings = raw_ratings[:threshold]
B_raw_ratings = raw_ratings[threshold:]
data.raw_ratings = A_raw_ratings  # data is now the set A
# define parameter grid and fit gridsearch on set A data
param_grid = {'n_epochs': [5, 10], 'lr_all': [0.002, 0.005]}
grid_search = GridSearchCV(SVD, param_grid, measures=['rmse'], cv=3)
grid_search.fit(data)
best_svd = grid_search.best_estimator['rmse']
# retrain on the whole set A
trainset = data.build_full_trainset()
best_svd.fit(trainset)
# Compute biased accuracy on A
predictions = best_svd.test(trainset.build_testset())
accuracy.rmse(predictions)
RMSE: 0.7786
# Compute unbiased accuracy on B
testset = data.construct_testset(B_raw_ratings)  # testset is now the set B
predictions = best_svd.test(testset)
print('Unbiased accuracy on B,', end=' ')
accuracy.rmse(predictions)
Unbiased accuracy on B, RMSE: 0.7924

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

Если вы хотите проверить весь код, на котором основано это сообщение, вы можете найти его здесь, на Github.