
Идея офлайн-оценки в общем-то не нова, и довольно логична — хочется еще до запуска A/B тестов хотя бы примерно прикинуть, получилось ли у нас улучшить модель рекомендации, или лучше оставить все как есть. Такой подход здорово экономит нервы и ресурсы: повышает шансы на «зеленый» свет в тестах, отсекает заведомо провальные идеи и не заставляет ML-инженеров зря тратить время на решение ненужных инфраструктурных задач.
Меня зовут Рустам Муртазин, я senior аналитик в отделе ML-аналитики (про отдел в целом и наши задачи можно почитать в этой статье) и в этой статье я расскажу про особенности офлайн оценки моделей рекомендаций в музыкальном сервисе Звук.

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

В этой статье я хотел бы сфокусироваться не на самих формулах, они в целом стандартные и не новые, а на особенностях их расчета у нас, которые позволяют шире оценить новое решение. Где-то я просто перечислю метрики, а на каких-то остановлюсь чуть более подробно.
Начнем с первой группы — метрики разнообразия. Их цель проста: понять, насколько разный контент мы рекомендуем. Здесь мы считаем следующее:
Coverage — метрика показывает, каким разнообразием треков (или других сущностей) оперирует наша рекомендация. Считаем мы её и в абсолютных числах, и в относительных (например, можно посчитать, какая часть от всех треков, которые вообще прослушивались за последние N дней (скажем, за 90), используется для рекомендаций в модели).
Freshness — метрика, которая отвечает на вопрос, много ли в рекомендациях новых треков. Для каждого рекомендованного трека смотрим, выпущен ли он за последние N дней (например, за полгода) или нет. Далее делим количество таких «свежих» треков на общее число уникальных рекомендаций. Метрика критически важная, потому что мы, как сервис, хотим быть в тренде и не отставать от выпуска новых релизов.
Average Percentage of Long Tail (APLT) — метрика, позволяющая оценить, насколько популярный контент рекомендует модель. Она позволяет дать ответ на важный вопрос: «А не залипаем ли мы слишком на популярном контенте, превращая рекомендации в бесконечный хит-парад?».
Технически всё просто: нужно построить распределение популярности всех треков в сервисе. Оно всегда получается скошенным влево, с классическим «длинным хвостом». Горстка хитов генерирует бóльшую часть прослушиваний, а основная масса контента живёт в этом самом «хвосте» с минимальной или нулевой популярностью. APLT как раз и показывает, какой процент от всех наших рекомендаций приходится на эти нетривиальные, малопопулярные треки.

Здесь нельзя ударяться в крайности: с одной стороны, если APLT близка к нулю — это плохо, ведь в таком случае мы перестаём предлагать пользователям новую музыку. С другой стороны, большие значения будут означать, что мы не учитываем предпочтения наших слушателей. Поэтому, опытным путем, через эксперименты, мы ищем баланс. Скажем так: если новая модель держит APLT в районе 5% ± несколько процентов, не проседая по другим метрикам, — это хороший знак. Это значит, что мы не лишаем пользователей известных и любимых треков, при этом сохраняем здоровую долю рекомендаций для открытий.
Вторая группа нужна, чтобы понять, насколько наши рекомендации близки к тому, что пользователь уже слушал и любил. Под «близостью» мы подразумеваем совпадение по ключевым признакам:
-
совпадение по жанрам
-
совпадение по артистам
-
совпадение по десятилетиям (проще говоря, насколько рекомендации попадают в ту же музыкальную эпоху, что и история прослушиваний)
-
совпадение по языкам
Первые три считаются практически по одной схеме, меняется только сущность, которую сравниваем. На примере совпадения по жанрам покажу, как это работает.
Допустим, у нас есть рекомендательная система, которая выдает пользователю 20 треков. В момент времени t мы формируем для него рекомендации и смотрим: для каждого из этих 20 треков определяем жанр. Далее, для каждого трека выставляем единицу, если этот жанр встречался в его недавней истории прослушиваний (тут важно отметить — мы берем ограниченную глубину истории и только «положительные» взаимодействия, например, длинные прослушки или лайки). Если жанра в истории не было — ставим ноль. Получаем вектор из 20 нулей и единиц. Далее для пользователя усредняем эти значения, получаем его персональный скор совпадения по жанрам. Имея оценки по пользователям, мы можем построить распределения, и посчитать среднее, медиану и другие интересующие статистики. Сравнив такие распределения для двух моделей (старой и новой), можно сделать вывод, насколько новое решение лучше или хуже стало попадать в предпочтения по жанрам, артистам или десятилетиям.

