Разработка AI-системы размерных рекомендаций Size Recommendation

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы размерных рекомендаций Size Recommendation
Средняя
~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

AI-система рекомендации размеров одежды и обуви

Возврат из-за несоответствия размера — крупнейшая статья потерь в fashion e-commerce: 30-40% возвратов связаны именно с fit-проблемами. AI-система рекомендации размеров снижает этот показатель на 20-35%, повышая конверсию за счёт уверенности покупателя в выборе.

Архитектура size recommendation

Задача состоит из двух компонентов: нормализация размерных сеток брендов и персонализация на основе истории покупок. Разные бренды используют разные стандарты (EU, UK, US, IT), плюс внутри одного бренда размеры варьируются по категориям.

import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.preprocessing import LabelEncoder

class SizeNormalizer:
    """Нормализация размерных сеток к единому стандарту"""

    SIZE_CHARTS = {
        'EU': {'36': 'XS', '38': 'S', '40': 'M', '42': 'L', '44': 'XL', '46': 'XXL'},
        'UK': {'8': 'XS', '10': 'S', '12': 'M', '14': 'L', '16': 'XL', '18': 'XXL'},
        'US': {'0': 'XS', '2': 'S', '4': 'M', '6': 'L', '8': 'XL', '10': 'XXL'},
    }

    def normalize_to_standard(self, size: str, brand: str,
                               category: str, system: str = 'EU') -> dict:
        """Конвертация к стандартному размеру с диапазоном измерений (см)"""
        # Стандартные измерения для женских топов
        measurements = {
            'XS': {'chest': (80, 84), 'waist': (60, 64), 'hips': (86, 90)},
            'S':  {'chest': (84, 88), 'waist': (64, 68), 'hips': (90, 94)},
            'M':  {'chest': (88, 92), 'waist': (68, 72), 'hips': (94, 98)},
            'L':  {'chest': (92, 96), 'waist': (72, 76), 'hips': (98, 102)},
            'XL': {'chest': (96, 100), 'waist': (76, 80), 'hips': (102, 106)},
        }

        chart = self.SIZE_CHARTS.get(system, {})
        standard = chart.get(str(size), size)

        # Бренд-специфичная поправка из исторических данных возвратов
        brand_offset = self._get_brand_offset(brand, category)

        return {
            'original_size': size,
            'standard_label': standard,
            'measurements_cm': measurements.get(standard, {}),
            'brand_offset': brand_offset,
            'adjusted_label': self._apply_offset(standard, brand_offset)
        }

    def _get_brand_offset(self, brand: str, category: str) -> int:
        """
        Поправка из анализа возвратов: +1 = бренд маломерит (рекомендовать на размер больше),
        -1 = бренд большемерит
        """
        # Загружается из таблицы, обученной на возвратах
        brand_offsets = {
            'zara': {'tops': 1, 'pants': 0, 'dresses': 1},
            'h&m': {'tops': 0, 'pants': 1, 'dresses': 0},
            'mango': {'tops': 0, 'pants': 0, 'dresses': -1},
        }
        return brand_offsets.get(brand, {}).get(category, 0)

    def _apply_offset(self, size: str, offset: int) -> str:
        order = ['XS', 'S', 'M', 'L', 'XL', 'XXL']
        if size not in order:
            return size
        idx = max(0, min(len(order) - 1, order.index(size) + offset))
        return order[idx]


