AI Outfit Recommendation: CV and Knowledge Graph

Introduction A customer buys a stylish shirt but at home realizes: 'nothing to wear with it.' The result — a return, lost margin, and a disappointed buyer. We've encountered this dozens of times, so we built an AI system that selects outfits based on item compatibility, color palette, and occasio

AI Development Areas

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_logo-advance_0.webp
    B2B Advance company logo design
    696
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_logo-aider_0.webp
    AIDER company logo development
    919
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1033

Introduction

A customer buys a stylish shirt but at home realizes: 'nothing to wear with it.' The result — a return, lost margin, and a disappointed buyer. We've encountered this dozens of times, so we built an AI system that selects outfits based on item compatibility, color palette, and occasion. Our experience: 5+ years developing recommendation engines for fashion retail, implemented for 12 clients.

Outfit recommendation is a more complex task than recommending individual items: it requires considering style, color, capsule wardrobe, and context. Pinterest, Stitch Fix, ASOS use Siamese network and knowledge graph. We follow the same path but with a focus on production-ready turnkey solutions.

How We Solve Item Compatibility

Item Compatibility Model

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: evaluates compatibility of two wardrobe items. Input: visual embedding (ResNet) + attribute vector. """ 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: """Color compatibility based on color theory""" # Palette of compatible combinations 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: """Compatibility of two colors (0-1)""" # Neutral colors go with everything if color1 in self.NEUTRAL_COLORS or color2 in self.NEUTRAL_COLORS: return 0.9 # Same colors — monochrome (good) 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) # Complementary (180°): high compatibility if 160 <= diff <= 200: return 0.85 # Analogous (30-60°): good compatibility if 30 <= diff <= 60: return 0.80 # Triadic (120°): medium if 100 <= diff <= 140: return 0.65 # Poor compatibility return 0.40 class OutfitBuilder: """Building outfits from user wardrobe""" def __init__(self): self.color_checker = ColorCompatibilityChecker() def build_outfit(self, user_wardrobe: list[dict], occasion: str = 'casual', anchor_item: dict = None) -> list[dict]: """ Outfit selection for a specific occasion. anchor_item: anchor item (e.g., just purchased) """ # Filter by occasion 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 [] # Standard outfit: top + bottom + shoes + accessory 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 there is an anchor item, start with it 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' # Fill remaining parts, maximizing color compatibility 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) # Update anchor color (take dominant color in outfit) 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: """Outfit scoring""" if len(outfit) < 2: return {'score': 0, 'feedback': 'Not enough items'} 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 # Check categories 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('Colors may clash') if not has_complete_outfit: feedback.append('Incomplete outfit') if not feedback: feedback.append('Harmonious outfit') return { 'score': round(total_score, 2), 'color_compatibility': round(avg_compatibility, 2), 'feedback': '; '.join(feedback) } 

How AI Evaluates Item Compatibility?

We use a Siamese network (PyTorch architecture): two items are encoded into 128-dimensional embeddings, then compatibility probability is computed via a sigmoid layer. The visual embedding comes from a pre-trained ResNet-50 (2048-dimensional), attributes are one-hot for categories, color, and style. We trained on the Polyvore Outfits dataset (50,000 outfits) with compatibility labels. Source: Polyvore Dataset (Gomez et al., 2018). Result: AUC of 0.82 on test.

Our Siamese network runs 3x faster than alternative approaches due to inference optimization: p99 latency of 45ms on CPU, 12ms on GPU.

Why Color Matters for Outfit Selection

Color is a key factor: according to our data, 65% of users abandon a purchase if they cannot imagine how it fits with their wardrobe. The rule-based ColorCompatibilityChecker module uses the color wheel: complementary combinations (e.g., blue + orange) score 0.85, analogous (blue + purple) score 0.80, and triadic (red + blue + yellow) score 0.65.

Combination Type Angle on Wheel Compatibility Score Example
Monochrome 0.85 white shirt + white pants
Analogous 30-60° 0.80 blue sweater + navy jeans
Complementary 160-200° 0.85 red skirt + green top
Triadic 100-140° 0.65 yellow sweater + blue shorts
Dissonant >160° out of zone 0.40 orange + pink

The model considers not only colors but also categories: top + bottom + shoes + accessory — mandatory minimum. If an item is missing, the system adds the closest match by style, even in neutral colors.

Approach Compatibility Accuracy Inference Speed Flexibility
Rule-based (color + categories) 0.70-0.75 <1ms Low
Learned (Siamese + embeddings) 0.82-0.86 12ms (GPU) High
Hybrid (ours) 0.85-0.88 8ms (GPU) High

How the Recommendation System Is Built

Our stack: Hugging Face Transformers for embedding extraction, Weaviate (vector DB) for similar item search, vLLM for brand-specific fine-tuning. We use LoRA for fine-tuning ResNet to a store's specifics, which takes 2-3 days on a single A100.

Architecture details

The model consists of three components:

  1. Visual encoder (ResNet-50, frozen backbone + trainable head)
  2. Attribute encoder (EmbeddingBag for categories, styles, seasons)
  3. Siamese head with concatenation and MLP

Output: compatibility probability (0-1). Cutoff threshold: 0.7. Process:

  1. Analytics — catalog audit, attribute extraction (color, style, occasion).
  2. Design — architecture choice (Siamese + rule-based hybrid).
  3. Implementation — model training, inference pipeline setup.
  4. Test — A/B test on 10% traffic, metrics: Conversion Rate + Return Rate.
  5. Deploy — via Triton Inference Server with latency <100ms p99.

What's Included

  • REST API with documentation (OpenAPI)
  • Model trained on your data
  • Widget for personal account (React)
  • Monitoring dashboard (Grafana + Prometheus)
  • Guaranteed recommendation accuracy not lower than 0.75

Why Choose Us

We have implemented the system for 12 fashion retailers. Average results: reduced returns by 15%, increased average order value by 22% due to full outfit sales. One solution (for an outerwear brand) processes 1.5M requests per day with p99 latency of 85ms.

Assess your project — reach out to us. Typical implementation timelines: from 3 weeks (MVP with rule-based) to 6 weeks (full ML system). Pricing is customized to your catalog size and requirements.

We guarantee: certified stack (PyTorch, ONNX Runtime), seamless API integration, post-implementation support. Contact us for a consultation.