Часть I · Постановка и измерение · глава 1 из 19
Задача: почему рекомендации устроены именно так
Почти всё устройство рекомендательной системы выводится из двух фактов: спрос распределён крайне неравномерно, а времени на ответ — десятки миллисекунд. Из первого следует, зачем вообще нужна персонализация и откуда берётся холодный старт. Из второго — почему система обязана быть многостадийной. Разбираем оба с числами, которые можно пересчитать самому.
- Хвост не метафора, а арифметика. При степенном законе с \(\alpha = 1.2\) верхний процент каталога забирает 85% взаимодействий, а 74% каталога ожидаемо не набирает и десяти. Холодный старт начинается здесь, а не в главе про него.
- Целевая функция следует из бизнес-модели. Подписка, реклама и маркетплейс оптимизируют разное, и подставить чужую метрику — самая дорогая ошибка на этапе постановки.
- Любая поверхность — это функция сортировки. Лента, «похожие», письмо и автоплей отличаются только тем, что подставлено на вход.
- Воронка многостадийна не из любви к архитектуре. Полный перебор каталога не влезает в бюджет задержки, а задержка стоит денег.
1. Спрос распределён неравномерно
Каталог большой, внимание — нет. Но само по себе это ещё не аргумент за персонализацию: если бы все хотели одного и того же, хватило бы одной витрины с хитами. Аргумент даёт форма распределения спроса.
Степенной закон и что из него следует
Эмпирическое наблюдение, воспроизводящееся в музыке, видео, книгах и товарах: частота \(k\)-го по популярности объекта убывает степенным образом.
$$ f(k) \;\propto\; k^{-\alpha} $$Параметр \(\alpha\) задаёт, насколько круто. Смысл у него простой: в логарифмических осях степенной закон — прямая с наклоном \(-\alpha\). Если вы построили частоты в log–log и увидели прямую, перед вами Ципф.
Каталог 100 000 позиций, частоты по \(k^{-\alpha}\). Доля всех взаимодействий, приходящаяся на топ каталога:
| \(\alpha\) | топ-0.1% | топ-1% | топ-4% | топ-10% | Джини |
|---|---|---|---|---|---|
| 0.8 | 17.9% | 34.0% | 47.9% | 59.5% | 0.63 |
| 1.0 | 42.9% | 61.9% | 73.4% | 81.0% | 0.83 |
| 1.2 | 70.8% | 85.2% | 91.1% | 94.3% | 0.95 |
| 1.5 | 92.6% | 97.8% | 99.0% | 99.5% | 1.00 |
Числа воспроизводятся скриптом _tools/longtail.py в этом репозитории.
Обратите внимание, как быстро всё меняется. Между \(\alpha = 0.8\) и \(\alpha = 1.2\) разница выглядит небольшой, а доля верхнего процента вырастает с 34% до 85%. Поэтому «у нас длинный хвост» — утверждение без содержания, пока не назван показатель.
Возьмём \(\alpha = 1.2\), каталог 100 000 и десять миллионов взаимодействий — вполне живой масштаб. Посчитаем, сколько взаимодействий ожидаемо достаётся каждому айтему:
- при \(\alpha = 1.0\) меньше десяти взаимодействий ожидается у 17.3% каталога;
- при \(\alpha = 1.2\) — уже у 74.2%.
Три четверти каталога, о которых почти нет данных. Не потому, что система молодая, а потому, что так устроен спрос. Отсюда растут разреженность и холодный старт, отсюда же — необходимость контентных признаков и вся линия про кодирование объектов.
Обратная задача тоже полезна: чтобы верхние 4% каталога собирали 90% спроса, нужно \(\alpha \approx 1.18\). То есть «4% дают 90%» и «\(\alpha\) чуть больше единицы» — это одно и то же утверждение, просто сказанное по-разному.
- Двигайте \(\alpha\) и смотрите на долю верхушки. Переход от 0.8 к 1.2 — это переход от «есть перекос» к «хвоста практически нет в выдаче».
- Переключитесь в логарифмические оси: степенной закон становится прямой, и наклон этой прямой и есть \(-\alpha\). Так его и оценивают на практике.
- Посмотрите на кумулятивную кривую. Она отвечает на вопрос, который реально задают на собеседовании: «какая доля каталога покрывает 80% спроса».
Что сказать на собесе: «Хвостатость — это не картинка, а параметр. Я бы назвал \(\alpha\) или Джини по показам, потому что от них зависит, сколько каталога вообще имеет шанс обучиться».
Чем измеряют хвостатость выдачи
Хвост в данных — это одно, хвост в выдаче — другое, и второе важнее: рекомендательная система может усугубить перекос, а может его сгладить. Три показателя, которые стоит уметь назвать.
| Показатель | Что считает | Что ловит |
|---|---|---|
| Coverage@k | доля каталога, попавшая хотя бы в одну выдачу за период | прямо отвечает на «сколько товара мы вообще показываем» |
| Джини по показам | неравенство распределения показов между айтемами, от 0 до 1 | перекос внутри показанного: coverage может быть высоким, а 99% показов уходить сотне айтемов |
| Энтропия показов | \(-\sum_i p_i \log p_i\) по долям показов | то же, что Джини, но чувствительнее к очень редким айтемам |
Классический размен: модель, которая всегда рекомендует популярное, показывает приличную точность и катастрофический coverage. Формально она права — популярное действительно чаще нравится, — но продукт при этом теряет смысл.
Отсюда практическое правило: точность и покрытие смотрят вместе. Прирост точности на два пункта, купленный обрушением coverage, — плохая сделка: вы отдали хвост и не купили ничего, что видно в деньгах.
Подробнее это разбирается там, где вводятся сами метрики: «Метрики ранжирования».
2. Кто платит и что из этого следует
Целевая функция рекомендательной системы не выводится из математики. Она выводится из того, кто платит деньги. Три модели — три разные постановки, и подставить чужую метрику здесь стоит дороже, чем ошибиться в архитектуре.
| Модель | Кто платит | Что на самом деле оптимизируется | Чем это ломается |
|---|---|---|---|
| Подписка | пользователь, ежемесячно | удержание: вероятность, что человек продлит. Не просмотры, а ощущение «здесь всегда есть что посмотреть» | краткосрочные метрики почти не связаны с удержанием, а измерять его дорого — цикл обратной связи месяц |
| Реклама | рекламодатель | внимание: время, показы, возвраты. Пользователь здесь не покупатель, а инвентарь | прямой конфликт интересов: то, что удерживает внимание, не обязано быть полезным |
| Маркетплейс | продавец с комиссии | GMV с поправкой на возвраты и на здоровье предложения: продавцу нужен спрос, иначе он уйдёт | оптимизация немедленного GMV даёт возвраты и вымывание мелких продавцов |
В рекламе и на маркетплейсе оптимизируется не одна величина, а компромисс между сторонами: пользователю нужна релевантность, продавцу — спрос, площадке — комиссия. Формально это задача с ограничениями, а не максимизация одного числа.
На практике почти всегда собирают взвешенную сумму и подбирают веса экспериментами. Это работает, но у решения есть цена: веса нельзя вывести из первых принципов, и каждое изменение продуктовой политики становится отдельным A/B. С этой же конструкцией вы столкнётесь в разборе живой системы — там веса действий лежат прямо в конфиге, см. x05.
Прокси-метрика и почему она портится
Ни одну из настоящих целей — удовольствие, удержание, доверие — измерить напрямую нельзя. Поэтому оптимизируют прокси: клик, время, конверсию. И тут возникает сюжет, который будет возвращаться всю дорогу.
Оптимизируем клики — получаем кликбейт. Оптимизируем время — получаем бесконечный скролл. Оптимизируем немедленный GMV — получаем возвраты и недовольных продавцов.
Механизм всегда один: у прокси и у настоящей цели есть общая часть и есть расхождение. Пока давление слабое, они идут рядом. Стоит начать давить — модель находит именно расхождение, потому что там дешевле всего добрать метрику.
Кори Доктороу назвал предельный случай этого процесса enshittification: площадка сначала хороша для пользователей, потом начинает выжимать их в пользу бизнес-клиентов, потом выжимает и бизнес-клиентов в свою пользу. Каждый шаг локально рационален и меряется положительно.
Что с этим делают. Держат вместе быстрые прокси и медленные метрики здоровья: удержание когорт, доля активных на горизонте месяцев, возвраты, жалобы. Быстрые двигают, медленные охраняют. Подробнее — в «Прокси-метрики и долгосрочные цели».
Практический инструмент, который стоит уметь читать. Берём когорту — всех, кто пришёл в месяц \(m_0\), — и смотрим долю активных через 1, 2, 3… месяца.
Здоровый продукт даёт кривую, которая выполаживается на ненулевом уровне: часть людей остаётся навсегда. Нездоровый — кривую, монотонно падающую к нулю, сколько бы новых ни приходило сверху.
Это тот случай, когда форма кривой информативнее её высоты: плато на 20% лучше, чем 40% на первом месяце с обвалом к третьему.
3. Рекомендательная система — это функция сортировки
Определение, из которого удобно рассуждать дальше. Как бы ни выглядел интерфейс, внутри всегда одно и то же: есть множество кандидатов, есть контекст, и надо расположить кандидатов в порядке.
$$ \mathrm{score}: (u, c, i) \;\longmapsto\; \mathbb{R}, \qquad \text{выдача} \;=\; \operatorname{top-}k_{\,i \in C}\ \mathrm{score}(u, c, i) $$Здесь \(u\) — пользователь, \(c\) — контекст (время, устройство, что человек делает прямо сейчас), \(i\) — кандидат, \(C\) — множество, из которого выбираем.
Оно объясняет, почему все поверхности продукта — одна задача. Меняется только то, что подставлено:
| Поверхность | Что подставлено |
|---|---|
| лента | \(u\) — пользователь, \(C\) — весь каталог |
| «похожие» | вместо \(u\) — айтем, \(C\) — каталог без него |
| «следующий трек» | то же, но \(k = 1\) и контекст важнее истории |
| письмо или пуш | тот же скор, но \(C\) урезан по частоте касаний |
| поиск с персонализацией | \(C\) сужен запросом, скор остаётся тем же |
Отсюда же видно, где проходит граница с поиском: у поиска запрос сужает \(C\), у рекомендаций сужать нечем, и всю работу делает \(\mathrm{score}\).
4. Почему воронка обязана быть многостадийной
Теперь второй факт, определяющий устройство: времени мало. Ответ нужно собрать за десятки-сотни миллисекунд, потому что задержка стоит денег.
Цифру приводят постоянно, поэтому стоит знать её происхождение. Грег Линден, работавший в Amazon, рассказывал о внутренних A/B-экспериментах, где страницу искусственно замедляли шагами по 100 мс: каждый шаг стоил около процента продаж. Публично это прозвучало в его выступлении Make Data Useful (2006), а позже цитировалось в работах по онлайн-экспериментам.
Важно понимать статус числа: это один внутренний эксперимент двадцатилетней давности на конкретном продукте. Порядок величины подтверждался и другими площадками, но переносить «1% на 100 мс» на свой сервис как константу нельзя. Что переносится — сам факт: задержка имеет измеримую цену, и её надо мерить у себя.
Пусть бюджет на ответ — 100 мс, каталог — миллион айтемов, а ранжирующая модель считает один скор за 100 микросекунд. Тогда полный перебор стоит
$$ 10^6 \times 100\ \text{мкс} \;=\; 100\ \text{секунд} $$Это в тысячу раз больше бюджета. Никакая оптимизация констант такой разрыв не закроет — нужна другая схема.
Схема одна и та же везде: сначала дёшево сузить, потом дорого упорядочить. Кандидатогенерация отбирает сотни из миллионов по дешёвой мере — обычно скалярное произведение в индексе. Ранжирование считает тяжёлую модель, но уже на сотнях.
Тогда бюджет сходится: сотни вычислений тяжёлой модели — это единицы миллисекунд, а поиск ближайших соседей в индексе — тоже единицы.
Ранжирование не может показать то, чего не отобрала кандидатогенерация. Значит сквозное качество ограничено сверху полнотой первой стадии, и никакая модель дальше этого потолка не поднимет.
Отсюда правило, которое часто проверяют вопросом на собеседовании: кандидатогенерацию меряют полнотой, ранжирование — порядком. Мерить кандидатогенератор точностью бессмысленно: его задача не угадать, а не потерять.
- Поставьте полноту ранжирования в 1.00 — идеальная вторая стадия. Сквозная полнота всё равно упрётся в потолок первой: потолок задаёт кандидатогенерация.
- Добавьте второй источник кандидатов. Сквозная полнота растёт, но не складывается: источники пересекаются, и пересечение не приносит нового.
- Увеличьте \(k\) первой стадии. Полнота растёт, но каждая следующая сотня кандидатов дороже предыдущей по времени и дешевле по пользе — типичная убывающая отдача.
Что сказать на собесе: «Сквозная полнота — произведение по стадиям, поэтому улучшать надо самую слабую. И у кандидатогенерации метрика — полнота, а не точность: она отбирает, а не решает».
Вопросы с собеседований
Что такое длинный хвост и как измерить, насколько он длинный?
Спрос распределён по степенному закону: частота \(k\)-го по популярности объекта пропорциональна \(k^{-\alpha}\). В логарифмических осях это прямая с наклоном \(-\alpha\), так его и оценивают.
Одной картинки недостаточно, надо называть показатель. Про данные — \(\alpha\) или Джини по частотам. Про выдачу — coverage@k (доля каталога, показанная хотя бы раз), Джини по показам и энтропия показов. Разница важна: система может как усугубить перекос, так и сгладить.
Порядок величин: при \(\alpha = 1.0\) верхний процент каталога собирает около 62% взаимодействий, при \(\alpha = 1.2\) — уже 85%.
Почему длинный хвост делает холодный старт неизбежным?
Это прямое следствие арифметики. При \(\alpha = 1.2\), каталоге в 100 тысяч и десяти миллионах взаимодействий у 74% айтемов ожидаемо меньше десяти взаимодействий. При \(\alpha = 1.0\) — у 17%.
То есть большая часть каталога всегда находится в состоянии, где коллаборативного сигнала практически нет, и это не проблема молодого сервиса, а свойство распределения спроса. Отсюда необходимость контентных признаков: они позволяют говорить об айтеме до того, как он накопил историю.
Как бизнес-модель влияет на целевую функцию рекомендаций?
Подписка платит за удержание: важна вероятность продления, а не просмотры. Метрика медленная, цикл обратной связи — месяц.
Реклама платит за внимание: показы, время, возвраты. Здесь встроен конфликт — удерживающее внимание не обязано быть полезным.
Маркетплейс платит комиссией с оборота, но с двумя поправками: возвраты и здоровье предложения. Продавцу нужен спрос, иначе он уйдёт, и площадка останется без ассортимента.
Практический вывод: подставить чужую метрику — самая дорогая ошибка постановки. И в двухстороннем рынке это задача с ограничениями, которую на практике сводят к взвешенной сумме с весами из экспериментов.
Почему оптимизация прокси-метрики со временем портит продукт?
У прокси и настоящей цели есть общая часть и расхождение. При слабом давлении они идут рядом; когда начинают давить, модель находит именно расхождение — там дешевле всего добрать метрику.
Отсюда кликбейт при оптимизации кликов, бесконечный скролл при оптимизации времени, возвраты при оптимизации немедленного GMV.
Лечение — не отказ от прокси, а связка: быстрые прокси двигают систему, медленные метрики здоровья (удержание когорт, доля активных на горизонте месяцев, жалобы, возвраты) её охраняют. Кривую удержания при этом читают по форме: здоровый продукт выполаживается на ненулевом уровне.
Почему рекомендательная система многостадийная, а не одна большая модель?
Бюджет не сходится. При каталоге в миллион и стоимости скора 100 мкс полный перебор — 100 секунд против бюджета в 100 мс, то есть в тысячу раз больше. Константами такой разрыв не закрывается.
Поэтому: дёшево сузить (кандидатогенерация, сотни из миллионов, обычно скалярное произведение в индексе), затем дорого упорядочить (ранжирование на сотнях). Иногда добавляют pre-ranking между ними и переранжирование поверх топа.
Задержка при этом не абстракция: замедление измеримо стоит денег — известное наблюдение про 100 мс и процент продаж восходит к внутренним экспериментам Amazon, о которых рассказывал Грег Линден в 2006 году.
Какой метрикой мерить кандидатогенерацию и почему не точностью?
Полнотой. Задача первой стадии — не угадать ответ, а не потерять его: чего она не отобрала, того ранжирование уже не покажет.
Отсюда следует, что сквозное качество ограничено сверху полнотой первой стадии. Даже идеальное ранжирование не поднимет систему выше этого потолка, поэтому улучшать надо самую слабую стадию, а не самую заметную.
Точность на этой стадии не только бесполезна, но и вредна как ориентир: она толкает отбирать поменьше и понадёжнее, то есть ровно в популярное, обрушая покрытие.
Шпаргалка одним экраном
Степенной закон
\(f(k) \propto k^{-\alpha}\), в log–log — прямая с наклоном \(-\alpha\). При \(\alpha=1.2\) топ-1% забирает 85%.
Холодный старт
При \(\alpha=1.2\) у 74% каталога меньше десяти взаимодействий. Свойство спроса, а не возраста сервиса.
Хвостатость выдачи
Coverage@k, Джини по показам, энтропия. Смотреть вместе с точностью, иначе выиграете точность и потеряете каталог.
Три модели
Подписка — удержание. Реклама — внимание. Маркетплейс — GMV минус возвраты плюс здоровье предложения.
Функция сортировки
\(\mathrm{score}(u,c,i)\) и \(\operatorname{top-}k\). Все поверхности отличаются тем, что подставлено на вход.
Воронка
10⁶ → 10² дёшево → 10¹ дорого. Потолок задаёт первая стадия; её метрика — полнота.
Первоисточники
- C. Anderson. The Long Tail, Wired, 2004 — работа, с которой термин вошёл в обиход.
- P. Covington, J. Adams, E. Sargin. Deep Neural Networks for YouTube Recommendations, RecSys 2016 — канонический разбор двухстадийной воронки: кандидатогенерация и ранжирование, и почему именно так.
- G. Linden. Make Data Useful, Stanford, 2006 — источник наблюдения про 100 мс и процент продаж; там же честно видно, что это один внутренний эксперимент.
- R. Kohavi, R. Longbotham et al. Online Controlled Experiments at Large Scale — как измеряют такие эффекты и где в них ошибаются.
- C. Doctorow. Enshittification, 2023 — формулировка того, как площадка последовательно портится, оставаясь локально рациональной.
- Числа главы:
_tools/longtail.pyв этом репозитории.