Разработка AI-системы персонализации поискового ранжирования в e-commerce

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы персонализации поискового ранжирования в e-commerce
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1240
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1167
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    867
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1084
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    829

Персонализация ранжирования поиска в e-commerce

Поисковый движок без персонализации показывает одинаковые результаты всем пользователям. ML-ранжирование учитывает историю просмотров, покупок, возвратов и контекст сессии — и переставляет выдачу индивидуально. Выигрыш: +8-15% к конверсии из поиска.

Learning-to-Rank архитектура

import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
import lightgbm as lgb

class SearchPersonalizationEngine:
    """
    LambdaMART (LightGBM ranker) для персонализированного поиска.
    Обучается на implicit feedback: клики, покупки, время просмотра.
    """

    def __init__(self):
        self.ranker = lgb.LGBMRanker(
            objective='lambdarank',
            n_estimators=300,
            learning_rate=0.05,
            num_leaves=63,
            min_child_samples=20,
            random_state=42
        )
        self.feature_names = []

    def build_features(self, query: str, products: pd.DataFrame,
                        user_history: dict, session_context: dict) -> pd.DataFrame:
        """Формирование feature-вектора для пары (query, product)"""
        features = []

        for _, product in products.iterrows():
            feat = {}

            # === Relevance features ===
            # BM25-подобный скор от поискового движка (Elasticsearch/OpenSearch)
            feat['bm25_score'] = product.get('search_score', 0)
            feat['title_match'] = int(all(
                word.lower() in product.get('title', '').lower()
                for word in query.split()
            ))
            feat['exact_match'] = int(query.lower() == product.get('title', '').lower())

            # === Product quality features ===
            feat['rating'] = product.get('rating', 3.0)
            feat['reviews_count'] = np.log1p(product.get('reviews_count', 0))
            feat['in_stock'] = int(product.get('in_stock', True))
            feat['days_since_added'] = product.get('days_since_added', 365)
            feat['photo_count'] = min(product.get('photo_count', 1), 10)

            # === Business features ===
            feat['margin_score'] = product.get('margin_percentile', 0.5)
            feat['is_promoted'] = int(product.get('is_promoted', False))
            feat['sales_velocity_7d'] = np.log1p(product.get('sales_7d', 0))

            # === Personalization features ===
            sku = product.get('sku', '')
            category = product.get('category', '')
            brand = product.get('brand', '')

            # Смотрел ли пользователь этот товар/категорию/бренд
            feat['user_viewed_sku'] = int(sku in user_history.get('viewed_skus', set()))
            feat['user_viewed_category'] = int(category in user_history.get('viewed_categories', set()))
            feat['user_purchased_brand'] = int(brand in user_history.get('purchased_brands', set()))
            feat['user_purchase_count_category'] = user_history.get('category_purchase_counts', {}).get(category, 0)

            # CTR пользователя в этой категории (personal CTR)
            feat['user_category_ctr'] = user_history.get('category_ctrs', {}).get(category, 0.05)

            # Ценовой диапазон пользователя
            user_avg_price = user_history.get('avg_order_value', 0)
            product_price = product.get('price', 0)
            if user_avg_price > 0:
                feat['price_ratio'] = product_price / user_avg_price
            else:
                feat['price_ratio'] = 1.0

            # === Session context ===
            feat['session_query_count'] = session_context.get('query_count', 1)
            feat['session_has_cart'] = int(session_context.get('has_cart', False))
            feat['device_mobile'] = int(session_context.get('device', 'desktop') == 'mobile')
            feat['hour_of_day'] = session_context.get('hour', 12)

            feat['sku'] = sku
            features.append(feat)

        df = pd.DataFrame(features)
        self.feature_names = [c for c in df.columns if c != 'sku']
        return df

    def train(self, training_data: pd.DataFrame):
        """
        training_data: query_id, sku, features..., relevance_label
        relevance_label: 0=impression, 1=click, 2=add_to_cart, 3=purchase
        """
        feature_cols = [c for c in training_data.columns
                        if c not in ['query_id', 'sku', 'relevance_label']]

        X = training_data[feature_cols]
        y = training_data['relevance_label']
        groups = training_data.groupby('query_id').size().values

        self.ranker.fit(X, y, group=groups)

    def rank(self, query: str, products: pd.DataFrame,
              user_history: dict, session_context: dict) -> pd.DataFrame:
        """Персонализированное ранжирование результатов поиска"""
        features_df = self.build_features(query, products, user_history, session_context)
        X = features_df[self.feature_names]

        scores = self.ranker.predict(X)
        products = products.copy()
        products['rank_score'] = scores
        return products.sort_values('rank_score', ascending=False)

Query Understanding и расширение

from anthropic import Anthropic

class QueryUnderstandingLayer:
    """Обработка поисковых запросов: исправление, расширение, интент"""

    def __init__(self):
        self.llm = Anthropic()

    def parse_query(self, raw_query: str, catalog_categories: list[str]) -> dict:
        """Структурированный разбор запроса"""
        response = self.llm.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=200,
            messages=[{
                "role": "user",
                "content": f"""Parse this e-commerce search query and return JSON.

Query: "{raw_query}"
Available categories: {catalog_categories[:20]}

Return JSON:
{{
  "corrected_query": "...",
  "intent": "informational|navigational|transactional",
  "extracted_brand": "...",
  "extracted_category": "...",
  "price_filter": {{"min": null, "max": null}},
  "color": null,
  "size": null,
  "synonyms": ["...", "..."]
}}"""
            }]
        )

        import json
        try:
            return json.loads(response.content[0].text)
        except Exception:
            return {'corrected_query': raw_query, 'intent': 'transactional', 'synonyms': []}

    def detect_seasonal_intent(self, query: str, current_month: int) -> float:
        """Сезонный буст для релевантных товаров"""
        seasonal_keywords = {
            'winter': [12, 1, 2],
            'summer': [6, 7, 8],
            'spring': [3, 4, 5],
            'autumn': [9, 10, 11]
        }
        query_lower = query.lower()
        for season, months in seasonal_keywords.items():
            if season in query_lower and current_month in months:
                return 1.2  # 20% буст для сезонных запросов
        return 1.0

Онлайн A/B тестирование ранжирования

class SearchRankingExperiment:
    """A/B/n тесты для алгоритмов ранжирования"""

    def __init__(self, variants: dict):
        """variants: {'control': ranker_v1, 'treatment': ranker_v2}"""
        self.variants = variants

    def assign_user(self, user_id: str) -> str:
        """Детерминированное назначение варианта"""
        bucket = hash(user_id) % 100
        if bucket < 50:
            return 'control'
        return 'treatment'

    def track_metrics(self, search_logs: pd.DataFrame) -> pd.DataFrame:
        """Метрики по вариантам"""
        return search_logs.groupby('variant').agg(
            ctr=('clicked', 'mean'),
            conversion_rate=('purchased', 'mean'),
            avg_position_clicked=('click_position', 'mean'),
            ndcg_at_5=('ndcg_5', 'mean'),
            revenue_per_search=('revenue', 'mean')
        ).round(4)

Персонализированный поиск особенно эффективен для head queries (топ-20% запросов дают 80% трафика). Для tail queries (редкие запросы) — semantic search через векторный индекс важнее персонализации. Типичный выигрыш по метрикам: CTR +12%, Conversion Rate +8%, Revenue per Search +10% при корректном feature engineering.