Skip to content

Repository files navigation

Рекомендательная система с контентными эмбеддингами

Сервис, который для каждого пользователя в любой момент времени возвращает топ-5 наиболее релевантных постов, объединяя семантические эмбеддинги текстов с градиентным бустингом.

Результат: HitRate@5 > 0.59 (порог зачёта проекта — 0.57)


Описание задачи

Существует социальная сеть со следующим функционалом: пользователи общаются, создают сообщества и публикуют в них посты. У каждой соцсети есть лента, которую пользователи листают и просматривают записи. Если пост нравится — можно поставить лайк автору. Все действия пользователей (просмотры, лайки) сохраняются в базе.

Изначально лента отдавала пользователям случайные посты случайных сообществ. Задача — заменить случайную выдачу на персональные рекомендации, учитывая:

  • демографические признаки пользователя из его профиля,
  • историю взаимодействий с лентой,
  • семантическое содержание самих постов.

Сервис должен в любой момент времени для конкретного user_id вернуть отсортированный список из топ-N постов, которые с наибольшей вероятностью получат лайк.

Метрика

Качество оценивается по скрытому ряду user_id и timestamp (запросы пользователей в разные моменты времени) метрикой HitRate@5:

$$ \text{HitRate@5} = \frac{1}{n \cdot T} \sum_{t=1}^{T} \sum_{i=1}^{n} \min!\left(1, \sum_{j=1}^{5} [a_j(x_i, t) = 1]\right) $$

где n — количество пользователей, T — количество периодов проверки, a_j(x_i, t) — j-я рекомендация i-му пользователю в момент t. Метрика бинарная: если из 5 рекомендаций хотя бы одна оказалась релевантной — единица, иначе ноль.


Что изменилось по сравнению с baseline-решением

Эта версия — развитие baseline-системы, в которой использовались TF-IDF + PCA + KMeans + CatBoost (классический ML-подход). Ключевые улучшения:

Компонент Baseline Текущая версия
Эмбеддинги текста TF-IDF (~40k признаков) + PCA до 20 intfloat/multilingual-e5-base (768D) + PCA до 64
Тематическая структура KMeans на 15 кластеров + расстояния до центроидов Убрана, передаём PCA-компоненты напрямую
Гиперпараметры CatBoost depth=2, iterations=500, lr=1.0 (вручную) Подобраны через Optuna (30 trials, AUC)
Признаки времени month, day, hour month, day, dow, hour
Архитектура хранения фичей Локальные таблицы Эмбеддинги постов в PostgreSQL
HitRate@5 ≈ 0.54 > 0.59

Ключевое содержательное изменение — переход с TF-IDF на семантические эмбеддинги предобученной мультиязычной модели intfloat/multilingual-e5-base. Это даёт CatBoost'у векторное представление, в котором близкие по смыслу посты находятся рядом в пространстве — независимо от совпадения конкретных слов.


Архитектура решения

Признаки

Пользовательские (из public.user_data): gender, age, country, city, exp_group, os, source.

Постовые:

  • 64 компоненты PCA от 768-мерного эмбеддинга текста (post_emb_pca_0post_emb_pca_63),
  • категория topic.

Временные — извлекаются из timestamp запроса: month, day, dow (день недели), hour.

Категориальные признаки (topic, country, city, os, source, gender, exp_group) подаются в CatBoost напрямую через cat_features — Ordered Target Encoding выполняется внутри модели.

Эмбеддинги постов

Тексты постов закодированы предобученной моделью intfloat/multilingual-e5-base:

  1. К каждому тексту добавляется обязательный префикс passage: (требование e5 для асимметричной задачи retrieval).
  2. Получаются нормализованные unit-norm векторы размерности 768.
  3. PCA снижает размерность до 64 главных компонент (сохраняется ~75–80% дисперсии).

Эмбеддинги считаются один раз в ноутбуке post-embeddings.ipynb на GPU T4 и загружаются в PostgreSQL в таблицу public.bezyazychnyy_a_post_embeddings. На сервере при старте они подгружаются в память за один SQL-запрос.

Модель

CatBoostClassifier обучается на 5M записях из feed_data (только просмотры, action != 'like'), отсортированных по времени. Разделение train/test — по timestamp = 2021-12-15 (≈5:1).

