Сервис, который для каждого пользователя в любой момент времени возвращает топ-5 наиболее релевантных постов, объединяя семантические эмбеддинги текстов с градиентным бустингом.
Результат: HitRate@5 > 0.59 (порог зачёта проекта — 0.57)
Существует социальная сеть со следующим функционалом: пользователи общаются, создают сообщества и публикуют в них посты. У каждой соцсети есть лента, которую пользователи листают и просматривают записи. Если пост нравится — можно поставить лайк автору. Все действия пользователей (просмотры, лайки) сохраняются в базе.
Изначально лента отдавала пользователям случайные посты случайных сообществ. Задача — заменить случайную выдачу на персональные рекомендации, учитывая:
- демографические признаки пользователя из его профиля,
- историю взаимодействий с лентой,
- семантическое содержание самих постов.
Сервис должен в любой момент времени для конкретного user_id вернуть отсортированный список из топ-N постов, которые с наибольшей вероятностью получат лайк.
Качество оценивается по скрытому ряду user_id и timestamp (запросы пользователей в разные моменты времени) метрикой HitRate@5:
где n — количество пользователей, T — количество периодов проверки, a_j(x_i, t) — j-я рекомендация i-му пользователю в момент t. Метрика бинарная: если из 5 рекомендаций хотя бы одна оказалась релевантной — единица, иначе ноль.
Эта версия — развитие 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_0…post_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:
- К каждому тексту добавляется обязательный префикс
passage:(требование e5 для асимметричной задачи retrieval). - Получаются нормализованные unit-norm векторы размерности 768.
- 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–10learning_rate: 0.01–0.3 (log-uniform)l2_leaf_reg: 1.0–10.0 (log-uniform)min_data_in_leaf: 1–100random_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):
- Достать профиль пользователя по
user_id(O(1) по индексу). - Декартово произведение
1 пользователь × N постов. - Добавить временные признаки из
time. - Привести колонки к фиксированному порядку
FEATURE_COLUMNS. model.predict_proba(...)→ вектор скоров для всех постов.- Отфильтровать посты, которые пользователь уже лайкал.
nlargest(limit)по скору → топ-N идентификаторов.- Собрать ответ с текстами и темами через
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
git clone https://github.com/jack1591/recommendation_system_project.git
cd recommendation_system_projectРекомендуется использовать виртуальное окружение:
python -m venv venv
source venv/bin/activate # на Windows: venv\Scripts\activate
pip install -r requirements.txtuvicorn app:app --reload --host 0.0.0.0 --port 8000При первом запуске сервис подгружает все таблицы и модель — это занимает 30–90 секунд. Готовность определяется по строке server is ready в логах.
После старта доступны:
- Swagger UI для интерактивного тестирования: http://localhost:8000/docs
- ReDoc для статичной документации: http://localhost:8000/redoc
Возвращает топ-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/:
post-embeddings.ipynb— кодирование текстов постов черезintfloat/multilingual-e5-base, PCA до 64 компонент, сохранение в parquet.recsys-data.ipynb— сборка итогового обучающего датасета: мержfeed_data×user_data× эмбеддинги постов, извлечение временных признаков.recsys-catboost-learning.ipynb— подбор гиперпараметров через Optuna, финальное обучение CatBoost на GPU, замер HitRate@5, сохранение модели и метаданных.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.