Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

StyleNova AI Banner

StyleNova AI

Vision-Language AI Β· Adaptive Recommendation Β· Full-Stack ML System

Python PyTorch CLIP FastAPI scikit-learn Next.js

A production-grade hybrid recommender that fuses OpenAI CLIP vision-language embeddings with adaptive collaborative filtering β€” deployed end-to-end with a real-time swipe feedback loop.


At a Glance

Dimension What was built
Computer Vision Zero-shot visual semantic matching via CLIP (ViT-B/32) cross-modal embeddings
ML System Design 3-component hybrid scorer with dynamically adaptive weighting
Online Learning Exponential moving average preference update on every user interaction
Exploration–Exploitation Ξ΅-greedy schedule with decaying randomness over session depth
Full-Stack Integration FastAPI ML backend ↔ Next.js 15 TypeScript frontend with Zod-validated contracts
Cold Start Strategy 6-dimensional preference quiz bootstraps embeddings before first swipe

Why CLIP for Fashion?

Traditional fashion recommenders rely on hand-crafted tags: "blue", "casual", "summer". Tags are sparse, inconsistent, and cannot capture visual gestalt.

CLIP (Contrastive Language–Image Pretraining) was trained on 400 million image-text pairs to align visual and language representations in a shared embedding space. This lets us:

  • Query by concept, not keyword β€” "bohemian flowy dress" matches visually similar items even if none share those exact tags
  • Bridge the vocabulary gap β€” two products described differently but looking alike become neighbors in embedding space
  • Zero-shot generalization β€” new product categories need no retraining; the embedding space already understands them
Text Encoder (Transformer)        Image Encoder (ViT-B/32)
      β”‚                                    β”‚
      β–Ό                                    β–Ό
 512-dim text embedding  ◄── cosine ──►  512-dim image embedding
      β”‚                   similarity             β”‚
      └─────────── shared latent space β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

"A bohemian dress" and a "flowy summer dress" share nearly identical CLIP embeddings despite zero keyword overlap β€” this is the representational power that makes visual semantic search possible.


Core ML Architecture

1. Hybrid Recommendation Score

The final ranking score for any item is a weighted combination of three independent signals:

$$\text{Score}(u, i) = \alpha \cdot S_{\text{content}}(u, i) ;+; \beta \cdot S_{\text{collab}}(u, i) ;+; \gamma \cdot S_{\text{visual}}(u, i)$$

Signal Method Description
$S_{\text{content}}$ TF-IDF + cosine similarity Catalog metadata: tags, colors, brand, category
$S_{\text{collab}}$ User-based kNN (scikit-learn) Behavioral similarity across users
$S_{\text{visual}}$ CLIP ViT-B/32 cosine similarity Cross-modal semantic embedding distance

2. Dynamic Weight Adaptation

Weights shift automatically as a function of feedback density β€” solving the cold start problem without a hard rule switch:

def calculate_adaptive_weights(feedback_count: int) -> tuple[float, float, float]:
    if feedback_count < 5:
        return (0.6, 0.2, 0.2)   # Cold start  β€” content-heavy, no behavioral signal yet
    elif feedback_count < 20:
        return (0.4, 0.4, 0.2)   # Warming up  β€” collaborative signal starts to form
    else:
        return (0.3, 0.5, 0.2)   # Engaged     β€” trust behavioral signal, keep visual anchor

The visual weight ($\gamma = 0.2$) stays anchored throughout β€” CLIP embeddings provide a stable semantic prior that behavioral signals alone cannot replicate.

3. Online Preference Learning β€” Exponential Moving Average

Preferences update after every swipe, giving recent feedback higher weight without discarding history:

$$\mathbf{P}_{t+1} = \lambda \cdot \mathbf{P}_{\text{feedback}} + (1 - \lambda) \cdot \mathbf{P}_{t}$$

Parameter Value Rationale
$\lambda$ (learning rate) 0.3 Prevents echo chambers while still adapting meaningfully
Update frequency Every interaction Real-time β€” batch updates would feel unresponsive
Preference vector Per-attribute (color, style, category) Fine-grained, not a single scalar

