虚拟试衣用户尺码匹配算法,降低线上服饰退换货比例,输出最优尺码推荐逻辑。
虚拟试衣用户尺码匹配算法 —— 降低线上服饰退换货比例
一、实际应用场景描述
在《时尚产业与品牌创新》课程中,线上购物的"尺码焦虑" 是服饰电商最核心的痛点之一。消费者面对 S/M/L/XL 的尺码表,往往陷入两难:
"我 165cm/55kg,胸围 86,腰围 68……这件 M 码胸围 90 能穿吗?紧不紧?"
最终结果:下单 → 收到 → 不合身 → 退货 → 重新选购 → 可能再退。这一循环不仅伤害用户体验,更直接吞噬品牌利润。
据行业数据,中国服饰电商平均退货率约 25%~40%,其中 尺码不合身占比超过 50%。对品牌方而言,每单退货的实际成本约为商品售价的 15%~25%(物流往返 + 质检翻新 + 库存占用 + 平台扣点)。
虚拟试衣与尺码推荐系统的目标,就是通过用户身体数据 + 版型数据库,在用户下单前给出最优尺码建议,将"盲选"变成"精准匹配"。
二、引入痛点
2.1 当前行业现状
痛点 具体表现 影响
静态尺码表 只有平铺尺寸(胸围/腰围/衣长),无立体信息 用户不会换算,选错率 > 40%
一刀切推荐 所有款用同一套"身高体重→尺码"映射 不同版型(宽松/修身/oversized)差异巨大
忽略个人偏好 不区分"喜欢合身"vs"喜欢宽松" 推荐了"对的尺码"但不符合用户期待
无反馈闭环 退了就退了,不记录"为什么退" 同类错误反复发生
特殊体型无适配 胸围大/腰细/肩宽等非常规比例 现有尺码表完全失效
2.2 一个典型损失场景
某女装品牌,月销量 2000 件,客单价 ¥599,退货率 35%
每月退货量: 2000 × 35% = 700 件
单件退货成本: ¥599 × 20% ≈ ¥120
月退货损失: 700 × ¥120 = ¥84,000
年退货损失: ¥84,000 × 12 ≈ ¥100万+
若将退货率降至 20%(降幅 15 个百分点):
年节省: ¥100万 × (35-20)/35 ≈ ¥43万
核心矛盾:不是"有没有尺码表"的问题,而是尺码表是二维的、静态的,而人体和版型是三维的、动态的。
三、核心逻辑讲解
3.1 整体架构
用户端 算法端 输出端
┌──────────────┐ ┌──────────────────┐ ┌────────────────┐
│ 身高/体重 │ │ 身体维度建模 │ │ 尺码推荐结果 │
│ 三围数据 │ → │ 版型特征匹配 │ → │ 置信度评分 │
│ 肩宽/臂长 │ │ 偏好模型 │ │ 备选尺码 │
│ 历史购买记录 │ │ 尺码映射引擎 │ │ 不推荐原因 │
└──────────────┘ └──────────────────┘ └────────────────┘
3.2 核心公式体系
① 身体维度模型
将用户身体数据标准化为多维向量:
用户身体向量 U = [身高, 体重, 胸围, 腰围, 臀围, 肩宽, 臂长]
标准化(z-score):
z_i = (U_i − μ_brand,i) / σ_brand,i
其中 μ_brand 和 σ_brand 是该品牌历史用户的均值和标准差
② 版型适配度计算
每款服饰有一个版型特征向量:
版型向量 G = [胸围放松量, 腰围放松量, 臀围放松量, 衣长比例, 肩宽适配]
适配度得分 = 1 − Σ w_i × |用户维度_i − 版型维度_i| / 版型维度_i
权重 w_i 由该款式的"合身敏感部位"决定:
- 修身款:胸围、腰围权重高
- 落肩款:肩宽权重低
- 高腰裙:腰围、臀围权重高
③ 尺码匹配算法(核心)
对每件可用尺码 S_j:
适配得分_j = Σ w_i × (1 − |用户尺寸_i − S_j尺寸_i| / S_j尺寸_i)
其中 w_i 是部位权重(胸围0.3, 腰围0.25, 臀围0.2, 肩宽0.15, 衣长0.1)
最终得分归一化到 0~100 分
分类:
85~100 → ★★★★★ 强烈推荐
70~84 → ★★★★☆ 推荐
55~69 → ★★★☆☆ 可用(建议看详情)
40~54 → ★★☆☆☆ 不推荐(但可穿)
< 40 → ★☆☆☆☆ 不建议
④ 偏好修正模型
最终推荐 = 适配得分 × (1 + α × 偏好修正)
偏好类型:
- "喜欢合身" → 选最贴近身体的尺码(+5% 权重给紧一码)
- "喜欢宽松" → 选放松量更大的尺码(+10% 权重给大一码)
- "喜欢长款" → 衣长权重加倍
- "首次购买" → 保守策略,推荐中间码
⑤ 置信度评估
置信度 = 基础置信 × 数据完整度 × 历史可靠度
基础置信:
- 用户提供了 5+ 维度数据 → 0.9
- 用户仅提供身高体重 → 0.5
数据完整度 = 已提供维度 / 总维度
历史可靠度:
- 有 3+ 次购买且退货率 < 15% → 0.95
- 有购买记录但退货率高 → 0.6
- 无购买记录 → 0.7(新用户默认)
四、项目结构
virtual_fitting_sizer/
├── config.py # 品牌尺码表、版型参数配置
├── data_models.py # 数据模型(用户体型/服装尺码/推荐结果)
├── body_matcher.py # 身体维度匹配引擎
├── size_recommender.py # 尺码推荐核心算法
├── preference_model.py # 用户偏好模型
├── confidence_scorer.py # 置信度评估器
├── report.py # 报告生成(控制台 + 可视化)
├── main.py # 主程序入口(含完整示例)
├── README.md # 项目说明
└── requirements.txt # 依赖声明
五、代码模块化实现
"requirements.txt"
numpy>=1.24.0
matplotlib>=3.7.0
"config.py"
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
config.py
品牌尺码表与版型参数配置中心
"""
from typing import Dict, List, Tuple
import numpy as np
# ========== 品牌尺码表(示例:女装平铺尺寸,单位 cm) ==========
# 每款服饰的尺码数据
BRAND_SIZE_CHARTS = {
# 款 A:法式碎花收腰连衣裙(修身版型)
"DR-2024-001": {
"category": "dress",
"fit_type": "fitted", # 修身
"size_system": "CN", # 中国码
"sizes": {
"S": {"bust": 84, "waist": 66, "hip": 90, "length": 95, "shoulder": 36},
"M": {"bust": 88, "waist": 70, "hip": 94, "length": 97, "shoulder": 37},
"L": {"bust": 92, "waist": 74, "hip": 98, "length": 99, "shoulder": 38},
"XL": {"bust": 96, "waist": 78, "hip": 102, "length": 101, "shoulder": 39},
},
# 版型特征:各部位放松量(正值=宽松,负值=贴身)
"ease": {
"bust": -2, # 胸围贴身 2cm
"waist": -4, # 腰围贴身 4cm(收腰效果)
"hip": 2, # 臀围略宽松
"length": 0, # 标准衣长
"shoulder": 0, # 标准肩宽
},
# 合身敏感部位权重(决定哪些维度"最重要")
"fit_sensitivity": {
"bust": 0.25,
"waist": 0.30, # 收腰款,腰围最关键
"hip": 0.15,
"length": 0.15,
"shoulder": 0.15,
},
},
# 款 B:oversized 落肩卫衣(宽松版型)
"SW-2024-002": {
"category": "sweatshirt",
"fit_type": "oversized",
"size_system": "CN",
"sizes": {
"S": {"bust": 104, "waist": 100, "hip": 104, "length": 68, "shoulder": 44},
"M": {"bust": 108, "waist": 104, "hip": 108, "length": 70, "shoulder": 46},
"L": {"bust": 112, "waist": 108, "hip": 112, "length": 72, "shoulder": 48},
},
"ease": {
"bust": 12, # 胸围宽松 12cm
"waist": 14, # 腰围宽松 14cm
"hip": 10,
"length": 3, # 略长
"shoulder": 6, # 落肩
},
"fit_sensitivity": {
"bust": 0.15,
"waist": 0.10,
"hip": 0.10,
"length": 0.35, # 卫衣长度最关键(oversized 效果)
"shoulder": 0.30, # 落肩量
},
},
# 款 C:高腰阔腿牛仔裤(特定版型)
"PT-2024-003": {
"category": "pants",
"fit_type": "wide_leg",
"size_system": "CN",
"sizes": {
"S": {"waist": 64, "hip": 90, "inseam": 78, "thigh": 54, "leg_opening": 40},
"M": {"waist": 68, "hip": 94, "inseam": 80, "thigh": 56, "leg_opening": 42},
"L": {"waist": 72, "hip": 98, "inseam": 82, "thigh": 58, "leg_opening": 44},
"XL": {"waist": 76, "hip": 102, "inseam": 84, "thigh": 60, "leg_opening": 46},
},
"ease": {
"waist": 2,
"hip": 4,
"inseam": 0,
"thigh": 6, # 阔腿裤大腿处宽松
"leg_opening": 8, # 裤口宽松
},
"fit_sensitivity": {
"waist": 0.35, # 裤子腰围最关键
"hip": 0.20,
"inseam": 0.25, # 裤长
"thigh": 0.10,
"leg_opening": 0.10,
},
},
}
# ========== 部位维度名称映射 ==========
DIMENSION_LABELS = {
"bust": "胸围",
"waist": "腰围",
"hip": "臀围",
"shoulder": "肩宽",
"length": "衣长",
"inseam": "裤长",
"thigh": "大腿围",
"leg_opening": "裤口",
}
# ========== 用户体型分类参考(中国女性均值) ==========
BODY_REFERENCE = {
"height_mean": 162.0, # 平均身高 cm
"weight_mean": 57.0, # 平均体重 kg
"bust_mean": 86.0,
"waist_mean": 68.0,
"hip_mean": 92.0,
"shoulder_mean": 37.0,
}
# ========== 推荐结果阈值 ==========
SCORE_THRESHOLDS = {
"strong_recommend": 85, # 强烈推荐
"recommend": 70, # 推荐
"acceptable": 55, # 可用
"not_ideal": 40, # 不推荐但可穿
# < 40: 不建议
}
# ========== 可视化配色 ==========
COLORS = {
"excellent": "#2E7D32", # 深绿 - 强烈推荐
"good": "#66BB6A", # 绿 - 推荐
"acceptable": "#FFA726", # 橙 - 可用
"poor": "#EF5350", # 红 - 不推荐
"bar_bg": "#E0E0E0", # 灰色 - 背景
"primary": "#1565C0", # 蓝色 - 主色
}
"data_models.py"
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
data_models.py
数据模型层:用户体型、服装尺码、推荐结果
"""
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
from enum import Enum
class FitPreference(Enum):
"""用户合身偏好"""
SNUG = "snug" # 喜欢贴身
REGULAR = "regular" # 标准合身
RELAXED = "relaxed" # 喜欢宽松
OVERSIZED = "oversized" # 喜欢超大号
class ConfidenceLevel(Enum):
"""置信度等级"""
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
@dataclass
class UserBody:
"""
用户身体数据
所有维度均为可选,算法根据数据完整度自适应
"""
user_id: str
height: Optional[float] = None # 身高 cm
weight: Optional[float] = None # 体重 kg
bust: Optional[float] = None # 胸围 cm
waist: Optional[float] = None # 腰围 cm
hip: Optional[float] = None # 臀围 cm
shoulder: Optional[float] = None # 肩宽 cm
inseam: Optional[float] = None # 裤长/内缝 cm
thigh: Optional[float] = None # 大腿围 cm
# 偏好
preference: FitPreference = FitPreference.REGULAR
# 历史购买(用于校准推荐)
purchase_history: List[Dict] = field(default_factory=list)
# 每条记录: {"product_id": "xxx", "size": "M", "returned": False}
def provided_dimensions(self) -> Dict[str, float]:
"""返回用户已提供的维度数据"""
dims = {}
mapping = {
"bust": self.bust, "waist": self.waist, "hip": self.hip,
"shoulder": self.shoulder, "inseam": self.inseam, "thigh": self.thigh,
}
for k, v in mapping.items():
if v is not None:
dims[k] = v
# 身高体重始终提供(用于辅助推断)
return dims
def completeness(self) -> float:
"""数据完整度 0~1"""
total = 6 # 核心维度数(不含身高体重)
provided = len(self.provided_dimensions())
return min(provided / total, 1.0)
def has_purchase_history(self) -> bool:
return len(self.purchase_history) > 0
def return_rate(self) -> float:
"""历史退货率"""
if not self.purchase_history:
return 0.0
returns = sum(1 for p in self.purchase_history if p.get("returned", False))
return returns / len(self.purchase_history)
def preferred_size_offset(self) -> int:
"""
偏好导致的尺码偏移
-1 = 偏小一码, 0 = 标准, 1 = 偏大一码
"""
mapping = {
FitPreference.SNUG: -1,
FitPreference.REGULAR: 0,
FitPreference.RELAXED: 1,
FitPreference.OVERSIZED: 2,
}
return mapping.get(self.preference, 0)
@dataclass
class SizeRecommendation:
"""
单尺码推荐结果
"""
size_label: str # S / M / L / XL
score: float # 适配得分 0~100
confidence: float # 置信度 0~1
is_recommended: bool = False
is_alternative: bool = False # 是否为备选尺码
warnings: List[str] = field(default_factory=list) # 风险提示
dimension_fit: Dict[str, float] = field(default_factory=dict) # 各维度适配度
def star_rating(self) -> str:
"""星级评价"""
if self.score >= 85:
return "★★★★★"
elif self.score >= 70:
return "★★★★☆"
elif self.score >= 55:
return "★★★☆☆"
elif self.score >= 40:
return "★★☆☆☆"
else:
return "★☆☆☆☆"
def level_label(self) -> str:
if self.score >= 85:
return "强烈推荐"
elif self.score >= 70:
return "推荐"
elif self.score >= 55:
return "可用"
elif self.score >= 40:
return "不推荐"
else:
return "不建议"
@dataclass
class RecommendationResult:
"""
完整推荐结果(一款服饰对所有尺码的评估)
"""
product_id: str
user_id: str
recommendations: List[SizeRecommendation]
best_size: Optional[SizeRecommendation] = None
confidence_level: ConfidenceLevel = ConfidenceLevel.MEDIUM
data_completeness: float = 0.0
overall_warnings: List[str] = field(default_factory=list)
def get_best(self) -> Optional[SizeRecommendation]:
"""返回得分最高的尺码"""
if not self.recommendations:
return None
return max(self.recommendations, key=lambda r: r.score)
"body_matcher.py"
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
body_matcher.py
身体维度匹配引擎:计算用户体型与各尺码的适配度
"""
import numpy as np
from typing import Dict, List, Optional
from config import BRAND_SIZE_CHARTS, DIMENSION_LABELS
from data_models import UserBody, SizeRecommendation
class BodyMatcher:
"""
身体维度匹配引擎
核心方法:
- compute_fit_scores: 计算用户对所有可用尺码的适配得分
- identify_problem_areas: 找出"最可能不合身"的部位
- suggest_alternatives: 当一个尺码不合适时,建议调整方向
"""
def __init__(self, product_id: str):
if product_id not in BRAND_SIZE_CHARTS:
raise ValueError(f"未注册的款式: {product_id}")
self.product_id = product_id
self.chart = BRAND_SIZE_CHARTS[product_id]
self.sizes = self.chart["sizes"]
self.ease = self.chart["ease"]
self.sensitivity = self.chart["fit_sensitivity"]
def compute_fit_scores(self, user: UserBody) -> List[SizeRecommendation]:
"""
核心方法:计算用户对每个尺码的适配得分
Returns:
SizeRecommendation 列表,按得分降序排列
"""
user_dims = user.provided_dimensions()
results = []
for size_label, size_dims in self.sizes.items():
# 计算各维度适配度
dim_fit = {}
weighted_sum = 0.0
total_weight = 0.0
for dim_name, user_val in user_dims.items():
if dim_name not in size_dims:
continue
size_val = size_dims[dim_name]
weight = self.sensitivity.get(dim_name, 0.1)
# 适配度 = 1 − |用户值 − 尺码值| / 尺码值
# 加放松量修正:尺码值 + 版型放松量 = 实际合身值
effective_size = size_val + self.ease.get(dim_name, 0)
if effective_size == 0:
dim_score = 0
else:
diff_ratio = abs(user_val - effective_size) / effective_size
# 用 sigmoid 将差异映射为 0~1 的适配度
dim_score = 1 / (1 + 3 * diff_ratio)
# 宽松版型下,尺码偏大的惩罚更小
if self.chart["fit_type"] == "oversized" and user_val < effective_size:
dim_score = min(dim_score + 0.15, 1.0)
dim_fit[dim_name] = round(dim_score * 100, 1)
weighted_sum += weight * dim_score
total_weight += weight
# 归一化为百分制
if total_weight > 0:
raw_score = (weighted_sum / total_weight) * 100
else:
raw_score = 50.0 # 无数据时的默认值
# 身高体重辅助修正(不纳入主评分,只做微调)
height_adjust = self._height_adjustment(user, size_label)
raw_score = max(0, min(100, raw_score + height_adjust))
# 生成警告
warnings = self._generate_warnings(user_dims, size_dims, dim_fit)
results.append(SizeRecommendation(
size_label=size_label,
score=round(raw_score, 1),
confidence=0.0, # 由 ConfidenceScorer 填充
dimension_fit=dim_fit,
warnings=warnings,
))
# 按得分排序
results.sort(key=lambda r: r.score, reverse=True)
return results
def _height_adjustment(self, user: UserBody, size_label: str) -> float:
"""
身高对尺码的微调
太高/太矮可能影响衣长/裤长适配
"""
if user.height is None:
return 0.0
# 获取衣长/裤长维度名
length_dim = "length" if "length" in self.sensitivity else \
"inseam" if "inseam" in self.sensitivity else None
if length_dim is None:
return 0.0
size_val = self.sizes[size_label].get(length_dim, 0)
if size_val == 0:
return 0.0
# 身高与尺码长度的比例
ratio = user.height / size_val
if ratio < 1.5:
return -5 # 可能偏短
elif ratio > 2.0:
return -3 # 可能偏长
return 0.0
def _generate_warnings(
self,
user_dims: Dict[str, float],
size_dims: Dict[str, float],
dim_fit: Dict[str, float],
) -> List[str]:
"""生成各维度不适配的警告"""
warnings = []
for dim_name, fit_score in dim_fit.items():
if fit_score < 50: # 适配度低于 50%
label = DIMENSION_LABELS.get(dim_name, dim_name)
user_val = user_dims[dim_name]
size_val = size_dims[dim_name]
effective = size_val + self.ease.get(dim_name, 0)
if user_val > effective:
warnings.append(
f"{label}偏紧(用户{user_val:.0f}cm vs 尺码{effective:.0f}cm)"
)
else:
warnings.append(
f"{label}偏松(用户{user_val:.0f}cm vs 尺码{effective:.0f}cm)"
)
return warnings
def identify_problem_areas(
self, user: UserBody, top_n: int = 2
) -> List[Tuple[str, float]]:
"""
找出用户体型与款式的"最不匹配部位"
用于向用户解释"为什么这款可能不适合你"
"""
user_dims = user.provided_dimensions()
if not user_dims:
return []
# 综合所有尺码,找到各维度的平均适配度
dim_scores = {d: [] for d in user_dims}
for size_dims in self.sizes.values():
for dim_name, user_val in user_dims.items():
if dim_name not in size_dims:
continue
size_val = size_dims[dim_name]
effective = size_val + self.ease.get(dim_name, 0)
if effective > 0:
diff_ratio = abs(user_val - effective) / effective
score = 1 / (1 + 3 * diff_ratio) * 100
dim_scores[dim_name].append(score)
# 平均适配度最低的 = 问题最大的
avg_scores = []
for dim_name, scores in dim_scores.items():
if scores:
avg = np.mean(scores)
avg_scores.append((dim_name, round(avg, 1)))
avg_scores.sort(key=lambda x: x[1])
return avg_scores[:top_n]
def suggest_size_adjustment(
self, user: UserBody, current_best: str
) -> Optional[str]:
"""
如果当前最佳尺码得分较低,建议用户考虑的方向
"""
user_dims = user.provided_dimensions()
if not user_dims or current_best not in self.sizes:
return None
current_size = self.sizes[current_best]
problems = []
for dim_name, user_val in user_dims.items():
if dim_name not in current_size:
continue
effective = current_size[dim_name] + self.ease.get(dim_name, 0)
if effective <= 0:
continue
ratio = user_val / effective
if ratio > 1.08:
problems.append(f"{DIMENSION_LABELS.get(dim_name, dim_name)}偏大,建议选大码")
elif ratio < 0.92:
problems.append(f"{DIMENSION_LABELS.get(dim_name, dim_name)}偏小,建议选小码")
return ";".join(problems) if problems else None
"size_recommender.py"
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
size_recommender.py
尺码推荐核心算法:整合匹配引擎 + 偏好 + 置信度,输出最终推荐
"""
from typing import Dict, List, Optional
from config import BRAND_SIZE_CHARTS, SCORE_THRESHOLDS
from data_models import (
UserBody, SizeRecommendation, RecommendationResult,
ConfidenceLevel, FitPreference
)
from body_matcher import BodyMatcher
from preference_model import PreferenceModel
from confidence_scorer import ConfidenceScorer
class SizeRecommender:
"""
尺码推荐器 —— 主入口
使用方式:
recommender = SizeRecommender("DR-2024-001")
result = recommender.recommend(user)
"""
def __init__(self, product_id: str):
self.product_id = product_id
self.matcher = BodyMatcher(product_id)
self.preference_model = PreferenceModel()
self.confidence_scorer = ConfidenceScorer()
def recommend(self, user: UserBody) -> RecommendationResult:
"""
核心方法:为指定用户推荐该款式的最优尺码
Returns:
RecommendationResult 包含完整推荐信息
"""
# ① 计算各尺码原始适配得分
raw_results = self.matcher.compute_fit_scores(user)
if not raw_results:
return RecommendationResult(
product_id=self.product_id,
user_id=user.user_id,
recommendations=[],
overall_warnings=["该款式无可用尺码数据"],
)
# ② 应用偏好修正
adjusted_results = self.preference_model.apply_preference(
user, raw_results, self.matcher.chart
)
# ③ 评估置信度
completeness = user.completeness()
for r in adjusted_results:
r.confidence = self.confidence_scorer.score(
user, r, completeness, self.matcher.chart
)
# ④ 标记推荐与备选
self._mark_recommendations(adjusted_results)
# ⑤ 综合置信度
overall_confidence = self.confidence_scorer.overall_confidence(
user, adjusted_results, completeness
)
# ⑥ 全局警告
warnings = self._generate_overall_warnings(user, adjusted_results)
# ⑦ 找出最佳
best = max(adjusted_results, key=lambda r: r.score)
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!
更多推荐


所有评论(0)