Розробка AI-системи рекомендацій Cross-Sell для продажів

Статичні правила не бачать прихованих закономірностей у покупках, через що втрачаються неочевидні cross-sell-зв'язки. Ми розробляємо AI-системи рекомендацій на основі машинного навчання, які знаходять такі патерни та персоналізують пропозиції для кожного клієнта. Наша команда реалізує проєкт під ключ — від аудиту даних до впровадження та подальшої підтримки.
Часті запитання

Реалізація AI-системи рекомендацій Cross-Sell для продажів

До нас звернувся e-commerce-проєкт із 50 000 SKU: правило «до принтера — картридж» давало лише 8% конверсії. Потрібна була рекомендаційна система на основі машинного навчання, яка знаходить неочевидні пари та адаптується під поведінку кожного клієнта. Ми реалізували гібридну систему для e-commerce та ритейлу: асоціативні правила для базових зв’язок + градієнтний бустинг для персоналізації. Результат — зростання середнього чека на 18% за перший місяць, що забезпечило значний приріст виручки. Розповідаю, як це працює.

Проблеми, які вирішуємо

Статичні правила не бачать прихованих закономірностей. Наприклад, клієнти, які купили дитяче автокрісло, з імовірністю 22% придбають органайзер для багажника — хоча товари з різних категорій. ML знаходить такі патерни через асоціативні правила (Lift > 2.0). А персоналізація через градієнтний бустинг враховує історію клієнта: якщо користувач уже брав органайзер, рекомендація зміщується на інший товар.

Інша типова проблема — товари з коротким циклом споживання (розхідники, підписки). Тут потрібне передбачення наступної покупки. Модель на основі розподілу інтервалів видає товари, які клієнт ось-ось замовить знову. Так ми збільшуємо повторні продажі без промоакцій.

Третя проблема — холодний старт для нових товарів. Якщо товар не має історії покупок, правила Apriori не спрацюють. Рішення — використовувати content-based ознаки (категорія, ціна, бренд) та колаборативну фільтрацію через ембеддінги. Для цього ми збагачуємо модель градієнтного бустингу фічами товару.

Як ми будуємо модель CrossSellRecommender

Market Basket Analysis + персоналізація

Гібридна архітектура: спочатку Apriori дає сирі правила, потім градієнтний бустинг ранжує кандидатів під конкретного користувача. Це поєднання забезпечує lift конверсії в 3 рази вищий, ніж rule-based підхід.

import pandas as pd
import numpy as np
from mlxtend.frequent_patterns import apriori, association_rules
from mlxtend.preprocessing import TransactionEncoder
from sklearn.ensemble import GradientBoostingClassifier

