AI-система підбору образу (Outfit Recommendation)

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
AI-система підбору образу (Outfit Recommendation)
Середній
~2-4 тижні
Часті запитання

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

Етапи розробки AI-рішення

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

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

AI-система рекомендації образів одягу

Рекомендація образів — задача більше складна, ніж рекомендація окремих товарів: потрібно підібрати сумісні речі з урахуванням стилю, кольорової сумісності, випадку і гардеробу користувача. Pinterest, Stitch Fix, ASOS використовують computer vision + knowledge graph для цієї задачі.

Модель сумісності предметів одягу

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.metrics.pairwise import cosine_similarity

class OutfitCompatibilityModel(nn.Module):
    """
    Siamese network: оцінює сумісність двох предметів гардеробу.
    Вхід: візуальний embedding (ResNet) + атрибутний вектор.
    """

    def __init__(self, visual_dim: int = 2048, attr_dim: int = 64,
                  hidden_dim: int = 256):
        super().__init__()
        input_dim = visual_dim + attr_dim

        self.item_encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(hidden_dim, 128),
            nn.LayerNorm(128)
        )

        self.compatibility_head = nn.Sequential(
            nn.Linear(256, 64),
            nn.ReLU(),
            nn.Linear(64, 1),
            nn.Sigmoid()
        )

    def encode_item(self, visual_emb: torch.Tensor,
                     attr_emb: torch.Tensor) -> torch.Tensor:
        combined = torch.cat([visual_emb, attr_emb], dim=-1)
        return self.item_encoder(combined)

    def forward(self, item1_visual: torch.Tensor, item1_attrs: torch.Tensor,
                item2_visual: torch.Tensor, item2_attrs: torch.Tensor) -> torch.Tensor:
        emb1 = self.encode_item(item1_visual, item1_attrs)
        emb2 = self.encode_item(item2_visual, item2_attrs)
        combined = torch.cat([emb1, emb2], dim=-1)
        return self.compatibility_head(combined)


class ColorCompatibilityChecker:
    """Кольорова сумісність за теорією кольору"""

    # Палітра сумісних комбінацій
    NEUTRAL_COLORS = {'white', 'black', 'grey', 'beige', 'navy'}

    COLOR_WHEEL = {
        'red': 0, 'orange': 30, 'yellow': 60, 'yellow_green': 90,
        'green': 120, 'teal': 150, 'blue': 180, 'purple': 270, 'pink': 330
    }

    def are_compatible(self, color1: str, color2: str) -> float:
        """Сумісність двох кольорів (0-1)"""
        # Нейтральні кольори сочетаються з усім
        if color1 in self.NEUTRAL_COLORS or color2 in self.NEUTRAL_COLORS:
            return 0.9

        # Однакові кольори — монохром (добре)
        if color1 == color2:
            return 0.85

        angle1 = self.COLOR_WHEEL.get(color1)
        angle2 = self.COLOR_WHEEL.get(color2)

        if angle1 is None or angle2 is None:
            return 0.5

        diff = abs(angle1 - angle2)
        diff = min(diff, 360 - diff)

        # Комплементарні (180°): висока сумісність
        if 160 <= diff <= 200:
            return 0.85
        # Аналогічні (30-60°): хороша сумісність
        if 30 <= diff <= 60:
            return 0.80
        # Триадні (120°): середня
        if 100 <= diff <= 140:
            return 0.65
        # Погана сумісність
        return 0.40


class OutfitBuilder:
    """Збірка образів з гардеробу користувача"""

    def __init__(self):
        self.color_checker = ColorCompatibilityChecker()

    def build_outfit(self, user_wardrobe: list[dict],
                      occasion: str = 'casual',
                      anchor_item: dict = None) -> list[dict]:
        """
        Підбір образу для конкретного випадку.
        anchor_item: якорний предмет (наприклад, тільки що куплений)
        """
        # Фільтруємо за випадком
        occasion_filter = {
            'casual': ['casual', 'smart_casual'],
            'work': ['business', 'smart_casual'],
            'formal': ['formal', 'business'],
            'sport': ['sport', 'activewear'],
        }
        valid_styles = occasion_filter.get(occasion, ['casual'])
        relevant_items = [
            item for item in user_wardrobe
            if item.get('style') in valid_styles
        ]

        if not relevant_items:
            return []

        # Стандартний образ: верх + низ + взуття + аксесуар
        categories = {'top': [], 'bottom': [], 'shoes': [], 'accessory': []}
        for item in relevant_items:
            cat = item.get('category', 'top')
            if cat in categories:
                categories[cat].append(item)

        outfit = []

        # Якщо є якорний елемент — починаємо з нього
        if anchor_item:
            outfit.append(anchor_item)
            anchor_cat = anchor_item.get('category', 'top')
            anchor_color = anchor_item.get('color', 'black')
            categories.pop(anchor_cat, None)
        else:
            anchor_color = 'black'

        # Добираємо решту частин, максимізуючи сумісність кольорів
        for cat in ['top', 'bottom', 'shoes', 'accessory']:
            items = categories.get(cat, [])
            if not items:
                continue

            best_item = max(items, key=lambda x:
                self.color_checker.are_compatible(anchor_color, x.get('color', 'black'))
            )
            outfit.append(best_item)

            # Оновлюємо якорний колір (берємо домінуючий)
            if best_item.get('color') not in self.color_checker.NEUTRAL_COLORS:
                anchor_color = best_item.get('color', anchor_color)

        return outfit

    def score_outfit(self, outfit: list[dict]) -> dict:
        """Оцінка образу"""
        if len(outfit) < 2:
            return {'score': 0, 'feedback': 'Недостатньо предметів'}

        colors = [item.get('color', 'black') for item in outfit]
        color_scores = []

        for i in range(len(colors)):
            for j in range(i+1, len(colors)):
                color_scores.append(self.color_checker.are_compatible(colors[i], colors[j]))

        avg_compatibility = np.mean(color_scores) if color_scores else 0.5

        # Перевірка категорій
        categories = [item.get('category') for item in outfit]
        has_complete_outfit = all(cat in categories for cat in ['top', 'bottom', 'shoes'])

        total_score = avg_compatibility * 0.6 + (0.4 if has_complete_outfit else 0)

        feedback = []
        if avg_compatibility < 0.55:
            feedback.append('Кольори можуть конфліктувати')
        if not has_complete_outfit:
            feedback.append('Образ неповний')
        if not feedback:
            feedback.append('Гармонійний образ')

        return {
            'score': round(total_score, 2),
            'color_compatibility': round(avg_compatibility, 2),
            'feedback': '; '.join(feedback)
        }

Системи рекомендації образів знижують повернення через "не знаю з чим носити" на 15-20%. Ключовий виклик: холодний старт гардеробу (потрібно мінімум 10-15 предметів для хороших рекомендацій) і суб'єктивність стилю. Рішення: явні переваги через onboarding-квіз.