Мой дополнительный пользовательский код Python, который позволяет вам выполнять более гибкие рекомендации на основе моделей стиля K-Nearest Neighbor библиотеки Surprise.

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

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

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

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

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

Мы сосредотачиваемся на KNNWithMeans моделях, это был тип модели, который дал наивысшие RMSE оценки в моем проекте, но этот метод также можно легко настроить для модели KNNBasic или KNNWithZScores. Вы можете найти код всего проекта в моем репозитории на GitHub.

Математический фон

Как я уже упоминал, я не хочу повторять свою предыдущую публикацию, но я думаю, что было бы хорошо иметь быстрое напоминание о математической формуле, лежащей в основе модели KNNWithMeans. rᵤᵢ - оценка, которую пользователь u поставил элементу i, μᵢ - это среднее значение всех оценок элемента i, Nᵤᵏ (i) - максимальное k количество «ближайших к элементов среди элементов, которые пользователь u оценил.

План

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

  • средние оценки других элементов или μᵢ;
  • сходства между элементами или sim (i, j).

Мы собираемся создать класс с именем KNNWithMeans_Recommender с тремя параметрами:

  • sim_dataframe: объект pandas DataFrame, квадратная матрица со сходствами между элементами. Нам не важно, какой метод используется, может быть cosine, pearson или MSD. Имена столбцов должны совпадать с именами индексов, именами элементов, в том же порядке.
  • average_ratings_by_item: a pandas 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, сохраненные на более раннем этапе, без необходимости полагаться на библиотеку в вашей текущей среде.

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

использованная литература