class CrossSellRecommender:
    def __init__(self, min_support: float = 0.01, min_confidence: float = 0.1):
        self.min_support = min_support
        self.min_confidence = min_confidence
        self.rules = None
        self.cross_sell_map = {}
        self.personalization_model = None

    def fit_association_rules(self, orders_df: pd.DataFrame, order_col: str = "order_id", item_col: str = "item_id"):
        """Пошук асоціативних правил через Apriori"""
        # Кошики транзакцій
        baskets = orders_df.groupby(order_col)[item_col].apply(list).tolist()
        te = TransactionEncoder()
        te_array = te.fit_transform(baskets)
        df_encoded = pd.DataFrame(te_array, columns=te.columns_)

        # Часті набори
        frequent_itemsets = apriori(
            df_encoded,
            min_support=self.min_support,
            use_colnames=True,
            max_len=3
        )

        # Правила асоціації
        self.rules = association_rules(
            frequent_itemsets,
            metric="lift",
            min_threshold=1.2
        )
        self.rules = self.rules[self.rules['confidence'] >= self.min_confidence]
        self.rules = self.rules.sort_values('lift', ascending=False)

        # Маппінг: item → список рекомендацій з метриками
        for _, rule in self.rules.iterrows():
            for antecedent in rule['antecedents']:
                if antecedent not in self.cross_sell_map:
                    self.cross_sell_map[antecedent] = []
                for consequent in rule['consequents']:
                    if antecedent != consequent:
                        self.cross_sell_map[antecedent].append({
                            'item_id': consequent,
                            'confidence': rule['confidence'],
                            'lift': rule['lift'],
                            'support': rule['support']
                        })

        # Сортування за lift
        for item in self.cross_sell_map:
            self.cross_sell_map[item].sort(key=lambda x: x['lift'], reverse=True)

    def recommend_cross_sell(self, cart_items: list[str], user_history: list[str] = None, n: int = 5) -> list[dict]:
        """Cross-sell для поточного кошика"""
        candidates = {}
        for item_id in cart_items:
            related = self.cross_sell_map.get(item_id, [])
            for rec in related:
                rec_id = rec['item_id']
                # Пропускаємо вже в кошику або в історії
                if rec_id in cart_items:
                    continue
                if user_history and rec_id in user_history:
                    continue
                if rec_id not in candidates:
                    candidates[rec_id] = {'score': 0, 'triggers': []}
                candidates[rec_id]['score'] += rec['lift']
                candidates[rec_id]['triggers'].append(item_id)

        # Нормалізація
        if not candidates:
            return []
        sorted_candidates = sorted(
            [{'item_id': k, **v} for k, v in candidates.items()],
            key=lambda x: x['score'],
            reverse=True
        )
        return sorted_candidates[:n]

    def get_complementary_categories(self, category: str) -> list[str]:
        """Взаємодоповнюючі категорії"""
        category_rules = self.rules[
            self.rules['antecedents'].apply(lambda x: category in str(x))
        ]['consequents'].apply(lambda x: list(x)).explode().value_counts()
        return category_rules.head(5).index.tolist()

Часові патерни: наступне замовлення

class NextPurchasePredictor:
    """Передбачення наступної покупки на основі історії"""

    def predict_next_items(self, user_id: str, purchase_history: list[dict], catalog_features: pd.DataFrame) -> list[tuple]:
        """
        purchase_history: [{item_id, date, quantity, category}]
        Повертає: [(item_id, probability)]
        """
        if len(purchase_history) < 3:
            return []

        # Патерн повторних покупок
        item_intervals = {}
        for i in range(1, len(purchase_history)):
            item = purchase_history[i]['item_id']
            prev_same = [h for h in purchase_history[:i] if h['item_id'] == item]
            if prev_same:
                days_between = (
                    pd.to_datetime(purchase_history[i]['date']) - pd.to_datetime(prev_same[-1]['date'])
                ).days
                if item not in item_intervals:
                    item_intervals[item] = []
                item_intervals[item].append(days_between)

        # Передбачення повторних покупок
        predictions = []
        last_purchase_date = pd.to_datetime(purchase_history[-1]['date'])
        today = pd.Timestamp.now()
        days_since_last = (today - last_purchase_date).days

        for item_id, intervals in item_intervals.items():
            avg_interval = np.mean(intervals)
            std_interval = np.std(intervals) if len(intervals) > 1 else avg_interval * 0.3

            # Ймовірність через normal distribution
            from scipy.stats import norm
            prob = norm.cdf(days_since_last + 7, avg_interval, std_interval + 1)
            prob -= norm.cdf(days_since_last - 7, avg_interval, std_interval + 1)
            prob = min(max(prob, 0), 1)

            if prob > 0.1:
                predictions.append((item_id, prob))

        return sorted(predictions, key=lambda x: x[1], reverse=True)[:10]
Докладніше про налаштування Apriori

Association rules з min_support=0.01, min_confidence=0.1 зазвичай дають 500-5000 значущих правил на 100K замовлень. Lift > 2.0 — сильна асоціація. Для підбору гіперпараметрів використовуємо крос-валідацію: розбиваємо дані за часом (train/test) і обираємо min_support, min_confidence за максимальним lift на валідаційній вибірці.

