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

Часть IV · Последовательности и выдача · глава 16 из 19

Exploration и бандиты

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

Что унести из главы
  • Круг замкнут буквально: чтобы узнать CTR, надо показать; чтобы показать, надо знать CTR. Жадная политика берёт 0.834 от идеальной выдачи и не находит спрятанный самородок ни в одном из восьми каталогов.
  • Бонус за незнание даёт +9% перестановкой того же пула кандидатов — без новой модели и без новых признаков. Но зависимость от \(\alpha\) — горб, а не лестница.
  • Главная ловушка — не всякая неопределённость полезна. \(\sqrt{p(1-p)}\) не убывает с числом показов вовсе; это шум события, а не незнание модели.
  • Thompson выбирают не за качество, а за измеримость. Детерминированная политика даёт склонности 0 и 1 — и вся офлайн-оценка превращается в деление на ноль.

1. Замкнутый круг

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

Как это выглядит в цифрах

Каталог из 40 айтемов, показываем топ-10, у шести айтемов истории нет вовсе. Среди новых спрятан самородок с истинным CTR 0.145 — лучший в каталоге, где у ближайшего конкурента 0.097.

Модель про него не знает ничего: его оценка равна априорной, 0.06. А значит он не попадёт в топ-10. А значит показов не получит. А значит оценка не изменится.

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

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

2. Два дешёвых механизма

Прежде чем строить что-то сложное, стоит знать: первые 95% выгоды от exploration обычно забираются двумя простыми приёмами. Всё остальное в этой главе — про оставшиеся проценты.

Случайная квота (ε-greedy)

На доле \(\varepsilon\) позиций показываем случайные рекомендации, в остальных — выдачу модели.

Плюс: сильно разнообразит обучающий пул, причём именно тем, чего модель никогда бы не показала. Можно усложнить, сделав «случайную» часть не случайной, а выученной под отдельный эксплоративный таргет.

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

Софтмакс-сэмплинг

Считаем софтмакс от скоров с температурой и сэмплируем из полученного распределения без возвращения:

$$ P(i) \;\propto\; \exp\bigl(f(u,i)/T\bigr) $$

Плюс: бережно к бизнес-метрикам — мы не показываем случайный мусор, а слегка перемешиваем хорошее. Легко подобрать сетап, и есть быстрый способ сэмплировать без возвращения через Gumbel-top-\(k\): прибавить к логитам гумбелевский шум и взять топ.

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

3. Алгоритмы

Теперь собственно бандитская теория — ровно в том объёме, который спрашивают.

UCB: оптимизм перед лицом неопределённости

Выбираем действие с наибольшей верхней оценкой матожидания награды:

$$ a_t = \arg\max_a \bigl[\,q_t(a) + u_t(a)\,\bigr] $$

Красота в том, что бонус \(u_t\) не выдумывается, а выводится.

Неравенство Хёфдинга. Пусть \(X_1 \dots X_n\) — независимая выборка из распределения на \([0,1]\) с истинным средним \(\mu\), а \(\hat\mu\) — выборочное среднее. Тогда для любого \(u > 0\):

$$ \mathbb{P}\bigl(\mu \ge \hat\mu + u\bigr) \le e^{-2nu^2} $$

Приравняем правую часть к \(\delta\) и разрешим относительно \(u\). С вероятностью \(1-\delta\) истинное \(Q(a)\) не превосходит \(Q_k(a) + U_k(a)\), где

$$ U_k(a) = \sqrt{\frac{-\ln \delta}{2\,n_k(a)}} $$

Осталось выбрать \(\delta\). Берём \(\delta = 1/k^{c}\) — то есть с ростом времени требуем всё более надёжную границу — и получаем каноническую формулу:

$$ a_k = \arg\max_a \Bigl[\, Q_k(a) + c\sqrt{\frac{\log k}{n_k(a)}} \,\Bigr] $$

Читается прозрачно: оценка средней награды плюс ширина доверительного интервала.

Как бонус сходит на нет сам
Показов у ручкиШирина при \(\delta = 0.05\)Бонус \(\sqrt{\log k / n}\) при \(k = 10^4\)
11.22393.0349
100.38700.9597
1000.12240.3035
10000.03870.0960
100000.0122

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

Бонус убывает как \(1/\sqrt{n}\), и отсюда главное свойство: отдельной «ручки исследования» не нужно. Ручка, которую дёргали мало, всплывает сама; по мере накопления данных исследование сходит на нет автоматически.

