Разработка AI для прогнозирования трендов в моде
Fashion trend forecasting — анализ визуального контента (подиумные показы, социальные сети, e-commerce) для прогнозирования актуальных трендов. Традиционно: аналитики-трендсеттеры тратят месяцы на ручной анализ. AI обрабатывает миллионы изображений за часы: выявляет доминирующие цвета, силуэты, принты, паттерны, строит временные ряды популярности каждого атрибута.
Экстрактор модных атрибутов
import numpy as np
import cv2
import torch
from transformers import CLIPProcessor, CLIPModel
from torchvision import transforms
from PIL import Image
from dataclasses import dataclass, field
from collections import Counter, defaultdict
from datetime import datetime
from typing import Optional
@dataclass
class FashionAttributes:
image_path: str
timestamp: str
categories: list[str] # dress / top / pants / jacket / etc.
colors: list[str] # red / navy / beige / etc.
patterns: list[str] # solid / stripes / floral / plaid / etc.
silhouette: str # slim / oversized / fitted / flared
style: str # casual / formal / streetwear / sport / etc.
season: Optional[str] # spring / summer / fall / winter
class FashionAttributeExtractor:
"""
CLIP-based извлечение модных атрибутов из изображений одежды.
FashionCLIP (fine-tuned на Farfetch) лучше vanilla CLIP для fashion domain.
"""
# Атрибуты для CLIP zero-shot classification
CATEGORIES = ['dress', 'top', 'blouse', 't-shirt', 'pants', 'jeans',
'skirt', 'jacket', 'coat', 'blazer', 'sweater', 'hoodie',
'shorts', 'suit', 'jumpsuit', 'cardigan']
COLORS = ['red', 'dark red', 'orange', 'yellow', 'lime green', 'green',
'teal', 'light blue', 'navy blue', 'purple', 'pink', 'white',
'off-white', 'beige', 'light gray', 'gray', 'dark gray', 'black',
'brown', 'camel', 'olive green', 'multicolor']
PATTERNS = ['solid color', 'vertical stripes', 'horizontal stripes',
'plaid check', 'floral print', 'animal print', 'geometric pattern',
'abstract print', 'polka dots', 'camouflage', 'paisley']
SILHOUETTES = ['slim fit', 'oversized loose fit', 'fitted tailored',
'flared wide leg', 'cropped short', 'longline maxi']
STYLES = ['casual everyday', 'business formal', 'business casual',
'streetwear urban', 'sport athletic', 'evening cocktail',
'bohemian romantic', 'minimalist', 'vintage retro']
def __init__(self, model_name: str = 'patrickjohncyh/fashion-clip',
device: str = 'cuda'):
self.device = device
try:
# Fashion-CLIP если доступен
self.model = CLIPModel.from_pretrained(model_name).to(device)
self.processor = CLIPProcessor.from_pretrained(model_name)
except Exception:
# Fallback на vanilla CLIP
self.model = CLIPModel.from_pretrained('openai/clip-vit-base-patch32').to(device)
self.processor = CLIPProcessor.from_pretrained('openai/clip-vit-base-patch32')
self.model.eval()
# Pre-compute text features для всех категорий
self._features_cache = {}
for attr_name, attr_list in [
('categories', self.CATEGORIES), ('colors', self.COLORS),
('patterns', self.PATTERNS), ('silhouettes', self.SILHOUETTES),
('styles', self.STYLES)
]:
self._features_cache[attr_name] = self._encode_texts(
[f'a fashion photo of {a}' for a in attr_list]
)
@torch.no_grad()
def _encode_texts(self, texts: list) -> torch.Tensor:
inputs = self.processor(text=texts, return_tensors='pt',
padding=True, truncation=True).to(self.device)
features = self.model.get_text_features(**inputs)
return features / features.norm(dim=-1, keepdim=True)
@torch.no_grad()
def extract(self, image: np.ndarray,
image_path: str = '') -> FashionAttributes:
pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
inputs = self.processor(images=pil, return_tensors='pt').to(self.device)
img_features = self.model.get_image_features(**inputs)
img_features = img_features / img_features.norm(dim=-1, keepdim=True)
def top_k_matches(feature_key, attr_list, top_k=2, threshold=0.18):
features = self._features_cache[feature_key]
sims = (img_features @ features.T).squeeze().cpu().numpy()
top_idx = sims.argsort()[-top_k:][::-1]
return [attr_list[i] for i in top_idx if sims[i] > threshold]
categories = top_k_matches('categories', self.CATEGORIES, top_k=2)
colors = top_k_matches('colors', self.COLORS, top_k=3, threshold=0.15)
patterns = top_k_matches('patterns', self.PATTERNS, top_k=1)
sils = top_k_matches('silhouettes', self.SILHOUETTES, top_k=1)
styles = top_k_matches('styles', self.STYLES, top_k=1)
# Сезонность по цветам
season = self._infer_season(colors, patterns)
return FashionAttributes(
image_path=image_path,
timestamp=datetime.now().isoformat(),
categories=categories,
colors=colors,
patterns=patterns,
silhouette=sils[0] if sils else 'unknown',
style=styles[0] if styles else 'unknown',
season=season
)
def _infer_season(self, colors: list, patterns: list) -> str:
summer_colors = {'lime green', 'yellow', 'orange', 'light blue', 'white'}
winter_colors = {'dark gray', 'black', 'navy blue', 'dark red'}
spring_colors = {'pink', 'light blue', 'off-white', 'beige'}
if any(c in summer_colors for c in colors):
return 'summer'
elif any(c in winter_colors for c in colors):
return 'winter'
elif any(c in spring_colors for c in colors):
return 'spring'
return 'fall'
Анализ трендов по временным рядам
class TrendAnalyzer:
"""
Накопление атрибутов из изображений → построение трендовых временных рядов.
Выявление восходящих и нисходящих трендов через скользящее среднее.
"""
def __init__(self):
self.data: list[FashionAttributes] = []
def add_batch(self, attributes: list[FashionAttributes]) -> None:
self.data.extend(attributes)
def analyze_color_trends(self, period_days: int = 30) -> dict:
"""Топ цветов + динамика за период"""
from datetime import datetime, timedelta
recent_cutoff = datetime.now() - timedelta(days=period_days)
recent = [a for a in self.data
if datetime.fromisoformat(a.timestamp) > recent_cutoff]
all_colors = [c for a in recent for c in a.colors]
prev_all_colors = [c for a in self.data if a not in recent
for c in a.colors]
current_counts = Counter(all_colors)
prev_counts = Counter(prev_all_colors)
total_current = max(sum(current_counts.values()), 1)
total_prev = max(sum(prev_counts.values()), 1)
trends = {}
all_keys = set(list(current_counts.keys()) + list(prev_counts.keys()))
for color in all_keys:
curr_pct = current_counts.get(color, 0) / total_current * 100
prev_pct = prev_counts.get(color, 0) / total_prev * 100
change = curr_pct - prev_pct
trends[color] = {
'current_share_pct': round(curr_pct, 2),
'change_pct': round(change, 2),
'trend': 'rising' if change > 1 else ('falling' if change < -1 else 'stable')
}
# Топ-10 по текущей доле
top10 = sorted(trends.items(), key=lambda x: x[1]['current_share_pct'], reverse=True)[:10]
return {k: v for k, v in top10}
def get_emerging_trends(self, min_growth_pct: float = 2.0) -> list[dict]:
"""Выявление быстро растущих трендов"""
color_trends = self.analyze_color_trends()
emerging = [
{'attribute': color, **data}
for color, data in color_trends.items()
if data['change_pct'] >= min_growth_pct
]
return sorted(emerging, key=lambda x: x['change_pct'], reverse=True)
| Метрика |
FashionCLIP |
Vanilla CLIP |
| Category Top-1 |
82–87% |
68–75% |
| Color Detection |
78–84% |
65–72% |
| Pattern Detection |
72–80% |
58–68% |
| Trend Correlation (vs sales) |
r=0.73 |
r=0.58 |
| Задача |
Срок |
| Атрибут-экстрактор + базовый анализ |
4–6 недель |
| Трендовый мониторинг + temporal analysis |
8–14 недель |
| Fashion Intelligence платформа + API |
14–22 недели |