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-квіз.







