RecSys · учебник
Тренажёр Виджеты Повторение О проекте Все главы ← Задача Метрики →

Часть I · Постановка и измерение · глава 2 из 19

Данные и смещения

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

Что унести из главы
  • Мы предсказываем не то, что думаем. Не \(P(\text{click})\), а \(P(\text{click} \mid \text{show}, \pi_{\text{log}})\) — всё обусловлено тем, что показывала прошлая версия системы.
  • Пропуск — это не ноль, а неизвестность. Отсутствие клика по непоказанному айтему не несёт вообще никакой информации.
  • Пул для ранкера строится из слейтов. Негативы — то, что было показано рядом и не выбрано, а не случайные айтемы каталога.
  • Контур замыкается быстро. В симуляции без exploration покрытие каталога встаёт на 0.6% уже после первого раунда и дальше не двигается никогда.

1. Что мы вообще наблюдаем

Явный фидбек и почему на нём почти не учат

Явный фидбек — это когда человек прямо сообщает своё мнение: звёздочки, лайк, палец вниз. Выглядит идеальным сигналом, а на практике используется редко, и причины стоит знать.

Проблема явного фидбекаВ чём состоит
Его почти нетоценку ставят единицы процентов пользователей; на этом объёме не обучить ничего, кроме верхушки каталога
Он смещён по составуоценивают то, что вызвало эмоцию: восторг или раздражение. Середина, которая и составляет основную массу потребления, не оценивается вовсе
Он смещён по людямоценки ставит особая подвыборка — активные и мотивированные. Их вкусы не представляют аудиторию
Он расходится с поведениемклассическое «поставил пять звёзд документальному фильму, а смотрит комедии». Люди оценивают, каким хотят быть, а ведут себя иначе
Шкала плывётдля одного четвёрка — похвала, для другого — разочарование. Это чинится вычитанием baseline, но остаточный шум велик

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

Четыре проблемы неявных данных

Проблема 1. Прокси может быть неверным дважды

Мы проксируем релевантность наблюдаемым событием, и промахнуться можно на двух независимых уровнях.

  • Плохо спроксировали цель бизнеса. Взяли клик вместо удовлетворённости — получили кликбейт. Это чинится инженерно: сменить таргет, добавить головы, взвесить.
  • Сама цель бизнеса неудачна. Максимизируем watch time там, где нужна ценность. Это инженерно не чинится вообще — только на уровне продуктового решения.

Разделять эти два уровня полезно на собеседовании: первый вопрос — «а тот ли таргет», второй — «а та ли цель».

Проблема 2. Логирующая политика — главная формула главы

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

$$ \text{хотим } P(\text{click}) \qquad\text{а предсказываем}\qquad P(\text{click} \mid \text{show},\, \pi_{\text{log}}) $$

где \(\pi_{\text{log}}\) — логирующая политика: предыдущая версия модели плюс все бизнес-ограничения, фильтры и правила, стоявшие в проде на момент сбора данных.

Всё, что вы обучаете, обусловлено тем, что показывала прошлая система. Это не помеха, которую можно уменьшить аккуратностью, — это структурное свойство данных, и дальше вся работа с смещениями сводится к тому, чтобы либо учесть это условие, либо изменить \(\pi_{\text{log}}\).

Проблема 3. Контур замыкается

Обусловленность текущей политикой называют exposure bias. Если ничего не делать, возникает цикл:

модель влияет на показы → показы влияют на данные → данные влияют на следующую модель

Каждый оборот сужает то, что система вообще способна увидеть. Ниже мы посчитаем, насколько быстро.

Проблема 4. Данные приходят слейтами

Пользователь видит не один айтем, а целую выдачу — слейт, — и иногда только её часть. Строго говоря, мы предсказываем \(P(\text{click} \mid \text{show}, \text{slate})\): вероятность клика зависит от того, что стояло рядом.

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

Отсюда правило построения обучающего пула

что было в слейте — это данные чего в слейте не было — это неизвестность показан и выбран позитив показан и не выбран негатив для ранкера не показан Нулём это делать нельзя: мы не знаем, понравилось бы или нет. Годится как негатив для кандидатогенерации, но не для финального ранкера. Самая частая ошибка постановки: взять случайные айтемы каталога негативами для ранкера. Ранкер учится отличать показанное от случайного — задачи, которую в проде перед ним никогда не поставят.
Всё, что слева, — данные. Всё, что справа, — неизвестность, а не нули.
Практический перевод

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

Случайные айтемы из каталога — законные негативы для кандидатогенерации и двухбашенных моделей, потому что те действительно применяются ко всему каталогу. Для финального ранкера — нет: он всегда работает на сотне кандидатов, которые до него дошли, и обучать его отличать их от случайного шума значит тратить ёмкость на задачу, которой не существует.

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

2. Каталог смещений

