Разработка рекомендательной системы контента на Content-Based Filtering

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1Все 1564 услуг
Разработка рекомендательной системы контента на Content-Based Filtering
Средний
~1-2 недели
Часто задаваемые вопросы

Направления AI-разработки

Этапы разработки AI-решения

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1348
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    949
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1183
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    642
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    921

Новый сайт с видеоуроками, где первым пользователям рекомендуют случайные ролики — отток 80% за первую неделю. Мы решаем эту проблему с помощью Content-Based Filtering — метода, который не требует данных о других пользователях и выдаёт релевантные рекомендации уже после 3–5 взаимодействий. Наш опыт — более 50 внедрённых рекомендательных систем для e-commerce, медиа и EdTech. Наши инженеры сертифицированы по MLOps и NLP, что гарантирует качество каждого этапа.

Как Content-Based Recommendation System решает холодный старт?

Минимальная история взаимодействий — главная проблема онбординга. Content-Based Filtering строит профиль пользователя исключительно на основе атрибутов объектов, с которыми он взаимодействовал. Метод использует семантические эмбеддинги текста, TF-IDF, категориальные и числовые признаки, объединяя их в единый мультимодальный профиль. Precision@10 на новых пользователях достигает 0.15–0.30, что в 10–30 раз выше, чем у популярного бейзлайна (0.01–0.03).

Архитектура Content-Based Recommendation System

Многомодальный контентный профиль: пошаговый подход — разработка рекомендательной системы

  1. Сбор данных: item_id, title, description, категории, теги, цена, рейтинг и другие числовые признаки.
  2. Векторизация текста: используем мультиязычный sentence-transformers (paraphrase-multilingual-mpnet-base-v2), получаем 768-мерные эмбеддинги.
  3. TF-IDF признаки: дополнительно извлекаем 5000 n-граммов (1-2), сжимаем до 50 компонент через SVD.
  4. Категориальные признаки: бинаризация через MultiLabelBinarizer.
  5. Числовые признаки: StandardScaler для price, rating, review_count, release_year.
  6. Взвешенная конкатенация: текст (вес 2), TF-IDF (0.5), категории (1.0), числа (0.3). Результат L2-нормализуется.
import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import MultiLabelBinarizer, StandardScaler
from sklearn.metrics.pairwise import cosine_similarity
import torch
from sentence_transformers import SentenceTransformer