Гиперпараметры подобраны через Optuna (30 trials, направление — максимизация AUC на test):

  • depth: 4–10
  • learning_rate: 0.01–0.3 (log-uniform)
  • l2_leaf_reg: 1.0–10.0 (log-uniform)
  • min_data_in_leaf: 1–100
  • random_strength, bagging_temperature, border_count

Финальная модель учится на лучших параметрах с iterations=5000 и early_stopping_rounds=200 на GPU. Сохраняется в нативном CatBoost-формате .cbm.

Сервис инференса

FastAPI с предзагрузкой всех артефактов при старте:

  • модель (catboost_model.cbm),
  • эмбеддинги постов (bezyazychnyy_a_post_embeddings в PostgreSQL),
  • тексты постов (post_text_df),
  • профили пользователей (user_data),
  • история лайков (для фильтрации уже понравившихся постов).

Алгоритм обработки запроса (user_id, time, limit=5):

  1. Достать профиль пользователя по user_id (O(1) по индексу).
  2. Декартово произведение 1 пользователь × N постов.
  3. Добавить временные признаки из time.
  4. Привести колонки к фиксированному порядку FEATURE_COLUMNS.
  5. model.predict_proba(...) → вектор скоров для всех постов.
  6. Отфильтровать посты, которые пользователь уже лайкал.
  7. nlargest(limit) по скору → топ-N идентификаторов.
  8. Собрать ответ с текстами и темами через post_text_df.

Среднее время отклика — 50–200 мс при ~10k постов в каталоге.


Структура репозитория

recommendation_system_project/
├── entities/                       # SQLAlchemy-модели и Pydantic-схемы
│   ├── schema.py                   # PostGet, UserGet, FeedGet
│   ├── table_feed.py               # таблица активностей
│   ├── table_post.py               # таблица постов
│   └── table_user.py               # таблица пользователей
│
├── loading_from_server/            # модули загрузки артефактов при старте
│   ├── feature_loading.py          # загрузка эмбеддингов, юзеров, лайков
│   └── model_loading.py            # загрузка CatBoost-модели
│
├── model_data/                     # обученная модель и метаданные
│   ├── catboost_info/              # логи обучения CatBoost
│   ├── catboost_model.cbm          # модель в нативном бинарном формате
│   ├── catboost_model              # тот же файл без расширения
│   ├── best_params.json            # гиперпараметры от Optuna
│   ├── cat_features.json           # список категориальных признаков
│   ├── feature_columns.json        # порядок колонок для инференса
│   └── model_loading.py            # утилита загрузки
│
├── notebooks/                      # ноутбуки построения системы
│   ├── post-embeddings.ipynb       # шаг 1: эмбеддинги e5-base + PCA
│   ├── recsys-data.ipynb           # шаг 2: сборка обучающего датасета
│   ├── recsys-catboost-learning.ipynb  # шаг 3: Optuna + обучение CatBoost
│   └── send-data-to-server.ipynb   # шаг 4: загрузка эмбеддингов в PostgreSQL
│
├── app.py                          # FastAPI-приложение, эндпоинты
├── database.py                     # настройки подключения к БД
├── requirements.txt                # зависимости Python
├── test_cilent.py                  # тест эндпоинта рекомендаций
└── README.md

Установка и запуск

1. Клонирование репозитория

git clone https://github.com/jack1591/recommendation_system_project.git
cd recommendation_system_project

2. Установка зависимостей

Рекомендуется использовать виртуальное окружение:

python -m venv venv
source venv/bin/activate         # на Windows: venv\Scripts\activate
pip install -r requirements.txt

3. Запуск сервиса

uvicorn app:app --reload --host 0.0.0.0 --port 8000

При первом запуске сервис подгружает все таблицы и модель — это занимает 30–90 секунд. Готовность определяется по строке server is ready в логах.

4. Проверка работоспособности

После старта доступны:


API

GET /post/recommendations/

Возвращает топ-N персональных рекомендаций для пользователя в указанный момент времени.

Параметры запроса:

Параметр Тип По умолчанию Описание
id int обязательный ID пользователя
time datetime (ISO 8601) обязательный Момент запроса, например 2021-12-20T14:00:00
limit int 5 Сколько постов вернуть

Пример запроса:

GET /post/recommendations/?id=1000&time=2021-12-20T14:00:00&limit=5

Пример ответа:

[
  {"id": 4567, "text": "Lorem ipsum dolor sit amet...", "topic": "covid"},
  {"id": 8921, "text": "Another article about...", "topic": "sport"},
  {"id": 1234, "text": "Tech industry...", "topic": "tech"},
  {"id": 5678, "text": "Latest movies...", "topic": "movie"},
  {"id": 3456, "text": "Entertainment news...", "topic": "entertainment"}
]

Сервисные эндпоинты

Метод Эндпоинт Описание
GET /users/{limit} Список первых limit пользователей из таблицы user
GET /user/{id} Информация о пользователе по ID
GET /post/{id} Информация о посте по ID
GET /user/{id}/feed Активности конкретного пользователя
GET /post/{id}/feed Активности по конкретному посту

Тестирование

В репозитории есть test_cilent.py — простой тест эндпоинта рекомендаций через fastapi.testclient:

from datetime import datetime
from fastapi.testclient import TestClient
import app

client = TestClient(app.app)

user_id = 1000
time = datetime(2021, 12, 20)

try:
    r = client.get(
        f'/post/recommendations/',
        params={'id': user_id, 'time': time, 'limit': 5}
    )
except Exception as e:
    raise ValueError(f"Ошибка при выполнении запроса {type(e)} {str(e)}")

print(r.json())

Запуск:

python test_cilent.py

Тест поднимает приложение в режиме TestClient (без отдельного uvicorn-процесса), отправляет GET-запрос и печатает результат. Должны вернуться 5 объектов вида {"id": ..., "text": "...", "topic": "..."}.

Для тестирования через внешние клиенты — Postman, curl или браузер — сначала запускается uvicorn (см. раздел «Запуск»), затем:

curl "http://localhost:8000/post/recommendations/?id=1000&time=2021-12-20T14:00:00&limit=5"

Воспроизведение обучения

Полный цикл от данных до развёрнутой модели разнесён по четырём ноутбукам в notebooks/:

  1. post-embeddings.ipynb — кодирование текстов постов через intfloat/multilingual-e5-base, PCA до 64 компонент, сохранение в parquet.
  2. recsys-data.ipynb — сборка итогового обучающего датасета: мерж feed_data × user_data × эмбеддинги постов, извлечение временных признаков.
  3. recsys-catboost-learning.ipynb — подбор гиперпараметров через Optuna, финальное обучение CatBoost на GPU, замер HitRate@5, сохранение модели и метаданных.
  4. send-data-to-server.ipynb — загрузка таблицы эмбеддингов в PostgreSQL (public.bezyazychnyy_a_post_embeddings).

Для воспроизведения нужны:

  • Доступ к PostgreSQL karpov.courses (postgres.lab.karpov.courses:6432/startml).
  • GPU для шагов 1 и 3 (ноутбуки заточены под Kaggle T4×2).

Используемые технологии

  • Python 3.10+
  • CatBoost — градиентный бустинг, обучение и инференс
  • sentence-transformers — предобученная модель intfloat/multilingual-e5-base для эмбеддингов
  • scikit-learn — PCA для снижения размерности
  • Optuna — байесовский подбор гиперпараметров
  • PostgreSQL — хранение исходных таблиц и эмбеддингов постов
  • SQLAlchemy — ORM для работы с БД
  • FastAPI + Uvicorn — веб-сервис инференса
  • pandas, numpy — обработка данных
  • loguru — структурированное логирование

Возможные направления развития

  • User embeddings: агрегат эмбеддингов постов из истории пользователя (среднее с time decay, отдельно по лайкам). Главная производная фича — cosine(user_emb, post_emb).
  • Коллаборативный сигнал: ALS-факторы на матрице взаимодействий, дополняющие контентные эмбеддинги (особенно для cold start).
  • Двухстадийный pipeline: candidate generation через FAISS-retrieval по <user_emb, post_emb> + ранжирование топ-200 кандидатов через CatBoost.
  • Ranking objective: переход с Logloss на YetiRank или LambdaRank с группировкой по (user_id, timestamp_bucket) — целевая функция совпадёт с метрикой задачи.
  • Дообучение эмбеддингов: contrastive learning на парах постов, лайкнутых одним пользователем, через MultipleNegativesRankingLoss.

About

Рекомендательная система постов для пользователей в рамках karpov.courses

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages