Часть I · Постановка и измерение · глава 2 из 19
Данные и смещения
Рекомендательная система учится не на предпочтениях, а на логах — то есть на следах поведения, отфильтрованных предыдущей версией самой себя. Из этого следуют четыре проблемы, каждая из которых порождает отдельную линию в учебнике, и целый каталог смещений, о которых спрашивают на любом собеседовании. Начинать надо с одной формулы.
- Мы предсказываем не то, что думаем. Не \(P(\text{click})\), а \(P(\text{click} \mid \text{show}, \pi_{\text{log}})\) — всё обусловлено тем, что показывала прошлая версия системы.
- Пропуск — это не ноль, а неизвестность. Отсутствие клика по непоказанному айтему не несёт вообще никакой информации.
- Пул для ранкера строится из слейтов. Негативы — то, что было показано рядом и не выбрано, а не случайные айтемы каталога.
- Контур замыкается быстро. В симуляции без exploration покрытие каталога встаёт на 0.6% уже после первого раунда и дальше не двигается никогда.
1. Что мы вообще наблюдаем
Явный фидбек и почему на нём почти не учат
Явный фидбек — это когда человек прямо сообщает своё мнение: звёздочки, лайк, палец вниз. Выглядит идеальным сигналом, а на практике используется редко, и причины стоит знать.
| Проблема явного фидбека | В чём состоит |
|---|---|
| Его почти нет | оценку ставят единицы процентов пользователей; на этом объёме не обучить ничего, кроме верхушки каталога |
| Он смещён по составу | оценивают то, что вызвало эмоцию: восторг или раздражение. Середина, которая и составляет основную массу потребления, не оценивается вовсе |
| Он смещён по людям | оценки ставит особая подвыборка — активные и мотивированные. Их вкусы не представляют аудиторию |
| Он расходится с поведением | классическое «поставил пять звёзд документальному фильму, а смотрит комедии». Люди оценивают, каким хотят быть, а ведут себя иначе |
| Шкала плывёт | для одного четвёрка — похвала, для другого — разочарование. Это чинится вычитанием baseline, но остаточный шум велик |
Поэтому в продакшене учатся на неявном фидбеке: покупка, добавление в корзину, клик, функция от времени просмотра. Его много, он не требует усилий от пользователя и он ближе к тому, что человек делает на самом деле. Цена — четыре проблемы, из которых состоит вся остальная глава.
Четыре проблемы неявных данных
Мы проксируем релевантность наблюдаемым событием, и промахнуться можно на двух независимых уровнях.
- Плохо спроксировали цель бизнеса. Взяли клик вместо удовлетворённости — получили кликбейт. Это чинится инженерно: сменить таргет, добавить головы, взвесить.
- Сама цель бизнеса неудачна. Максимизируем watch time там, где нужна ценность. Это инженерно не чинится вообще — только на уровне продуктового решения.
Разделять эти два уровня полезно на собеседовании: первый вопрос — «а тот ли таргет», второй — «а та ли цель».
В логах есть данные только о взаимодействии пользователя с айтемами из самой системы, и только с теми, которые были показаны. Значит, обучаемся мы не на том, на чём думали:
$$ \text{хотим } P(\text{click}) \qquad\text{а предсказываем}\qquad P(\text{click} \mid \text{show},\, \pi_{\text{log}}) $$где \(\pi_{\text{log}}\) — логирующая политика: предыдущая версия модели плюс все бизнес-ограничения, фильтры и правила, стоявшие в проде на момент сбора данных.
Всё, что вы обучаете, обусловлено тем, что показывала прошлая система. Это не помеха, которую можно уменьшить аккуратностью, — это структурное свойство данных, и дальше вся работа с смещениями сводится к тому, чтобы либо учесть это условие, либо изменить \(\pi_{\text{log}}\).
Обусловленность текущей политикой называют exposure bias. Если ничего не делать, возникает цикл:
модель влияет на показы → показы влияют на данные → данные влияют на следующую модель
Каждый оборот сужает то, что система вообще способна увидеть. Ниже мы посчитаем, насколько быстро.
Пользователь видит не один айтем, а целую выдачу — слейт, — и иногда только её часть. Строго говоря, мы предсказываем \(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 портит обучение (модели не на чем выучить хвост).
Позиционное смещение: это проблема данных, а не модели
Человек чаще кликает на то, что стоит выше, — независимо от содержания. Ключевое наблюдение: испорчен сам таргет. Никакая модель, обученная на таком таргете, от смещения не избавится, потому что оно уже внутри разметки.
Отсюда три уровня работы с ним, по возрастанию честности и стоимости:
- Смягчить выбором метрики. Top-heavy метрики — NDCG, MRR — ценят верх выдачи сильнее, и это частично компенсирует то, что верх выдачи и в данных влиятельнее. Дёшево, но смещение не убирает.
- Смоделировать позицию явно. Позиция подаётся в модель как признак, а на инференсе подставляется единица. Просто и работает, но модель может «спрятать» часть релевантности внутрь позиционного признака.
- Оценить и разделить. Отдельная башня смещения, обучаемая на позиции и контексте, или IPS-взвешивание с оценкой вероятности просмотра. Честнее всего и дороже всего: требует случайного трафика, чтобы вообще оценить propensity.
Механика третьего уровня разбирается там же, где вводится IPS, — виджет обратных склонностей на странице тренажёра показывает, во что превращается оценка, когда вероятности показа малы.
Популярность, встроенная в геометрию
Отдельный случай, который любят на собеседованиях, потому что он не про данные, а про постановку.
Когда эмбеддинги обучены, рекомендация — это поиск максимума скалярного произведения:
$$ \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. Замыкание контура: насколько быстро
«Модель влияет на данные, данные влияют на модель» звучит как отдалённая угроза. Посчитаем.
Модель оценивает айтемы сглаженным CTR по собранному логу, показывает топ-10, собирает клики и переобучается. Просмотр позиции затухает как \(0.85^{\,p}\). Никакого exploration.
| Раунд | без exploration | со случайной квотой 5% | ||
|---|---|---|---|---|
| покрытие | доля от идеала | покрытие | доля от идеала | |
| 1 | 0.50% | 0.37 | 2.85% | 0.43 |
| 3 | 0.60% | 0.37 | 7.20% | 0.48 |
| 6 | 0.60% | 0.37 | 13.65% | 0.53 |
| 12 | 0.60% | 0.37 | 24.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.
Первоисточники
- Y. Hu, Y. Koren, C. Volinsky. Collaborative Filtering for Implicit Feedback Datasets, ICDM 2008 — постановка «уверенность вместо оценки», из которой выросла работа с неявным фидбеком.
- T. Joachims, L. Granka et al. Accurately Interpreting Clickthrough Data as Implicit Feedback, SIGIR 2005 — эксперименты с перестановкой выдачи, которыми позиционное смещение измерили впервые.
- T. Schnabel, A. Swaminathan et al. Recommendations as Treatments: Debiasing Learning and Evaluation, ICML 2016 — IPS для рекомендаций.
- H. Steck. Training and Testing of Recommender Systems on Data Missing Not at Random, KDD 2010 — почему пропуски нельзя считать случайными.
- Числа главы:
_tools/feedback.pyв этом репозитории.