Смещений много, и на собеседовании обычно хотят услышать, что вы различаете их по механизму, а не просто перечисляете. Четыре основных:

СмещениеМеханизмЧто с ним делают
Selection bias пользователь сам решает, что оценивать; оцениваются крайности уходят от явного фидбека к неявному — там выбор делает не пользователь, а система
Positional bias клик вероятнее на верхних позициях независимо от содержания частично смягчают top-heavy метриками; лечат позицией как признаком, отдельной башней смещения или IPS-взвешиванием
Exposure bias в логах есть только показанное; непоказанное отсутствует как класс меняют логирующую политику: exploration, случайная квота, рандомизация соседних позиций
Popularity bias популярных айтемов в данных на порядки больше, хвост выучить не на чем коррекции лосса, нормировка эмбеддингов, разнообразие в переранжировании, exploration
Как их различать одной фразой

Selection — выбирал пользователь. Exposure — выбирала система. Positional — испортила позиция. Popularity — испортил объём данных.

И у них разная природа вреда: три первых портят оценку (мы неверно измеряем то, что есть), а popularity портит обучение (модели не на чем выучить хвост).

Позиционное смещение: это проблема данных, а не модели

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

Отсюда три уровня работы с ним, по возрастанию честности и стоимости:

  1. Смягчить выбором метрики. Top-heavy метрики — NDCG, MRR — ценят верх выдачи сильнее, и это частично компенсирует то, что верх выдачи и в данных влиятельнее. Дёшево, но смещение не убирает.
  2. Смоделировать позицию явно. Позиция подаётся в модель как признак, а на инференсе подставляется единица. Просто и работает, но модель может «спрятать» часть релевантности внутрь позиционного признака.
  3. Оценить и разделить. Отдельная башня смещения, обучаемая на позиции и контексте, или IPS-взвешивание с оценкой вероятности просмотра. Честнее всего и дороже всего: требует случайного трафика, чтобы вообще оценить propensity.

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

Популярность, встроенная в геометрию

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

MIPS — это не поиск ближайших соседей

Когда эмбеддинги обучены, рекомендация — это поиск максимума скалярного произведения:

$$ \operatorname{top-}k(u) \;=\; \operatorname*{arg\,max}_{i \in I}{}^{(k)}\; p_u^{\top} q_i $$

У косинусной близости длина \(\lVert q_i\rVert\) сокращается, у скалярного произведения — нет. Значит, MIPS систематически предпочитает айтемы с большой нормой, а норма при обучении растёт с популярностью: популярный айтем участвует в большем числе градиентных шагов.

Получается popularity bias, не заложенный ни в данные, ни в лосс, а возникший из выбора меры близости. Приёмы: нормировать эмбеддинги и перейти к косинусу; свести MIPS к поиску ближайших соседей добавлением координаты; либо корректировать популярность прямо в лоссе.

3. Замыкание контура: насколько быстро

«Модель влияет на данные, данные влияют на модель» звучит как отдалённая угроза. Посчитаем.

Симуляция: каталог 2000, десять слотов, двенадцать раундов

Модель оценивает айтемы сглаженным CTR по собранному логу, показывает топ-10, собирает клики и переобучается. Просмотр позиции затухает как \(0.85^{\,p}\). Никакого exploration.

Раундбез explorationсо случайной квотой 5%
покрытиедоля от идеалапокрытиедоля от идеала
10.50%0.372.85%0.43
30.60%0.377.20%0.48
60.60%0.3713.65%0.53
120.60%0.3724.75%0.70

Числа воспроизводятся скриптом _tools/feedback.py в этом репозитории.

Что здесь важно увидеть
  • Контур замыкается на первом раунде. Покрытие 0.6% — это двенадцать айтемов из двух тысяч. Дальше двенадцать раундов ничего не меняют: система показывает то, про что у неё есть данные, и получает данные только про то, что показывает.
  • Качество замирает на 0.37 от идеала и не растёт. Модель не «недоучилась» — она обучилась идеально на том, что видела. Проблема не в модели.
  • Пяти процентов случайной квоты хватает, чтобы разорвать круг. Покрытие растёт до 24.8%, качество — до 0.70. Не бесплатно: эти 5% показов заведомо хуже, и именно так exploration и оплачивается.

Отсюда практический вывод, который стоит произносить вслух: без exploration офлайн-метрики будут расти, а система деградировать, и по самим метрикам это не увидеть — они считаются на данных, порождённых той же политикой.

Как это ловят в проде
  • Coverage и Джини по показам во времени. Если покрытие ползёт вниз от релиза к релизу, контур сужается. Метрика дешёвая и её почти никто не смотрит.
  • Доля показов у топ-1% каталога. Растёт — значит система схлопывается в популярное.
  • Возраст показанного контента. Если медиана возраста растёт, новое перестаёт пробиваться.
  • Случайный трафик как измерительный инструмент. Даже доля процента полностью случайных показов даёт несмещённую выборку, на которой можно честно оценить, насколько плохи дела.

