AI Outfit Recommendation: CV and Knowledge Graph

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1566 services
AI Outfit Recommendation: CV and Knowledge Graph
Medium
~2-4 weeks
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1318
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1226
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    926
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1160
  • image_logo-advance_0.webp
    B2B Advance company logo design
    620
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    894

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.