class ContentBasedRecommender:
    def __init__(self):
        self.text_model = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
        self.tfidf = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))
        self.mlb = MultiLabelBinarizer()
        self.scaler = StandardScaler()
        self.item_vectors = None
        self.item_ids = []

    def build_item_profiles(self, items_df: pd.DataFrame) -> np.ndarray:
        """
        items_df: item_id, title, description, category (list), tags (list),
                  price, rating, release_year, ...
        """
        feature_parts = []

        # 1. Семантические эмбеддинги текста (описание + название)
        texts = (
            items_df['title'].fillna('') + '. ' +
            items_df.get('description', pd.Series([''] * len(items_df))).fillna('')
        ).tolist()

        print(f"Encoding {len(texts)} items...")
        text_embeddings = self.text_model.encode(
            texts, batch_size=64, show_progress_bar=True,
            convert_to_numpy=True, normalize_embeddings=True
        )
        feature_parts.append(text_embeddings)

        # 2. TF-IDF признаки из текста
        tfidf_features = self.tfidf.fit_transform(texts).toarray()
        # PCA для сжатия
        from sklearn.decomposition import TruncatedSVD
        svd = TruncatedSVD(n_components=50)
        tfidf_compact = svd.fit_transform(tfidf_features)
        feature_parts.append(tfidf_compact)

        # 3. Категориальные признаки
        if 'categories' in items_df.columns:
            cat_features = self.mlb.fit_transform(
                items_df['categories'].apply(lambda x: x if isinstance(x, list) else [])
            )
            feature_parts.append(cat_features.astype(float))

        # 4. Числовые признаки
        num_cols = ['price', 'rating', 'review_count', 'release_year']
        available_num = [c for c in num_cols if c in items_df.columns]
        if available_num:
            num_features = self.scaler.fit_transform(
                items_df[available_num].fillna(items_df[available_num].median())
            )
            feature_parts.append(num_features)

        # Взвешенная конкатенация
        weights = [2.0, 0.5, 1.0, 0.3][:len(feature_parts)]
        weighted_parts = [p * w for p, w in zip(feature_parts, weights)]
        combined = np.hstack(weighted_parts)

        # L2 нормализация
        norms = np.linalg.norm(combined, axis=1, keepdims=True)
        self.item_vectors = combined / (norms + 1e-10)
        self.item_ids = items_df['item_id'].tolist()

        return self.item_vectors

    def build_user_profile(self, liked_items: list[str],
                            weights: list[float] = None) -> np.ndarray:
        """Профиль пользователя как взвешенное среднее понравившихся товаров"""
        item_indices = [
            self.item_ids.index(item_id)
            for item_id in liked_items
            if item_id in self.item_ids
        ]

        if not item_indices:
            return None

        liked_vectors = self.item_vectors[item_indices]

        if weights is not None and len(weights) == len(item_indices):
            w = np.array(weights[:len(item_indices)]).reshape(-1, 1)
            user_vector = np.average(liked_vectors, axis=0, weights=w.flatten())
        else:
            # Последние взаимодействия важнее
            recency_weights = np.exp(np.linspace(-1, 0, len(item_indices)))
            user_vector = np.average(liked_vectors, axis=0, weights=recency_weights)

        norm = np.linalg.norm(user_vector)
        return user_vector / (norm + 1e-10)

    def recommend(self, user_profile: np.ndarray,
                   exclude_items: list[str] = None,
                   n: int = 10,
                   diversity_penalty: float = 0.1) -> list[tuple]:
        """Рекомендации с MMR штрафом за похожесть"""
        if user_profile is None:
            return []

        # Базовые скоры
        scores = cosine_similarity(
            user_profile.reshape(1, -1), self.item_vectors
        )[0]

        # Исключение уже просмотренных
        if exclude_items:
            for item_id in exclude_items:
                if item_id in self.item_ids:
                    idx = self.item_ids.index(item_id)
                    scores[idx] = -1

        # MMR (Maximal Marginal Relevance) для разнообразия
        selected_indices = []
        selected_embeddings = []

        while len(selected_indices) < n:
            if not selected_embeddings:
                # Первый — самый релевантный
                best_idx = np.argmax(scores)
            else:
                # Следующие: релевантность минус штраф за схожесть с уже выбранными
                selected_matrix = np.vstack(selected_embeddings)
                similarity_to_selected = cosine_similarity(
                    self.item_vectors, selected_matrix
                ).max(axis=1)

                adjusted_scores = scores - diversity_penalty * similarity_to_selected
                # Маскировка уже выбранных
                for idx in selected_indices:
                    adjusted_scores[idx] = -1
                best_idx = np.argmax(adjusted_scores)

            if scores[best_idx] < 0:
                break

            selected_indices.append(best_idx)
            selected_embeddings.append(self.item_vectors[best_idx])

        return [
            (self.item_ids[idx], float(scores[idx]))
            for idx in selected_indices
        ]

    def item_to_item(self, item_id: str, n: int = 10) -> list[tuple]:
        """Похожие товары для страницы товара"""
        if item_id not in self.item_ids:
            return []

        item_idx = self.item_ids.index(item_id)
        item_vector = self.item_vectors[item_idx]

        scores = cosine_similarity(
            item_vector.reshape(1, -1), self.item_vectors
        )[0]
        scores[item_idx] = -1  # Исключаем сам товар

        top_indices = np.argsort(scores)[-n:][::-1]
        return [(self.item_ids[idx], float(scores[idx])) for idx in top_indices]

Обновление профиля в реальном времени

class OnlineUserProfileUpdater:
    """Инкрементальное обновление профиля без перестройки"""

    def __init__(self, recommender: ContentBasedRecommender):
        self.rec = recommender
        self.user_profiles = {}
        self.user_history = {}

    def update_on_interaction(self, user_id: str, item_id: str,
                               interaction_type: str):
        """Обновление профиля после взаимодействия"""
        weights = {
            'view': 1.0, 'click': 1.5, 'add_to_cart': 3.0,
            'purchase': 5.0, 'dislike': -2.0, 'skip': -0.5
        }
        weight = weights.get(interaction_type, 1.0)

        if user_id not in self.user_history:
            self.user_history[user_id] = []

        self.user_history[user_id].append({
            'item_id': item_id,
            'weight': weight,
            'interaction_type': interaction_type
        })

        # Пересчёт профиля (последние 50 взаимодействий)
        history = self.user_history[user_id][-50:]
        liked = [h['item_id'] for h in history if h['weight'] > 0]
        liked_weights = [h['weight'] for h in history if h['weight'] > 0]

        if liked:
            self.user_profiles[user_id] = self.rec.build_user_profile(
                liked, liked_weights
            )

    def get_recommendations(self, user_id: str, n: int = 10) -> list[tuple]:
        profile = self.user_profiles.get(user_id)
        if profile is None:
            return []
        exclude = [h['item_id'] for h in self.user_history.get(user_id, [])]
        return self.rec.recommend(profile, exclude_items=exclude, n=n)