Для жанров и десятилетий также считаем допметрику — совпадение доминирующей сущности. Определяем, какой жанр (или десятилетие) преобладает в рекомендациях и какой — в истории пользователя. И просто смотрим, у какой доли пользователей эти «мажоритарные» признаки совпали.
С метрикой совпадения по языкам история немного другая. Мы также определяем язык для каждого трека в рекомендациях и в истории. Сейчас у нас в сервисе язык делится на две большие группы: русский язык и все остальные (в идеале стоит разделять английский и другие языки, но пока мы их объединяем).
В итоге для каждого пользователя получаем два значения: доля русскоязычных треков в его истории и доля русскоязычных треков в рекомендациях. Это позволяет построить простой scatter plot, где по оси X — доля русского в истории, по Y — доля русского в рекомендациях.

На этом графике нас интересуют две зоны — правый верхний и левый нижний квадраты. Логика простая: если пользователь слушает в основном русскую музыку (доля > 0.5), то и в рекомендациях мы хотим видеть преимущественно русскоязычные треки. И наоборот: если в истории преобладает музыка на других языках, то и рекомендации должны этому соответствовать. Задача модели — «попадать» в эти квадраты.
В качестве итоговой числовой метрики мы считаем долю пользователей, для которых это правило выполнилось. Если эта доля растёт — значит, модель лучше уловила языковые предпочтения.
Третья группа метрик — метрики точности и ранжирования — является, пожалуй, наиболее важной для оценки рекомендательных систем. Каким бы разнообразным и свежим ни был контент, если модель плохо подбирает и ранжирует треки, ценность сервиса для пользователя быстро упадет. Однако с офлайн-оценкой именно этой группы связана ключевая методологическая сложность — если новая модель порекомендует что-то, чего не было в «старой» модели, мы не знаем наверняка, как на это отреагирует пользователь (честно говоря, в оффлайне мы никогда этого не знаем).
Одно из возможных решений — оценивать модели только по пересечению рекомендаций, игнорируя новые айтемы. Однако этот подход опасен: новая модель, генерирующая много свежих рекомендаций, будет искусственно проигрывать “старой” модели.
Поэтому мы дополнительно применяем альтернативную методологию: определяем потенциальную релевантность треков не по прямым логам взаимодействий с рекомендациями, а на основе агрегированной исторической статистики. По сути, это продолжение второй группы метрик, где мы оцениваем соответствие не просто по признакам, а по реальным паттернам потребления.
Недостаток этого подхода в том, что он поощряет модель возвращать уже знакомый пользователю контент. Чтобы решить данную проблему, мы, помимо истории самого пользователя, анализируем статистику его K-ближайших соседей — пользователей со схожим вкусовым профилем. Логика здесь простая: если трек популярен среди людей с близкими предпочтениями, высока вероятность, что он будет релевантен и для нашего пользователя. Это позволяет присвоить ненулевую релевантность трекам, которые сам пользователь ещё не слушал, но которые потенциально ему интересны (качество этого подхода, разумеется, зависит от точности сервиса поиска похожих пользователей).
Ещё один важный нюанс — какие веса (релевантности) выставлять для разных действий пользователя (лайк, дослушивание, добавление в плейлист). Разные комбинации весов могут кардинально менять итоговые оценки. Изначально мы выставляли их экспертным путём, но быстро поняли, что это не самый надёжный вариант. В итоге пришлось итеративно проводить эксперименты, подбирая ту комбинацию, которая даёт наиболее устойчивые и осмысленные результаты.
Что касается самих метрик — здесь мы не изобретаем велосипед. Для оценки точности считаем классические Hit-rate, Precision и Recall. Для оценки ранжирования — NDCG и MRR. Здесь стоит добавить: поскольку наша релевантность не бинарная, а имеет несколько градаций, мы смотрим не только на нормированный NDCG, но и на его абсолютный аналог — DCG. Это важно, потому что эти две метрики отвечают на разные вопросы. NDCG показывает, насколько хорошо модель использовала относительный потенциал конкретного пользователя (насколько близко она подобралась к идеальному порядку). А вот DCG отвечает на более прямой для бизнеса вопрос: сколько абсолютной ценности (лайков, прослушиваний) эта модель в принципе может принести бизнесу.
Хочется отметить, что в этой статье мы разбирали оффлайн-оценку готового сервиса. Но на самом деле, сам процесс оценки начинается значительно раньше — на этапе обучения и валидации отдельных компонентов системы (кандидатогенератор, ранжирующая модель, RL-агент), и здесь важно провести оценку так, чтобы каждая модель (этап) оценивался независимо от других. Это — отдельная большая тема, и я очень надеюсь подробно рассказать о ней в следующий раз.

Спасибо, что дочитали до конца! До скорого.
Автор: MR8808