Thompson sampling

Байесовская альтернатива. Для каждого действия задаём модель награды и распределение над её параметрами, затем на каждом шаге:

  1. сэмплируем параметры из апостериора: \(\theta \sim p(\theta \mid \text{данные})\);
  2. действуем жадно относительно сэмпла: \(a_t = \arg\max_a \mathbb{E}[r \mid a, \theta]\).

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

Beta-Bernoulli — рабочая лошадка. Награда бинарная, \(r \sim \mathrm{Bernoulli}(\theta_a)\), априор \(\theta_a \sim \mathrm{Beta}(\alpha_a, \beta_a)\). Бета сопряжена с бернуллиевским, поэтому обновление тривиально:

$$ r_t = 1: \ \alpha_a \mathrel{+}= 1, \qquad r_t = 0: \ \beta_a \mathrel{+}= 1 $$

Сэмплируем \(\theta_a\) для каждой ручки, берём максимум. Три строки кода и ни одного гиперпараметра, кроме априора.

Почему ε-greedy проигрывает: форма кривой, а не высота

Три ручки с CTR 0.30, 0.10, 0.05; горизонт 20 000 шагов. Regret — суммарная упущенная награда.

Политикаt = 1000t = 5000t = 10000t = 20000
ε-greedy 0.1025.991.0164.9307.5
UCB, c = 128.046.357.267.1
Thompson14.119.421.023.8

Смотреть надо не на высоту в конце, а на прирост при удвоении отрезка:

Политика5000 → 1000010000 → 20000Отношение
ε-greedy 0.1073.9142.51.93
UCB, c = 110.99.90.91
Thompson1.62.81.70

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

У ε-greedy отрезок вдвое длиннее даёт вдвое больше сожаления — regret линейный. Фиксированная доля случайного трафика тратится вечно: и через миллион шагов алгоритм отдаёт 10% показов заведомо плохим ручкам.

У UCB прирост даже слегка падает — кривая загибается. У Thompson отношение 1.70 смотреть бессмысленно: приросты 1.6 и 2.8 при общем regret 23.8 — это шум, важно, что абсолютная величина в 13 раз меньше, чем у ε-greedy.

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

4. Бандит как слой переранжирования

Теория выше говорит про «ручки». Инженерный вопрос — как вставить её в готовую воронку, у которой уже есть кандидатогенерация, ранкер и блендер.

Что считать ручкой

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

РучкаСколько ихЧто получаемЧего не получаем
Айтеммиллионынастоящее исследование каталога: холодный старт закрывается сам собойстатистику — работает только если неопределённость даёт модель, а не счётчик показов
Источник кандидатовединицы–десяткидинамические квоты: сколько слотов отдать ANN, сколько подпискам, сколько трендамничего про конкретный айтем — внутри источника по-прежнему жадно
Политика целикомединицыавтоматический выбор между версиями ранкераэто уже не рекомендации, а самонастраивающийся A/B
Группа айтемовтысячикомпромисс: статистика копится на группу и переносится на новый айтем внутри неёточности внутри группы — хороший айтем в плохой категории останется незамеченным
Почти всегда выбирают «айтем» — и вот почему это возможно

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

У нас уже есть обученная модель, и она умеет отвечать «я не знаю» про айтем, который видит впервые: незнакомые признаки, пустая история, эмбеддинг из инициализации. Неопределённость берётся из модели, а не из счётчика — и тогда миллионы ручек перестают быть проблемой.

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

Почему слой встаёт именно на переранжировании

retrieval ~10 000 ранкер · ~500 даёт μ бандитный слой сортировка по μ + α·σ выдача топ-10 клики и их отсутствие → обновляют μ и σ потолок исследования задаёт retrieval: слой переставит только то, что до него дошло
Слой не добавляет кандидатов — он меняет порядок уже отобранных. Это и его главное достоинство, и его главное ограничение.