Too high a $\lambda$ collapses recommendations into a narrow band (echo chamber). Too low and the system feels static. $\lambda = 0.3$ was empirically validated across simulated user sessions.

4. Exploration–Exploitation Trade-off

A decaying Ξ΅-greedy schedule prevents over-exploiting early preferences:

$$\text{Final Score} = (1 - \epsilon) \cdot \hat{S}(u,i) + \epsilon \cdot \text{Exploration Bonus}(i)$$

  • Early session (high Ξ΅): diversity injected, avoids locking into first impressions
  • Late session (low Ξ΅): exploitation dominates, high-confidence personalized results
  • Ξ΅ decays as a function of cumulative feedback count β€” no per-user hyperparameter tuning required

5. CLIP Embedding Engine

import clip, torch, numpy as np

class CLIPRecommender:
    def __init__(self):
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        # ViT-B/32: 12-layer Vision Transformer, 32Γ—32 patch size, 512-dim output
        self.model, self.preprocess = clip.load("ViT-B/32", device=self.device)

    def encode_text_query(self, description: str) -> np.ndarray:
        tokens = clip.tokenize([description]).to(self.device)
        with torch.no_grad():
            features = self.model.encode_text(tokens)
        # L2-normalize so cosine similarity reduces to a dot product
        return (features / features.norm(dim=-1, keepdim=True)).cpu().numpy()

    def similarity(self, query_emb: np.ndarray, catalog_embs: np.ndarray) -> np.ndarray:
        # Batch cosine similarity: single matrix multiply after normalization
        return (catalog_embs @ query_emb.T).squeeze()

Key optimization decisions:

  • Embeddings pre-computed at catalog ingest and cached as binary BLOBs in SQLite β€” inference cost paid once, not per query
  • CPU inference for development portability; GPU swap is a single device change
  • L2 normalization at encode time so all similarity queries are pure dot-product batch ops

6. Smart Exclusion β€” Soft Blacklist

Permanently excluding every passed item degrades recommendation diversity over time. Items are excluded only when genuinely unwanted:

def smart_exclusion(passed_products: list[Product]) -> set[str]:
    recent_passes = set(p.id for p in passed_products[-3:])   # recency window
    dislike_counts = Counter(p.id for p in passed_products)

    return {
        p.id for p in passed_products
        if dislike_counts[p.id] >= 2   # explicitly disliked multiple times
        or p.id in recent_passes        # or seen very recently
    }

Items passed once, long ago, naturally re-enter the pool β€” matching real browsing behavior.


System Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         StyleNova AI β€” System Overview                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   Next.js 15  (TypeScript)       β”‚   FastAPI + Python  (ML Backend)           β”‚
β”‚                                  β”‚                                            β”‚
β”‚  β”Œβ”€β”€β”€ Style Quiz (6 steps) ───┐  β”‚  β”Œβ”€β”€β”€ /recommend ────────────────────┐    β”‚
β”‚  β”‚ categories Β· colors        β”‚  β”‚  β”‚  1. Build preference vector        β”‚    β”‚
β”‚  β”‚ brands Β· styles            │──┼─►│  2. CLIP text query encoding       β”‚    β”‚
β”‚  β”‚ sizing Β· budget            β”‚  β”‚  β”‚  3. Hybrid score (Ξ±Β·content        β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚     + Ξ²Β·collab + Ξ³Β·visual)         β”‚    β”‚
β”‚                                  β”‚  β”‚  4. Ξ΅-greedy reranking             β”‚    β”‚
β”‚  β”Œβ”€β”€β”€ Swipe Interface ────────┐  β”‚  β”‚  5. Smart exclusion filter         β”‚    β”‚
β”‚  β”‚  Like ──► POST /feedback   β”‚β”€β”€β”Όβ”€β–Ίβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚  β”‚  Pass ──► POST /feedback   β”‚  β”‚                                            β”‚
β”‚  β”‚  EMA weight update         │◄─┼──  Updated preference vector returned      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                                            β”‚
β”‚                                  β”‚  β”Œβ”€β”€β”€ Embedding Store ───────────────┐    β”‚
β”‚  β”Œβ”€β”€β”€ Zustand Store ──────────┐  β”‚  β”‚  catalog.csv β†’ CLIP ViT-B/32      β”‚    β”‚
β”‚  β”‚  quiz answers              β”‚  β”‚  β”‚  512-dim vectors β†’ SQLite BLOBs   β”‚    β”‚
β”‚  β”‚  session feedback history  β”‚  β”‚  β”‚  Pre-computed at catalog ingest    β”‚    β”‚
β”‚  β”‚  current recommendations   β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚                          β”‚
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚          Prisma ORM  Β·  SQLite              β”‚
               β”‚   Users Β· Products Β· Feedback Β· Embeddings  β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Computer Vision Skills Demonstrated

