RecTools – OpenSource библиотека для рекомендательных систем

1b72c3e52e5ec0a878cda42098bac20a.png

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

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

К такому же выводу, видимо, пришли ребята из МТС — и выкатили в опенсурс RecTools. Это библиотека, где собраны самые часто используемые модели для рекомендательных систем. Также с её помощью можно максимально просто и быстро оценивать необходимые метрики. 

Давайте же посмотрим, что RecTools умеет, и как с этим работать.

Установка

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

pip install rectools

Помимо этого, можно установить дополнительные расширения:

  • LightFM: wrapper для модели LightFM

  • torch: модели на основе нейронных сетей

  • nmslib: fast ANN рекомендации

Как установить расширение:

pip install rectools[extension-name]

Если хотите установить все расширения сразу:

pip install rectools[all]

Как подавать данные

Мы уже упоминали, что разные библиотеки зачастую требуют разные форматы данных на вход. RecTools решает и эту проблему. Внутри реализован контейнер, который агрегирует всё, что есть по задаче. Так что обработали один раз — и подаем во все модели одинаково.

Формат данных на вход

Формат данных на вход

По факту от пользователя требуется обычная таблица, где каждая строка отражает одно взаимодействие: в первом столбце id юзера, во втором — айтема, а в третьем — скор взаимодействия (например, купил/ не купил). Если есть данные по времени, их тоже можно добавить.

Из особенностей: имена столбцов фиксированы. Поэтому колонки нужно будет переименовать с помощью класса Columns. Затем остается просто обернуть все в Dataset и готово:

from rectools import Columns

ratings = pd.read_csv(
    "ml-1m/ratings.dat",
    sep="::",
    engine="python",  
    header=None,
    names=[Columns.User, Columns.Item, Columns.Weight, Columns.Datetime],
)

# Prepare a dataset to build a model
dataset = Dataset.construct(ratings)

Модели библиотеки

В библиотеке реализовано единое API для всех широко известных моделей: Implicit ItemKNN, ALS, SVD, Lightfm, DSSN и пр. То есть вся разнообразная механика инструментов оказывается спрятанной под капотом, а сами модели работают из коробки — с помощью единых методов fit — recommend.

Модель «популярное»

Это база. Алгоритм работает просто: на основе данных делает выводы о том, с какими продуктами пользователи чаще всего взаимодействуют (смотрят или покупают) и рекомендует их всем. Конечно, особой положительной репутацией метод не обладает, но его можно улучшить с помощью простого трюка. Нужно всего лишь рекомендовать с некоторой рандомизацией. Например, каждый раз брать 10 случайных айтемов из топ-100 самых популярных. Называется reranking diversity.

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

Короче, если ищете, с чего начать — начните с популярного. В RecTools реализовать модельку можно вот так:

from rectools.models import PopularModel

# Fit model and generate recommendations for all users

model = PopularModel()
model.fit(dataset)
recos = model.recommend(
    users=ratings[Columns.User].unique(),
    dataset=dataset,
    k=10,
    filter_viewed=True,
)

1b75f54186f2cffb3d52cffdad4f437a.png

Матричные разложения

Матричные разложения — это такие методы коллаборативной фильтрации. Вкратце: это прогнозирования действий пользователя на основе действий других пользователей с похожим поведением. Представим, что у нас есть некоторая матрица user-item. Строки — пользователи, столбцы — айтемы. На пересечении пользователя и айтема стоит число, отражающее взаимодействие.

685227b34aff1ee064df8d523de6e9ce.png

На основе такой матрицы мы можем описать поведение пользователей и характеристики айтемов. Для этого используются методы спектрального разложения. Один из них — алгебраическое разложение матриц, SVD (singular value decomposition). В основе — разложение исходной матрицы в произведение 3 других:

M = U*D*S

Здесь U и S — матрицы представлений пользователей и айтемов соответственно. Используя эти матрицы, далее мы можем получать рекомендации.

В RecTools этот инструмент можно использовать с помощью класса IPureSVDModel:

from rectools.models import PureSVDModel

# Fit model and generate recommendations for all users
model = PureSVDModel()
model.fit(dataset)
recos = model.recommend(
    users=ratings[Columns.User].unique(),
    dataset=dataset,
    k=10,
    filter_viewed=True,
)

8d4dfca8397dad5730f68764771d49ce.png

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

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

Использование этого алгоритма из волшебной коробки RecTools немного отличается от предыдущих алгоритмов. Так как эта имплементация — просто обертка для модели из implicit, на вход объекту класса нужно подать Base model (подробнее тут), внутри которой уже можно указать необходимые параметры, вот так:

from rectools.models import ImplicitALSWrapperModel
from implicit.als import AlternatingLeastSquares

model = ImplicitALSWrapperModel(
        AlternatingLeastSquares(
            factors=64,
            regularization=0.01,
            alpha=1,
            random_state=2023,
            use_gpu=False,
            iterations=15))

Обучение и получение же самих рекомендаций не меняется — для этого, как и прежде, можно использовать методы fit и recommend.

Implicit KNN

Помимо обертки для модели ALS из библиотеки implicit, в RecTools также представлен wrapper для модели item-to-item KNN recommender оттуда же. Суть такова: ищем в матрице по методу ближайших соседей похожих на нашего пользователя людей, и усредняем их оценки айтема — получаем оценку для целевого юзера.