Исследовать можно на любой стадии, но переранжирование удобно сразу по четырём причинам, и все они инженерные, а не математические.

  1. Цена ошибки ограничена и известна заранее. Слой переставляет сотню кандидатов, каждый из которых уже прошёл фильтры и ранкер. Худшее, что может случиться, — показать девятого вместо второго. На стадии retrieval цена ошибки не ограничена ничем.
  2. Здесь есть все признаки. Оценка неопределённости нужна не «про айтем вообще», а про пару (пользователь, айтем) в текущем контексте. Полный вектор признаков существует только на стадии ранжирования.
  3. Кандидатов мало. Ансамбль из пяти моделей на сотне кандидатов — пятьсот вычислений, посильно. На десяти тысячах — уже нет.
  4. Это точка, где решение и так принимается. Порядок выдачи всё равно формируется здесь; добавить к скору слагаемое дешевле, чем встроить исследование внутрь ANN-индекса.
И обратная сторона, видная на схеме

Слой исследует только то, что отобрала воронка. Если retrieval тоже жадный — ANN по тому же скору, — бандит исследует внутри уже смещённого пула.

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

Что слой делает со скором

Два способа превратить \((\mu, \sigma)\) в порядок

Ранкер отдаёт число \(\hat f(u,i)\). Слой считает его средним распределения награды и добавляет второй параметр — разброс \(\sigma(u,i)\).

$$ \text{UCB: } \ s_i = \mu_i + \alpha\,\sigma_i \qquad\qquad \text{Thompson: } \ s_i = \mu_i + \alpha\,\sigma_i \cdot \xi_i, \ \ \xi_i \sim \mathcal{N}(0,1) $$

И в обоих случаях — сортировка по \(s_i\). Здесь спрятан шаг, который в теории обычно проговаривают вскользь: классический бандит выбирает одно действие через \(\arg\max\), а выдача — это \(k\) действий сразу. Переход от «взять максимум» к «отсортировать и взять топ-\(k\)» ломает три предпосылки теории.

  • Награда наблюдается не за все \(k\). До десятой позиции доходит меньше половины пользователей. Отсутствие клика на позиции 10 — почти всегда «не посмотрел», а не «не понравилось», и обновлять апостериор так, будто это отказ, значит систематически занижать оценки внизу выдачи.
  • Награда приходит с задержкой. Между показом и кликом — секунды, между показом и возвратом на следующий день — сутки. Пока награда не пришла, апостериор не обновился, и одному айтему успевают выдать сотни показов авансом.
  • Обновление батчевое. Апостериор пересчитывается раз в \(N\) минут, а не после каждого запроса, — значит внутри окна политика фиксирована и все гарантии, доказанные для пошагового обновления, работают лишь приближённо.
Сколько это даёт и где перестаёт давать

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

\(\alpha\)ЖадныйUCBThompsonUCB нашёл самородокUCB вывел в топ-1
0.000.8340.8340.8343/80/8
0.500.8340.8580.8587/82/8
1.000.8340.8660.8378/84/8
3.000.8340.9090.7568/88/8
5.000.8340.8910.7238/88/8

Числа воспроизводятся скриптом _tools/bandit_demo.py; он же независимо повторяет вычисления виджета.

Четыре наблюдения, и третье — самое интересное.

  • \(\alpha = 3\) даёт 0.909 против 0.834 — это +9% к выдаче, полученные перестановкой того же самого пула кандидатов. Ни новой модели, ни новых признаков.
  • \(\alpha = 5\) даёт уже 0.891. Исследование перестало окупаться: мы поднимаем наверх и тех, про кого всё понятно. Зависимость от \(\alpha\) — горб, а не лестница, и это первое, что стоит проверить, настраивая такой слой.
  • При \(\alpha = 0.5\) самородок заглядывает в топ в 7 каталогах из 8, а закрепляется в топ-1 только в 2. Айтем получает несколько показов, ловит неудачную серию, его оценка проседает, а бонус \(\propto 1/\sqrt{n}\) уже подсох — и он выпадает обратно. Малый бонус хуже, чем никакой: он тратит показы, но не доводит дело до конца.
  • Thompson по чистой награде проигрывает UCB и при больших \(\alpha\) уходит ниже жадного: он шумит там, где всё и так понятно. Почему в проде всё равно выбирают его — в разделе 6.
Что здесь надо увидеть
  1. Поставьте \(\alpha = 0\): самородок не попадает в топ-1 ни разу. Круг замкнут.
  2. Поднимите до 3: он первый во всех восьми каталогах, доля растёт до 0.909.
  3. Поднимите до 5 — доля падает. Найдите вершину горба.
  4. Переключитесь на Thompson и опустите \(\alpha\) до 1: он возвращается к отвергнутым, потому что сэмпл иногда выпадает большим, — но платит за это шумом постоянно.

