Требования к данным рекомендательной системы¶
~4 минуты чтения
Предварительно: Определение задачи | Компоненты системы | Feature Engineering
Качество рекомендательной системы на 80% определяется данными и features, а не архитектурой модели. Netflix тратит ~$1B/год на контент, но именно данные о 230M пользователях (5B+ событий/день) позволяют персонализировать каталог из 15K+ тайтлов. Типичная production система использует 500-1000 features из 4 источников: user data, item data, interaction data и context. Feature engineering для рекомендаций -- это отдельная дисциплина, где training-serving skew является причиной #1 деградации моделей в production.
Источники данных¶
1. User Data (Данные пользователей)¶
-- Таблица пользователей
CREATE TABLE users (
user_id BIGINT PRIMARY KEY,
created_at TIMESTAMP,
country VARCHAR(2),
language VARCHAR(5),
device_type VARCHAR(20),
subscription_tier VARCHAR(20),
age_group VARCHAR(10),
gender VARCHAR(10)
);
Feature Engineering:
user_features = {
# Демография
"user_age_bucket": "25-34",
"user_country": "RU",
"user_language": "ru",
# Активность
"days_since_signup": 365,
"total_sessions": 500,
"avg_session_duration_min": 25,
"last_active_hours_ago": 2,
# Предпочтения (агрегаты)
"favorite_categories": ["action", "comedy"],
"avg_item_price_viewed": 1500.0,
"preferred_time_of_day": "evening",
# Embeddings
"user_embedding": [0.1, 0.2, ...], # 128-dim
}
2. Item Data (Данные items)¶
-- Таблица items (товары/контент)
CREATE TABLE items (
item_id BIGINT PRIMARY KEY,
title VARCHAR(500),
description TEXT,
category_id INT,
subcategory_id INT,
brand VARCHAR(100),
price DECIMAL(10,2),
created_at TIMESTAMP,
is_active BOOLEAN,
content_type VARCHAR(50)
);
-- Таблица атрибутов
CREATE TABLE item_attributes (
item_id BIGINT,
attribute_name VARCHAR(100),
attribute_value VARCHAR(500)
);
Feature Engineering:
item_features = {
# Метаданные
"item_category": "electronics",
"item_subcategory": "smartphones",
"item_brand": "Apple",
"item_price": 99999,
"item_age_days": 30,
# Популярность
"views_7d": 10000,
"clicks_7d": 500,
"purchases_7d": 50,
"ctr_7d": 0.05,
"conversion_rate_7d": 0.005,
# Content features
"title_embedding": [0.3, 0.1, ...], # 768-dim BERT
"image_embedding": [0.5, 0.2, ...], # 512-dim ResNet
# Quality signals
"avg_rating": 4.5,
"num_reviews": 1000,
"return_rate": 0.02,
}
3. Interaction Data (Взаимодействия)¶
-- Event log
CREATE TABLE interactions (
event_id BIGINT PRIMARY KEY,
user_id BIGINT,
item_id BIGINT,
event_type VARCHAR(20), -- view, click, add_to_cart, purchase
timestamp TIMESTAMP,
session_id VARCHAR(50),
device_type VARCHAR(20),
position INT, -- позиция в списке
source VARCHAR(50) -- homepage, search, recommendation
);
Типы событий:
event_types = {
"impression": 0.0, # Показан в списке
"view": 0.1, # Открыл страницу
"click": 0.3, # Кликнул
"add_to_cart": 0.5, # Добавил в корзину
"purchase": 1.0, # Купил
"rating_5": 1.0, # Поставил 5 звёзд
"rating_1": -1.0, # Поставил 1 звезду
"skip": -0.2, # Пропустил
"hide": -0.5, # Скрыл
}
4. Context Data (Контекст)¶
context_features = {
# Время
"hour_of_day": 20,
"day_of_week": 5, # Friday
"is_weekend": True,
"is_holiday": False,
# Устройство
"device_type": "mobile",
"os": "iOS",
"app_version": "5.2.1",
# Сессия
"items_viewed_this_session": 5,
"search_query": "iphone case",
"referring_page": "search_results",
# Геолокация
"city": "Moscow",
"timezone": "Europe/Moscow",
}
Data Pipeline Architecture¶
graph TD
AE["App Events<br/>(Kafka)"] --> DL["Data Lake<br/>(S3/GCS)"]
DB["Database<br/>(PostgreSQL)"] --> DL
TP["3rd Party<br/>(APIs)"] --> DL
CT["Content<br/>(S3)"] --> DL
DL --> FE["Feature Engineering<br/>(Spark/Flink)"]
FE --> FS["Feature Store<br/>(Feast/Tecton)<br/>Online: Redis/DynamoDB<br/>Offline: S3/BigQuery"]
FE --> TD["Training Data<br/>(Parquet/TFRecord)<br/>Historical pairs with labels"]
style AE fill:#e8eaf6,stroke:#3f51b5
style DB fill:#e8eaf6,stroke:#3f51b5
style TP fill:#e8eaf6,stroke:#3f51b5
style CT fill:#e8eaf6,stroke:#3f51b5
style DL fill:#fff3e0,stroke:#ef6c00
style FE fill:#f3e5f5,stroke:#9c27b0
style FS fill:#e8f5e9,stroke:#4caf50
style TD fill:#e8f5e9,stroke:#4caf50
Feature Store Schema¶
Online Features (низкая latency)¶
# Redis/DynamoDB
user_online_features = {
"user:123": {
"last_viewed_items": [1, 5, 3, 8], # последние 10
"last_categories": ["electronics", "books"],
"session_click_count": 5,
"user_embedding": [0.1, 0.2, ...],
}
}
item_online_features = {
"item:456": {
"click_count_1h": 150,
"purchase_count_1h": 10,
"current_stock": 500,
"item_embedding": [0.3, 0.1, ...],
}
}
Offline Features (batch computed)¶
# Parquet/BigQuery
user_offline_features = {
"user_id": 123,
"total_purchases_30d": 5,
"total_spend_30d": 15000.0,
"category_affinity_vector": [0.8, 0.2, 0.1, ...], # per category
"brand_affinity_vector": [0.5, 0.3, ...],
"price_sensitivity": 0.7, # 0=cheap, 1=expensive
}
Training Data Format¶
Pointwise (для classification/regression)¶
# Каждый пример = (user, item, label)
{
"user_id": 123,
"item_id": 456,
"user_features": {...},
"item_features": {...},
"context_features": {...},
"label": 1, # clicked or not
"weight": 1.0, # importance
}
Pairwise (для ranking)¶
# Каждый пример = (user, positive_item, negative_item)
{
"user_id": 123,
"positive_item_id": 456, # clicked
"negative_item_id": 789, # not clicked
"user_features": {...},
"positive_item_features": {...},
"negative_item_features": {...},
}
Listwise (для ranking)¶
# Каждый пример = (user, [items], [relevance_scores])
{
"user_id": 123,
"candidate_items": [456, 789, 101, 202],
"relevance_labels": [3, 0, 1, 2], # graded relevance
"user_features": {...},
"items_features": [{...}, {...}, {...}, {...}],
}
Data Quality Checks¶
# Great Expectations / Deequ checks
data_quality_checks = {
"users": [
"user_id is unique",
"created_at is not null",
"country is in valid_countries",
],
"items": [
"item_id is unique",
"price > 0",
"category_id exists in categories table",
],
"interactions": [
"user_id exists in users",
"item_id exists in items",
"timestamp is not in future",
"event_type in valid_event_types",
],
"features": [
"no nulls in required features",
"embeddings have correct dimension",
"numerical features in expected range",
],
}
Data Freshness Requirements¶
| Data Type | Update Frequency | Latency Tolerance |
|---|---|---|
| User profile | Daily | 24 hours |
| Item catalog | Hourly | 1 hour |
| Interactions (batch) | Hourly | 1 hour |
| Interactions (streaming) | Real-time | < 1 min |
| Embeddings | Daily | 24 hours |
| Popularity scores | Hourly | 1 hour |
Privacy & Compliance¶
- PII Handling: Hash/encrypt user identifiers
- Data Retention: Delete interactions older than X days
- Right to be Forgotten: Ability to delete user data
- Consent: Only use data with user consent
- Anonymization: Aggregate data for analytics
Заблуждение: Для обучения достаточно положительных примеров (кликов)
Модель, обученная только на positive samples, не умеет различать хорошее от среднего. Для качественного ranking нужны negative samples. Но random negatives слишком легкие -- модель не учится. Оптимальная стратегия: hard negatives (items, показанные но не кликнутые на позициях 1-10) + in-batch negatives + easy negatives (random). Соотношение positive:negative обычно 1:4 до 1:10.
Заблуждение: Implicit feedback (клики) = explicit feedback (рейтинги)
Клик != релевантность. Пользователь может кликнуть из любопытства (clickbait CTR до 15%, но watch time < 10 секунд). Для рекомендаций лучше использовать weighted implicit feedback: impression=0, click=0.3, add_to_cart=0.5, purchase=1.0, return=-0.5. YouTube перешёл от оптимизации CTR к watch time и увидел рост долгосрочного engagement на 70%.
Заблуждение: Training-serving skew -- редкая проблема
По данным Google, training-serving skew -- причина #1 деградации ML-моделей в production. Типичный пример: при training используется avg_rating из BigQuery (обновляется раз в сутки), а при serving -- из Redis (обновляется в real-time). Feature Store (Feast/Tecton) решает проблему единым вычислением features для обоих сценариев. Без него модель видит разные данные при training и inference.
Собеседование¶
Какие данные нужны для рекомендательной системы?¶
"Нам нужны user-item interactions -- клики и покупки."
"Четыре категории данных: (1) User data -- демография, активность, preferences embedding (128-dim); (2) Item data -- метаданные, popularity scores, content embeddings (BERT 768-dim для текста, ResNet 512-dim для изображений); (3) Interaction data -- event log с весами (impression=0, click=0.3, purchase=1.0), position bias correction; (4) Context -- время, устройство, сессия, геолокация. Итого 500-1000 features на пару user-item."
Как организовать Feature Store?¶
"Храним всё в Redis, подтягиваем при каждом запросе."
"Двухслойная архитектура: Online store (Redis Cluster, 10 shards, < 5ms) для real-time features (last actions, session data, trending scores). Offline store (S3/BigQuery) для batch features (30-day aggregates, embeddings, affinity vectors). Feature Registry обеспечивает единую точку определения features для training и serving -- это решает training-serving skew. Feast или Tecton как orchestration layer. Критично: одна и та же feature computation pipeline для training data и online serving."
Как формировать training data для ranking?¶
"Берём все клики как positive, всё остальное как negative."
"Три формата: Pointwise (user, item, label) для CTR prediction, Pairwise (user, pos_item, neg_item) для ranking, Listwise (user, [items], [grades]) для NDCG-оптимизации. Negative sampling стратегия: hard negatives (показанные но не кликнутые, позиции 1-10), in-batch negatives, easy negatives (random). Ratio 1:4-1:10. Важно: position bias correction через Inverse Propensity Weighting, иначе модель учит позицию, а не релевантность."