class PersonalizedSizeRecommender:
    """Персонализация на основе истории покупок и возвратов"""

    def __init__(self):
        self.model = GradientBoostingClassifier(
            n_estimators=150, learning_rate=0.05, max_depth=4, random_state=42
        )
        self.label_encoder = LabelEncoder()

    def build_user_profile(self, purchase_history: pd.DataFrame,
                            user_id: str) -> dict:
        """Профиль пользователя из истории покупок"""
        user_purchases = purchase_history[
            (purchase_history['user_id'] == user_id) &
            (purchase_history['returned'] == False)
        ]

        if user_purchases.empty:
            return {}

        # Какие размеры оставил (не вернул) по категориям
        kept_sizes = user_purchases.groupby(['category', 'brand'])['size_eu'].agg(
            lambda x: x.mode().iloc[0] if len(x) > 0 else None
        ).to_dict()

        # Количество возвратов по размерным причинам
        all_purchases = purchase_history[purchase_history['user_id'] == user_id]
        size_returns = all_purchases[
            all_purchases['return_reason'].isin(['too_small', 'too_large'])
        ]

        return_pattern = 'neutral'
        if len(size_returns) > 0:
            too_small = (size_returns['return_reason'] == 'too_small').sum()
            too_large = (size_returns['return_reason'] == 'too_large').sum()
            if too_small > too_large * 1.5:
                return_pattern = 'tends_small'  # Обычно берёт маленький размер
            elif too_large > too_small * 1.5:
                return_pattern = 'tends_large'

        return {
            'user_id': user_id,
            'kept_sizes': kept_sizes,
            'return_pattern': return_pattern,
            'total_purchases': len(user_purchases),
            'return_rate': len(size_returns) / max(len(all_purchases), 1)
        }

    def recommend_size(self, user_profile: dict, product: dict,
                        normalizer: SizeNormalizer) -> dict:
        """Рекомендация размера с объяснением"""
        category = product.get('category', 'tops')
        brand = product.get('brand', '')

        # Базовый размер из профиля
        kept_sizes = user_profile.get('kept_sizes', {})

        # Ищем: точное совпадение бренд+категория → только категория → любой
        base_size = (
            kept_sizes.get((category, brand)) or
            next((v for (cat, _), v in kept_sizes.items() if cat == category), None) or
            next(iter(kept_sizes.values()), None)
        )

        if not base_size:
            return {'recommended_size': None, 'confidence': 0.0,
                    'reason': 'Недостаточно данных о покупателе'}

        # Нормализация + бренд-поправка
        normalized = normalizer.normalize_to_standard(base_size, brand, category)
        recommended = normalized['adjusted_label']

        # Поправка на паттерн возвратов
        return_pattern = user_profile.get('return_pattern', 'neutral')
        if return_pattern == 'tends_small':
            recommended = normalizer._apply_offset(recommended, 1)
        elif return_pattern == 'tends_large':
            recommended = normalizer._apply_offset(recommended, -1)

        # Уверенность: больше покупок → выше уверенность
        purchases_count = user_profile.get('total_purchases', 0)
        confidence = min(0.95, 0.5 + purchases_count * 0.05)

        # Причина для UI
        reasons = []
        if normalized['brand_offset'] != 0:
            direction = 'маломерит' if normalized['brand_offset'] > 0 else 'большемерит'
            reasons.append(f'{brand} {direction} в категории {category}')
        if return_pattern != 'neutral':
            reasons.append(f'На основе ваших предыдущих возвратов')

        return {
            'recommended_size': recommended,
            'size_range': normalized.get('measurements_cm', {}),
            'confidence': round(confidence, 2),
            'brand_adjusted': normalized['brand_offset'] != 0,
            'reason': '; '.join(reasons) if reasons else 'На основе вашей истории покупок',
            'also_consider': normalizer._apply_offset(recommended, 1)  # Соседний размер
        }

Интеграция с продуктовой карточкой

На карточке товара система отображает персонализированную рекомендацию с уровнем уверенности. При отсутствии истории покупок — fallback на общую статистику по размерам для данного бренда.

class SizeRecommendationService:
    def get_recommendation(self, user_id: str, product_id: str,
                            db) -> dict:
        product = db.get_product(product_id)
        purchase_history = db.get_user_purchases(user_id, limit=50)

        normalizer = SizeNormalizer()
        recommender = PersonalizedSizeRecommender()

        user_profile = recommender.build_user_profile(purchase_history, user_id)

        if not user_profile:
            # Cold-start: статистика по бренду
            popular_sizes = db.get_brand_size_distribution(
                product['brand'], product['category']
            )
            return {
                'source': 'brand_statistics',
                'popular_size': popular_sizes.get('mode'),
                'distribution': popular_sizes.get('distribution'),
                'confidence': 0.4
            }

        recommendation = recommender.recommend_size(user_profile, product, normalizer)
        recommendation['source'] = 'personalized'
        return recommendation

Метрики системы

Метрика До системы После системы
Возвраты по размеру 28% 18%
Конверсия на карточке 3.2% 4.1%
Confidence > 0.7 у % пользователей 65%
Coverage (есть история) 72% пользователей

Система обучается постоянно: каждый возврат с причиной «не подошёл размер» уточняет бренд-поправку. Минимальная история для персонализации: 3 завершённые покупки без возврата. При горизонте 6 месяцев эксплуатации coverage достигает 80%+ активной базы.