Что сказать на собесе: «Бандит на переранжировании — это сортировка не по предикту, а по предикту плюс мера незнания. Главное решение — откуда берётся σ: из ансамбля или MC-dropout, но не из \(p(1-p)\), потому что это алеаторический шум, который от показов не убывает».

5. Откуда берётся σ

ИсточникКакЦена
Ансамбль\(N\) моделей, берём выборочную дисперсию предсказаний\(N\) моделей в обучении и в рантайме
MC-dropoutвключаем дропаут на инференсе, делаем несколько проходов\(N\) прогонов головы; эмбеддинги считаются один раз
Neural linearбайесовская линейная регрессия поверх последнего скрытого слоя; сеть даёт представление, линейный слой — честную ковариацию и \(\sqrt{x^\top A^{-1} x}\)поддержка матрицы \(A\), зато интервал точный

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

Главная ловушка: не всякая неопределённость полезна

Разброс бывает двух сортов, и складывать их нельзя.

  • Эпистемическая — «модель не знает». Уменьшается от новых данных. Исследовать имеет смысл именно её.
  • Алеаторическая — «событие само по себе случайно». Пользователь кликает с вероятностью 0.3, и определённее это не станет ни от какого объёма данных.

Опасность конкретная. Модель предсказывает вероятность \(p\); у бернуллиевской величины дисперсия равна \(p(1-p)\), и заманчиво взять \(\sigma = \sqrt{p(1-p)}\) — формула под рукой, считать нечего.

Показов у айтема\(\sqrt{p(1-p)}\)\(\sqrt{p(1-p)/n}\)
10.45830.4583
100.45830.1449
1000.45830.0458
10000.45830.0145
100000.45830.0046

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

Левый столбец не меняется вовсе. Это чистая алеаторика: такой «бонус» максимален при \(p = 0.5\) (значение 0.5000) и просто тянет наверх середнячков, никогда не затухая.

Как проверить, что вы измеряете нужное: постройте \(\sigma\) как функцию числа показов айтема. Если зависимости нет — вы измеряете не то.

И второй способ убить эпистемическую оценку: собрать ансамбль из моделей, делящих одну таблицу эмбеддингов. Предсказания окажутся почти одинаковыми, \(\sigma\) выйдет крошечной, и слой не будет делать ничего. Различаться должны и инициализация, и порядок данных.

6. UCB или Thompson: аргумент, который решает

По чистой награде UCB обычно выигрывает — таблица выше это показывает. В проде тем не менее выбирают Thompson, и причина лежит за пределами бандитской задачи.

Стохастичность нужна не ради исследования, а ради измеримости

UCB детерминирован: при данном состоянии он выдаёт один и тот же порядок. Значит вероятность показа \(\pi(i \mid u)\) равна нулю или единице, и всё, что построено на обратных склонностях — IPS, SNIPS, doubly robust, — превращается в деление на ноль. Оценить офлайн новую модель по таким логам нельзя.

Thompson задаёт распределение над перестановками. Склонности положительны и оцениваются повторным сэмплированием при тех же \((\mu, \sigma)\), а значит логи годятся для off-policy оценки. Один и тот же слой закрывает обе задачи: и исследует, и делает данные пригодными для оценки.

Практический вывод: если склонности всё равно нужны — а они нужны, как только появляется желание сравнивать модели офлайн, — то стохастическая политика не роскошь, а условие. И сохранять надо не только факт показа, но и \(\mu\), \(\sigma\) и версию модели на момент решения.

Почему одних положительных склонностей мало

Оценка IPS формально несмещённая, но её дисперсия определяется разбросом весов \(w = \pi_{\text{new}}/\pi_{\text{log}}\). Эффективный размер выборки: \(\mathrm{ESS} = (\sum w)^2 / \sum w^2\).

Пусть у 99% наблюдений вес 1, а у оставшегося процента — вес \(W\):

\(W\)110100100010000
ESS из 10001000.0597.039.212.110.2
доля100%59.7%3.9%1.2%1.0%

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

Один процент наблюдений с большим весом съедает 99% эффективной выборки: тысяча логов работает как десять. Отсюда обрезка весов, SNIPS и doubly robust — и отсюда же требование, чтобы политика не только была стохастической, но и не уходила слишком далеко от логирующей.

Посмотреть, как расходятся IPS и SNIPS при росте разброса весов, можно в тренажёре.

7. Что ломает слой в реальной системе

