Sequential RecSys
SASRec, BERT4Rec, GRU4Rec — учёт последовательности действий пользователя.
Sequential RecSys — когда порядок действий важнее набора
Загрузка интерактивного виджета...
Ты посмотрел «Игру престолов», потом «Ведьмака», потом «Властелина колец». Что дальше? Collaborative filtering видит набор — три фэнтези-фильма — и предложит ещё один фэнтези. Но sequential-модель видит тренд: ты двигаешься от сериалов к фильмам, от тёмного фэнтези к классическому. Может, «Хоббит»?
Разница между «человек купил кроссовки, шорты и футболку» и «человек купил кроссовки → шорты → футболку» — огромна. Во втором случае видно: он собирает спортивный комплект. Следующее — спортивная сумка. Порядок = намерение. Sequential-модели ловят паттерны поведения, которые невозможно увидеть в статичном наборе взаимодействий.
Большая картина: как работают sequential-модели
Вход — упорядоченная последовательность действий пользователя: [item₁, item₂, ..., itemₙ]. Это могут быть просмотры, клики, покупки — любые события с временнóй меткой. Модель читает эту последовательность и предсказывает следующий айтем — itemₙ₊₁.
Полный pipeline: 1. Embedding — каждый item_id превращается в вектор (обучаемая таблица, как word2vec для товаров) 2. Positional encoding — добавляем информацию о позиции в последовательности (недавние действия важнее давних) 3. Sequence encoder — GRU, self-attention или bidirectional attention — модель «читает» историю и строит контекстное представление 4. Prediction — скалярное произведение выхода модели с эмбеддингами всех айтемов каталога → ранжированный список рекомендаций
Три ключевых архитектуры определили развитие этой области: GRU4Rec (2015) — рекуррентная сеть, SASRec (2018) — causal self-attention (как GPT), BERT4Rec (2019) — bidirectional attention (как BERT). Каждая следующая решала проблемы предыдущей.
GRU4Rec — первый нейросетевой подход
GRU4Rec (Hidasi et al., 2015) — статья, которая запустила направление. До неё сессионные рекомендации строились на item-based kNN или марковских цепях. GRU4Rec показал: рекуррентная сеть может моделировать сессию целиком.
Идея прямолинейная: каждое действие пользователя (клик, просмотр) — вход GRU-ячейки. Скрытое состояние h_t после t шагов — «понимание» текущей сессии. На каждом шаге предсказываем следующий айтем. Обучение через BPR loss (Bayesian Personalized Ranking): модель учится ставить положительный айтем выше случайного отрицательного.
BPR loss: i — положительный айтем, j — случайный негативный, σ — сигмоида. Модель учится: score позитива > score негатива.
Проблемы GRU4Rec унаследованы от RNN: • Затухание на длинных последовательностях — после 50-100 шагов ранние действия почти не влияют на предсказание • Последовательное обучение — нельзя параллелить по позициям; GPU простаивает • Нет прямого доступа к прошлому — чтобы «вспомнить» первый айтем, информация должна пройти через все скрытые состояния
Но для коротких сессий (5-20 действий) GRU4Rec работает хорошо и остаётся сильным бейзлайном. Если сессии короткие и инфраструктура простая — это разумный выбор.
SASRec — self-attention для последовательностей

