AI Outfit Recommendation System

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 1 servicesAll 1566 services
AI Outfit Recommendation System
Medium
~2-4 weeks
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822

AI-powered outfit recommendation system

Outfit recommendation is more complex than recommending individual items: you need to match compatible pieces accounting for style, color compatibility, occasion, and the user's wardrobe. Pinterest, Stitch Fix, ASOS use computer vision and knowledge graphs for this task.

Clothing 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 by 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:
        """Color compatibility (0-1)"""
        # Neutral colors match 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:
    """Assemble outfits from user's 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]:
        """
        Assemble outfit for specific occasion.
        anchor_item: anchor piece (e.g., newly 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 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 (use dominant)
            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:
        """Rate outfit"""
        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 conflict')
        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)
        }

Outfit recommendation systems reduce returns due to "don't know what to wear with" by 15-20%. Key challenge: cold start wardrobe (needs minimum 10-15 items for good recommendations) and subjectivity of style. Solution: explicit preferences via onboarding quiz.