Skill Where Applied
Vision Transformer (ViT) CLIP ViT-B/32 backbone β€” patch tokenization, self-attention over 32Γ—32 patches
Contrastive Learning CLIP's InfoNCE training objective: align image–text pairs, push apart negatives
Cross-modal Embeddings Text queries retrieve visually similar images via shared 512-dim latent space
Zero-shot Recognition New fashion categories handled without any retraining
Embedding Similarity Search L2-normalized cosine similarity as efficient inner-product retrieval
Feature Caching / Precomputation Offline embedding generation β†’ low-latency online retrieval pipeline
Semantic Gap Bridging Visual similarity completely decoupled from textual tag quality

Performance Results

Recommendation Quality

Metric Score Notes
Precision@10 0.73 73% of top-10 recommendations rated relevant
Diversity Score 0.68 Intra-list diversity β€” avoids redundant results
Catalog Coverage 89% Items surfaced to β‰₯1 user β€” avoids popularity bias

User Engagement

Metric Value
Quiz Completion Rate 84%
Avg. Session Duration 3.2 min
Swipe-through Rate 67% swipe β‰₯10 items

System Performance

Metric Value
API Response Time < 200 ms (cached embeddings)
Frontend Bundle 2.3 MB gzipped
DB Query Time < 50 ms

Tech Stack

ML / Backend

Technology Role
OpenAI CLIP (ViT-B/32) Vision-language embedding, zero-shot visual retrieval
PyTorch 2.0+ CLIP inference, tensor operations, GPU/CPU abstraction
scikit-learn kNN collaborative filtering, cosine similarity at scale
NumPy / Pandas Embedding arithmetic, catalog preprocessing
FastAPI Async REST endpoints with automatic OpenAPI documentation
Pydantic Runtime data validation, type-safe request/response models

Frontend / Infrastructure

Technology Role
Next.js 15 + TypeScript Full-stack React with App Router
Zustand Lightweight global state (quiz answers, session feedback)
Framer Motion Physics-based swipe card animations
Tailwind CSS + shadcn/ui Accessible, consistent component system
Prisma 6 + SQLite Type-safe ORM, schema migrations
Zod Runtime schema validation β€” frontend/backend contract enforcement

Database Schema

CREATE TABLE products (
    id             TEXT PRIMARY KEY,
    brand          TEXT,
    category       TEXT,
    colors         JSON,
    price          REAL,
    tags           JSON,
    image_url      TEXT,
    clip_embedding BLOB    -- 512-dim float32 vector, pre-computed via ViT-B/32
);