Content-based с sentence-transformers даёт Precision@10 = 0.15–0.30 на новых пользователях (против 0.01–0.03 у популярных товаров). Строить профиль достаточно из 3–5 взаимодействий. Инференс: SentenceTransformer энкодинг 10K товаров — 5–10 минут на CPU, 30–60 секунд на GPU.

Метрика Content-Based (новые пользователи) Popular Baseline Улучшение
Precision@10 0.15–0.30 0.01–0.03 в 10–30 раз
Recall@10 0.10–0.25 0.005–0.01 в 10–25 раз
Coverage 80%+ 1–5% в 10–30 раз

Почему Content-Based Filtering выгоднее Collaborative Filtering для нового проекта?

Для платформ с малым числом пользователей или высокой ротацией контента Collaborative Filtering неэффективен из-за разреженности матрицы взаимодействий. Content-Based использует богатые метаданные и не страдает от холодного старта — выдача релевантна уже после первого клика. Например, в нишевом онлайн-кинотеатре с 5000 фильмов и 200 активными пользователями контентная фильтрация даёт coverage 80%+, тогда как collaborative filtering покрывает лишь 5% каталога. Подробнее о Collaborative Filtering. Средняя окупаемость системы — 6–12 месяцев за счёт снижения оттока.

Характеристика Content-Based Collaborative Filtering
Зависимость от других пользователей Нет Да
Холодный старт 3–5 действий Требует много данных
Разнообразие (coverage) 80%+ 5–20%
Serendipity Низкая Высокая
Вычислительная сложность O(N * F) O(пользователи * товары)

Что входит в работу

  • Документация по архитектуре, входным данным и API endpoints.
  • Исходный код пайплайна векторизации и рекомендательного модуля.
  • Доступы к обученным model weights и векторным индексам.
  • Обучение команды администрированию и обновлению модели.
  • Поддержка в течение первого месяца после внедрения (решение инцидентов, оптимизация).

Ориентировочные сроки и стоимость

Срок реализации зависит от объёма данных и сложности интеграции. Типичный проект занимает от 4 до 8 недель. Для точной оценки пришлите описание вашей предметной области и объём каталога — мы рассчитаем стоимость и сроки за один рабочий день. Свяжитесь с нами — мы проанализируем ваши данные и подберём оптимальную архитектуру.

Получите консультацию: мы покажем, как контентная фильтрация повлияет на вашу воронку. Система существенно поднимает вовлечённость на этапе онбординга, когда у пользователя ещё нет истории. А с нашей реализацией вы получаете гарантию качества: мы уже прошли все типовые грабли — от выбора эмбеддингов до калибровки штрафа за разнообразие. Более 50 проектов, 7+ лет опыта и сертифицированные инженеры — вот почему нам доверяют.

Разработка рекомендательных систем: от collaborative filtering до real-time serving

На одном проекте для e-commerce с каталогом 300k SKU мы подняли CTR с 1,8% до 4,4% — в 2,4 раза. Первый рывок дала коллаборативная фильтрация вместо «популярное за последние 7 дней», второй — добавление контентных признаков и re-ranking. Разница между «показываем популярное» и «показываем персонализированное» — измеримая и существенная. Ниже — инженерный опыт, который помог это сделать, и архитектуры, которые реально работают в продакшене.

Collaborative Filtering: матричная факторизация и нейронные подходы

Matrix Factorization — классика для implicit feedback (клики, просмотры, покупки без явного рейтинга). ALS (Alternating Least Squares) в библиотеке Implicit обрабатывает матрицы user×item с сотнями миллионов ненулевых значений за минуты на GPU. Latent factors 64–256, регуляризация λ=0.01–0.1 — стартовые параметры. Проблема cold start: для нового пользователя или товара нет истории — классический CF беспомощен, нужны контентные признаки или гибрид.

Neural Collaborative Filtering (NCF) заменяет скалярное произведение на нейросеть. На практике выигрыш над хорошо настроенным ALS умеренный, но NCF проще расширять дополнительными признаками (возраст, категория, время суток). Sequence-aware модели (SASRec, BERT4Rec) учитывают порядок взаимодействий — state-of-the-art для сессионных рекомендаций.

Как выбрать архитектуру рекомендательной системы?

Ответ зависит от данных, нагрузки и требований к холодному старту. Ниже — три основных подхода с критериями выбора.

