Production RecSys
~14 мин

Graph-based RecSys

GNN для рекомендаций (PinSage, LightGCN), графовые эмбеддинги.

Graph-based RecSys — рекомендации через графы

Загрузка интерактивного виджета...

Пользователи и айтемы — это не просто таблица. Это граф: пользователь → айтем (купил), айтем → айтем (похожи), пользователь → пользователь (друзья). Графовые нейросети (GNN) могут использовать эту структуру для более точных рекомендаций.

Зачем графы в RecSys

Граф user-item: многопрыжковая информация
GNN видит не только прямые связи, но и транзитивные — через соседей соседей

Матричная факторизация видит только прямые связи: пользователь купил айтем. GNN видит транзитивные: пользователь A купил айтем X, айтем X покупают вместе с Y, Y нравится пользователям похожим на A. Это «многопрыжковая» информация.

LightGCN — простой и мощный

LightGCN (He et al., 2020) — упрощённый графовый подход для рекомендаций. Убрали из GCN нелинейные преобразования и оставили только агрегацию соседей. Эмбеддинг пользователя = среднее эмбеддингов купленных айтемов (и наоборот), с несколькими слоями «распространения».

Агрегация в LightGCN: эмбеддинг обновляется через нормализованную сумму соседей

import torch
import torch.nn as nn

class LightGCN(nn.Module):
    """LightGCN: графовая рекомендательная модель без нелинейностей."""
    def __init__(self, n_users, n_items, emb_dim=64, n_layers=3):
        super().__init__()
        self.user_emb = nn.Embedding(n_users, emb_dim)
        self.item_emb = nn.Embedding(n_items, emb_dim)
        self.n_layers = n_layers
        nn.init.xavier_normal_(self.user_emb.weight)
        nn.init.xavier_normal_(self.item_emb.weight)

    def forward(self, adj_norm):
        # adj_norm — нормализованная матрица смежности user-item графа
        all_emb = torch.cat([self.user_emb.weight, self.item_emb.weight])
        embs = [all_emb]
        for _ in range(self.n_layers):
            all_emb = torch.sparse.mm(adj_norm, all_emb)  # агрегация соседей
            embs.append(all_emb)
        # Финальный эмбеддинг = среднее по всем слоям (включая layer 0)
        final = torch.stack(embs).mean(dim=0)
        users, items = final[:self.user_emb.num_embeddings], final[self.user_emb.num_embeddings:]
        return users, items

    def predict(self, user_emb, item_emb):
        return (user_emb * item_emb).sum(dim=-1)  # dot product

PinSage — графы в масштабе Pinterest

PinSage (Ying et al., 2018) — первая GNN, работающая на графе с миллиардами узлов. Ключевые идеи: 1) не агрегируем всех соседей — семплируем N случайных, 2) используем случайные блуждания для определения важности соседей, 3) mini-batch обучение.

Pinterest в цифрах

PinSage работает на графе 3 миллиарда узлов (пины) и 18 миллиардов рёбер (pin-board связи). Генерирует эмбеддинги для всех пинов, используемые в candidate generation. +40% к вовлечённости относительно предыдущей системы.

Когда использовать GNN

  • Есть богатая структура связей (социальный граф, категории, атрибуты)
  • Важна транзитивная информация (друзья друзей, похожие на похожие)
  • Холодный старт: GNN может получить эмбеддинг нового айтема через его связи в графе
  • НЕ стоит, если граф разреженный и мало транзитивности — MF будет проще и не хуже

GNN vs матричная факторизация

LightGCN — это обобщение MF: если 0 слоёв агрегации, получается обычная MF. Каждый дополнительный слой добавляет «прыжок» по графу. На практике 2-3 слоя обычно оптимальны — больше приводит к oversmoothing (все эмбеддинги становятся похожими).

Knowledge Graph — внешние знания в рекомендациях

Помимо user-item графа, можно использовать графы знаний: актёр → снялся в → фильм → принадлежит жанру → комедия. Это помогает для холодного старта (у нового фильма есть связи через актёров/режиссёров) и для объяснимости (рекомендуем потому что тебе нравится этот актёр).

На собесе

Что такое oversmoothing в GNN? С каждым слоём эмбеддинги всё больше усредняются с соседями. После 5-6 слоёв все узлы получают почти одинаковые эмбеддинги. Решения: residual connections, jumping knowledge, DropEdge.

🎯 Суть для собеса

• GNN для рекомендаций — когда оправдано? (явный граф: соцсети, knowledge graph. Для обычного CF — overhead) • Oversmoothing — что это? (после 3+ слоёв GNN все ноды становятся одинаковыми. Решение: skip connections, dropout) • GraphSAGE vs GCN? (GraphSAGE: сэмплирует соседей, масштабируется. GCN: все соседи, но не скалируется) • Knowledge graph в рекомендациях? (обогащает item-фичи: жанр→режиссёр→актёры. Помогает с объяснимостью) • На практике: граф друзей + подписок для рекомендаций сообществ