AI Outfit Recommendation: CV and Knowledge Graph

Online clothing purchases often end in disappointment: an item has nothing to wear with, and it gets returned. We develop an AI outfit matching system that analyzes wardrobe compatibility, color schemes, and occasions, helping customers see complete looks before buying. Our team delivers such solutions turnkey—from modeling to integration with your catalog, ensuring reliable operation and ongoing support.

AI Development Areas

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1344
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1307
  • B2B Advance company logo design
    B2B Advance company logo design
    754
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1049
  • AIDER company logo development
    AIDER company logo development
    994
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1097

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.