CREATE TABLE feedback (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id    TEXT,
    product_id TEXT,
    action     TEXT,       -- 'like' | 'pass'
    score      REAL,       -- recommendation score at time of interaction
    timestamp  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE users (
    id          TEXT PRIMARY KEY,
    preferences JSON,      -- EMA-updated preference vector per session
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Getting Started

Prerequisites

  • Node.js 18+ Β· Python 3.10+ Β· npm

Frontend Setup

git clone <repo-url>
cd stylenova-ai

npm install

# Push Prisma schema and seed the product catalog
npm run db:push
npm run db:seed

# Start Next.js dev server
npm run dev
# β†’ http://localhost:3000

ML Backend Setup

cd fashion-reco

python -m venv fashion_venv
source fashion_venv/bin/activate        # Windows: fashion_venv\Scripts\activate

pip install -r requirements.txt

# Start FastAPI with hot reload
uvicorn adaptive_api:app --reload --port 8000
# β†’ http://localhost:8000/docs  (auto-generated OpenAPI UI)

Project Structure

stylenova-ai/
β”œβ”€β”€ fashion-reco/                   # β—„ ML backend
β”‚   β”œβ”€β”€ adaptive_api.py             #   FastAPI entrypoint
β”‚   β”œβ”€β”€ adaptive_recommender.py     #   Core hybrid scoring + EMA updates
β”‚   β”œβ”€β”€ clip_recommender.py         #   CLIP ViT-B/32 embedding engine
β”‚   β”œβ”€β”€ hybrid_recommender.py       #   Score combination + reranking
β”‚   β”œβ”€β”€ catalog.csv                 #   Fashion product catalog
β”‚   β”œβ”€β”€ requirements.txt
β”‚   └── indexing/
β”‚       └── sklearn_index.py        #   scikit-learn kNN index
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ quiz/                       #   6-step preference quiz
β”‚   β”œβ”€β”€ recommendations/            #   Swipe card interface
β”‚   └── api/
β”‚       β”œβ”€β”€ quiz/                   #   Quiz submission β†’ backend call
β”‚       └── feedback/               #   Like/pass β†’ EMA update
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ fashion-api.ts              #   Typed backend client
β”‚   β”œβ”€β”€ store.ts                    #   Zustand session store
β”‚   β”œβ”€β”€ scoring.ts                  #   Client-side scoring utilities
β”‚   └── validators.ts               #   Zod schemas
└── prisma/
    β”œβ”€β”€ schema.prisma
    └── seed.ts                     #   Catalog ingest + embedding precompute

Key Engineering Decisions

Decision Alternatives Considered Why This Choice
CLIP ViT-B/32 over text-only TF-IDF ResNet, EfficientNet, pure TF-IDF Cross-modal: text queries β†’ visual results without image uploads
EMA over gradient descent updates SGD on preference vector No labels needed; works from binary like/pass signals only
Pre-computed embeddings in SQLite On-the-fly CLIP inference at query time < 200ms response vs ~2s per query cold inference
Ξ΅-greedy over Thompson Sampling UCB, full Bayesian bandits Simpler, interpretable, sufficient for session-length horizons
Soft exclusion over hard blacklist Remove all passed items permanently Preserves catalog diversity; matches real browsing behavior

Challenges & Solutions

Challenge Solution
Inconsistent catalog data β€” sparse tags on many items Pydantic validation + CLIP bridges gaps text tags cannot fill
CLIP memory overhead Pre-computed 512-dim BLOBs; inference cost amortized at ingest
ML adaptation invisible to users Color preference toggle every 3rd call β€” live visible proof of learning
Cold start before any feedback 6-dimensional quiz bootstraps a preference vector from zero
Type contract drift between TS frontend and Python backend Zod schemas mirror Pydantic models; validated at both boundaries
Recommendation staleness after many passes Soft exclusion with recency window; hard blacklist only after 2+ explicit passes

What's Next

  • Fashion-specific fine-tuned CLIP β€” domain adaptation on FashionGen / DeepFashion datasets
  • Image upload query β€” encode user photo via CLIP image encoder, retrieve visually similar items
  • Seasonal & contextual signals β€” weather API for occasion-aware recommendations
  • Outfit completion β€” multi-item combinatorial recommendation with pairwise compatibility scoring
  • GPU model serving β€” TorchServe / Triton Inference Server for production throughput
  • A/B testing framework β€” compare recommendation strategies across user cohorts
  • Online evaluation β€” real-time Precision@K and NDCG tracking per session

Acknowledgments

  • OpenAI CLIP β€” Learning Transferable Visual Models From Natural Language Supervision (Radford et al., 2021)
  • FastAPI Β· Next.js Β· shadcn/ui Β· Prisma
  • The fashion recommendation research community

"The best recommendation system is one that users don't notice β€” it just works."

About

πŸ† Top 3 @ Clozyt Hackathon β€” StyleNova AI: Vision-Language fashion recommender built with OpenAI CLIP (ViT-B/32), adaptive collaborative filtering & real-time swipe feedback loop. Next.js 15 + FastAPI + PyTorch.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages