用 Python 构建地域文化适配穿搭推荐算法,根据用户输入的城市,结合当地气候特征与地域文化气质,输出适配的服饰风格方案,并以中立视角呈现分析过程。
一、实际应用场景描述
在《时尚产业与品牌创新》课程中,“在地化(Localization)”是品牌扩张的核心议题。同一品牌进入不同城市,往往面临水土不服:
- 哈尔滨 vs 三亚:冬季温差可达 50°C,羽绒服在哈尔滨是刚需,在三亚则是累赘。
- 成都 vs 上海:成都“巴适”“松弛”的地域文化偏好宽松、慵懒的穿搭;上海“摩登”“精致”的都市文化则更接受修身、干练的风格。
- 西安 vs 杭州:西安古城厚重,消费者更易接受“国风”“唐装”元素;杭州江南水乡,偏好“淡雅”“新中式”的柔美风格。
- 国际视野:纽约(快节奏/实用主义)vs 巴黎(浪漫/美学至上)vs 东京(克制/功能性)。
品牌与电商平台面临核心问题:
“如何根据用户的地理位置,自动推荐既符合当地气候,又契合当地文化气质的穿搭?”
二、引入痛点
- 气候与文化脱节:现有推荐系统多只考虑气温(如“今天降温,推荐羽绒服”),忽略了“穿这件羽绒服是否符合当地审美”。
- 数据孤岛:气象数据(温度/湿度)与文化数据(城市气质标签)割裂,缺乏统一的适配模型。
- 缺乏量化标准:什么是“成都风格”?什么是“上海风格”?缺乏可计算的特征向量。
- 静态推荐:季节变化、极端天气(寒潮/梅雨)缺乏动态调整机制。
⇒ 用 Python 构建城市画像数据库 + 气候-文化双因子权重模型 + 动态推荐引擎,实现“千人千城”的穿搭推荐。
三、核心逻辑讲解
1. 城市画像维度设计
我们将每个城市抽象为一个特征向量:
维度 子维度 说明 量化方式
气候特征 温度带 热带/亚热带/温带/寒带 年均温、1月均温、7月均温
降水特征 干燥/湿润/雨季 年降水量、湿度
特殊气候 季风/高原/沿海 布尔标记
文化气质 历史底蕴 古都/新城/移民城市 0-100 评分
审美倾向 厚重/轻盈/前卫/保守 0-100 评分
生活节奏 快/慢/慵懒/紧凑 0-100 评分
时尚开放度 国际/本土/保守 0-100 评分
2. 服饰风格特征库
定义几种典型风格的特征向量(与城市画像同构):
- 北方御寒风:保暖性↑↑,色彩厚重,版型宽松
- 江南淡雅风:透气性↑,色彩清新,面料柔软
- 都市摩登风:挺括度↑,色彩中性,剪裁利落
- 巴蜀慵懒风:舒适度↑,版型宽松,面料亲肤
- 海滨度假风:抗紫外线↑,色彩明快,面料轻薄
3. 适配算法核心公式
综合适配度 = w_climate × ClimateMatch + w_culture × CultureMatch
ClimateMatch = Σ(气候特征_i × 风格气候需求_i)
CultureMatch = Σ(文化气质_j × 风格文化需求_j)
最终推荐 = argmax(综合适配度)
4. 动态调节机制
- 季节系数:冬季提高保暖权重,夏季提高透气权重。
- 天气突变系数:寒潮预警时,临时提高保暖推荐优先级。
- 湿度修正:梅雨季节自动推荐防潮、速干材质。
四、代码模块化(regional_style_recommender.py)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
regional_style_recommender.py
地域文化适配穿搭推荐算法
根据城市气候与文化气质,输出最优服饰风格推荐
依赖: numpy, pandas, matplotlib
安装: pip install numpy pandas matplotlib
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams
from dataclasses import dataclass, field
from typing import Dict, List, Tuple, Optional
from enum import Enum
# 中文字体设置
rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'SimHei', 'Microsoft YaHei']
rcParams['axes.unicode_minus'] = False
# ──────────────────────────────────────────────
# 1. 枚举与基础数据结构
# ──────────────────────────────────────────────
class ClimateZone(Enum):
"""气候带"""
FRIGID = "寒带"
TEMPERATE = "温带"
SUBTROPICAL = "亚热带"
TROPICAL = "热带"
PLATEAU = "高原"
class StyleTag(Enum):
"""风格标签"""
URBAN_CHIC = "都市摩登"
NORTHERN_WARM = "北方御寒"
JIANGNAN_ELEGANT = "江南淡雅"
BASHU_RELAXED = "巴蜀慵懒"
COASTAL_VACATION = "海滨度假"
ANCIENT_CAPITAL = "古都厚重"
AVANT_GARDE = "先锋前卫"
@dataclass
class CityProfile:
"""城市画像"""
city_name: str
region: str
# 气候特征 (0-100 评分)
temp_winter: float # 冬季温度(低=冷)
temp_summer: float # 夏季温度(高=热)
humidity: float # 湿度(高=潮湿)
wind_strength: float # 风力
sunshine: float # 日照时长
# 地理特征
climate_zone: ClimateZone
is_coastal: bool
is_plateau: bool
# 文化气质 (0-100 评分)
historical_depth: float # 历史底蕴
aesthetic_tendency: float # 审美倾向(0=厚重,100=轻盈)
life_pace: float # 生活节奏(0=慢,100=快)
fashion_openness: float # 时尚开放度
# 特殊标签
tags: List[str] = field(default_factory=list)
@dataclass
class StyleProfile:
"""服饰风格画像"""
style_tag: StyleTag
description: str
# 气候适应性 (0-100,越高越适应)
warmth_need: float # 保暖需求
breathability_need: float # 透气需求
moisture_resistance: float # 防潮需求
uv_protection_need: float # 防晒需求
# 文化适配性 (0-100)
elegance_match: float # 适配厚重/轻盈审美
pace_match: float # 适配快慢节奏
tradition_match: float # 适配传统/现代
boldness_match: float # 适配保守/前卫
# 推荐单品
recommended_items: List[str] = field(default_factory=list)
# ──────────────────────────────────────────────
# 2. 城市数据库模块
# ──────────────────────────────────────────────
class CityDatabase:
"""
中国主要城市画像数据库
数据基于气象局公开数据与城市文化研究综合评定
"""
@staticmethod
def get_city_profiles() -> Dict[str, CityProfile]:
return {
'北京': CityProfile(
city_name='北京', region='华北',
winter_temp=-5, summer_temp=30, humidity=45,
wind_strength=60, sunshine=70,
climate_zone=ClimateZone.TEMPERATE,
is_coastal=False, is_plateau=False,
historical_depth=95, aesthetic_tendency=40,
life_pace=80, fashion_openness=85,
tags=['首都', '古都', '政治中心', '干燥']
),
'上海': CityProfile(
city_name='上海', region='华东',
winter_temp=5, summer_temp=32, humidity=75,
wind_strength=40, sunshine=50,
climate_zone=ClimateZone.SUBTROPICAL,
is_coastal=True, is_plateau=False,
historical_depth=70, aesthetic_tendency=65,
life_pace=95, fashion_openness=98,
tags=['魔都', '金融中心', '国际化', '潮湿']
),
'成都': CityProfile(
city_name='成都', region='西南',
winter_temp=8, summer_temp=29, humidity=80,
wind_strength=20, wind_strength=20, sunshine=30,
climate_zone=ClimateZone.SUBTROPICAL,
is_coastal=False, is_plateau=False,
historical_depth=85, aesthetic_tendency=75,
life_pace=40, fashion_openness=80,
tags=['慢生活', '巴适', '休闲', '阴雨']
),
'哈尔滨': CityProfile(
city_name='哈尔滨', region='东北',
winter_temp=-20, summer_temp=25, humidity=65,
wind_strength=70, sunshine=60,
climate_zone=ClimateZone.TEMPERATE,
is_coastal=False, is_plateau=False,
historical_depth=75, aesthetic_tendency=30,
life_pace=60, fashion_openness=70,
tags=['冰城', '严寒', '冰雪文化']
),
'杭州': CityProfile(
city_name='杭州', region='华东',
winter_temp=6, summer_temp=31, humidity=78,
wind_strength=30, sunshine=45,
climate_zone=ClimateZone.SUBTROPICAL,
is_coastal=False, is_plateau=False,
historical_depth=90, aesthetic_tendency=85,
life_pace=65, fashion_openness=82,
tags=['江南', '西湖', '淡雅', '湿润']
),
'广州': CityProfile(
city_name='广州', region='华南',
winter_temp=15, summer_temp=33, humidity=82,
wind_strength=25, sunshine=55,
climate_zone=ClimateZone.SUBTROPICAL,
is_coastal=True, is_plateau=False,
historical_depth=80, aesthetic_tendency=90,
life_pace=85, fashion_openness=90,
tags=['花城', '湿热', '务实', '开放']
),
'西安': CityProfile(
city_name='西安', region='西北',
winter_temp=-2, summer_temp=32, humidity=55,
wind_strength=50, sunshine=65,
climate_zone=ClimateZone.TEMPERATE,
is_coastal=False, is_plateau=False,
historical_depth=98, aesthetic_tendency=35,
life_pace=60, fashion_openness=75,
tags=['十三朝古都', '厚重', '历史感']
),
'三亚': CityProfile(
city_name='三亚', region='海南',
winter_temp=25, summer_temp=32, humidity=85,
wind_strength=45, sunshine=80,
climate_zone=ClimateZone.TROPICAL,
is_coastal=True, is_plateau=False,
historical_depth=60, aesthetic_tendency=95,
life_pace=50, fashion_openness=85,
tags=['热带', '海滨', '度假', '紫外线强']
)
}
# ──────────────────────────────────────────────
# 3. 风格数据库模块
# ──────────────────────────────────────────────
class StyleDatabase:
"""服饰风格特征库"""
@staticmethod
def get_style_profiles() -> Dict[StyleTag, StyleProfile]:
return {
StyleTag.NORTHERN_WARM: StyleProfile(
style_tag=StyleTag.NORTHERN_WARM,
description="北方御寒风:注重保暖与层次感,色彩偏向厚重沉稳",
warmth_need=95, breathability_need=20,
moisture_resistance=60, uv_protection_need=30,
elegance_match=30, pace_match=60,
tradition_match=70, boldness_match=40,
recommended_items=['羽绒服', '羊毛大衣', '围巾', '雪地靴', '保暖内衣']
),
StyleTag.JIANGNAN_ELEGANT: StyleProfile(
style_tag=StyleTag.JIANGNAN_ELEGANT,
description="江南淡雅风:注重透气与水墨意境,色彩清新淡雅",
warmth_need=40, breathability_need=80,
moisture_resistance=85, uv_protection_need=50,
elegance_match=85, pace_match=50,
tradition_match=80, boldness_match=30,
recommended_items=['丝绸衬衫', '棉麻长裙', '针织开衫', '油纸伞', '软底鞋']
),
StyleTag.URBAN_CHIC: StyleProfile(
style_tag=StyleTag.URBAN_CHIC,
description="都市摩登风:注重剪裁与挺括感,色彩中性利落",
warmth_need=50, breathability_need=60,
moisture_resistance=50, uv_protection_need=40,
elegance_match=50, pace_match=95,
tradition_match=30, boldness_match=80,
recommended_items=['西装外套', '直筒裤', '衬衫', '高跟鞋', '手提包']
),
StyleTag.BASHU_RELAXED: StyleProfile(
style_tag=StyleTag.BASHU_RELAXED,
description="巴蜀慵懒风:注重舒适与松弛感,版型宽松随意",
warmth_need=45, breathability_need=75,
moisture_resistance=70, uv_protection_need=45,
elegance_match=70, pace_match=30,
tradition_match=60, boldness_match=50,
recommended_items=['宽松卫衣', '阔腿裤', '拖鞋', '渔夫帽', '帆布袋']
),
StyleTag.COASTAL_VACATION: StyleProfile(
style_tag=StyleTag.COASTAL_VACATION,
description="海滨度假风:注重防晒与轻盈感,色彩明快活泼",
warmth_need=20, breathability_need=95,
moisture_resistance=90, uv_protection_need=95,
elegance_match=90, pace_match=40,
tradition_match=20, boldness_match=90,
recommended_items=['防晒衣', '沙滩裙', '凉鞋', '太阳镜', '遮阳帽']
),
StyleTag.ANCIENT_CAPITAL: StyleProfile(
style_tag=StyleTag.ANCIENT_CAPITAL,
description="古都厚重风:注重历史感与文化符号,色彩浓郁深沉",
warmth_need=55, breathability_need=50,
moisture_resistance=55, uv_protection_need=40,
elegance_match=25, pace_match=55,
tradition_match=95, boldness_match=20,
recommended_items=['唐装', '汉服改良', '盘扣上衣', '布鞋', '折扇']
),
StyleTag.AVANT_GARDE: StyleProfile(
style_tag=StyleTag.AVANT_GARDE,
description="先锋前卫风:注重个性与实验性,打破常规",
warmth_need=50, breathability_need=65,
moisture_resistance=50, uv_protection_need=50,
elegance_match=50, pace_match=85,
tradition_match=10, boldness_match=100,
recommended_items=['解构外套', '不规则裙', '机能靴', '金属配饰', '透明包']
)
}
# ──────────────────────────────────────────────
# 4. 推荐算法核心模块
# ──────────────────────────────────────────────
class RecommendationEngine:
"""
地域文化适配穿搭推荐引擎
核心算法:双因子加权匹配
"""
def __init__(self, city_db: Dict[str, CityProfile], style_db: Dict[StyleTag, StyleProfile]):
self.city_db = city_db
self.style_db = style_db
# 权重配置
self.weights = {
'climate': 0.55, # 气候权重(生存需求优先)
'culture': 0.45 # 文化权重(精神需求)
}
# 气候特征权重
self.climate_feature_weights = {
'warmth': 0.35, # 保暖
'breathability': 0.25, # 透气
'moisture': 0.20, # 防潮
'uv_protection': 0.20 # 防晒
}
# 文化特征权重
self.culture_feature_weights = {
'elegance': 0.30, # 审美适配
'pace': 0.25, # 节奏适配
'tradition': 0.25, # 传统适配
'boldness': 0.20 # 前卫适配
}
def _normalize_city_climate(self, city: CityProfile) -> Dict[str, float]:
"""归一化城市气候特征"""
# 温度特征:冬季温度越低,保暖需求越高
warmth_score = max(0, min(100, (city.temp_winter + 30) / 60 * 100))
# 透气需求:夏季温度越高,透气需求越高
breathability_score = max(0, min(100, (city.temp_summer - 10) / 30 * 100))
# 防潮需求:湿度越高,防潮需求越高
moisture_score = city.humidity
# 防晒需求:日照越强/海拔越高,防晒需求越高
uv_score = city.sunshine
if city.is_plateau:
uv_score += 20
if city.is_coastal and city.climate_zone == ClimateZone.TROPICAL:
uv_score += 15
return {
'warmth': warmth_score,
'breathability': breathability_score,
'moisture': moisture_score,
'uv_protection': min(100, uv_score)
}
def _calculate_climate_match(self, city: CityProfile, style: StyleProfile) -> float:
"""计算气候匹配度"""
city_norm = self._normalize_city_climate(city)
# 计算各项气候特征的匹配度(差值越小越好)
warmth_match = 100 - abs(city_norm['warmth'] - style.warmth_need)
breathability_match = 100 - abs(city_norm['breathability'] - style.breathability_need)
moisture_match = 100 - abs(city_norm['moisture'] - style.moisture_resistance)
uv_match = 100 - abs(city_norm['uv_protection'] - style.uv_protection_need)
# 加权平均
climate_score = (
warmth_match * self.climate_feature_weights['warmth'] +
breathability_match * self.climate_feature_weights['breathability'] +
moisture_match * self.climate_feature_weights['moisture'] +
uv_match * self.climate_feature_weights['uv_protection']
)
return max(0, min(100, climate_score))
def _calculate_culture_match(self, city: CityProfile, style: StyleProfile) -> float:
"""计算文化匹配度"""
# 审美适配:城市审美倾向 vs 风格优雅度
elegance_match = 100 - abs(city.aesthetic_tendency - style.elegance_match)
# 节奏适配:城市生活节奏 vs 风格节奏适配
pace_match = 100 - abs(city.life_pace - style.pace_match)
# 传统适配:城市历史底蕴 vs 风格传统度
tradition_match = 100 - abs(city.historical_depth - style.tradition_match)
# 前卫适配:城市时尚开放度 vs 风格前卫度
boldness_match = 100 - abs(city.fashion_openness - style.boldness_match)
# 加权平均
culture_score = (
elegance_match * self.culture_feature_weights['elegance'] +
pace_match * self.culture_feature_weights['pace'] +
tradition_match * self.culture_feature_weights['tradition'] +
boldness_match * self.culture_feature_weights['boldness']
)
return max(0, min(100, culture_score))
def recommend_for_city(self, city_name: str, season: str = 'winter') -> Dict:
"""
为指定城市生成推荐
Args:
city_name: 城市名称
season: 季节 (winter/spring/summer/autumn)
"""
if city_name not in self.city_db:
raise ValueError(f"城市 '{city_name}' 不在数据库中")
city = self.city_db[city_name]
results = []
# 季节调整系数
season_modifiers = {
'winter': {'warmth': 1.3, 'breathability': 0.7},
'spring': {'warmth': 1.0, 'breathability': 1.0},
'summer': {'warmth': 0.6, 'breathability': 1.4},
'autumn': {'warmth': 1.1, 'breathability': 0.9}
}
modifier = season_modifiers.get(season, {'warmth': 1.0, 'breathability': 1.0})
for style_tag, style in self.style_db.items():
# 计算基础匹配度
climate_match = self._calculate_climate_match(city, style)
culture_match = self._calculate_culture_match(city, style)
# 季节调整
adjusted_climate_match = (
climate_match * 0.7 +
climate_match * modifier['warmth'] * 0.15 +
climate_match * modifier['breathability'] * 0.15
)
# 综合得分
final_score = (
adjusted_climate_match * self.weights['climate'] +
culture_match * self.weights['culture']
)
results.append({
'city': city_name,
'season': season,
'style_tag': style_tag.value,
'style_description': style.description,
'climate_match': round(climate_match, 1),
'culture_match': round(culture_match, 1),
'final_score': round(final_score, 1),
'recommended_items': style.recommended_items,
'match_level': self._get_match_level(final_score)
})
# 按得分排序
results.sort(key=lambda x: x['final_score'], reverse=True)
return {
'city_profile': city,
'recommendations': results,
'top_recommendation': results[0],
'season': season
}
def _get_match_level(self, score: float) -> str:
"""根据得分返回匹配等级"""
if score >= 85:
return "⭐⭐⭐⭐⭐ 完美适配"
elif score >= 75:
return "⭐⭐⭐⭐ 高度适配"
elif score >= 65:
return "⭐⭐⭐ 中度适配"
elif score >= 55:
return "⭐⭐ 轻度适配"
else:
return "⭐ 勉强适配"
def batch_recommend(self, cities: List[str], season: str = 'winter') -> pd.DataFrame:
"""批量推荐"""
rows = []
for city in cities:
try:
result = self.recommend_for_city(city, season)
top = result['top_recommendation']
rows.append({
'城市': city,
'季节': season,
'推荐风格': top['style_tag'],
'综合得分': top['final_score'],
'气候匹配': top['climate_match'],
'文化匹配': top['culture_match'],
'匹配等级': top['match_level'],
'核心单品': '、'.join(top['recommended_items'][:3])
})
except ValueError as e:
print(f"警告: {e}")
return pd.DataFrame(rows)
# ──────────────────────────────────────────────
# 5. 可视化仪表盘模块
# ──────────────────────────────────────────────
class Dashboard:
"""地域穿搭推荐可视化仪表盘"""
STYLE_COLORS = {
'都市摩登': '#3498DB',
'北方御寒': '#2C3E50',
'江南淡雅': '#1ABC9C',
'巴蜀慵懒': '#E67E22',
'海滨度假': '#F1C40F',
'古都厚重': '#8B4513',
'先锋前卫': '#9B59B6'
}
@classmethod
def plot_dashboard(cls,
result: Dict,
batch_df: pd.DataFrame,
filename: str = "regional_style_dashboard.png"):
fig = plt.figure(figsize=(22, 18))
fig.suptitle(f'地域文化适配穿搭推荐分析 — {result["city"].city_name} ({result["season"]})',
fontsize=20, fontweight='bold', y=0.99)
city = result['city']
recs = result['recommendations']
# ── 图1:城市画像雷达图 ──
ax1 = fig.add_subplot(2, 3, 1, polar=True)
cls._plot_city_radar(ax1, city)
# ── 图2:风格匹配度对比 ──
ax2 = fig.add_subplot(2, 3, 2)
cls._plot_style_comparison(ax2, recs)
# ── 图3:气候-文化双因子分解 ──
ax3 = fig.add_subplot(2, 3, 3)
cls._plot_factor_decomposition(ax3, recs)
# ── 图4:全国城市推荐地图(简化版:柱状图) ──
ax4 = fig.add_subplot(2, 3, 4)
cls._plot_national_recommendations(ax4, batch_df)
# ── 图5:推荐单品词云(柱状图替代) ──
ax5 = fig.add_subplot(2, 3, 5)
cls._plot_item_recommendations(ax5, result['top_recommendation'])
# ── 图6:推荐报告摘要 ──
ax6 = fig.add_subplot(2, 3, 6)
cls._plot_summary_report(ax6, result)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.savefig(filename, dpi=150, bbox_inches='tight')
plt.show()
print(f"[INFO] 仪表盘已保存: {filename}")
@classmethod
def _plot_city_radar(cls, ax, city: CityProfile):
"""城市画像雷达图"""
categories = ['历史底蕴', '审美倾向', '生活节奏', '时尚开放度',
'冬季温度', '夏季温度', '湿度', '日照']
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!