Четыре типовых поломки
  1. Бизнес-правила поверх бандита. Слой выдал стохастический порядок, а правило «не более двух айтемов одного автора подряд» его переставило. Фактическая политика — уже не та, склонности посчитаны для не той выдачи, и off-policy оценка тихо смещается. Либо правила учитываются при расчёте склонностей, либо оценке нельзя верить.
  2. Обновление страницы. Пользователь перезагрузил ленту и увидел другой порядок — при стохастической политике это происходит само собой и читается как сбой. Результат рандомизации кэшируют на сессию, а вместе с ним кэшируют и склонность.
  3. Дрейф. Апостериор с накопленными счётчиками помнит всё. Айтем, который был хорош в декабре, к марту таким быть перестал, а бонус у него давно нулевой, и слой этого не проверит. Лечится экспоненциальным забыванием: старые наблюдения обесцениваются, \(\sigma\) снова растёт.
  4. Двойной учёт неопределённости. Если ранкер обучен на логах, где холодные айтемы уже поднимались отдельным правилом, модель это выучила, и бонус добавится поверх — новые айтемы поедут наверх вдвое сильнее ожидаемого.

8. Когда всё это не нужно

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

Тот же холодный старт решается и жёстким правилом: детерминированной подстановкой — ровно один пост малоизвестного автора поднимается на скор, соответствующий условной позиции 15–16, независимо от того, насколько модель в нём не уверена. Именно так это сделано в открытом коде ленты X.

Размен ровно тот, что в таблице про ручки. Правило предсказуемо, объяснимо и стоит десять строк. Бонус за неопределённость сам решает, кому и сколько показов дать, но требует честной \(\sigma\), стохастичности и логирования склонностей.

Если единственная задача — не дать новому автору умереть в безвестности, правило справится и обойдётся дешевле.

Три условия, при которых слой окупается

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

И отдельная методическая сложность. Выигрыш exploration проявляется в качестве будущих моделей, а меряют его обычно недельным A/B по сегодняшним метрикам. На этом горизонте исследование почти всегда выглядит проигрышем — ровно как MMR в главе про разнообразие, и по той же причине: измеряется не то, ради чего это делается.

Стоит помнить и про границы теории: классические теоремы доказываются для неконтекстных бандитов, где награда не зависит ни от пользователя, ни от времени суток. Для рекомендаций это слишком сильное ограничение — весь смысл в том, что \(\mu\) и \(\sigma\) считаются для пары (пользователь, айтем). Контекстные варианты — linUCB, linTS, neural linear — гарантии сохраняют, но при более сильных предположениях.

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

Что такое петля обратной связи и как из неё выбраться?

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

Выход — менять логирующую политику. Два дешёвых способа: случайная квота (ε-greedy) и софтмакс-сэмплинг с температурой. Ими обычно забирают первые 95% выгоды. Дальше — бандитный слой с оценкой неопределённости.

Важная оговорка про софтмакс-сэмплинг: он переранжирует только то, что уже отобрала воронка, а воронка отбирала жадно. Настоящее исследование требует механизма и на retrieval.

Выведите бонус UCB.

Из неравенства Хёфдинга: для выборки на [0,1] с истинным средним μ и выборочным \(\hat\mu\) верно \(\mathbb{P}(\mu \ge \hat\mu + u) \le e^{-2nu^2}\). Приравниваем правую часть к δ и разрешаем относительно u: \(U = \sqrt{-\ln\delta / 2n}\).

Дальше выбираем δ = 1/k^c — с ростом времени требуем всё более надёжную границу — и получаем \(a_k = \arg\max [Q_k(a) + c\sqrt{\log k / n_k(a)}]\).

Читается как «оценка средней награды плюс ширина доверительного интервала». Бонус убывает как \(1/\sqrt{n}\): при 1 показе он 3.03, при 1000 уже 0.096. Поэтому отдельной ручки исследования не нужно — алгоритм сам сокращает его по мере накопления данных.

Чем Thompson отличается от UCB?

UCB детерминирован и оптимистичен: берёт верхнюю границу интервала. Thompson байесовский: сэмплирует параметры из апостериора и действует жадно относительно сэмпла. Для бинарной награды это Beta-Bernoulli — априор Beta сопряжён с бернуллиевским, обновление тривиально (успех → α+1, неуспех → β+1), три строки кода и никаких гиперпараметров кроме априора.