Вопросы с собеседований

Почему в продакшене учат на неявном фидбеке, а не на оценках?

Явного фидбека мало (единицы процентов пользователей), он смещён по составу (оценивают крайности, а не середину, которая составляет основную массу потребления), смещён по людям (оценки ставит особая активная подвыборка) и расходится с поведением — человек оценивает, каким хочет быть.

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

Что на самом деле предсказывает модель, обученная на логах?

Не \(P(\text{click})\), а \(P(\text{click} \mid \text{show}, \pi_{\text{log}})\), где \(\pi_{\text{log}}\) — логирующая политика: предыдущая модель плюс все фильтры и бизнес-правила, стоявшие в проде.

Это структурное свойство, а не погрешность: в логах физически нет данных о том, чего система не показывала. Дальнейшая работа со смещениями сводится к двум вариантам — либо учесть это условие в оценке (IPS и родственники), либо изменить саму \(\pi_{\text{log}}\) через exploration.

Почему нельзя брать случайные айтемы каталога негативами для ранкера?

Потому что ранкер никогда не применяется к случайным айтемам. Он работает на сотне кандидатов, которые отобрала первая стадия, и отличать их от случайного шума — задача, которой в проде не существует.

Правило: пул ранкера строится из слейтов. Позитивы — то, с чем было взаимодействие; негативы — показанное рядом и не выбранное. Случайные айтемы каталога — законные негативы для кандидатогенерации и двухбашенных моделей, потому что те как раз применяются ко всему каталогу.

И отдельно: отсутствие клика по непоказанному айтему — это неизвестность, а не отказ. Ставить туда ноль значит выдумывать данные.

Перечислите основные смещения и чем они отличаются по механизму.

Selection — выбирал пользователь: он сам решает, что оценивать, и оценивает крайности. Exposure — выбирала система: в логах только показанное. Positional — испортила позиция: клик вероятнее наверху независимо от содержания. Popularity — испортил объём: у популярного данных на порядки больше.

Полезное различение: первые три портят оценку (мы неверно измеряем то, что есть), popularity портит обучение (хвост не на чем выучить).

Отдельный случай — popularity, встроенный в геометрию: у скалярного произведения норма не сокращается, а в обучении она растёт с популярностью, поэтому MIPS систематически предпочитает популярное. Лечится нормировкой или коррекцией в лоссе.

Почему позиционное смещение нельзя вылечить моделью?

Потому что оно уже в таргете. Клик собран под влиянием позиции, и любая модель, обученная на этой разметке, воспроизведёт смещение — оно для неё часть сигнала.

Три уровня работы, по возрастанию стоимости: смягчить top-heavy метриками (дёшево, но не убирает); подать позицию признаком и подставлять единицу на инференсе (просто, но модель может спрятать релевантность в позиционный признак); оценить вероятность просмотра и разделить эффекты — отдельная башня смещения или IPS-взвешивание, что честнее всего, но требует случайного трафика для оценки propensity.

Как быстро замыкается feedback loop и как это заметить?

Быстрее, чем ожидают. В симуляции с каталогом на 2000 айтемов и десятью слотами покрытие останавливается на 0.6% — двенадцати айтемах — уже после первого раунда, и двенадцать последующих раундов его не меняют. Качество замирает на 0.37 от достижимого.

Причём модель при этом не сломана: она отлично обучена на том, что видела. Случайная квота в 5% поднимает покрытие до 24.8%, а качество до 0.70 — ценой того, что эти 5% показов заведомо хуже.

Ловят дешёвыми метриками во времени: coverage и Джини по показам, доля показов у топ-1% каталога, медианный возраст показанного. Плюс доля процента полностью случайного трафика — это несмещённая выборка, на которой можно честно измерить, насколько всё плохо.

Шпаргалка одним экраном

Главная формула

Учим не \(P(\text{click})\), а \(P(\text{click} \mid \text{show}, \pi_{\text{log}})\). Всё обусловлено прошлой политикой.

Пропуск ≠ ноль

Непоказанное — неизвестность. Ноль туда ставить нельзя.

Пул ранкера

Из слейтов: негатив — показанное рядом и не выбранное. Случайный каталог — негативы для кандгена, не для ранкера.

Четыре смещения

Selection — выбирал юзер. Exposure — выбирала система. Positional — испортила позиция. Popularity — испортил объём.

Позиция

Испорчен таргет, а не модель. Лечение: метрики → позиция как признак → башня смещения или IPS.

Контур

Без exploration покрытие 0.6% с первого раунда, качество 0.37 навсегда. Квота 5% → 24.8% и 0.70.

Первоисточники