Мой дополнительный пользовательский код Python, который позволяет вам выполнять более гибкие рекомендации на основе моделей стиля K-Nearest Neighbor библиотеки Surprise.
В моем предыдущем сообщении в блоге я подробно обсуждал, как работать с системой рекомендаций на Python с использованием библиотеки Surprise, от обработки данных до прогнозов и лучших рекомендаций. Я предлагаю вам сначала прочитать этот пост как введение в системы рекомендаций с совместной фильтрацией, если вы не знакомы с этой темой, я не утомлю вас напрасным повторением отрывков из них.
Я начал свой предыдущий пост по аналогии с длинными очередями кексов в кондитерской и с тем, как вы, как потребитель, не можете попробовать их все из-за ограниченных ресурсов, отсюда и важность систем рекомендаций. Продолжая эту аналогию, если после того, как мы проанализировали все доступные кексы, кто-то придумал еще один кекс, мы бы предпочли получить результаты, не начиная весь процесс заново. (И я действительно с нетерпением жду, когда пойму, как продолжить аналогию и какой вид обложки будет соответствовать логике систем рекомендаций, основанных на моделях, о которых я напишу дальше ...)
Одна вещь, которая мне не понравилась во время работы с Surprise, - это то, что для того, чтобы давать рекомендации новому пользователю, вам нужно добавить оценки пользователя к большим обучающим данным, а затем заново подогнать всю вашу модель. Причина этого в том, что модели прогнозирования построены на основе одной и той же структуры классов, а с более сложными моделями, использующими матричную факторизацию, повторная подгонка является единственным вариантом.
В этом посте я покажу, как можно давать рекомендации без повторного запуска модели. Мы собираемся создать рекомендательный класс, который
- принимает параметры, которые вы должны вычислить только один раз для большой популяции с библиотекой Surprise,
- возвращает упорядоченный список рекомендуемых элементов при вводе новых индивидуальных оценок пользователей.
Мы сосредотачиваемся на KNNWithMeans
моделях, это был тип модели, который дал наивысшие RMSE
оценки в моем проекте, но этот метод также можно легко настроить для модели KNNBasic
или KNNWithZScores
. Вы можете найти код всего проекта в моем репозитории на GitHub.
Математический фон
Как я уже упоминал, я не хочу повторять свою предыдущую публикацию, но я думаю, что было бы хорошо иметь быстрое напоминание о математической формуле, лежащей в основе модели KNNWithMeans
. rᵤᵢ - оценка, которую пользователь u поставил элементу i, μᵢ - это среднее значение всех оценок элемента i, Nᵤᵏ (i) - максимальное k количество «ближайших к i» элементов среди элементов, которые пользователь u оценил.
План
Мы собираемся использовать тот факт, что для оценки того, как пользователь u оценил бы элемент i на основе других своих оценок, все, что нам нужно, это:
- средние оценки других элементов или μᵢ;
- сходства между элементами или sim (i, j).
Мы собираемся создать класс с именем KNNWithMeans_Recommender
с тремя параметрами:
sim_dataframe
: объектpandas
DataFrame
, квадратная матрица со сходствами между элементами. Нам не важно, какой метод используется, может бытьcosine
,pearson
илиMSD
. Имена столбцов должны совпадать с именами индексов, именами элементов, в том же порядке.average_ratings_by_item
: apandas
Series
, средняя оценка каждого элемента, где индекс - это название элемента. Это обеспечит часть «WithMeans» моделиKNN
.k_factor
: целое число, ограничивающее количество соседних элементов, которые мы принимаем во внимание при оценке рейтинга.
Мы рассмотрим, как создавать эти параметры позже, а пока мы сосредоточимся на рекомендательном классе. Сохранение этих параметров - это все, что происходит, когда мы запускаем объект.
У класса будет две функции: estimate_all_for_user
и estimate_item
.
По умолчанию мы будем использовать estimate_all_for_user
при взаимодействии с классом. У него будет один параметр user_input_dict
, который представляет собой словарь, в котором имена элементов являются ключами, а рейтинги - значениями. (Мы увидим пример позже.) Функция перебирает элементы, которые не были оценены пользователем, и оценивает оценки с помощью функции estimate_item
. Функция вернет словарь, где ключи - это названия элементов, а значения - оценочные рейтинги.
Другая функция, estimate_item
, будет в основном математической формулой, которую мы видели ранее, с использованием сходства и средних оценок по каждому элементу.
Код
Следующий блок содержит код Python для только что описанного класса.
Обратите внимание, что я опустил большую часть строк документации и комментариев из приведенного выше Gist, учитывая, что мы только что обсудили структуру. Если вы все же хотите использовать код, я рекомендую скопировать оригинал из моего репозитория GitHub. (Есть некоторые различия в именах, мой проект изначально был системой рекомендаций по настольным играм, поэтому предмет и игра используются как синонимы.)
Как использовать - Входные параметры
Во-первых, мы собираемся обсудить, как вы можете создать параметры, необходимые для объекта ourKNNWithMeans_Recommender
.
Начнем с average_ratings_by_item
, нам нужен pandas
Series
, где названия элементов - это индексы, а рейтинги - значения. Я не думаю, что вы можете сгенерировать это из библиотеки surprise
, но вы можете просто использовать метод groupby
, предполагая, что у вас есть все оценки в объекте с именем df
:
avg_ratings_by_item = \ df.groupby('item_name').mean()['rating'].copy()
sim_dataframe
немного сложнее. Я собираюсь сначала сохранить все в файл, чтобы его было легче использовать позже. Нам нужны две вещи: сходство и названия предметов.
В surprise
модели типа kNN имеют sim
метод, который возвращает матрицу подобия в формате numpy
array
без индексов. Я сохранил его в csv
файле под названием pearson_sim, используя numpy
.
my_sim_option = {'name':'pearson', 'user_based':False} model = KNNWithMeans(sim_options = my_sim_option, verbose = False) model.fit(trainsetfull) np.savetxt('./results/pearson_sim.csv', model.sim, delimiter=',')
Обратите внимание на две вещи:
- В приведенном выше коде я использую сходство
pearson
, но есть и другие, см. Документацию. Я использовалpearson
, потому что это привело к наивысшейRMSE
оценке в моем проекте. - Вероятно, не имеет значения, какую KNN-модель вы используете из библиотеки Surprise для этого шага, сходство вычисляется аналогичным образом. Чтобы оставаться последовательными, мы можем использовать
KNNWithMeans
.
Следующим шагом будет получение имен элементов, они будут использоваться в качестве индексов и имен столбцов. Мы можем использовать функции all_items
и to_raw_iid
библиотеки Surprise (подробности еще раз см. В моем предыдущем посте).
trainsetfull_iids = list(trainsetfull.all_items()) iid_converter = lambda x: trainsetfull.to_raw_iid(x) trainsetfull_raw_iids = list(map(iid_converter, trainsetfull_iids))
Я также сохранил их в файле csv
:
with open('./results/item_ids_for_sim_matrix.csv', 'w') as f: writer = csv.writer(f) writer.writerows(zip(trainsetfull_raw_iids, trainsetfull_iids))
Позже в проекте я объединил два файла, рейтинги и списки элементов в aDataFrame
под названием pearson_sim
. Это будет параметр, который мы передадим, когда мы создадим наш рекомендательный объект.
pearson_sim = \ pd.read_csv('./results/pearson_sim.csv', header = None) sim_matrix_itemlist = \ pd.read_csv( './results/item_ids_for_sim_matrix.csv', header = None) pearson_sim.columns = sim_matrix_itemlist[0] pearson_sim.index = sim_matrix_itemlist[0]
Сохранение результатов в файлах и их объединение - не самое эффективное решение, но я обнаружил, что это чистая структура, и стало удобнее работать с этими результатами в различных областях процесса.
Как использовать - Прогнозы
Хорошо, вот и самое интересное!
Во-первых, нам нужно импортировать класс, предполагая, что вы назвали файл Recomm_func.py:
from recomm_func import kNNWithMeans_Recommender
Затем вы можете инициировать новый объект, передав два только что обсужденных входных параметра и целое число для k_factor
:
recommender = kNNWithMeans_Recommender( pearson_sim, average_ratings_by_item, 10)
Как я уже упоминал, я использовал проект для создания рекомендателя настольных игр. Один из примеров пользовательского ввода: если вы увлекаетесь настольными играми, вы можете видеть, что этот тестовый пользователь предпочитает заголовки в европейском стиле:
user_input = { 'Le Havre': 8, 'Concordia': 9, 'The Castles of Burgundy': 8, 'Mansions of Madness: Second Edition': 5, 'Kingdom Death: Monster':6 }
Метод estimate_all_for_user
возвращает словарь со всеми элементами и оценочными рейтингами, а следующая строка в нижеследующем блоке упорядочивает их по рейтингу, чтобы мы могли легко определить N лучших игр, рекомендованных для пользователя.
estimated_ratings = recommender.estimate_all_for_user(user_input) sorted_ratings = \ sorted(estimated_ratings.items(), key=lambda x: x[1])
Мы можем получить 10 лучших рекомендаций, нарезав sorted_ratings:
sorted_ratings[-10:]
вернет следующий список:
[('Agricola', 7.436929246134879), ('Agricola (Revised Edition)', 7.456466489174002), ('Through the Ages: A New Story of Civilization', 7.46818223297934), ('Orléans', 7.4808225652209), ('Terra Mystica', 7.498203401133102), ('Terraforming Mars', 7.5063889188525685), ('Gaia Project', 7.621611348138763), ('Puerto Rico', 7.665136353318606), ('Great Western Trail', 7.786750410443334), ('Brass: Birmingham', 7.82265622044594)]
Еще раз, как фанат настольных игр, я считаю, что рекомендации имеют смысл для этого конкретного тестового пользователя.
Код также можно использовать для создания антирекомендаций, элементов, которые, вероятно, не понравятся пользователю, путем разрезания списка sorted_ratings
спереди. Если вас интересуют другие рекомендации тестовых пользователей, загляните в конец этого блокнота Jupyter.
Вывод
На этом мы завершаем нашу работу над функциональностью настраиваемого рекомендателя. На мой взгляд, после настройки с классом действительно удобно работать, и это значительно упростило структуру кода моего проекта. Еще одно преимущество заключается в том, что вы можете использовать результаты библиотеки Surprise, сохраненные на более раннем этапе, без необходимости полагаться на библиотеку в вашей текущей среде.
Надеюсь, вы сочли это полезным. Не стесняйтесь использовать код в своих проектах и дайте мне знать, если у вас возникнут какие-либо проблемы.