Часть II · Кандидатогенерация · глава 7 из 19
Двухбашенные модели: folding, негативы и LogQ
Переход от выученных векторов к вычисляемым — и главная ловушка на этом пути. Ранжирующий лосс, отлично работающий на второй стадии, для кандидатогенератора смертелен, и причина не в качестве модели, а в вырожденности задачи. Дальше — правильная постановка через софтмакс по каталогу и три инженерных вопроса, которые она порождает: где взять негативы, что с ними не так и как это чинится.
- Folding — не переобучение, а вырожденность. Если группы не сравниваются между собой, лосс распадается на независимые подзадачи, и взаимное расположение групп не определено ничем.
- Симуляция: без межгрупповых негативов половина топ-5 — из чужой группы. При этом внутри своей группы такая модель лучше: 0.392 против 0.362. Она не сломана, она не определена вне обучающего носителя.
- Вклад негатива в градиент \(\propto P(d \mid u)\) — тому, насколько высоко его поставила сама модель. Хард-негатив весит в 67 раз больше случайного.
- Чем полезнее негативы, тем сильнее смещение, которое они вносят. Выбор источника и LogQ-коррекция — две половины одного решения.
1. Зачем нужны две башни
Матричная факторизация упёрлась в три вещи: нет холодного старта по айтемам, нет контекста, нет признаков. Все три снимаются одним ходом — сделать вектор не выученным, а вычисляемым.
$$ s(u, i) \;=\; \bigl\langle f_\theta(\text{признаки } u),\; g_\phi(\text{признаки } i) \bigr\rangle $$Можно было бы подать пару \((u, i)\) в одну сеть и получить скор. Так делает ранкер, и качество будет выше. Но для кандидатогенерации такая архитектура непригодна арифметически: чтобы найти топ по каталогу, придётся прогнать сеть \(|I|\) раз на каждый запрос.
Разделение на башни даёт то, ради чего всё затевается: векторы айтемов считаются заранее, складываются в ANN-индекс, и на запрос остаётся посчитать один вектор пользователя и сходить в индекс. Цена — скор обязан быть скалярным произведением, то есть взаимодействие между \(u\) и \(i\) может произойти только в самом конце.
Это фундаментальный размен: две башни жертвуют выразительностью ради того, чтобы половину вычислений можно было сделать до запроса.
2. Folding: почему ранжирующий лосс здесь не работает
Соблазн понятный: у нас уже есть ранжирующий лосс, который хорошо работает. Обучим им и кандидатогенератор. Получится плохо, и разобраться почему — половина главы.
Что значит «impression-aware»
Термин описывает состав обучающей выборки. Строка в логе появляется, только если айтем был показан. Пара, которую прошлая система не отобрала, в датасет не попадает никогда — ни позитивом, ни негативом.
$$ P(\text{клик} \mid \text{показан},\, u, i) \qquad\text{вместо}\qquad P(\text{релевантен} \mid u, i) $$В попарной постановке негативы берутся из того же показа. Значит, все сравнения, которые модель когда-либо делала, происходили внутри одного слейта — то есть внутри множества, которое прошлая система сочла подходящим для этого пользователя.
Между «хорошим айтемом для одной аудитории» и «хорошим айтемом для другой» сравнения не было ни одного: они никогда не встречались в одной выдаче.
Механизм: задача распадается
Термин ввели Xin и соавторы: непреднамеренное наложение непересекающихся групп пользователей и айтемов в низкоранговом пространстве, вызванное неправильной работой с пропущенными данными.
Пусть матрица наблюдений блочно-диагональна: \(K\) групп пользователей, \(K\) групп айтемов, взаимодействия только внутри блоков. Если непоказанные пары в лосс не входят, то между пользователями группы \(k\) и айтемами группы \(l \ne k\) нет ни одного ограничения.
Раз ограничений нет, лосс распадается на \(K\) независимых подзадач. Внутри блока эмбеддинги встают правильно, но расположение блоков между собой не определено ничем: любое одинаково оптимально. Один из этих оптимумов — тот, где блоки лежат друг на друге.
Это не переобучение и не плохая сходимость, а вырожденность задачи: у неё континуум решений с одинаковым лоссом, и оптимизатор честно возвращает одно из них.
- Связанные. Пользователь мог бы иметь мнение, но не увидел; или айтем настолько популярен, что его отсутствие само по себе сигнал. Отсюда берутся удачные неожиданные рекомендации.
- Несвязанные. Пользователю айтем не интересен в принципе — детские мультфильмы и хорроры, кино на языке, которого он не знает. Таких пар подавляющее большинство.
Folding — это приписывание высокой близости несвязанным пропускам. А приписывание близости связанным — наоборот, обобщение, ради которого модель и строится.
Отсюда неприятное следствие: обобщение и folding — одно и то же действие. Разница только в том, в какой пропуск модель попала. Поэтому «быть осторожнее» не помогает — вместе с folding умрёт способность рекомендовать новое.
Почему матрица наблюдений вообще блочная? Страна, язык, интересы, таргетинг. И чем лучше работает прошлая система, тем чётче блоки — она ведь ровно этим и занимается, отсекает нерелевантное. Хорошая система порождает данные, на которых нельзя обучить кандидатогенератор.
- Две непересекающиеся группы, двумерные эмбеддинги. Слева негативы берутся из своей же группы — так выглядит обучение на показах. Справа из всего каталога.
- Тяните эпохи от нуля: справа группы расходятся в противоположные стороны, слева остаются перемешанными — лоссу это ничего не стоит.
- На 40 эпохах слева 50.8% топ-5 по каталогу — айтемы чужой группы. При двух группах случайное угадывание даёт 50%: сигнала о принадлежности нет вообще. Справа — 0.0%.
- Главное — нижние строки. Внутри своей группы «показная» модель лучше: HitRate@5 равен 0.392 против 0.362. Она не хуже обучена, она потратила всю ёмкость на ту задачу, которую ставил лосс.
- Расширьте кандидатов до каталога: 0.392 → 0.196, ровно вдвое. У правильно обученной модели 0.362 → 0.362. Эта пара чисел и есть диагностика folding.
Что сказать на собесе: «Ранкер учится impression-aware и все сравнения делает внутри слейта. Кандген применяется ко всему каталогу, поэтому ему нужны негативы из каталога. Проверяется разницей между HitRate на исходном пуле и по всему каталогу».
В работе Xin и соавторов по MSE на отложенной выборке модель, игнорирующая пропуски, оказалась лучше той, что их учитывает. Метрика голосует за сломанную модель.
Причина общая для всех стандартных метрик: они считаются на наблюдённых данных. Отложенная выборка — тоже показы, тоже внутри блоков. Метрика физически не может увидеть, что происходит между блоками.
Плюс наблюдение авторов: мусорные рекомендации приходят вперемешку с хорошими. Такая ошибка не ловится ни автотестами, ни дашбордом — она всплывает жалобами.
Да, и это главная причина, по которой ретривал учат батч-софтмаксом. Батч набирается из глобального потока, а не из слейта одного пользователя, поэтому в нём есть представители всех групп, и лосс требует: «скор пользователя для его айтема выше, чем для айтема соседа по батчу».
Но спасает не слово «in-batch», а глобальное перемешивание. Если данные шардированы по стране или сегменту, на каждый регион учится своя модель, или примеры сгруппированы по сессии ради локальности чтения — соседи по батчу снова «свои». В симуляции такой режим даёт 43.8% чужих в топ-5, то есть folding возвращается полностью.
3. Правильная постановка: софтмакс по каталогу
Смена вопроса, из которой всё следует.
«Айтем \(i\) показали пользователю \(u\). Кликнет?» Бинарная классификация на показах — то есть ранжирующая постановка со всеми проблемами folding.
«Известно, что пользователь \(u\) что-то выбрал. Что это могло быть?» Задача экстремальной классификации: каждому айтему каталога соответствует свой класс.
Знаменатель по всему каталогу — и это то самое, чего не хватало: межгрупповые ограничения возникают по построению.
Распределение получается умножением скрытого состояния на матрицу-декодер: \(s_\theta(u,i) = \langle g_\theta(u), v_i\rangle\). А декодер — просто матрица обучаемых эмбеддингов айтемов.
То есть «экстремальная классификация с softmax» и «две башни» — одна и та же конструкция, описанная с разных сторон. Полезно знать оба языка: в статьях встречаются оба.
Полный софтмакс невычислим
Знаменатель с миллионом слагаемых не посчитать на каждом шаге. Каталог заменяют выборкой — это sampled softmax, и весь вопрос в том, откуда её брать.
Посмотрим на градиент полного софтмакс-лосса. Кроме члена, подтягивающего позитив, там стоит сумма по каталогу:
$$ \sum_{j \in \mathcal{I}} P(j \mid u)\, \nabla_\theta s_\theta(u, j) $$Множитель \(P(j \mid u)\) — вероятность, которую выдаёт сама модель. Вклад айтема в градиент пропорционален тому, насколько высоко модель его уже поставила. Численно, для позитива со скором 3.0:
| Негатив | Скор | \(P(d\mid u)\) | Вклад в градиент |
|---|---|---|---|
| случайный из каталога | −2.0 | 0.0035 | ×1 |
| популярный (типичный in-batch) | +0.5 | 0.0430 | ×11 |
| хард-негатив | +2.8 | 0.4292 | ×67 |
Числа воспроизводятся скриптом _tools/twotower.py в этом репозитории.
Отсюда фраза «полный софтмакс сам майнит хард-негативы»: никто их не ищет — веса расставляются автоматически, и почти вся масса градиента достаётся тем, кого модель ошибочно поставила высоко. Именно это свойство теряется при замене каталога выборкой.
4. Где брать негативы
Айтем, которому текущая модель дала высокий скор, но который не позитив. Определение относительно модели, а не контента: хард на первой эпохе к десятой станет лёгким.
Отсюда: «похожий по контенту» и «хард» — разные вещи. И правильный источник недостижим в принципе: чтобы узнать, кто сейчас хард, надо посчитать скоры по всему каталогу, то есть сделать ровно то, чего мы избегаем. Все практические источники — приближения.
| Источник | Что берём | Распределение \(Q\) | Плюсы | Минусы |
|---|---|---|---|---|
| Uniform | случайные айтемы каталога | \(1/|\mathcal{I}|\) | несмещённое \(Q\), коррекция не нужна | почти все «лёгкие», вклад минимален, нужно очень много |
| In-batch | позитивы других пользователей того же батча | униграммное, \(Q \propto\) популярность | бесплатно по вычислениям, заметно «сложнее» равномерных | смещено к популярному, управлять \(Q\) нельзя |
| Hard | высокий скор текущей модели | сосредоточено на верхушке | максимальный вклад при минимуме негативов | много ложных негативов, обучение легко разваливается |
| Mixed | смесь основы и хардов | смесь с известной пропорцией | \(Q\) известно явно, пропорция — рычаг | ещё один гиперпараметр |
Почему in-batch «сложнее» равномерных. In-batch негатив — это чей-то позитив, то есть айтем, который кто-то действительно выбрал. Значит, он в среднем популярнее случайного, а популярный модель скорит выше. Больше скор → больше вес \(P(d\mid u)\) → больше вклад. Поэтому их нужно меньше при том же качестве.
Почему они «бесплатны». Башня айтемов уже посчитала эмбеддинги всех \(B\) айтемов батча — они нужны как позитивы. Взять их же негативами значит переиспользовать посчитанное: вместо \(B\) скалярных произведений одна матрица \(B \times B\). Ни одного лишнего прохода по сети.
Как только негативы взяты не равномерно, а из \(Q\), оценка градиента перестала быть несмещённой: айтемы с большим \(Q(d)\) попадают в выборку чаще и штрафуются чаще, чем следует. Модель выучивает не \(\log p\), а \(\log p - \log Q\).
Чем «сложнее» и полезнее негативы, тем сильнее смещение, которое они вносят. Выбор источника и коррекция — две половины одного решения, и по отдельности они не работают.
Виджет с распределениями негативов — на странице тренажёра и в каталоге.
5. LogQ-коррекция
Откуда берётся смещение
Батч набирается из потока взаимодействий, а не из каталога. Значит вероятность айтема оказаться в батче равна его доле в логе — то есть популярности, а она распределена по Ципфу. При каталоге 2000 и \(\alpha = 1.5\):
| Группа каталога | Доля попаданий в батч | Перекос к справедливой доле |
|---|---|---|
| топ-4% | 93.1% | 23× |
| остальные 96% | 6.9% | 0.072× |
Числа воспроизводятся скриптом _tools/twotower.py.
Разрыв между группами — 322×. Самый популярный айтем при \(B = 1024\) оказывается негативом около 399 раз за батч: он же чей-то позитив почти для каждой второй пары. В полном софтмаксе каждый айтем стоит в знаменателе ровно один раз.
Лосс толкает скор негатива вниз. Раз популярный айтем в знаменателе на порядки чаще, он получает во столько же раз больше толчков вниз, чем следует из полного софтмакса.
То есть смещение работает против популярного, а не в его пользу. Модель без коррекции систематически занижает скоры хитов — притом что интуиция подсказывает обратное.
Вывод коррекции
Нам нужен градиент полного софтмакс-лосса. Для позитива \(d_+\):
$$ \nabla_\theta\bigl(-\log P(d_+\mid u)\bigr) \;=\; -\nabla_\theta s_\theta(u,d_+) \;+\; \underbrace{\sum_{d} P(d\mid u)\,\nabla_\theta s_\theta(u,d)}_{\text{неподъёмно}} $$Второе слагаемое — матожидание по целевому распределению \(P(\cdot\mid u)\). Оценим его сэмплированием из предложенного \(Q\):
$$ \mathbb{E}_{d \sim P}\bigl[\nabla_\theta s\bigr] \;=\; \mathbb{E}_{d \sim Q}\Bigl[\tfrac{P(d\mid u)}{Q(d)}\,\nabla_\theta s\Bigr] \;\approx\; \sum_{d \in N} \frac{\omega_d}{\sum_{d'}\omega_{d'}}\,\nabla_\theta s_\theta(u,d) $$где веса самонормированной оценки
$$ \omega_d \;=\; \frac{e^{\,s_\theta(u,d)}}{Q(d)} \;=\; e^{\,s_\theta(u,d) - \log Q(d)} $$Вот и вся коррекция: деление на \(Q\) внутри экспоненты превращается в вычитание \(\log Q\) из логита:
$$ s^{c}_\theta(u,d) \;=\; s_\theta(u,d) - \log Q(d) $$Читается прямо: чем чаще айтем попадает в выборку, тем больше вычитаем, тем меньше его вес в знаменателе — и тем слабее он наказывается. Ровно на тот множитель, на который перепредставлен.
- Это не схема, а настоящее обучение: две модели — с коррекцией и без — учатся на одном и том же потоке батчей.
- Бирюзовые точки ложатся на диагональ «выучил = истина». Розовые нет, и чем крупнее точка, тем сильнее она уехала вниз — популярное занижается.
- Без коррекции скор коррелирует с \(\log p - \log Q\) — модель выучила ровно то, что предсказывает теория, а не то, что нужно.
- При равномерной популярности коррекция не меняет ничего: \(Q\) константа, вычитание константы порядок не трогает.
Что сказать на собесе: «In-batch негативы приходят из распределения популярности, поэтому без коррекции модель сходится к \(\log p - \log Q\) и топит популярное. LogQ вычитает \(\log Q\) из логита прямо при обучении».
Откуда взять \(Q\) на потоке
Формула требует \(Q(d)\) — вероятность попадания айтема в случайный батч. Если каталог фиксирован, это просто частота. На потоке словаря нет, распределение дрейфует, а обучение распределённое.
Приём из работы Yi и соавторов: вместо частоты оценивать \(\delta\) — среднее число шагов между двумя последовательными попаданиями айтема в батч. Тогда \(p = 1/\delta\).
Держим два хеш-массива: \(A[h(y)]\) — номер шага последнего попадания, \(B[h(y)]\) — скользящая оценка \(\delta\). На шаге \(t\):
$$ B[h(y)] \leftarrow (1-\alpha)\,B[h(y)] + \alpha\bigl(t - A[h(y)]\bigr), \qquad A[h(y)] \leftarrow t $$Оценка \(\hat p = 1/B[h(y)]\). Смещение стремится к нулю с ростом \(t\); \(\alpha\) — обычный компромисс между скоростью забывания ошибки инициализации и дисперсией.
Коллизии занижают оценку интервала (бакет обновляют несколько айтемов) и потому завышают частоту. Лечится как в count-min sketch: несколько независимых пар массивов и \(\hat p = 1/\max_i B_i[h_i(y)]\) — максимум, потому что каждая отдельная оценка занижена.
Yang и соавторы указывают на вторую проблему, помимо смещения. Айтем, у которого нет пользовательского фидбека, никогда не попадёт в обучающие данные как позитив — а значит при in-batch сэмплировании никогда не станет и негативом. Его эмбеддинг не отталкивал никто.
Это тот же механизм, что folding, только для одного айтема: свежие и хвостовые получают произвольно высокие скоры и протекают в выдачу.
Mixed Negative Sampling добавляет к батчу \(B'\) айтемов, равномерно сэмплированных из корпуса, а не из лога. Тогда \(Q\) становится явной смесью униграммного и равномерного, а \(B'\) — той ручкой, которой этой смесью управляют.
6. Косинус и температура
Эмпирически \(u \leftarrow u/\lVert u\rVert\), \(v \leftarrow v/\lVert v\rVert\) улучшает и обучаемость, и качество ретривала. Причина знакома по главе про смещения: у скалярного произведения норма не сокращается, а в обучении растёт с популярностью.
Но у нормировки есть побочный эффект: логиты оказываются зажаты в \([-1, 1]\), и софтмакс от них почти равномерен.
Логиты \((1, -1, -1, -1, -1)\) — правильный ответ один, остальные явно хуже:
| \(\tau\) | softmax | вес правильного |
|---|---|---|
| 1.00 | 0.649 · 0.088 · 0.088 · 0.088 · 0.088 | 0.649 |
| 0.50 | 0.932 · 0.017 · 0.017 · 0.017 · 0.017 | 0.932 |
| 0.20 | 1.000 · 0.000 · 0.000 · 0.000 · 0.000 | 0.9998 |
Числа воспроизводятся скриптом _tools/twotower.py.
Без температуры правильный ответ получает 64.9% массы, при \(\tau = 0.5\) — 93.2%. То есть на зажатых логитах софтмакс почти не различает правильное и неправильное, и градиент размазывается.
Варианты: фиксировать \(\tau\); расписание с постепенным уменьшением; сделать обучаемой — тогда она становится выученной мерой уверенности модели.
Вопросы с собеседований
Что такое folding и почему это фатально для кандидатогенератора?
Наложение непересекающихся групп пользователей и айтемов в пространстве эмбеддингов. Причина формальная: если непоказанные пары в лосс не входят, между пользователями одной группы и айтемами другой нет ни одного ограничения, лосс распадается на независимые подзадачи, и взаимное расположение групп не определено ничем. Один из равноправных оптимумов — тот, где группы лежат друг на друге.
Это не переобучение, а вырожденность задачи. Ранкеру она не мешает: он применяется к кандидатам из той же системы, что породила логи. Кандген идёт в ANN по всему каталогу — и получает айтемы чужой группы с высоким скором.
Диагностика: разница между HitRate на исходном пуле и по всему каталогу. В симуляции 0.392 против 0.196 при 50.8% чужих в топ-5; у модели с негативами из каталога 0.362 против 0.362 и 0.0%.
Почему модель с folding внутри своей группы работает лучше?
Потому что она не сломана. Она потратила всю ёмкость ровно на ту задачу, которую ставил лосс — различать айтемы внутри показанного пула, — и делает это хорошо: 0.392 против 0.362 у правильно обученной.
Проблема в том, что вне обучающего носителя она не определена. Расширение множества кандидатов до каталога стоит ей половины качества (0.392 → 0.196), тогда как правильная модель не теряет ничего.
Отсюда же следует, почему folding не виден офлайн: метрики считаются на отложенной выборке, а она собрана той же политикой и тоже лежит внутри блоков.
Решают ли in-batch негативы проблему folding?
Да, и это главная причина, по которой ретривал учат батч-софтмаксом. Батч набирается из глобального потока, поэтому в нём есть представители всех групп, и лосс создаёт межгрупповые ограничения.
Но спасает не «in-batch», а глобальное перемешивание. Если данные шардированы по стране или сегменту, на регион учится своя модель, либо примеры сгруппированы по сессии — соседи по батчу снова свои, и folding возвращается: в симуляции 43.8% чужих в топ-5.
И на уровне отдельных айтемов in-batch не чинит ничего: айтем без фидбека никогда не станет ни позитивом, ни негативом, потому что в батч попадают только позитивы. Отсюда Mixed Negative Sampling с равномерной добавкой из корпуса.
Почему вклад негатива в градиент зависит от того, как модель его оценила?
В градиенте полного софтмакса стоит сумма по каталогу с множителем \(P(d \mid u)\) — вероятностью, которую выдаёт сама модель. Айтем с низким скором входит почти с нулевым весом: про него модель уже всё поняла.
Численно при позитиве со скором 3.0: случайный негатив со скором −2 даёт вклад ×1, популярный со скором +0.5 — ×11, хард-негатив со скором +2.8 — ×67.
Отсюда фраза «полный софтмакс сам майнит хард-негативы»: их никто не ищет, веса расставляются автоматически. Это свойство и теряется при замене каталога выборкой, и выбором источника негативов его пытаются вернуть.
Выведите LogQ-коррекцию и объясните, что она делает.
В градиенте полного софтмакса нужно матожидание \(\nabla s\) по целевому распределению \(P(\cdot \mid u)\). Сэмплируем из предложенного \(Q\) и применяем importance sampling: вес становится \(\omega_d = e^{s}/Q(d) = e^{\,s - \log Q(d)}\).
То есть деление на \(Q\) внутри экспоненты — это вычитание \(\log Q\) из логита: \(s^c = s - \log Q(d)\).
Смысл: чем чаще айтем попадает в выборку, тем больше вычитаем и тем меньше его вес в знаменателе — он наказывается слабее ровно на тот множитель, на который перепредставлен. Без коррекции модель сходится к \(\log p - \log Q\) и систематически занижает популярное — смещение работает против хитов, а не в их пользу.
Как оценить Q на потоке, где нет фиксированного словаря?
Оценивать не частоту, а среднее число шагов между попаданиями айтема в батч: \(p = 1/\delta\). Держатся два хеш-массива — шаг последнего попадания и скользящее среднее интервала, обновляемое как \(B \leftarrow (1-\alpha)B + \alpha(t - A)\).
Схема работает без фиксированного словаря, адаптируется к дрейфу и живёт на parameter servers при распределённом обучении.
Коллизии занижают интервал и потому завышают частоту, поэтому берут несколько независимых пар массивов и максимум по ним — каждая отдельная оценка занижена, максимум ближе к истине.
Зачем нормировать эмбеддинги и зачем при этом температура?
Нормировка убирает влияние нормы, которая в обучении растёт с популярностью: без неё скалярное произведение систематически предпочитает популярное независимо от релевантности.
Но после нормировки логиты зажаты в \([-1,1]\), и софтмакс от них почти равномерен. На логитах \((1,-1,-1,-1,-1)\) правильный ответ получает всего 64.9% массы — градиент размазывается между правильным и явно неправильными.
Температура \(s/\tau\) возвращает резкость: при \(\tau = 0.5\) вес правильного ответа 93.2%. Её фиксируют, задают расписанием или делают обучаемой.
Шпаргалка одним экраном
Две башни
Векторы айтемов считаются заранее и ложатся в индекс. Цена — взаимодействие только скалярным произведением.
Folding
Лосс распадается на K подзадач, расположение групп не определено. 50.8% чужих в топ-5; внутри группы модель даже лучше.
Постановка
Не \(P(y{=}1|u,i)\), а \(P(i|u)\) — softmax по каталогу. Межгрупповые ограничения по построению.
Негативы
Вклад \(\propto P(d|u)\): случайный ×1, in-batch ×11, хард ×67. Хард — свойство модели, а не контента.
LogQ
\(s^c = s - \log Q\) из importance sampling. Без неё модель учит \(\log p - \log Q\) и топит популярное.
Температура
После нормировки логиты в [−1,1], softmax почти равномерен: 64.9% против 93.2% при \(\tau=0.5\).
Первоисточники
- D. Xin, N. Mayoraz, H. Pham, K. Lakshmanan, J. R. Anderson. Folding: Why Good Models Sometimes Make Spurious Recommendations, RecSys 2017 — связанные и несвязанные пропуски, формальная причина, метрика folding.
- X. Yi, J. Yang, L. Hong et al. Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations, RecSys 2019 — LogQ и потоковая оценка частот.
- J. Yang, X. Yi et al. Mixed Negative Sampling for Learning Two-tower Neural Networks in Recommendations, WWW 2020 — selection bias и ручка для \(Q\).
- Y. Bengio, J.-S. Senécal. Adaptive Importance Sampling to Accelerate Training of a Neural Probabilistic Language Model, 2008 — откуда вообще взялась идея корректировать логит на \(\log Q\).
- Числа главы:
_tools/twotower.pyв этом репозитории.