Реализация рекомендательной системы для видеостримингового сервиса
Представьте: пользователь открывает стриминговый сервис, видит ленту с контентом, который ему не интересен, и уходит к конкуренту. Коллаборативные фильтры не работают — они не учитывают, что вечером пользователь хочет смотреть комедию, а утром — документалку. Чтобы удержать зрителя, нужна рекомендательная система, которая понимает контекст: время суток, устройство, настроение, историю просмотров. Наш подход базируется на мультиконтекстной модели, которая показала прирост watch time на 30-50% в продакшене. Экономия бюджета на привлечение новых пользователей за счёт удержания существующих достигает 40%.
Какие метрики используются для оценки?
Основная метрика — watch time на сессию. Дополнительно отслеживают continuation rate — доля пользователей, которые начали следующий эпизод сериала, и diversity потребляемого контента. CTR второстепенен, так как цель — удержание, а не клики. Наши A/B-тесты показывают, что прирост watch time в 3-5 раз выше, чем при использовании коллаборативной фильтрации.
Как учесть контекст просмотра пользователя?
Ключевая идея — предсказывать не просто 'понравится/не понравится', а вероятность досмотра с учётом текущего состояния. Для этого строим user tower и item tower с общим embedding-пространством и добавляем контекстные признаки: час дня, тип устройства, длительность предыдущей сессии, жанровый профиль.
Мультиконтекстная модель
import numpy as np import pandas as pd import torch import torch.nn as nn class VideoStreamingRecommender(nn.Module): """Учитывает контекст просмотра: время, устройство, соратники""" def __init__(self, n_users, n_items, n_genres, embed_dim=128): super().__init__() # User tower self.user_emb = nn.Embedding(n_users + 1, embed_dim) self.genre_emb = nn.Embedding(n_genres + 1, 32) # Context features self.context_mlp = nn.Sequential( nn.Linear(10, 32), # hour, day, device_type, etc. nn.ReLU() ) # Item tower self.item_emb = nn.Embedding(n_items + 1, embed_dim) self.genre_item_emb = nn.Embedding(n_genres + 1, 32) # Scoring head self.scoring = nn.Sequential( nn.Linear(embed_dim + 32 + 32 + embed_dim + 32, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 1), nn.Sigmoid() ) def forward(self, user_id, context_features, item_id, item_genre, user_top_genres): u = self.user_emb(user_id) g = self.genre_emb(user_top_genres).mean(dim=1) c = self.context_mlp(context_features) i = self.item_emb(item_id) ig = self.genre_item_emb(item_genre) combined = torch.cat([u, g, i, ig], dim=1) return self.scoring(combined).squeeze(1) class WatchHistoryFeatureExtractor: """Признаки из истории просмотров""" def extract_user_features(self, watch_history: pd.DataFrame) -> dict: """ watch_history: user_id, item_id, watched_seconds, total_seconds, genre, timestamp, device """ completion_rates = watch_history['watched_seconds'] / watch_history['total_seconds'].clip(1) features = { 'completion_rate_avg': completion_rates.mean(), 'completion_rate_std': completion_rates.std(), 'binge_sessions': self._count_binge_sessions(watch_history), 'preferred_genres': watch_history.groupby('genre')['watched_seconds'].sum().nlargest(3).index.tolist(), 'preferred_device': watch_history['device'].value_counts().index[0], 'avg_session_items': self._avg_items_per_session(watch_history), 'evening_watcher': self._is_evening_watcher(watch_history), 'weekend_preference': self._weekend_ratio(watch_history), } return features def _count_binge_sessions(self, history: pd.DataFrame) -> int: """Сессии с 3+ эпизодами подряд""" history = history.sort_values('timestamp') history['session_gap'] = history['timestamp'].diff().dt.total_seconds() > 1800 history['session_id'] = history['session_gap'].cumsum() session_counts = history.groupby('session_id').size() return int((session_counts >= 3).sum()) def _is_evening_watcher(self, history: pd.DataFrame) -> bool: evening_views = history[ pd.to_datetime(history['timestamp']).dt.hour.between(18, 23) ] return len(evening_views) / max(len(history), 1) > 0.5 def _weekend_ratio(self, history: pd.DataFrame) -> float: weekend = pd.to_datetime(history['timestamp']).dt.dayofweek >= 5 return weekend.mean() def _avg_items_per_session(self, history: pd.DataFrame) -> float: if 'session_id' not in history.columns: return 1.5 return history.groupby('session_id').size().mean() Почему важна серийность контента?
В стриминге сериалы — основной драйвер удержания. Пользователь, который смотрит сериал, проводит на платформе в 3 раза больше времени. Стандартные модели не дают приоритет следующему эпизоду, и пользователь может потеряться среди рекомендованного. Мы реализовали модуль, который повышает вероятность следующего эпизода в 2.5 раза.
Ранжирование с учётом продолжаемости серий
class SeriesContinuationBooster: """Буст для следующих эпизодов серий, которые пользователь смотрит""" def boost_continuation(self, candidates: list[tuple], user_watch_history: pd.DataFrame, content_metadata: dict) -> list[tuple]: """Повышение приоритета продолжений""" # Серии в прогрессе series_progress = ( user_watch_history .groupby('series_id')['episode_number'] .max() .to_dict() ) boosted = [] for item_id, score in candidates: meta = content_metadata.get(item_id, {}) series_id = meta.get('series_id') episode = meta.get('episode_number', 1) boost = 1.0 if series_id and series_id in series_progress: watched_episode = series_progress[series_id] if episode == watched_episode + 1: boost = 2.5 # Следующий эпизод elif episode <= watched_episode: boost = 0.1 # Уже смотрел boosted.append((item_id, score * boost)) return sorted(boosted, key=lambda x: x[1], reverse=True) Сравнение подходов к рекомендациям
| Подход | Преимущества | Недостатки | Прирост watch time |
|---|---|---|---|
| Коллаборативная фильтрация | Простота, холодный старт | Не учитывает контекст, слабая персонализация | +5-10% |
| Контентная фильтрация | Работает без истории, учитывает жанры | Не использует коллективное поведение | +10-15% |
| Гибридная (наша) | Учёт контекста и серийности | Требует больше данных | +30-50% |
Гибридная модель обеспечивает прирост watch time в 3-5 раз выше, чем коллаборативная фильтрация. Это подтверждено A/B-тестами на проектах с аудиторией от 500 тыс. до 5 млн пользователей.
Процесс внедрения рекомендательной системы
| Этап | Длительность | Результат |
|---|---|---|
| Аудит данных и инфраструктуры | 1-2 недели | Отчёт о качестве данных, схема ETL |
| Проектирование и обучение модели | 3-4 недели | Baseline модель, эксперименты |
| A/B-тестирование | 2 недели | Статистически значимый прирост метрик |
| Интеграция и деплой | 1-2 недели | REST API, мониторинг latency p99 < 50 мс |
Типичные ошибки при разработке
- Игнорирование контекста: лента выглядит одинаково утром и вечером.
- Линейное ранжирование: все фичи взвешиваются одинаково, нет буста серий.
- Отсутствие мониторинга: не отслеживается дрейф предпочтений.
- Переобучение на популярные элементы: модель рекомендует только топ-100, не открывая длинный хвост.
Что входит в разработку под ключ
- Модель с детальной документацией (model card, описание фич, метрики).
- REST API для выдачи рекомендаций (latency p99 < 50 мс, throughput 10k RPS).
- A/B-инфраструктура для онлайн-оценки.
- Интеграция с вашей платформой (веб, мобильные приложения, Smart TV).
- Обучение команды и сопровождение после запуска.
Как мы обеспечиваем качество
Мы разработали более 20 рекомендательных систем для streaming-платформ. Наш опыт подтверждает прирост watch time на 30-50% в первые 3 месяца после внедрения. Используем Netflix как бенчмарк, но адаптируем архитектуру под конкретный контент и аудиторию. Гарантируем прозрачность: вы получаете не чёрный ящик, а интерпретируемую модель с объяснением рекомендаций.
Пример оценки производительности
Для одного из проектов (видеосервис с 5 млн пользователей) мы достигли: - watch time per session: +35% - continuation rate: +40% - latency p99: 35 мс при throughput 5000 RPS - GPU utilization: 70% на инференсе с batch size 64Закажите предварительный аудит данных: мы проанализируем вашу аудиторию и контент и предложим архитектуру системы. Получите консультацию эксперта — правильно настроенная система окупается в первые месяцы за счёт роста времени просмотра и снижения оттока.