Критерий Collaborative Filtering Content-Based Filtering Гибридный (two-stage)
Данные для старта История взаимодействий Признаки объектов и пользователей И то, и другое
Cold start Провальный Работает для новых items Частично решён
Diversity (long-tail) Низкий, popularity bias Высокий Средний–высокий
Latency serving <5 ms (precomputed) <10 ms (FAISS) 20–50 ms
Сложность внедрения Низкая Средняя Высокая

Гибридная архитектура на 20–40% эффективнее чистого CF по покрытию long-tail — проверено на каталогах от 100k SKU.

Content-Based Filtering: когда истории взаимодействий мало

Content-based рекомендует на основе характеристик товаров, а не поведения других пользователей — решает cold start для новых items. Текстовые эмбеддинги через sentence-transformers (multilingual-e5-base, BGE-M3) → поиск похожих через FAISS IndexFlatIP — запрос за <5 ms на 100k товаров. Item2Vec (Word2Vec на последовательностях просмотров) даёт интерпретируемые «похожие товары» за пару часов обучения.

Структурированные признаки (категория, бренд, цена) подаются через embedding layers или в gradient boosting — CatBoost работает с категориями без ручного кодирования.

Почему гибридные модели работают лучше?

Production-системы почти всегда двухуровневые. Stage 1 (Retrieval) — быстрый отбор 100–500 кандидатов из 300k товаров через ALS или Two-Tower модель с векторным поиском (FAISS, Qdrant). Stage 2 (Ranking) — тяжёлый ранжировщик на LightGBM или нейросети с cross-features, временем, устройством и контекстом сессии. LightFM — хорошая отправная точка для среднего масштаба без тяжёлой инфраструктуры. Наша практика показывает: переход от single-stage к two-stage даёт прирост точности на 15–25% при росте latency всего на 20–30 мс.

Real-Time Serving: архитектура под нагрузку

Latency SLA — 50–100 ms при тысячах запросов в секунду. Base-рекомендации precompute (batch job раз в час) → Redis по user_id → <5 ms. Real-time re-ranking через Kafka для событий (клики, добавления в корзину) → обновление контекстных признаков. Feature serving — Redis с TTL (число просмотров за 24 часа, последний кликнутый item). При нагрузке 10k req/s ставим Redis Cluster с репликацией.

A/B тестирование — единственный достоверный способ оценить улучшения. Офлайн-метрики коррелируют с онлайн не всегда. Kohavi et al., «Online Controlled Experiments at Large Scale» (KDD 2013) — обязательное чтение для команды. Тест с 5–10% трафика, мониторинг CTR, конверсии, revenue per session. Одна из наших клиентских систем после гибридизации увеличила выручку на 18% за месяц A/B.

Сроки разработки рекомендательной системы

Этапы и типичные временные затраты — в таблице ниже. Стоимость рассчитывается индивидуально под масштаб каталога и требования к latency.

Этап Длительность Результат
Аудит данных и baseline 1–2 недели Отчёт с плотностью матрицы, cold start‑зонами, метриками «популярного»
Прототип (offline validation) 2–3 недели Работающая модель с офлайн-метриками (Recall@k, NDCG)
Production-система (two-stage, A/B) 1.5–2.5 месяца Low-latency сервис с мониторингом и A/B-инфраструктурой
Обучение команды и документация 1–2 недели Model card, runbook по деплою, сессия по дообучению

Что входит в разработку под ключ

  1. Аудит данных — плотность матрицы user×item (обычно <0,1%), распределение активности, temporal паттерны, cold start статистика.
  2. Baseline — «популярное» как простой порог, который часто трудно обогнать.
  3. Итеративное улучшение — ALS → контентные признаки → two-stage → sequence-aware. Каждый шаг с A/B.
  4. Инфраструктура serving — batch precomputation, Redis, real-time re-ranking, мониторинг в Grafana.
  5. Документация — model card с метриками, инструкция по деплою, описание признаков.
  6. Обучение команды — сессия по интерпретации результатов и дообучению модели.
  7. Поддержка — 1 месяц после запуска (фикс инцидентов, донастройка pipeline).

Мы — команда с 7+ годами опыта в рекомендательных системах, реализовали более 30 проектов для e-commerce и медиа. Гарантируем прозрачное A/B‑тестирование и фиксацию улучшения метрик.

Хотите оценить потенциал роста вашего каталога? Свяжитесь с нами для бесплатного аудита данных. Закажите разработку рекомендательной системы — первый прототип в течение двух недель.

Пример конфига ALS для implicit feedback
from implicit.als import AlternatingLeastSquares

model = AlternatingLeastSquares(
    factors=64,
    regularization=0.05,
    iterations=15,
    use_gpu=True
)
model.fit(user_item_matrix)

Больше о математике рекомендательных систем — в Wikipedia.