Часть III · Ранжирование · глава 12 из 19
Взаимодействия признаков
Задача главы формулируется одной фразой: научить модель тому, что признаки важны не по отдельности, а вместе. «Пользователь из России» и «жанр — рок» по отдельности говорят мало, их сочетание может говорить много. Дальше — история из шести шагов, где каждый следующий чинит конкретную поломку предыдущего. Эта цепочка и есть готовый ответ на собеседовании.
- Явные кросс-признаки не выживают дважды. При суммарной кардинальности 3 млн это 18 ТБ весов — и, что важнее, вес невиданной комбинации не обучен вовсе.
- Факторизация чинит обе проблемы одним движением. 384 МБ вместо 18 ТБ, в 47 тысяч раз меньше, плюс обобщение по транзитивности.
- MLP не выучивает произведение сам — это главный сюжет главы. Универсальность аппроксимации говорит о существовании весов, а не о том, что их найдёт градиентный спуск на разреженных данных.
- По умолчанию берут DCN-v2. Всё, что левее в цепочке, — история; всё, что правее, — исследовательский фронт.
1. Цепочка из шести шагов
2. Линейная модель и кросс-признаки
Линейная модель на конкатенации one-hot представлений равносильна тому, что каждому значению признака сопоставили обучаемый скаляр, а потом сложили скаляры реализовавшихся значений:
$$ \hat y = w_0 + \sum_{i} w_i x_i $$Взаимодействий здесь нет вовсе — вклад «Россия» одинаков независимо от жанра. Добавим все кросс-признаки второй степени, то есть комбинации вида «language=ru, genre=rock»:
$$ \hat y = w_0 + \sum_i w_i x_i + \sum_{i < j} w_{ij}\, x_i x_j $$Формально задача решена. Практически — сломана в двух местах.
Параметров \(O(n^2)\), где \(n\) — суммарная кардинальность всех признаков:
| Суммарная кардинальность | Весов | Память во float32 |
|---|---|---|
| 1e+04 | 5.00e+07 | 200.0 МБ |
| 1e+05 | 5.00e+09 | 20.0 ГБ |
| 1e+06 | 5.00e+11 | 2.0 ТБ |
| 3e+06 | 4.50e+12 | 18.0 ТБ |
Числа воспроизводятся скриптом _tools/cross_demo.py в этом репозитории.
Три миллиона суммарной кардинальности — это скромно по меркам рекомендаций. Восемнадцать терабайт весов ради взаимодействий второй степени.
Если комбинация не встречалась в обучении, соответствующий \(w_{ij}\) просто не обучен. Про невиданные сочетания модель не может сказать вообще ничего.
Насколько это критично? Возьмём 30 полей по 100 000 значений и датасет на миллиард сэмплов:
- пар полей: 435;
- возможных комбинаций значений: 4.35e+12;
- наблюдений пар во всём датасете: 4.35e+11.
Даже в идеальном случае, когда все наблюдения различны, хотя бы раз встретится не более 10% комбинаций. А распределение степенное (глава 1), так что реальная доля на порядки меньше.
Числа воспроизводятся скриптом _tools/cross_demo.py.
То есть подавляющее большинство весов останется в начальном значении навсегда. Это не проблема масштаба, которую решают железом, — это проблема представления.
3. Factorization Machines
Решение: каждому признаку дадим вектор, а вес пары получим скалярным произведением.
$$ w_{ij} \approx \langle v_i, v_j\rangle \qquad\Longrightarrow\qquad \hat y = w_0 + \sum_i w_i x_i + \sum_{i < j} \langle v_i, v_j\rangle\, x_i x_j $$Одно движение чинит обе поломки сразу — и это стоит проговаривать именно так, а не как два отдельных достоинства.
- Память \(O(nd)\) вместо \(O(n^2)\). Матрица \(V\) — это просто эмбеддинги признаков, ровно те же, что в предыдущей главе.
- Обобщение на невиданные комбинации. Чтобы оценить вес пары \((i,j)\), не нужно было видеть именно эту пару: достаточно, что \(v_i\) обучился на других парах с участием \(i\), а \(v_j\) — на парах с \(j\).
Второй пункт — та же логика транзитивности, что вытаскивала матричную факторизацию: низкий ранг связывает наблюдаемое с ненаблюдаемым.
При суммарной кардинальности 3 млн: явно 4.50e+12 весов (18.0 ТБ), факторизационная машина с \(d = 32\) — 9.60e+07 (384.0 МБ). Разница в 47 тысяч раз.
Но интереснее посмотреть на то же самое со стороны информации. Сколько независимых чисел нужно определить:
| Признаков \(n\) | Всего пар | Параметров FM при \(d=16\) | Доля |
|---|---|---|---|
| 100 | 4 950 | 1 600 | 32.32% |
| 1000 | 499 500 | 16 000 | 3.20% |
| 10000 | 49 995 000 | 160 000 | 0.32% |
Числа воспроизводятся скриптом _tools/cross_demo.py.
При тысяче признаков информации от 3.2% пар достаточно, чтобы восстановить остальные. Вот это и есть обобщение — не метафора, а счётный факт про число степеней свободы.
Field-aware FM идёт дальше: у признака не один вектор, а по вектору на каждое поле, с которым он взаимодействует. Историческая справка, которую иногда спрашивают: FFM выиграла Kaggle Criteo CTR Prediction Challenge.
4. Ранние нейросетевые архитектуры
FM остановилась на второй степени: она моделирует пары и только пары. Естественное желание — тройки, четвёрки и нелинейности. Самый очевидный ход — поставить сверху MLP и понадеяться, что он сам разберётся. Вся дальнейшая история про то, что сам он не разбирается.
| Модель | Идея | Что нового |
|---|---|---|
| Concat + MLP | конкатенируем эмбеддинги категориальных и нормализованные вещественные, сверху MLP | простейшая ранжирующая сеть; взаимодействия только неявные |
| Wide & Deep | линейная модель с вручную отобранными кросс-признаками плюс сеть; предсказание — сумма | явное разделение ролей: wide отвечает за меморизацию и взаимодействия до второго порядка, deep — за обобщение и высокие порядки |
| DeepFM | заменили ручные кросс-признаки на факторизационную машину | убрали ручной feature engineering; эмбеддинги переиспользуются и в FM, и в сети |
| DLRM | нижний MLP превращает вещественные признаки в единый эмбеддинг, верхний работает над конкатенацией попарных скалярных произведений | явные попарные взаимодействия без \(O(n^2)\) параметров |
Детали Wide & Deep, которые полезно помнить как образец инженерных решений своего времени: эмбеддинги размера 32 для категориальных, CDF-преобразование для вещественных, конкатенация в вектор размерности 1200, три слоя с ReLU.
Обе модели требуют, чтобы все эмбеддинги признаков имели одинаковую размерность — иначе скалярное произведение попросту не определено.
А в промышленных данных кардинальности варьируются от десятков до миллионов, и единая размерность заведомо неоптимальна: мы только что видели, что размер должен расти с кардинальностью. Это ограничение и снимет DCN-v2.
5. Почему MLP не выучивает произведение сам
Ключевой вопрос главы, и ответ у него неочевидный настолько, что его стоит выучить дословно.
MLP — универсальный аппроксиматор, значит теоретически он способен приблизить и произведение. Но универсальность — утверждение о существовании весов, а не о том, что их найдёт градиентный спуск на конечных данных.
Произведение \(x_i x_j\) для MLP — неудобная функция: он строит её кусочно-линейными сплайнами, и на это уходит много нейронов и много данных. Rendle и соавторы показали это прямо: выученная MLP-похожесть проигрывает обычному скалярному произведению.
А в рекомендациях положение хуже, чем в среднем по машинному обучению: признаки разреженные, конкретная комбинация встречается редко, и «выучить произведение по данным» просто не на чем — мы посчитали выше, что не наберётся и 10% комбинаций.
Отсюда вся дальнейшая линия: не надеяться на MLP, а вписать умножение в архитектуру.
6. DCN-v2: умножение как часть слоя
Развязка сюжета. Идея — сделать умножение частью самого слоя, причём так, чтобы степень взаимодействий росла с глубиной автоматически.
Разберём по частям, потому что в этой строчке всё существенное.
- \(W_l x_l\) — линейная комбинация всей конкатенации, то есть внутри неё можно получить скалярное произведение любых векторов признаков. Значит любые пары, независимо от их размерностей — ограничение DeepFM и DLRM снято.
- \(x_0 \odot (\cdot)\) — покоординатное умножение на исходный вход. Это и есть вписанное в архитектуру произведение: каждый слой поднимает степень на единицу.
- \(+\, x_l\) — остаточная связь, о которой отдельный разговор в следующем разделе.
\(L\) кросс-слоёв моделируют все комбинации степени до \(L+1\).
| Слоёв \(L\) | Степень | Различных мономов при 100 признаках |
|---|---|---|
| 1 | 2 | 4.950e+03 |
| 2 | 3 | 1.617e+05 |
| 3 | 4 | 3.921e+06 |
| 4 | 5 | 7.529e+07 |
Числа воспроизводятся скриптом _tools/cross_demo.py.
Число комбинаций растёт быстрее, чем данные способны их покрыть: на четвёртой степени это уже 3.92 млн мономов при всего сотне признаков. Отсюда и наблюдаемое на практике правило — больше 2–3 слоёв прироста не дают.
Обратите внимание, что это тот же аргумент, что и против явных кросс-признаков, только теперь применённый к глубине. Ограничивает не ёмкость модели, а покрытие данными.
Кросс-слой вычислительно тяжёл: матрица \(W_l\) имеет размер \(d \times d\), где \(d\) — размерность всей конкатенации. При \(d = 1024\) и трёх слоях это 3.15e+06 параметров.
Лечение — факторизация \(W_l \approx U_l V_l^{\top}\) с узкими матрицами:
| Ранг \(r\) | Параметров | Дешевле в | Доля от полной |
|---|---|---|---|
| 16 | 9.83e+04 | 32.0 | 3.1% |
| 32 | 1.97e+05 | 16.0 | 6.2% |
| 64 | 3.93e+05 | 8.0 | 12.5% |
| 128 | 7.86e+05 | 4.0 | 25.0% |
Числа воспроизводятся скриптом _tools/cross_demo.py.
Дальнейшее развитие — смесь низкоранговых разложений: несколько экспертов вместо одного, с выбором по входу.
- Stacked обычно лучше parallel. То есть cross network сначала, MLP над его выходом, а не две ветки в сумму. Перед MLP широкий вектор из cross network сужается.
- Cross network не даёт прироста поверх абстрактных векторных представлений. Его нужно применять именно над конкатенацией векторов признаков. Деталь неочевидная и важная: если подать на вход уже «перемешанное» представление — скажем, выход трансформера, — умножать оказывается нечего, координаты уже не соответствуют признакам.
- Разделение труда, а не конкуренция. Cross network явно моделирует взаимодействия низкого порядка, MLP — неявно и высокого. Они дополняют друг друга, и потому оба и стоят в архитектуре.
7. Трансформеры над признаками
DCN-v2 моделирует взаимодействия одинаково для всех пар: одна матрица \(W_l\) на слой. Следующий вопрос напрашивается — а нельзя ли решать, какие пары важны, динамически, как это делает внимание? Ответ пока «можно, но не очевидно, что лучше».
| Модель | Механизм | Вердикт |
|---|---|---|
| AutoInt | multi-head attention над эмбеддингами признаков со skip-связью, сверху MLP | работает хуже DCN-v2 |
| Hiformer | гетерогенное внимание: раздельные \(Q, K, V\) для каждого признака и свой FFN на признак; плюс композитные признаки | решает реальную проблему, но слои требуют очень много памяти |
| Field-aware Transformer | для каждой пары признаков свой обучаемый коэффициент внутри внимания | перенос идеи FFM в трансформер |
| RankMixer | внимание заменено простым перемешиванием токенов плюс FFN на токен | дешевле внимания, линия развивается |
Хороший вопрос на собеседовании, и ответ короткий. Трансформер обрабатывает все токены одинаково. Для текста это правильно — токены однородны по природе.
Но в рекомендациях признаки гетерогенны: возраст пользователя, идентификатор артиста, цена, эмбеддинг из другой модели — это объекты разной природы и разной размерности. Обрабатывать их одним и тем же преобразованием — значит навязать однородность, которой нет.
Отсюда и устройство Hiformer: раздельные проекции и свой FFN на каждый признак. Проблема настоящая, а цена решения — память.
8. Почему нельзя просто добавить слоёв
Отдельный сюжет, который всплывает всякий раз, когда речь заходит про «сделаем сеть поглубже». Факт, ломающий наивную интуицию: с увеличением числа слоёв качество падает, потому что градиенты затухают.
Пусть якобиан каждого слоя умножает градиент на 0.8 — умеренное, вовсе не катастрофическое значение. В обычной сети множители перемножаются:
| Слоёв | Обычный MLP: \(0.8^L\) | Путь по skip-связям | Верхняя оценка \((1+0.8)^L\) |
|---|---|---|---|
| 4 | 4.10e-01 | 1.00 | 1.05e+01 |
| 8 | 1.68e-01 | 1.00 | 1.10e+02 |
| 16 | 2.81e-02 | 1.00 | 1.21e+04 |
| 32 | 7.92e-04 | 1.00 | 1.47e+08 |
Числа воспроизводятся скриптом _tools/cross_demo.py.
При 32 слоях градиент падает в 1262 раза — нижние слои практически не обучаются.
В остаточной сети якобиан блока равен \(I + F'\), и в раскрытии произведения есть слагаемое из одних единиц — путь целиком по skip-связям. Поэтому градиент снизу ограничен единицей и не затухает вовсе. Зато сверху растёт, и ровно поэтому skip connection всегда идёт в паре с нормализацией.
При разработке первой двухбашенной трансформерной модели для рекомендаций был замечен такой эффект: если башней айтема сделать MLP, сохраняется около половины прироста метрик; если заменить MLP на трансформер над одним токеном — около 80%.
А что такое трансформер над одним токеном? Внимание над последовательностью длины 1 вырождается в тождественное преобразование. Значит от трансформера остаются только skip connections и нормализация вокруг FFN — и весь дополнительный выигрыш дали именно они.
Мораль практическая: прежде чем усложнять архитектуру, стоит проверить, не даст ли большую часть эффекта правильная организация остаточных связей и нормализации.
ResNet: \(y = F(x) + x\). Градиент течёт напрямую через сумму; лишний слой можно «занулить» — при \(F \equiv 0\) он становится тождественным, и глубина перестаёт вредить. Проблема: сумма требует совпадения размерностей, поэтому слои получаются тяжёлыми.
DenseNet: вместо суммы конкатенация, \(x \to [x; F_1(x)] \to [x; F_1(x); F_2(\cdot)] \to \dots\) Размерности совпадать не обязаны, значит \(F\) может быть дешёвым сужающим слоем; каждый новый слой добавляет детализацию, а исходный вход остаётся доступен на всех уровнях.
Вход размерности 512, четыре блока:
| Слои | Параметров | |
|---|---|---|
| ResNet | 512 × 512 каждый | 1.05e+06 |
| DenseNet, рост 64 | 512×64, 576×64, 640×64, 704×64 | 1.56e+05 |
Числа воспроизводятся скриптом _tools/cross_demo.py.
В 6.7 раза меньше параметров, и на выходе размерность 768 вместо 512. Плата — растущая ширина: каждый блок добавляет к входу следующего, и на глубоких сетях это само становится проблемой.
Вопросы с собеседований
Расскажите цепочку моделей взаимодействий признаков.
Шесть шагов, каждый чинит поломку предыдущего. Линейная модель — взаимодействий нет вовсе. Кросс-признаки второй степени — есть, но \(O(n^2)\) параметров и никакого обобщения на невиданные комбинации. FM — факторизуем матрицу весов, память \(O(nd)\) и обобщение по транзитивности, но только вторая степень. Ранние сети (Wide & Deep, DeepFM, DLRM) — ставим MLP сверху, и выясняется, что пары он сам не находит. DCN-v2 — вписываем умножение прямо в слой. Трансформеры над признаками — пытаемся выбирать важные пары динамически.
Практический итог: по умолчанию берут DCN-v2. Всё левее — история, всё правее — исследовательский фронт.
Что не так с явными кросс-признаками?
Две вещи, и вторая серьёзнее. Размер: параметров \(O(n^2)\) по суммарной кардинальности; при n = 3 млн это 4.5 триллиона весов, 18 ТБ во float32 — и это ради взаимодействий всего второй степени.
Обобщение: вес невиданной комбинации не обучен вовсе, а невиданных подавляющее большинство. При 30 полях по 100 тысяч значений и миллиарде сэмплов даже в идеальном случае встретится не более 10% комбинаций, а с учётом степенного распределения — на порядки меньше. Это проблема представления, а не масштаба: железом её не решить.
Как FM решает обе проблемы?
Факторизацией матрицы весов: \(w_{ij} \approx \langle v_i, v_j \rangle\). Каждому признаку — вектор, вес пары — скалярное произведение.
Память падает с \(O(n^2)\) до \(O(nd)\): при n = 3 млн и d = 32 это 384 МБ вместо 18 ТБ, в 47 тысяч раз меньше.
Обобщение возникает по транзитивности: чтобы оценить вес пары (i, j), достаточно, чтобы \(v_i\) обучился на других парах с i, а \(v_j\) — на парах с j. Счётно это выглядит так: при 1000 признаков всего 499 500 пар, а параметров FM при d = 16 всего 16 000, то есть 3.2% — информации от такой доли пар хватает, чтобы восстановить остальные.
Почему нельзя просто поставить MLP и надеяться, что он выучит произведения?
Потому что универсальность аппроксимации — утверждение о существовании весов, а не о том, что градиентный спуск найдёт их на конечных данных.
Произведение для MLP неудобно: он строит его кусочно-линейными сплайнами, на что уходит много нейронов и много данных. Rendle и соавторы показали прямо, что выученная MLP-похожесть проигрывает обычному скалярному произведению.
А в рекомендациях ещё хуже: признаки разреженные, конкретная комбинация встречается редко, и учить произведение просто не на чем. Отсюда вывод — вписывать умножение в архитектуру, а не надеяться на него.
Как устроен кросс-слой DCN-v2?
\(x_{l+1} = x_0 \odot (W_l x_l + b_l) + x_l\). Три части. \(W_l x_l\) — линейная комбинация всей конкатенации, поэтому внутри можно получить скалярное произведение любых векторов признаков независимо от их размерностей: ограничение DeepFM и DLRM на единую размерность снято. Покоординатное умножение на \(x_0\) — вписанное в архитектуру произведение, каждый слой поднимает степень на единицу. Плюс остаточная связь.
L слоёв дают комбинации степени до L+1, но больше 2–3 слоёв прироста не дают: число мономов растёт быстрее, чем данные их покрывают — на четвёртой степени при сотне признаков это уже 3.9 млн комбинаций.
Слой тяжёлый: \(W_l\) размера d × d по всей конкатенации. Отсюда низкоранговый вариант \(W_l \approx U_l V_l^\top\) — при d = 1024 и r = 64 в 8 раз дешевле — и дальше смесь низкоранговых разложений.
Как комбинировать cross network с MLP?
Stacked обычно работает лучше parallel: сначала cross network, потом MLP над его выходом, с сужением широкого вектора перед MLP.
Важная и неочевидная деталь: cross network не даёт прироста поверх абстрактных векторных представлений — его надо применять именно над конкатенацией векторов признаков. Если подать уже перемешанное представление, например выход трансформера, умножать оказывается нечего: координаты больше не соответствуют признакам.
И разделение труда: cross явно моделирует низкие порядки, MLP неявно — высокие. Они дополняют друг друга, а не конкурируют.
Почему обычный трансформер плохо подходит для признаков?
Потому что он обрабатывает все токены одинаково. Для текста это верно — токены однородны. В рекомендациях признаки гетерогенны по природе: возраст, ID артиста, цена, эмбеддинг из другой модели. Единое преобразование навязывает однородность, которой нет.
Отсюда Hiformer с раздельными Q, K, V и своим FFN на признак — проблема настоящая, но слои требуют очень много памяти. AutoInt, применяющий обычное внимание, работает хуже DCN-v2.
Почему добавление слоёв в MLP ухудшает качество и что с этим делают?
Градиенты затухают: множители якобианов перемножаются. Если каждый слой умножает градиент на 0.8, то при 32 слоях остаётся 7.9e-04 — падение в 1262 раза, нижние слои практически не обучаются.
ResNet: \(y = F(x) + x\). Якобиан блока это \(I + F'\), и в произведении есть слагаемое из одних единиц — путь целиком по skip-связям, поэтому снизу градиент ограничен единицей. Заодно лишний слой можно занулить: при \(F \equiv 0\) он тождественный. Сверху произведение растёт, поэтому skip всегда идёт с нормализацией. Минус — сумма требует совпадения размерностей, слои тяжёлые.
DenseNet: вместо суммы конкатенация, размерности совпадать не обязаны, F может быть дешёвым сужающим слоем. При входе 512 и четырёх блоках с ростом 64 это 1.56e+05 параметров против 1.05e+06 у ResNet — в 6.7 раза меньше. Плата — растущая ширина.
Шпаргалка одним экраном
Цепочка
линейная → кросс → FM → ранние сети → DCN-v2 → трансформеры. По умолчанию DCN-v2.
Кросс-признаки
\(O(n^2)\): 18 ТБ при n=3e6. И вес невиданной пары не обучен — а таких больше 90%.
FM
\(w_{ij}=\langle v_i,v_j\rangle\). 384 МБ вместо 18 ТБ; 3.2% пар определяют все остальные.
Главный тезис
Универсальность ≠ обучаемость. Произведение MLP строит сплайнами, а данных на это нет.
Кросс-слой
\(x_0 \odot (W_l x_l + b_l) + x_l\). L слоёв → степень L+1. Разные размерности разрешены.
Низкий ранг
\(W_l \approx U_lV_l^\top\): при d=1024, r=64 дешевле в 8 раз. Дальше — смесь разложений.
Глубина
\(0.8^{32}\) = 7.9e-04. Skip даёт путь с множителем 1 — снизу ограничен, сверху растёт, отсюда нормализация.
ResNet / DenseNet
Сумма требует равных размерностей; конкатенация — нет. 1.56e+05 против 1.05e+06 параметров.
Первоисточники
- S. Rendle. Factorization Machines, ICDM 2010.
- H.-T. Cheng et al. Wide & Deep Learning for Recommender Systems, DLRS 2016.
- H. Guo et al. DeepFM: A Factorization-Machine based Neural Network for CTR Prediction, IJCAI 2017.
- M. Naumov et al. Deep Learning Recommendation Model for Personalization and Recommendation Systems, 2019 — DLRM.
- R. Wang et al. DCN V2: Improved Deep & Cross Network and Practical Lessons for Web-scale Learning to Rank Systems, WWW 2021.
- S. Rendle, W. Krichene, L. Zhang, J. Anderson. Neural Collaborative Filtering vs. Matrix Factorization Revisited, RecSys 2020 — про то, что MLP проигрывает скалярному произведению.
- W. Song et al. AutoInt: Automatic Feature Interaction Learning via Self-Attentive Neural Networks, CIKM 2019.
- K. He, X. Zhang, S. Ren, J. Sun. Deep Residual Learning for Image Recognition, CVPR 2016; G. Huang et al. Densely Connected Convolutional Networks, CVPR 2017.
- Числа главы:
_tools/cross_demo.pyв этом репозитории.