По regret оба сублинейны, в отличие от ε-greedy: у того при удвоении отрезка прирост удваивается (73.9 → 142.5), потому что фиксированная доля случайного трафика тратится вечно.

Главное отличие на практике — не в награде, а в измеримости, см. следующий вопрос.

Почему в проде чаще берут Thompson, если UCB даёт больше награды?

Из-за склонностей. UCB детерминирован: при данном состоянии порядок один и тот же, значит π(i|u) равна нулю или единице, и IPS, SNIPS, doubly robust превращаются в деление на ноль. Офлайн оценить новую модель по таким логам нельзя.

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

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

Что брать за ручку в рекомендациях?

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

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

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

Почему бандитный слой ставят на переранжирование?

Четыре инженерные причины. Цена ошибки ограничена: слой переставляет сотню уже отфильтрованных кандидатов, худшее — показать девятого вместо второго; на retrieval цена не ограничена ничем. Здесь есть полный вектор признаков, а σ нужна про пару (пользователь, айтем) в контексте. Кандидатов мало: ансамбль из пяти моделей на сотне — посильно, на десяти тысячах — нет. И это точка, где решение и так принимается.

Ограничение: слой исследует только то, что отобрала воронка. Если retrieval жадный, бандит исследует внутри уже смещённого пула.

Откуда брать σ и какая именно σ нужна?

Три источника: ансамбль моделей (выборочная дисперсия предсказаний), MC-dropout (дропаут на инференсе, несколько проходов головы) и neural linear (байесовская линейная регрессия поверх последнего слоя, даёт честную \(\sqrt{x^\top A^{-1}x}\)). Первые два почти ничего не стоят по разработке.

Главное — не перепутать эпистемическую неопределённость («модель не знает», убывает от данных) с алеаторической («событие случайно», не убывает никогда). Соблазн взять \(\sigma = \sqrt{p(1-p)}\) велик — формула под рукой. Но это чистая алеаторика: при p = 0.3 она равна 0.4583 и при одном показе, и при десяти тысячах. Такой бонус максимален при p = 0.5 и просто тянет наверх середнячков.

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

Как настраивать силу бонуса?

Помнить, что зависимость — горб, а не лестница. В симуляции α = 3 даёт 0.909 от идеальной выдачи против 0.834 у жадного (плюс 9% перестановкой того же пула), а α = 5 уже 0.891: мы начинаем поднимать наверх и тех, про кого всё понятно.

И отдельно — про малые α. При α = 0.5 самородок заглядывает в топ в 7 каталогах из 8, а закрепляется в топ-1 только в 2: он получает несколько показов, ловит неудачную серию, оценка проседает, а бонус \(\propto 1/\sqrt{n}\) уже подсох. Малый бонус хуже, чем никакой — тратит показы, но не доводит дело до конца.

Когда бандитный слой не нужен?

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

Плюс есть детерминированная альтернатива: жёстко поднимать ровно один пост малоизвестного автора на фиксированную позицию. Так сделано в открытом коде ленты X. Предсказуемо, объяснимо, десять строк — и если задача только в том, чтобы новый автор не умер в безвестности, этого достаточно.

И методическая ловушка: выигрыш exploration проявляется в качестве будущих моделей, а меряют его недельным A/B по сегодняшним метрикам. На этом горизонте он почти всегда выглядит проигрышем.

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

Круг

Чтобы узнать CTR, надо показать; чтобы показать — знать CTR. Жадный: 0.834 и 0/8 самородков.

Дёшево

Случайная квота и софтмакс-сэмплинг дают первые 95%. Второй исследует только внутри топа.

UCB

Из Хёфдинга: \(c\sqrt{\log k / n}\). Убывает как \(1/\sqrt n\) — исследование гаснет само.

Thompson

Beta-Bernoulli: успех → α+1. Три строки, гиперпараметров нет.

Regret

ε-greedy линеен (73.9 → 142.5), UCB и Thompson — нет.

Ручка = айтем

σ берём из модели, не из счётчика. Бандит — надстройка над ранкером, не замена.

Ловушка σ

\(\sqrt{p(1-p)}\) = 0.4583 при любом n. Нужна \(\sqrt{p(1-p)/n}\) или ансамбль.

Зачем стохастика

Ради склонностей. UCB даёт π ∈ {0,1} и убивает IPS. Разброс весов тоже убивает: ESS 39 из 1000.

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