Часть III · Ранжирование · глава 13 из 19
Мультизадачность, дебиасинг и дистилляция
Три сюжета, и каждый чинит свою поломку последнего слоя ранжирования: целей на самом деле больше одной, данные смещены позицией показа, а модель, которая лучше всех, не помещается в бюджет. Объединяет их то, что все три — про расхождение между тем, чему модель учится, и тем, для чего её применяют.
- Общий ствол — это компромисс, у которого есть цена. Если задачам нужны представления под углом 90°, каждая получает 0.71 сигнала; при 180° обе не получают ничего.
- MMoE стоит долю процента параметров. Гейты — 1.54% от модели; всё остальное — просто ёмкость экспертов.
- Голову CVR нельзя применять там, где она не училась. Она учится на 5% данных и применяется ко всем показам — в 20 раз больше объектов. Отсюда вся конструкция ESMM.
- Метка бинарна, предсказание учителя — нет. При десяти показах шум оценки клика составляет 138% от самой величины: метка почти ничего не говорит о конкретном объекте.
1. Мультизадачность
Ранжирование почти никогда не оптимизирует один сигнал. Классическая раскладка, на примере видеоплатформы:
- Вовлечённость — клики, время просмотра;
- Удовлетворённость — лайки, оценки, «не показывать такое»;
- Долгосрочные сигналы — возвращаемость, LTV.
Учить отдельную модель на каждый сигнал — в разы больше ресурсов, плюс сложнее улучшать и поддерживать. Отсюда мультизадачные архитектуры.
Shared bottom и его беда
Простейшая конструкция: сначала общие слои, затем отдельные головы под каждую задачу. Плюсы очевидны — регуляризация, поскольку задачи «поддерживают» друг друга, перенос знания и экономия ресурсов.
Простая модель, показывающая суть. Пусть задачам нужны представления, направления которых расходятся на угол \(\alpha\). Общий ствол способен выдать только одно направление, оптимум по сумме лежит на биссектрисе, и каждая задача получает \(\cos(\alpha/2)\) от своего сигнала:
| Угол \(\alpha\) | Доля сигнала каждой задаче | Потеря против отдельной модели |
|---|---|---|
| 0° | 1.0000 | 0.0% |
| 30° | 0.9659 | 3.4% |
| 60° | 0.8660 | 13.4% |
| 90° | 0.7071 | 29.3% |
| 120° | 0.5000 | 50.0% |
| 180° | 0.0000 | 100.0% |
Числа воспроизводятся скриптом _tools/mtl_demo.py в этом репозитории.
Модель нарочно упрощённая, но она объясняет два наблюдения сразу. Первое: при небольшом расхождении общий ствол почти бесплатен — при 30° потеря 3.4%, и мультизадачность выглядит чистым выигрышем. Второе: деградация нелинейна, и на конфликтующих задачах обе проседают разом.
Отсюда же лечение «в лоб», которое действительно работает: просто увеличить ёмкость ствола, чтобы места хватило обоим направлениям. Дорого, но честно.
Mixture of Experts и MMoE
Исходная конструкция:
$$ y = \sum_{i=1}^{K} g_i(x)\, f_i(x) $$где \(f_i\) — эксперты, а \(g\) — гейт, задающий веса. Ключевая идея — условные вычисления: на каждом объекте работает только часть экспертов.
MMoE заменяет общий ствол смесью экспертов, но главное в аббревиатуре — первая буква: multi-gate, у каждой задачи свой гейт. Если задачи не связаны, под каждую выучивается своё подмножество экспертов, и конфликт из предыдущего раздела просто не возникает.
Вход 1024, представление 256, четыре задачи. Shared bottom — один ствол на 2.62e+05 параметров.
| Экспертов | Параметры экспертов | Гейты | Всего | К shared bottom |
|---|---|---|---|---|
| 2 | 5.24e+05 | 8.19e+03 | 5.32e+05 | ×2.0 |
| 4 | 1.05e+06 | 1.64e+04 | 1.06e+06 | ×4.1 |
| 8 | 2.10e+06 | 3.28e+04 | 2.13e+06 | ×8.1 |
Числа воспроизводятся скриптом _tools/mtl_demo.py.
Обратите внимание на пропорцию: при четырёх экспертах гейты — это 1.54% параметров модели. То есть собственно идея MMoE стоит полтора процента, а платим мы за экспертов, то есть просто за ёмкость.
Отсюда правильная постановка вопроса при сравнении: MMoE надо сравнивать не с узким shared bottom, а с расширенным до той же ёмкости. Если прирост остаётся — работает механизм разделения задач; если исчезает — работала просто ёмкость.
Наблюдение из практики, подтверждающее, что механизм работает как задумано: при обучении действительно появляются эксперты, специализирующиеся на конкретных задачах, — то есть гейты расходятся, а не сходятся к равномерным весам.
2. ESMM: когда голова применяется не там, где училась
Отдельный и очень характерный сюжет. Пусть мы хотим ранжировать выдачу по покупкам. Есть две очевидные попытки, и обе неудачны — а разбор почему и есть содержание раздела.
Возьмём типичные цифры: 108 показов, CTR 5%, конверсия из клика в покупку 3%.
- кликов: 5.0e+06, покупок: 1.5e+05;
- \(P(\text{покупка} \mid \text{показ}) = 0.15\%\);
- дисбаланс классов при обучении напрямую: 1 к 666.
Числа воспроизводятся скриптом _tools/mtl_demo.py.
Попытка 1: учить \(P(\text{покупка} \mid \text{показ})\) напрямую. Не выходит по двум причинам. Разреженность — позитивов крайне мало, 1 к 666. И сложность сигнала: о покупке мы знаем слишком мало, чтобы её предсказывать, и вся неизвестная нам часть превращается в шум.
Попытка 2: моделировать \(P(\text{покупка} \mid \text{клик})\). Позитивов относительно больше — 3% против 0.15%, и обучение идёт заметно лучше. Но ранжировать по такой модели нельзя, и вот почему.
Голова CVR учится на 5.0e+06 кликах — это 5% данных, а применяется ко всем 108 показам, то есть к в 20 раз большему числу объектов. Мы ранжируем показы так, будто все они уже кликнуты. Это несоответствие срезов обучения и применения — и его стоит уметь называть именно так.
| Айтем | pCTR | pCVR | pCTR · pCVR |
|---|---|---|---|
| A | 0.010 | 0.400 | 0.00400 |
| B | 0.080 | 0.090 | 0.00720 |
| C | 0.040 | 0.150 | 0.00600 |
Порядок по pCVR: A > C > B. Порядок по произведению: B > C > A — полностью обратный.
Наверх по pCVR встаёт A с ожидаемой покупкой 0.00400 вместо B с 0.00720 — потеря 44% на верхней позиции.
Числа воспроизводятся скриптом _tools/mtl_demo.py.
Айтем A — это нишевый товар: кликают редко, но кто кликнул, тот покупает. По условной вероятности он чемпион, по безусловной — аутсайдер.
Две головы, CTR и CVR, и произведение как итоговый скор. Но заметьте: вторая голова всё ещё применяется не на том срезе, на котором учится. Декомпозиция сама по себе проблему не решает.
Поэтому ключевой приём ESMM в другом: вторую голову учат на лосс по \(P(\text{покупка}\mid\text{показ})\), то есть на всём пространстве показов, а не только на кликах. Градиент до головы CVR доходит через произведение, и она обучается на том же срезе, на котором потом работает.
Тот же приём используют в рекламе для моделирования конверсий: конверсии нужны, чтобы корректировать ставки в аукционах, где платят за конверсию, а не за клик.
3. Позиционный дебиасинг
На ранних позициях выдачи CTR выше — по двум разным причинам, и первое, что надо сделать, это их различить.
- Пользователи чаще кликают на ранние позиции, потому что видят их раньше и больше им доверяют. Это смещение.
- На высоких позициях действительно стоят более релевантные айтемы. Это не смещение, это работа модели.
Смешать их — значит либо «вычесть» вместе со смещением настоящую релевантность, либо не вычесть ничего.
Пусть вероятность просмотра позиции убывает как \(1/\text{pos}^{0.7}\):
| Позиция | Вероятность просмотра | Вес IPW = \(1/e\) |
|---|---|---|
| 1 | 1.0000 | 1.00 |
| 2 | 0.6156 | 1.62 |
| 5 | 0.3241 | 3.09 |
| 10 | 0.1995 | 5.01 |
| 20 | 0.1228 | 8.14 |
Теперь два айтема. У X истинная релевантность 0.12, но показали его на позиции 5. У Y релевантность 0.10, показали на позиции 1.
- Наблюдаемый CTR: X — 0.0389, Y — 0.1000.
- Наивная модель уверенно ставит выше Y, хотя на самом деле лучше X.
- После деления на вероятность просмотра: X — 0.1200, Y — 0.1000. Порядок восстановлен.
Числа воспроизводятся скриптом _tools/mtl_demo.py.
Отсюда же два следствия, о которых спрашивают. False negatives: часть некликнутых айтемов на самом деле хороши, их просто не увидели. И петля обратной связи: наверху то, что нравится системе, из-за позиционного смещения оно чаще получает положительный фидбек и подтверждает само себя.
| Способ | Как работает | Цена |
|---|---|---|
| Учить только на первой позиции | берём \(P(\text{клик}\mid\text{показ}, pos=1)\) | при слейте в 20 позиций остаётся 5% данных |
| Позиция как признак | добавляем позицию в модель, на инференсе подставляем \(pos = 1\) | просто, но модель может «спрятать» релевантность внутрь позиционного признака |
| Inverse propensity weighting | перевзвешиваем сэмплы обратно вероятности просмотра | нужны истинные вероятности, а для этого — случайный трафик; и разброс весов в 8.1 раза раздувает дисперсию |
| Bias-башня | отдельная маленькая башня, моделирующая смещение | требует аккуратной настройки, но это продовый стандарт |
Устройство простое, и в нём три существенные детали.
- На входе башни — вся контекстная информация, полезная для моделирования смещения. Не только позиция: например, устройство сильно влияет на позиционный эффект, потому что на телефоне видно меньше позиций, чем на десктопе.
- При обучении выходы складываются: \(\text{logit} = f_{\text{main}}(u,c,i) + f_{\text{bias}}(pos, \text{device}, \dots)\). И обязательно дропаут выхода башни — иначе она объяснит позицией слишком многое и перетянет на себя сигнал релевантности, а основная модель недоучится.
- На инференсе башню просто выкидывают — остаётся чистый скор релевантности.
Идея хороша тем, что смещение моделируется явно и отделяемо. Мы не пытаемся вычесть его постфактум из готового скора, а даём модели отдельный канал, куда его удобно списать, — и потом отрезаем этот канал.
AUC у головы может быть отличным, а значения — систематически смещёнными. Для ранжирования внутри одной головы это неважно, но как только скоры разных голов складывают или умножают на деньги, смещение начинает работать в полную силу.
Проверять удобно виджетом калибровки в тренажёре; аргументация — в главе 10.
4. Дистилляция знаний
Мотивация чисто инженерная: размен качества на задержку. Модель больше — качество выше, но у ранкера жёсткий потолок в десятки миллисекунд на сотни кандидатов. Дистилляция разрывает эту связь: большая модель-учитель обучается офлайн, а маленький ученик учится воспроизводить её выход и работает в рантайме.
Почему предсказания учителя полезнее меток
Момент, который на собеседовании звучит парадоксально: у нас же есть настоящие метки, зачем учиться на чужие предсказания? Причин две, и обе стоит уметь развернуть.
Метка говорит «клика не было». Учитель говорит «вероятность клика была 0.40» или «0.02» — и это совершенно разные ситуации, которые метка не различает вовсе.
Такой сигнал называют dark knowledge: учитель передаёт не ответ, а форму распределения — в том числе информацию о том, насколько задача была трудной.
Пусть истинная вероятность клика 0.05. Насколько точно её оценивает сама метка, если объект показали \(n\) раз?
| Показов у объекта | Стандартная ошибка | Относительный шум |
|---|---|---|
| 1 | 0.2179 | 435.9% |
| 10 | 0.0689 | 137.8% |
| 100 | 0.0218 | 43.6% |
| 1000 | 0.0069 | 13.8% |
| 10000 | 0.0022 | 4.4% |
Числа воспроизводятся скриптом _tools/mtl_demo.py.
При десяти показах шум оценки — 138% от самой величины. Метка почти ничего не говорит о конкретном объекте, только о его классе. А мы уже знаем, что у большинства каталога показов именно столько.
Учитель усреднил этот шум по всей обучающей выборке и отдаёт ученику сглаженную цель. А шум — главный источник переобучения на хвосте, как в главе 8.
Почему у ученика две головы
При прямой дистилляции ученик учится сразу на две цели — настоящие метки и предсказания учителя — с весом между ними. Это простейший вариант, и в онлайн-ранжировании он приносит специфическую проблему.
Приём: у ученика делают две головы. Одна учится на целевые метки, другая — на предсказания учителя. Для ранжирования используется голова с целевыми метками.
Зачем так сложно? Потому что у учителя есть свои смещения, и мы не хотим, чтобы они влияли на ранжирование. Учитель обучался на логах той же системы и унаследовал её позиционное смещение, её перекос к популярному, её петлю обратной связи — всё, о чём был предыдущий раздел. Дистиллируя его напрямую, мы дистиллируем и это.
Дистилляционная голова работает как вспомогательная задача в смысле мультизадачности: она заставляет общее представление вобрать знание учителя, но не диктует итоговый порядок. Знание проходит через общий ствол, смещения остаются в чужой голове.
Второй мотив — калибровка. Предсказания учителя её ломают, а голова, обученная на настоящих метках, сохраняет. Для рекламы, где скор умножается на ставку, это решающий аргумент.
- Учитель дрейфует. Его переучивают, и цель ученика меняется скачком. Ученик, обученный на старого учителя, начинает противоречить новому — а мониторинг этого не покажет, потому что обе модели по отдельности выглядят нормально.
- Учителя надо чем-то считать. Он не обязан работать в рантайме, но обязан пройти по всему обучающему потоку. Дистилляция не бесплатна — она переносит стоимость из инференса в обучение.
- Ученик наследует потолок учителя. Если учитель ошибается систематически, ученик воспроизведёт ошибку и никогда не превзойдёт его на этом подмножестве — хотя на настоящих метках мог бы.
- Замкнутый круг версий. Учитель обучен на логах, собранных предыдущим учеником. Через несколько итераций система дистиллирует саму себя — та же петля обратной связи, только на уровне моделей.
Вопросы с собеседований
Зачем ранжирующей модели несколько задач и в чём беда shared bottom?
Затем, что сигналов всегда несколько: вовлечённость (клики, время), удовлетворённость (лайки, оценки), долгосрочные метрики. Отдельная модель на каждый — в разы больше ресурсов и сложнее поддержка.
Shared bottom даёт регуляризацию, перенос знания и экономию. Беда возникает на конфликтующих задачах: градиенты голов тянут общее представление в разные стороны. В простой геометрической модели, где направления задач расходятся на угол α, каждая получает cos(α/2): при 90° это 0.71, потеря 29%, при 180° — ноль.
Лечение «в лоб» — расширить ствол, чтобы места хватило обоим направлениям. Аккуратнее — MMoE.
Что такое MMoE и чем он отличается от MoE?
MoE: \(y = \sum_i g_i(x) f_i(x)\), где \(f_i\) — эксперты, \(g\) — гейт; ключевая идея — условные вычисления, на объекте работает часть экспертов.
MMoE — multi-gate: у каждой задачи свой гейт, то есть свои веса экспертов. Если задачи не связаны, под каждую выучивается своё подмножество экспертов, и конфликт не возникает.
Важное замечание для честного сравнения: гейты — это порядка полутора процентов параметров, всё остальное просто ёмкость экспертов. Поэтому MMoE надо сравнивать не с узким shared bottom, а с расширенным до той же ёмкости — иначе непонятно, что дало прирост.
Что такое ESMM и какую проблему он решает?
Проблема — несоответствие срезов обучения и применения. Учить P(покупка | показ) напрямую мешает разреженность: при CTR 5% и CVR 3% дисбаланс 1 к 666, плюс сигнал слишком сложный. Учить P(покупка | клик) проще, но такая голова обучается на 5% данных (только клики) и применяется ко всем показам — в 20 раз большему числу объектов.
Почему это ломает ранжирование: pCVR — вероятность при условии клика. Нишевый товар с pCTR 0.01 и pCVR 0.40 по условной вероятности чемпион, а по ожидаемой покупке 0.004 против 0.0072 у популярного — потеря 44% на верхней позиции.
ESMM: моделируем цепочку показ → клик → покупка, P(покупка|показ) = P(клик|показ)·P(покупка|клик), две головы. И ключевой приём — вторую голову учат на лосс по P(покупка|показ), то есть на всём пространстве показов; градиент доходит до неё через произведение.
Почему CTR выше на первых позициях и что с этим делать?
По двум разным причинам, и их надо различать: пользователи чаще кликают наверх, потому что видят раньше и больше доверяют (это смещение), и наверху действительно стоят более релевантные айтемы (это работа модели). Смешать их — значит вычесть вместе со смещением настоящую релевантность.
Конкретно смещение переворачивает порядок: айтем с релевантностью 0.12 на позиции 5 даёт наблюдаемый CTR 0.039, а айтем с релевантностью 0.10 на позиции 1 — 0.100. Наивная модель уверенно ставит выше худший.
Четыре лечения: учить только на первой позиции (при слейте 20 остаётся 5% данных), позиция как признак с подстановкой pos=1 на инференсе (модель может спрятать релевантность в позиционный признак), IPW (нужен случайный трафик, а разброс весов в 8 раз раздувает дисперсию) и bias-башня — продовый стандарт.
Как устроена bias-башня?
Отдельная маленькая башня, на вход которой идёт вся контекстная информация, полезная для моделирования смещения, — не только позиция, но и, например, устройство: на телефоне видно меньше позиций, чем на десктопе.
При обучении выходы складываются в логите: основная модель плюс башня. Обязательный элемент — дропаут выхода башни, иначе она объяснит позицией слишком многое, перетянет сигнал релевантности и основная модель недоучится. На инференсе башню выкидывают, остаётся чистый скор.
Красота идеи в том, что смещение моделируется явно и отделяемо: мы не вычитаем его постфактум, а даём отдельный канал, куда его удобно списать, и потом отрезаем этот канал.
Зачем учиться на предсказания учителя, если есть настоящие метки?
Две причины. Первая: метка бинарна, предсказание — нет. «Клика не было» одинаково выглядит для объекта с вероятностью 0.40 и с вероятностью 0.02, а учитель эти случаи различает. Это dark knowledge — передаётся не ответ, а форма распределения.
Вторая: учитель — сглаженная версия данных. При истинной вероятности клика 0.05 и десяти показах стандартная ошибка оценки 0.069, то есть 138% от самой величины: метка почти ничего не говорит о конкретном объекте. Учитель усреднил шум по всей выборке, и ученик получает менее шумную цель — а шум и есть главный источник переобучения на хвосте.
Мотивация всего приёма — размен качества на задержку: учитель считается офлайн, ученик влезает в бюджет ранкера.
Почему у ученика делают две головы?
Потому что у учителя есть свои смещения: он обучался на логах той же системы и унаследовал её позиционное смещение, перекос к популярному и петлю обратной связи. Дистиллируя его напрямую, мы дистиллируем и это.
Решение: одна голова учится на целевые метки, вторая — на предсказания учителя, а для ранжирования используется первая. Дистилляционная голова работает как вспомогательная задача: она заставляет общее представление вобрать знание учителя, но не диктует порядок. Знание проходит через общий ствол, смещения остаются в чужой голове.
Второй мотив — калибровка: предсказания учителя её ломают, а голова на настоящих метках сохраняет. Для рекламы, где скор умножается на ставку, это решает.
Где дистилляция ломается в проде?
Четыре места. Учитель дрейфует при переобучении, и цель ученика меняется скачком — причём мониторинг этого не покажет, обе модели по отдельности выглядят нормально. Учителя надо считать по всему обучающему потоку — дистилляция переносит стоимость из инференса в обучение, а не убирает её. Ученик наследует систематические ошибки учителя и на этом подмножестве никогда его не превзойдёт, хотя на настоящих метках мог бы. И замкнутый круг версий: учитель обучен на логах предыдущего ученика, через несколько итераций система дистиллирует саму себя.
Шпаргалка одним экраном
Shared bottom
Регуляризация и экономия, но при расхождении задач на 90° каждая получает 0.71 сигнала.
MMoE
Свой гейт у каждой задачи. Гейты — 1.54% параметров; сравнивать надо при равной ёмкости.
Воронка
CTR 5%, CVR 3% → 0.15% покупок, дисбаланс 1 к 666.
ESMM
P(buy|imp)=P(clk|imp)·P(buy|clk), и CVR-голову учить на всём пространстве показов.
Две причины
Наверху выше CTR и из-за смещения, и из-за релевантности. Смешать — потерять сигнал.
Bias-башня
Контекст на входе, сумма логитов, дропаут выхода, на инференсе выкинуть.
Dark knowledge
При 10 показах шум метки 138%. Учитель отдаёт сглаженную цель, метка — только класс.
Две головы
Ранжируем головой на метках; дистилляционная — вспомогательная. Смещения остаются в чужой голове.
Первоисточники
- J. Ma, Z. Zhao, X. Yi, J. Chen, L. Hong, E. Chi. Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts, KDD 2018 — MMoE.
- Z. Zhao et al. Recommending What Video to Watch Next: A Multitask Ranking System, RecSys 2019 — MMoE и shallow bias tower в проде.
- X. Ma et al. Entire Space Multi-Task Model: An Effective Approach for Estimating Post-Click Conversion Rate, SIGIR 2018 — ESMM.
- T. Joachims, A. Swaminathan, T. Schnabel. Unbiased Learning-to-Rank with Biased Feedback, WSDM 2017 — IPW для позиционного смещения.
- G. Hinton, O. Vinyals, J. Dean. Distilling the Knowledge in a Neural Network, 2015 — dark knowledge.
- «Bridging the Gap: Unpacking the Hidden Challenges in Knowledge Distillation for Online Ranking Systems» — источник приёма с двумя головами и разбора отказов в онлайне.
- Числа главы:
_tools/mtl_demo.pyв этом репозитории.