Разработка AI-системы корзинного анализа Market Basket Analysis

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы корзинного анализа Market Basket Analysis
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-система анализа рыночных корзин

Market basket analysis — классика ритейл-аналитики, но современные ML-подходы выходят далеко за пределы Apriori и association rules. Нейросетевые модели улавливают нелинейные зависимости, сезонные паттерны и контекст покупки, недоступные правиловым алгоритмам.

Классический и нейросетевой подходы

import pandas as pd
import numpy as np
from mlxtend.frequent_patterns import fpgrowth, association_rules
from mlxtend.preprocessing import TransactionEncoder
import torch
import torch.nn as nn

class BasketAnalyzer:
    """Классический FP-Growth + нейросетевое расширение"""

    def __init__(self, min_support: float = 0.01, min_confidence: float = 0.3):
        self.min_support = min_support
        self.min_confidence = min_confidence
        self.rules = None
        self.te = TransactionEncoder()

    def fit(self, transactions: list[list[str]]) -> pd.DataFrame:
        """
        transactions: [['молоко', 'хлеб', 'масло'], ['молоко', 'яйца'], ...]
        """
        te_array = self.te.fit_transform(transactions)
        df = pd.DataFrame(te_array, columns=self.te.columns_)

        # FP-Growth быстрее Apriori на 10-100x для больших датасетов
        frequent_itemsets = fpgrowth(df, min_support=self.min_support, use_colnames=True)

        self.rules = association_rules(
            frequent_itemsets,
            metric='confidence',
            min_threshold=self.min_confidence
        )

        # Добавляем lift и conviction для фильтрации
        self.rules = self.rules[self.rules['lift'] > 1.2]
        self.rules = self.rules.sort_values('lift', ascending=False)

        return self.rules

    def get_recommendations(self, basket: list[str],
                              top_k: int = 5) -> list[dict]:
        """Рекомендации для текущей корзины"""
        if self.rules is None:
            return []

        basket_set = frozenset(basket)
        matching_rules = self.rules[
            self.rules['antecedents'].apply(lambda x: x.issubset(basket_set))
        ]

        # Убираем товары, уже в корзине
        recommendations = []
        seen = set()
        for _, rule in matching_rules.iterrows():
            for item in rule['consequents']:
                if item not in basket_set and item not in seen:
                    recommendations.append({
                        'item': item,
                        'confidence': rule['confidence'],
                        'lift': rule['lift'],
                        'support': rule['support']
                    })
                    seen.add(item)
                    if len(recommendations) >= top_k:
                        break
            if len(recommendations) >= top_k:
                break

        return recommendations

    def get_category_affinity(self, transactions: pd.DataFrame) -> pd.DataFrame:
        """Матрица аффинности между категориями товаров"""
        # Транзакции с категориями вместо конкретных товаров
        cat_transactions = transactions.groupby('order_id')['category'].apply(list).tolist()
        te_cat = TransactionEncoder()
        cat_array = te_cat.fit_transform(cat_transactions)
        cat_df = pd.DataFrame(cat_array, columns=te_cat.columns_)

        # Совместная встречаемость категорий
        co_occurrence = cat_df.T.dot(cat_df)
        np.fill_diagonal(co_occurrence.values, 0)

        # Нормализуем по поддержке (PMI-подобная мера)
        totals = cat_df.sum()
        n = len(cat_df)
        affinity = co_occurrence / n / (totals.values[:, None] * totals.values[None, :] / n**2 + 1e-9)

        return affinity


class NeuralBasketPredictor(nn.Module):
    """
    Нейросетевая модель для предсказания следующего товара в корзину.
    Входной вектор: bag-of-items бинарный вектор текущей корзины.
    """

    def __init__(self, n_items: int, embedding_dim: int = 64):
        super().__init__()
        self.item_embedding = nn.Embedding(n_items, embedding_dim, padding_idx=0)
        self.attention = nn.MultiheadAttention(embedding_dim, num_heads=4, batch_first=True)
        self.fc = nn.Sequential(
            nn.Linear(embedding_dim, 256),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(256, n_items)
        )

    def forward(self, basket_item_ids: torch.Tensor) -> torch.Tensor:
        """
        basket_item_ids: (batch, seq_len) — индексы товаров в корзине
        Returns: (batch, n_items) — logits для каждого товара
        """
        emb = self.item_embedding(basket_item_ids)  # (batch, seq, dim)
        attended, _ = self.attention(emb, emb, emb)  # Self-attention по корзине
        pooled = attended.mean(dim=1)  # (batch, dim)
        return self.fc(pooled)

Временные паттерны и сезонность

class TemporalBasketAnalyzer:
    """Анализ временных паттернов покупок"""

    def find_sequential_patterns(self, orders: pd.DataFrame,
                                   days_window: int = 7) -> pd.DataFrame:
        """Что покупают через N дней после покупки X"""
        orders_sorted = orders.sort_values(['user_id', 'order_date'])

        sequential = []
        for user_id, user_orders in orders_sorted.groupby('user_id'):
            order_list = user_orders.to_dict('records')
            for i, order_i in enumerate(order_list):
                for order_j in order_list[i+1:]:
                    days_diff = (order_j['order_date'] - order_i['order_date']).days
                    if days_diff > days_window:
                        break
                    if days_diff > 0:
                        sequential.append({
                            'item_a': order_i['sku'],
                            'item_b': order_j['sku'],
                            'days_between': days_diff
                        })

        df = pd.DataFrame(sequential)
        if df.empty:
            return df

        # Топ последовательных пар
        return (df.groupby(['item_a', 'item_b'])
                  .agg(count=('days_between', 'count'),
                       avg_days=('days_between', 'mean'))
                  .reset_index()
                  .sort_values('count', ascending=False))

    def get_seasonal_baskets(self, transactions: pd.DataFrame) -> dict:
        """Сезонные корзины: что обычно покупают вместе в конкретный период"""
        transactions['month'] = transactions['order_date'].dt.month
        transactions['season'] = transactions['month'].map({
            12: 'winter', 1: 'winter', 2: 'winter',
            3: 'spring', 4: 'spring', 5: 'spring',
            6: 'summer', 7: 'summer', 8: 'summer',
            9: 'autumn', 10: 'autumn', 11: 'autumn'
        })

        seasonal_rules = {}
        for season, group in transactions.groupby('season'):
            season_transactions = group.groupby('order_id')['sku'].apply(list).tolist()
            if len(season_transactions) < 100:
                continue

            te = TransactionEncoder()
            arr = te.fit_transform(season_transactions)
            df_season = pd.DataFrame(arr, columns=te.columns_)
            freq = fpgrowth(df_season, min_support=0.02, use_colnames=True)

            if not freq.empty:
                rules = association_rules(freq, metric='lift', min_threshold=1.5)
                seasonal_rules[season] = rules.sort_values('lift', ascending=False).head(20)

        return seasonal_rules

Применение в интерфейсах

Точка применения Источник данных Метрика успеха
Корзина ("Также берут с этим") Текущая корзина + rules CTR рекомендации
PDP ("Часто покупают вместе") Исторические транзакции Add-to-cart rate
Checkout ("Вы забыли") Корзина + sequential Attachment rate
Email after purchase История + sequential Repeat purchase
Bundle formation Category affinity Bundle take rate

FP-Growth на датасете 1M транзакций с 10k SKU при support=0.01: обработка за 3-8 минут на single CPU. Нейросетевая модель требует GPU, но даёт +15-20% к Recall@10 против чистых association rules на разреженных данных. Гибридный подход: rules для высокоподдерживаемых пар, нейросеть для long-tail.