Решайте свои проблемы, экономьте время и избегайте ошибок

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

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

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

Набор данных

Для начала нам нужен минимальный доступный набор данных, состоящий из 3 объектов: user, item, rating. Каждая запись этого набора данных расскажет нам о взаимодействии пользователя с элементом. Для этой статьи мы выбрали набор данных MovieLens [1] (используется с разрешения) со 100 тыс. записей, содержащих 943 уникальных пользователя и 1682 фильма. Для этого набора данных user - UserId, item - MovieId, rating - Rating.

MovieLens также содержит метаданные для каждого фильма. Здесь есть информация по жанрам. Это понадобится нам для интерпретации предсказаний.

Предварительная обработка

Здесь есть специальные шаги предварительной обработки для рекомендательной системы. Я хочу пропустить очевидные шаги, такие как удаление элементов NaN, удаление повторяющихся элементов, очистка данных.

Генерация рейтинга

Если у нас нет рейтинга, то создайте столбец рейтинга со значением 1.

df['rating'] = 1

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

Кодировка объекта. Если у вас есть объекты в полях элемента и пользователя, эти поля необходимо преобразовать в числовой формат. Отличный способ добиться этого — использовать LabelEncoder.

from sklearn.preprocessing import LabelEncoder
u_transf = LabelEncoder()
item_transf = LabelEncoder()
# encoding
df['user'] = u_transf.fit_transform(df['user'])
df['item'] = item_transf.fit_transform(df['item'])
# decoding
df['item'] = item_transf.inverse_transform(df['item'])
df['user'] = u_transf.inverse_transform(df['user'])

Индекс разреженности

Индекс разреженности должен быть снижен для качественного обучения модели.

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

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

Уменьшение разреженности имеет решающее значение для обучения. Допустим, вы загрузили свои данные и пытаетесь обучить модель, и вы получаете крайне низкие показатели. Вам не нужно начинать искать специальные гиперпараметры или искать другие лучшие модели. Начните с проверки индекса разреженности.

Из графика видно, что снижение этого показателя почти на 2% очень положительно сказывается на показателях.

На графике видно, что 81% пользователей неактивны (посмотрели менее 20 фильмов). Их необходимо удалить. А поможет нам в этом эта функция:

def reduce_sparsity(df, min_items_per_user, min_user_per_item, user_col=USER_COL, item_col=ITEM_COL):
    good_users = df[user_col].value_counts()[df[user_col].value_counts() > min_items_per_user].index
    df = df[df[user_col].isin(good_users)]

    good_items = df[item_col].value_counts()[df[item_col].value_counts() > min_user_per_item].index
    df = df[df[item_col].isin(good_items)].reset_index(drop=1)

    return df

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

Выбор метрик

Есть хорошие статьи с подробным описанием популярных метрик для рекомендательных систем. Например, Системы рекомендаций: метрики машинного обучения и бизнес-метрики Зузанны Дойчман и Автоматическая оценка систем рекомендаций: охват, новизна и разнообразие Захры Ахмад. В этой статье я решил сосредоточиться на 4 метриках, которые могут служить минимальным набором для начала работы.

Precision@k

P = (соответствующие элементы) / k

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

MAP (средняя средняя точность)

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

Покрытие

Охват = количество уникальных элементов в рекомендациях / все уникальные элементы

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

Разнообразие

Цель этой метрики — рассчитать, насколько разнообразны рекомендации.

В статье Захры Ахмад Автоматическая оценка систем рекомендаций: покрытие, новизна и разнообразие разнообразие — это среднее сходство для top_n.

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

Интерпретация показателей

Есть отличный репозиторий по рекомендательным системам, который содержит не только сами модели, но и анализ метрик. Изучение этой таблицы дает нам понимание возможного диапазона метрик и интуицию в оценке модели. Например, значения Precision@k ниже 0,02 в большинстве случаев следует считать плохими.

Таким образом, у нас есть показатели качества, привязанные и не привязанные к рангу. Есть метрики, косвенно отвечающие за бизнес и деньги, а также за использование и доступность контента. Теперь можно перейти к выбору модели.

Выбор модели

Разложение матрицы ALS

Это отличная модель для начала. Написанный в Спарке алгоритм относительно прост

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

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

Двусторонний вариационный автоэнкодер (BiVAE)

Модель основана на статье Двусторонний вариационный автоэнкодер для совместной фильтрации Quoc Tuan TRUONG, Aghiles SALAH, Hady W. LAUW.

Модель во многом аналогична предыдущей — в процессе обучения обучается матрица пользователей Theta и матрица блоков Beta.

Но структура модели намного сложнее, чем в ALS. У нас есть кодировщик User и кодировщик Item, состоящие из последовательности линейных слоев. Их задача — обучать скрытые переменные Theta и Beta соответственно. Декодирование и вывод выполняются путем скалярного умножения этих двух переменных. Функция ошибки (в данном случае нижняя граница доказательства) дважды подсчитывается между созданными пользовательскими векторами и фактическими значениями, затем то же самое делается для кодировщика элементов.

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

Самый популярный

Да, самая простая модель, а в некоторых ситуациях и самая эффективная.

df[item_col].value_counts()[:top_n]

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

Случайная модель

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

Итак, для эксперимента мы выбрали 4 модели. Один оптимизирован по скорости, другой по качеству и 2 других для сравнения и лучшего понимания результатов. Теперь мы готовы начать обучение.

Обучение модели

Мы будем обучать сразу 4 модели, чтобы их было удобно сравнивать. Мы будем использовать настройки, которые были в репозитории recomenders.