SASRec (Kang & McAuley, 2018) заменил рекуррентность на self-attention. Каждый айтем в истории напрямую «смотрит» на все предыдущие и решает, какие из них важны для текущего предсказания. Нет цепочки скрытых состояний — прямой доступ к любой позиции.
Ключевой элемент — каузальная маска (causal mask). Позиция i видит только позиции 1..i, но не i+1, i+2, ... Точно как в GPT: модель не может «подглядывать» в будущее. Без маски модель выучит тривиальное решение — скопировать следующий айтем из входа.
Self-attention с каузальной маской M. M[i][j] = 0 для j ≤ i (разрешено), M[i][j] = -∞ для j > i (запрещено). d — размерность.
Почему SASRec лучше GRU4Rec: • Параллелизм — все позиции обрабатываются одновременно (GRU — строго последовательно) • Прямой доступ к прошлому — позиция 100 напрямую «видит» позицию 1 через attention (в GRU информация должна пройти 99 скрытых состояний) • Адаптивное внимание — модель сама решает, что важно: вчерашний просмотр или покупка месяц назад
На практике SASRec удивительно компактный: 2 слоя, 1-2 головы attention, размерность 64 — и он уже бьёт GRU4Rec. Это стандартный Transformer decoder (как GPT), только вместо токенов — айтемы каталога.
BERT4Rec — bidirectional подход
BERT4Rec (Sun et al., 2019) перенёс идею BERT в рекомендации. Вместо каузальной маски — bidirectional attention: каждая позиция видит контекст и слева, и справа. Обучение через Cloze task (маскирование): случайные 15-20% айтемов заменяются на токен [MASK], и модель предсказывает, что было на их месте.
Представь последовательность: «Интерстеллар» → [MASK] → «Гравитация» → «Марсианин». SASRec для предсказания замаскированной позиции видит только «Интерстеллар» слева. BERT4Rec видит и «Интерстеллар» слева, и «Гравитацию» + «Марсианина» справа — полный контекст. По аналогии с NLP: GPT читает текст слева направо, BERT — в обе стороны.
Проблема BERT4Rec — train-test mismatch. При обучении маски стоят в случайных позициях. При инференсе нужно добавить [MASK] в конец последовательности, чтобы «спросить» модель о следующем айтеме. Но модель никогда не видела [MASK] именно в последней позиции — это создаёт расхождение между обучением и применением.
SASRec vs BERT4Rec — что выбрать
Сравнение подходов
На практике SASRec чаще побеждает — и вот почему. Next-item prediction точно соответствует задаче: мы хотим предсказать следующий айтем. Маскирование в BERT4Rec — это прокси-задача, которая не совпадает с целевой. Плюс SASRec стабильнее в обучении и проще в деплое.
BERT4Rec стоит пробовать, когда контекст «вокруг» пропуска важен: например, пользователь вернулся к старым интересам, и правая часть последовательности помогает понять, что изменилось. Но для большинства сценариев SASRec — базовый выбор.
Практика: SASRec на PyTorch
Разберём, что конкретно обучается в SASRec. Это частый вопрос на собесе — «а что именно внутри?»
import torch
import torch.nn as nn
class SASRec(nn.Module):
def __init__(self, n_items, d=64, max_len=50, n_heads=2, n_layers=2):
super().__init__()
# 1. Item embedding: (n_items+1, d) — вектор каждого товара
self.item_emb = nn.Embedding(n_items + 1, d, padding_idx=0)
# 2. Positional embedding: (max_len, d) — кодирует позицию в сессии
self.pos_emb = nn.Embedding(max_len, d)
# 3. Transformer layers: Q, K, V + FFN в каждом слое
layer = nn.TransformerEncoderLayer(
d, n_heads, dim_feedforward=d * 4, batch_first=True
)
self.encoder = nn.TransformerEncoder(layer, n_layers)
def forward(self, seq): # seq: (batch, seq_len) — последовательность item_id
positions = torch.arange(seq.size(1), device=seq.device)
x = self.item_emb(seq) + self.pos_emb(positions) # (batch, seq_len, d)
# Каузальная маска: позиция i не видит позиции j > i
mask = nn.Transformer.generate_square_subsequent_mask(seq.size(1))
out = self.encoder(x, mask=mask.to(x.device)) # (batch, seq_len, d)
# Предсказание: dot product с той же embedding matrix
logits = out @ self.item_emb.weight.T # (batch, seq_len, n_items+1)
return logitsЧто обучаемое: • Item embedding matrix (n_items × d) — вектор каждого товара. Используется и на входе (превращает ID в вектор), и на выходе (скалярное произведение для предсказания). Единое пространство — экономит параметры. • Positional embeddings (max_len × d) — кодируют «давность» действия. Модель учится, что недавние действия обычно важнее давних. • Self-attention веса — Q, K, V матрицы в каждом слое и каждой голове. • FFN веса — feed-forward сеть после attention в каждом слое.
Training vs Inference
Loss: softmax на миллионах айтемов
Модель выдаёт logits для каждого айтема каталога. Идеальный loss — softmax cross-entropy по всему каталогу. Но если айтемов миллион, это O(|V|) на каждый пример — нереально по памяти.
Full softmax: экспонента для КАЖДОГО айтема. При |V| = 10M — невозможно.
Sampled softmax — практичное решение. Берём target + K случайных negative samples (обычно K = 100-10000). Считаем softmax только по ним. Вместо O(|V|) получаем O(K).
Но есть подвох: log-Q коррекция. Негативы сэмплируются пропорционально популярности — «Coca-Cola» попадёт в негативы почти в каждом батче. Модель начинает занижать score популярных айтемов. Log-Q correction компенсирует это: вычитаем log(q(j)) из logit, где q(j) — вероятность сэмплирования. Без этой коррекции рекомендации будут странными.
Log-Q correction: убираем bias от неравномерного сэмплирования негативов.
В продакшене: двухэтапная система
Sequential-модель в продакшене не работает в одиночку. Стандартная схема: 1. Candidate generation — SASRec/BERT4Rec выбирает top-500 из миллионов. Быстро: один forward pass + approximate nearest neighbors. 2. Ranking — тяжёлая модель с дополнительными фичами (цена, CTR, свежесть, контекст) переранжирует top-500 в финальный top-20. Sequential-модель — это первый этап. Она отвечает за «что вообще показать?», а не за финальный порядок.
🎯 На собеседовании
Junior
Middle
Senior
Собираем всё вместе
Sequential RecSys — это переход от «что пользователю нравится» к «что пользователь делает прямо сейчас». Три архитектуры определили область: GRU4Rec (RNN, простой бейзлайн для коротких сессий), SASRec (causal self-attention, основной выбор на практике), BERT4Rec (bidirectional, мощнее теоретически, но сложнее в деплое).
Если запомнить одну вещь: SASRec — это GPT для рекомендаций. Та же архитектура (Transformer decoder), та же идея (предсказание следующего элемента), та же каузальная маска. Если ты понимаешь GPT — ты понимаешь SASRec.
Дальше на роадмапе: Multi-Stage Ranking покажет, как sequential-модель встраивается в полный production pipeline — от candidate generation до финального ранжирования.
Материалы
Оригинальная статья SASRec — каузальный self-attention для рекомендаций. Обязательна к прочтению.
BERT4Rec — bidirectional attention + маскирование для sequential рекомендаций.
GRU4Rec — первая нейросетевая модель для сессионных рекомендаций. Классика.
Яндекс про трансформерные рекомендации: ARGUS, мультисервисность, масштабирование.
WildBERT: BERT-based рекомендации на десятках миллионов товаров.
МТС (KION) про путь от SASRec к state-of-the-art + сравнение моделей.