from rectools.models import ImplicitItemKNNWrapperModel
from implicit.nearest_neighbours import TFIDFRecommender

model = ImplicitItemKNNWrapperModel(
        model=TFIDFRecommender(K=5)
        )

model.fit(dataset)

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

LightFM

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

Воспользоваться моделью по-прежнему просто, только не забудьте установить библиотеку lightfm с помощью pip install lightfm и соответствующе расширение для RecTools.

from rectools.models import LightFMWrapperModel
from lightfm import LightFM

model = LightFMWrapperModel(
        # внутри модели указываем параметр no_components
        # это размезность эмбеддингов, которые выучит модель
        model=LightFM(no_components = 30)
        )

model.fit(dataset)
recos = model.recommend(
    users=ratings[Columns.User].unique(),
    dataset=dataset,
    k=10,
    filter_viewed=True,
)

Подробнее про алгоритм можно прочитать в оригинальной статье 2015 года.

DSSM

Мы уже поняли, что если пользователи и айтемы описываются векторами одного пространства, то релевантность айтема пользователю описывается близостью их векторов. Получается, поиск топ-к самых релевантных айтемов сводится к поиску k ближайших соседей. Это называется Approximate KNN.

То есть, нам нужна модель, которая моделирует релевантность с помощью скалярного произведения эмбеддингов. Одна из таких моделей — DSSM — была придумана в 2013 году исследователями из Microsoft. Идея этой простой нейросети такая: давайте сделаем две «башни» с полносвязными слоями и нелинейностями: одна башня будет для юзеров, вторая для айтемов.

15ff27909dd63c76a307dbeb5c794a3a.png

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

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

from rectools.models import DSSMModel

model = DSSMModel(dataset, 
                  max_epochs = 10,
                  batch_size = 64
                 )
model.fit(dataset)

Добавление атрибутов в модели

Ну, а что, если мы хотим учитывать не только взаимодействия пользователей и объектов, но и какие-то их отличительные признаки — атрибуты? Понятно ведь, что часто одинаковые товары нравятся людям одного возраста, пола, профессии и пр. А для объектов атрибутами могут стать производитель, цена и многое другое, в зависимости от того, что именно мы рекомендуем.

В RecTools есть функционал, который позволяет добавлять фичи пользователей и айтемов во многие перечисленные в предыдущем разделе модели (iALS, LightFM, DSSM, PopularInCategory).

Для начала нужно приготовить данные о юзерах и/или айтемах специальным образом. Во-первых, все признаки нужно «вытянуть» в одну колонку value, пометив каждое значение названием фичи, к которой оно относится.

# загружаем данные
users = pd.read_csv(
    "ml-1m/users.dat",
    sep="::",
    engine="python", 
    header=None,
    names=[Columns.User, "sex", "age", "occupation"],
)

# отбираем только тех юзеров, которые есть в таблице взаимодействий 
users = users.loc[users["user_id"].isin(ratings["user_id"])].copy()

# вытягиваем фичи в одну колонку и метим
user_features_frames = []
for feature in ["sex", "age", "occupation"]:
    feature_frame = users.reindex(columns=["user_id", feature])
    feature_frame.columns = ["id", "value"]
    feature_frame["feature"] = feature
    user_features_frames.append(feature_frame)
user_features = pd.concat(user_features_frames)

Результат работы кода

Результат работы кода

После остается заново обернуть основной датасет в специальный класс, при этом указав значения аргументов, которые отвечают за фичи. Если у вас представлены только числовые фичи, то можно поставить make_dense_user_features=True. Иначе стоит придать этому аргументу значение False — это будет соответствовать sparse-формату представления данных.

sparse_features_dataset = Dataset.construct(
    ratings,
    # датасет фич
    user_features_df=user_features,
    # к этим фичам будет применен One Hot Encoding
    cat_user_features=["sex", "age"],
    # для `sparse` формата
    make_dense_user_features=False 
)

После этого мы снова готовы скармливать датасет моделям (здесь никаких изменений в коде нет). Например:

model = ImplicitALSWrapperModel(AlternatingLeastSquares(10, num_threads=32))
model.fit(sparse_features_dataset)

Метрики

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

«Мы решили использовать подход, который позволяет считать метрики на основе табличных данных. Это похоже на Single Instruction Multiple Data. То есть это инструкции в самом процессоре, которые могут применять одну и ту же простую операцию (типа сложения и умножения) к нескольким блокам данных. Это занимает меньше кода, сохраняет интерпретируемость, так ещё и работает в сотни раз быстрее.»

Вот так вы можете посчитать, например, классическую ndcg для ваших рекомендаций:

ndcg = NDCG(k=10, log_base=3)

print("NDCG: ", ndcg.calc(reco=recos, interactions=df_test))
# 0.068

Помимо этой метрики, в библиотеке также представлены: Accuracy, F1Beta, IntraListDiversity, MAP, MCC, MRR, MeanInvUserFreq, Precision, Recall, Serendipity.

Заключение

RecTools — отличная библиотека, которая собрала в себе все самые необходимые модели, метрики и инструменты для построения рекомендательных систем. Туда также внедрены методы ускорения работы рекомендаций, многие модели поддерживают GPU.

Библиотека постоянно дорабатывается и оптимизируется, так что не поскупитесь — поставьте ребятам звездочку на GitHub:)

Дополнительные материалы

Если хотите глубже погрузиться в тему, вам будут полезны следующие ресурсы:

© Habrahabr.ru