import json
from pathlib import Path

import pandas as pd
from sklearn.model_selection import train_test_split

from models import RandomModel, MostPopular, ModelALS, BIVAE, evaluate
from setup import ITEM_COL, TOP_K_METRICS, TOP_K_PRED


def main(out_folder='outputs'):
    df = pd.read_csv('personalize.csv.zip', compression='zip').iloc[:, :3]

    genres = pd.read_csv('movies.csv').rename({"movieId": ITEM_COL}, axis=1).dropna()

    train, test = train_test_split(df, test_size=None, train_size=0.75, random_state=42)

    metrics = {}
    for model_cls in [BIVAE, ModelALS, RandomModel, MostPopular]:
        model = model_cls()
        model.fit(train)

        preds = model.transform(TOP_K_PRED)

        preds.to_csv(Path(out_folder) / f"{model_cls.__name__}_preds.csv", index=False)
        metrics[model_cls.__name__] = evaluate(train, test, preds, genres, TOP_K_METRICS)

    with open('outputs/metrics.json', 'w') as fp:
        json.dump(metrics, fp)


if __name__ == "__main__":
    main()

Вывод

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

Затем:

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

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

Анализ результатов

Как мы видим, модель BIVAE показала лучшие показатели точности, она смогла очень точно подстроиться под вкусы пользователей. И у хорошей точности есть обратная сторона — покрытие и разнообразие хуже, чем у модели ALS. Это означает, что огромное количество контента, скорее всего, никогда не увидят пользователи. В данном конкретном случае BIVAE все же выглядит предпочтительнее.

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

То же самое можно сказать и о модели MostPopular, эта модель имеет лучшую производительность, чем модель машинного обучения ALS. И кажется, зачем вся эта сложность ML, когда у нас есть готовая модель! Но если внимательно посмотреть то мы видим, что Охват очень низкий, и обычно с увеличением контента процент будет падать еще больше, например у нас всего 1682 фильма, но что будет если завтра бизнес решит расширить библиотеку на 100к фильмы? Процент покрытия для этой модели упадет еще больше. Это же правило работает и в обратную сторону — чем меньше у вас данных, тем больше вероятность того, что сработает простая модель MostPopular.

Также интересно рассмотреть RandomModel, так как с точки зрения метрики Precision он выглядит не так уж плохо по сравнению с ALS, а его покрытие составляет 100%. Опять же, не торопитесь с выводами. Этому успеху способствует небольшое количество фильмов в этом наборе данных.

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

Советы по отладке машинного обучения

Иногда отладка мл может быть очень сложной. Где и как искать проблемы, если метрики не очень? В коде? В данных? В выборе модели или ее гиперпараметров?

Есть несколько советов:

  1. Если у вас низкие метрики, например PrecisionK ниже 0,1, и вы не знаете, в чем причина — данные или модель, а может расчет метрики, вы можете взять набор данных MovieLens и обучить на нем свою модель. Если его метрики низкие и на MovieLens тоже, то причина в модели, если метрики хорошие, то вероятная причина кроется в этапах препроцессинга и постобработки.
  2. Если метрики «Случайная модель» и «Самая популярная модель» близки к моделям машинного обучения, стоит проверить данные — возможно, количество уникальных элементов слишком мало. Это также может произойти, если у нас очень мало данных или, возможно, в обучающем коде есть ошибка.
  3. Значения выше 0,5 для PrecisionK выглядят слишком высокими, и стоит проверить, нет ли ошибки в скрипте или мы слишком сильно занижаем индекс разреженности.
  4. Всегда сравнивайте, сколько пользователей и элементов у вас осталось после снижения индекса разреженности. Иногда в погоне за качеством можно потерять практически всех пользователей, поэтому стоит искать компромисс.

Оценка необходимых улучшений

Итак, что нам делать дальше, если мы хотим запустить модель в производство? Нам нужно выяснить, что еще нам нужно сделать и сколько работы нам нужно сделать.

Оптимизация BIVAE и расширение функционала

  • Механизмов холодного пуска из коробки нет. С учетом того, что мы снизили индекс разреженности, то есть у многих пользователей просто не будет прогнозов.
  • Необходимо реализовать алгоритм пакетного прогнозирования. Сейчас реализован механизм прогнозирования одного пользователя, т.е. batch_size=1, естественно это сильно замедляет скорость работы.
  • Использование метаданных о пользователях и объектах (фильмах).

Постобработка

  • Могут потребоваться расширенные алгоритмы диверсификации данных. Которое иногда может быть сравнимо со временем разработки модели.

Мониторинг

  • разработка новых метрик
  • проверка качества данных

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

Выводы

  1. Мы протестировали разные модели, в том числе новейшие решения, чтобы убедиться в их эффективности, и научились выбирать наиболее подходящую модель на основе технических и бизнес-показателей.
  2. Сделал первые прогнозы, которые уже можно использовать для показа пользователям.
  3. Кроме того, мы наметили дальнейший план действий, связанных с развитием модели и ее выпуском в производство (prod).

Весь код статьи вы можете найти здесь.

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

Понравилась эта история?

Подпишитесь, чтобы получать уведомления, когда я публикую новую историю.

Ссылки:

[1]Ф. Максвелл Харпер и Джозеф А. Констан. 2015. Наборы данных MovieLens: история и контекст. Транзакции ACM в интерактивных интеллектуальных системах (TiiS) 5, 4: 19: 1–19: 19. https://doi.org/10.1145/2827872

Все изображения, если не указано иное, принадлежат автору.