Як навчити модель CrossSellRecommender на своїх даних

  1. Підготовка даних. Зберіть історію замовлень з колонками: order_id, item_id, date. Мінімальна кількість транзакцій — 10 000.
  2. Запустіть Apriori. Використовуйте метод fit_association_rules з min_support=0.01 та min_confidence=0.1. Це дасть ~1000 правил.
  3. Налаштуйте параметри. Перевірте розподіл lift і confidence. Якщо правил забагато, збільште min_support до 0.02 або min_confidence до 0.2.
  4. Оцініть якість. Використовуйте метрики precision@k та recall@k на відкладеній вибірці. Cross-sell через правила дає середній uplift кошика 15-25%. Комбінація з персоналізацією (історія користувача) додає ще 5-10% до acceptance rate.

Як виміряти якість рекомендацій?

Основні offline-метрики: precision@k, recall@k, lift@k. Ми використовуємо усічений кошик — приховуємо частину товарів з кожної транзакції та перевіряємо, чи потрапляють вони в рекомендації. Онлайн-метрики — acceptance rate, AOV uplift, частка кошиків з cross-sell. На A/B-тесті з 50% аудиторії типовий uplift acceptance rate становить 10-25%.

Чому гібридна модель ефективніша за rule-based?

Rule-based (наприклад, "купив А — покажи Б") не масштабується на тисячі товарів: правила стають розрідженими і не враховують контекст. ML знаходить асоціації з високим lift навіть для рідкісних пар. Крім того, гібрид персоналізує ранжування: один і той самий товар ранжується по-різному для різних користувачів. У наших проєктах acceptance rate гібрида в 3 рази вищий, ніж у правил.

Етапи впровадження

Етап Що робимо Результат Тривалість
1. Аудит даних Перевіряємо повноту та якість: кількість SKU, довжину історії, частоту замовлень Дашборд метрик, список пропусків 1-2 дні
2. Побудова пайплайну ETL для збору даних з CRM, очищення, агрегація кошиків Датасет orders_df 3-4 дні
3. Навчання правил Apriori, підбір min_support та min_confidence через крос-валідацію Файл правил (.pkl) 1-2 дні
4. Персоналізація Навчаємо градієнтний бустинг на історії користувачів Ранжуюча модель 3-5 днів
5. A/B-тест 50% аудиторії — стара система, 50% — нова. Рахуємо конверсію та AOV Звіт з uplift 7-14 днів
6. Продакшн-деплой Інтеграція через REST API, моніторинг latency p99 Документація, дашборд 2-3 дні

Що входить в результат

  • Документація: опис правил, метрик, інструкція для оновлення моделі.
  • Вихідний код: репозиторій з модулями CrossSellRecommender, NextPurchasePredictor, конфіги та тести.
  • API-ендпоінти: два методи — recommend_by_cart (за поточним кошиком) та recommend_by_user (за історією).
  • Навчання команди: показуємо, як інтерпретувати результати та донавчати модель.
  • Гарантія: ми супроводжуємо A/B-тест до досягнення статистично значущого uplift.

Як оцінити ROI для вашого бізнесу

Метрика Типове значення Як розраховуємо
Приріст середнього чека 15-25% (AOV_тест - AOV_контроль) / AOV_контроль * 100%
Acceptance rate 5-12% Кліки за рекомендаціями / покази
Частка кошиків з cross-sell 30-50% Кошики з ≥1 рекомендованим товаром / всього кошиків
Час окупності 2-4 місяці Вартість впровадження / щомісячний приріст виручки

Щоб оцінити потенціал cross-sell для вашого асортименту, зв'яжіться з нами. Ми проведемо аудит даних і надамо прототип за 2 тижні з гарантією конфіденційності. Замовте консультацію — обговоримо деталі вашого проєкту та підготуємо індивідуальну пропозицію.

Напрямки AI-розробки

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1543
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1374
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    816
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1142