1. 项目概述与选题价值
1.1 这个毕设到底解决什么问题
我第一次看到“基于Python+协同过滤算法的天气穿搭推荐系统”这个题目时,第一反应是:这不就是把天气数据和推荐算法拼在一起吗?等真正做下来才发现,这个选题的精妙之处就在于它完美踩中了毕业设计的三个核心诉求——有算法深度、有完整业务闭环、有可视化展示。
这个系统做的事很简单:用户输入一个城市,系统根据实时天气情况(温度、湿度、风力、天气状况),结合用户的历史穿搭偏好和相似人群的穿搭选择,推荐出最适合今天的衣服组合。它不像市面上那些“天气APP自带的穿衣指数”那样一刀切——温度低就让你穿厚点,而是真正做到了“千人千面”的个性化推荐。
对于准备做毕设的同学来说,这个项目的技术栈覆盖面非常让人舒服:Python作为主力语言、爬虫或API获取天气数据、协同过滤算法做推荐、Flask搭Web界面、MySQL存数据,面试时能聊的点非常集中。就算毕业后不搞推荐算法方向,这段经历也能证明你完整走过“数据采集→数据清洗→特征构建→算法建模→系统实现”的全流程。
1.2 需要具备哪些基础
我接触过不少拿这个题目做毕设的同学,最后做崩的基本都是因为基础没打牢就急着写码。这里我先给个自测清单:
- Python基础:至少能熟练使用字典、列表、文件读写,能用pandas做简单的DataFrame操作,不用精通但得能看懂别人写的代码并改得动。
- 数据库基础:懂SQL的增删改查,知道什么是主键、外键、多表关联。系统里用户表、穿搭表、评分表是三个核心表,这些表的关联逻辑搞不清楚后面必乱。
- 推荐算法认知:不要求你手推公式,但必须理解协同过滤的核心思想——“物以类聚,人以群分”,知道基于用户和基于物品两种协同过滤的区别。
- 前端基础:这个其实要求最低,用现成的Bootstrap模板或简单HTML页面就够了,真正的工作量在Flask的模板渲染和后端数据处理上。
如果你已经具备这些基础,接下来的内容会帮你避开大部分坑。如果还没全掌握,建议先把pandas和Flask快速过一遍,不需要精通,够用就行。
1.3 我的整体评价
帮别人调试这个项目时我最大的感触是:它的难点不在于某个单独的技术点,而在于如何把天气数据和推荐算法有机结合起来。很多人做出来的东西只是个简单的“if温度<10度推荐棉袄”,那根本用不上协同过滤,纯属自欺欺人。真正的关键在于:天气数据作为“过滤条件”,协同过滤作为“排序引擎”,两者分工合作,让推荐结果在符合天气前提的同时,又贴合个人审美偏好——这条主线想清楚,系统架构不会跑偏,论文也能有真正的核心章节可写。
2. 系统架构与核心设计思路
2.1 整体架构选型
先聊技术选型。我自己做毕设辅导时,给出的推荐组合是:
后端框架:Flask。有人喜欢用Django,但我更推荐Flask——轻量、灵活、上手快,把RESTful API写好后,前端页面直接用Jinja2模板渲染就能跑通。毕设答辩时老师问起框架原理,Flask这点东西半小时能讲透,Django的中间件、ORM、Admin后台反而容易把自己绕晕。
数据库:MySQL。虽然SQLite更轻量,但简历上写“熟练使用MySQL”和“用过SQLite”是完全两个效果。而且课题给的源码文档里基本都配了MySQL,保持同环境习惯,最后导数据和答辩演示都方便。如果你的本机没装MySQL,直接用Navicat或Docker起一个也行,记住导出SQL文件时要带上结构。
天气数据来源:公开天气API。我推荐用和风天气的免费开发者接口,注册后能拿到Key,按城市代码请求JSON数据,包括当前温度、相对湿度、风速、天气现象。免费版完全够毕设演示用了。如果API本身挂了,那就用准备好的历史天气JSON做降级兜底,保证系统不崩。
前端展示:Bootstrap模板 + ECharts。ECharts用来画温度趋势、风格分布这些图表,是答辩时的亮点。不建议自己手写CSS,浪费时间且不好看。
2.2 核心设计难点:天气数据如何参与协同过滤
这是整个系统的灵魂环节。我见过好几份开源的项目源码,都存在一个普遍问题:把天气标签硬编码成几个规则,比如“温度低于5度就推荐羽绒服”,然后协同过滤只管“猜你喜欢”,天气和算法完全脱钩。
我的做法是把天气信息转换为“场景化特征向量”,让算法在算相似度时能感知天气带来的影响。具体拆解成三层:
第一层是天气特征归一化。把温度、湿度、风力映射到合适的区间。比如人体舒适温区是18-25度,那我把最高温和最低温按“距舒适温区的偏差”做归一化。这样比直接用“30度”“5度”这种绝对数字要合理得多。
第二层是穿搭标签的向量化映射。每件衣服入库时给它打上适用天气标签,比如“短袖T恤”是“高温/炎热”,“风衣”是“微风/凉爽”,“羽绒服”是“严寒/强风”。这些标签不是拍脑袋定的,而是从穿衣指数分级对照表里提炼的。
第三层才是协同过滤。系统先根据当前天气筛出符合条件的所有穿搭物品形成候选集,再用协同过滤算相似度,对候选集里的物品做个性化排序。换句话说,天气负责圈定范围,协同过滤负责在范围内做精细排序。这样既不会推荐出“大冬天穿短袖”的离谱结果,又能照顾到“同样20度,有人觉得穿卫衣就行,有人觉得得加件薄外套”的个人差异。
2.3 用户冷启动问题的折中方案
推荐系统里有个绕不开的痛点——新用户没有任何历史交互数据,协同过滤根本无从算起。毕设系统里用户量本来就少,这个问题会被放大得更明显。
我采用的折中方案是:新用户注册时强制勾选“体感偏好”,比如“怕冷”“怕热”“正常体感”,外加“偏好的穿搭风格”多选项。这样系统把从“体感偏好”映射的规则分数作为基础推荐分数,随着用户逐渐产生浏览和收藏行为,再逐步过渡到协同过滤结果上。
这个设计对毕设来说有着双重的价值:提升了真实使用体验,论文里也多了一个可以展开写的点——冷启动问题的分析与解决。答辩时老师很爱问这个,能把这块讲清楚,拿分点就有了。
3. 数据库设计与算法实操拆解
3.1 三张核心表的设计思路
推荐系统的数据层逃不开三张表:用户表、物品表、交互表。我们这个项目里具体化为以下设计:
用户表
CREATE TABLE `user` ( `user_id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) UNIQUE NOT NULL, `password` VARCHAR(255) NOT NULL, `hot_tolerance` TINYINT DEFAULT 1 COMMENT '体感偏好:0怕冷,1正常,2怕热', `style_pref` VARCHAR(255) COMMENT '风格偏好,多选用逗号分隔', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP );穿搭物品表
CREATE TABLE `outfit` ( `outfit_id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `category` VARCHAR(20) COMMENT '上衣/下装/外套/鞋履', `style` VARCHAR(20) COMMENT '简约/运动/通勤/甜美/休闲', `min_temp` FLOAT COMMENT '适用最低温度', `max_temp` FLOAT COMMENT '适用最高温度', `weather_tag` VARCHAR(50) COMMENT '晴/雨/风/雪/阴', `image_url` VARCHAR(255), `description` TEXT );交互评分表
CREATE TABLE `ratings` ( `rating_id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `outfit_id` INT NOT NULL, `score` TINYINT DEFAULT 0 COMMENT '评分1-5,浏览3,收藏4,直接使用5', `weather_tmp` FLOAT COMMENT '当时的温度', `weather_code` VARCHAR(20) COMMENT '当时的天气编码', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `user`(`user_id`), FOREIGN KEY (`outfit_id`) REFERENCES `outfit`(`outfit_id`) );这里我特别想强调ratings表里的weather_tmp和weather_code两个字段。这是我踩坑后加上的——协同过滤算法评分时,不应该只看“人和衣服”的关系,还得看“这种关系发生在什么天气条件下”。比如有用户25度时给某件衬衫打了5分,系统不该把这个高分直接套用到15度的天气里。加上天气字段后做条件过滤,推荐合理性会有质的提升。
3.2 协同过滤算法的落地实现
我们选的是基于物品的协同过滤(ItemCF),核心思想是:如果用户A喜欢衣服X,而衣服X和衣服Y在大量用户的行为里表现出高度相似(被同一批人喜欢),那当用户A面对衣服X和衣服Y时,系统会优先把Y也推荐给他。
套用经典公式,物品间相似度的计算用余弦相似度:
sim(i,j) = |N(i) ∩ N(j)| / sqrt(|N(i)| * |N(j)|)Pieter(i)和Pieter(j)分别代表对衣物品i和物品j有过行为(如评分、收藏)的用户集合。这个公式计算的是:同时喜欢i和j的人数,占喜欢i和j总人数的比例。数字越接近1,说明这两个物品的受众越重叠,相似度越高。
Python实现的核心代码很简洁:
import pandas as pd from sklearn.metrics.pairwise import cosine_similarity # 构造用户-物品评分矩阵 ratings_df = pd.read_sql("SELECT user_id, outfit_id, score FROM ratings", conn) matrix = ratings_df.pivot_table(index='user_id', columns='outfit_id', values='score').fillna(0) # 计算物品间相似度矩阵 item_sim = cosine_similarity(matrix.T) item_sim_df = pd.DataFrame(item_sim, index=matrix.columns, columns=matrix.columns) def recommend_outfits(user_id, top_n=5): user_rated = matrix.loc[user_id] # 找到用户已评分的物品 rated_items = user_rated[user_rated > 0].index # 初始化推荐分数 scores = {} for item in rated_items: # 和当前物品相似的其他物品 sim_items = item_sim_df[item].sort_values(ascending=False)[1:10] for sim_item, sim_score in sim_items.items(): if sim_item in rated_items: continue scores[sim_item] = scores.get(sim_item, 0) + sim_score * user_rated[item] # 按分数排序返回 sorted_items = sorted(scores.items(), key=lambda x: x[1], reverse=True) return [item for item, _ in sorted_items[:top_n]]这段代码的逻辑是:遍历用户所有已评分物品,找到和这些物品最像的其他物品,然后用“相似度乘以用户对原物品的评分”累加得到候选物总分。用户给某件衣服打了5分,系统就认为他喜欢和这件衣服相似的其他衣服,且原评分越高,这种信任转移的权重就越大。
3.3 天气过滤与算法候选集的融合方式
协同过滤算完之后,不能直接把结果抛给用户。正确的流程是:
- 获取当前城市天气,解析出温度temp、天气现象code、湿度humidity、风速windDir。
- 用规则引擎初筛:
outfit.min_temp <= temp <= outfit.max_temp,再把天气标签weather_tag对应上。比如下雨天,候选集里必须保留“防雨”标签的衣物。 - 协同过滤只对初筛后的候选集算分排序。
这一步非常关键——如果先算协同过滤再过滤天气条件,很可能协同过滤出来的推荐结果刚好被天气规则全部过滤掉,最后系统什么都推荐不出来。那场面在答辩演示时说实话挺尴尬的。正确的做法是先粗筛,后精排,这也是我这个项目在答辩时最常讲的优化点,老师对此很认可。
用代码表达式就是:
weather_filtered = outfit_df[(outfit_df['min_temp'] <= current_temp) & (outfit_df['max_temp'] >= current_temp) & (outfit_df['weather_tag'].str.contains(current_condition))] final_recommend = itemcf_recommend(weather_filtered, user_id)3.4 初始化数据:让协同过滤“有东西可算”
初用系统时,一个最大的“坑”是把ratings表做成了空的,测试协同过滤只能得到“无结果”。后来我学乖了,用脚本批量造数据:
import random # 构造模拟评分数据 for user_id in range(1, 11): for outfit_id in range(1, 31): if random.random() < 0.4: rating = random.randint(3, 5) # 插入评分数据 cursor.execute("INSERT INTO ratings (user_id, outfit_id, score) VALUES (%s, %s, %s)", (user_id, outfit_id, rating))构造了至少几百条评分记录,推荐结果才稳定。而且我建议评分分布的“形状”要贴合实际——夏天的衣服在夏天数据里评分高,冬天的衣服在冬天评分高;它们并存,协同过滤才能有季节区分的“直觉”。
我还编了一些“用户画像”进入init_data.py:比如“用户A”高温天爱穿清凉通勤装、下雨天爱穿防潮外套;“用户B”是运动风格重度用户。种子数据越有倾向性,后面的个性化推荐就越有区分度,演示起来也更有说服力。
4. 实操过程与关键环节实现
4.1 整体开发流程概览
我在实际动手时把开发流程切成了六大步骤,每一步有明确的输出物。照这个顺序做,基本不会乱:
- 环境准备:装Python 3.9+,建虚拟环境,安装flask、pandas、sklearn、requests、pymysql。
- 创建数据库:按上面的表结构创建三个表,写入初始数据。
- 对接天气API:先用Postman测试接口,确认返回字段,再封一个weather_util.py。
- 实现协同过滤算法模块:写一个recommend.py,封装上面的核心代码。
- 开发Flask Web项目:路由分4个:首页/今日推荐/历史记录/用户偏好设置。
- 联调与美化:把前端Bootstrap模板套上,ECharts图表插入,测试整条链路。
4.2 天气API对接的细节
去和风天气的开放平台注册一个免费账号,创建一个应用拿到API Key后,核心请求就一个:
import requests def fetch_weather(city): # city传城市代码,比如"101010100"代表北京 url = ( f"https://devapi.qweather.com/v7/weather/now?" f"location={city}&key=你的API_KEY" ) resp = requests.get(url) if resp.status_code == 200: data = resp.json()["now"] return { "temp": float(data["temp"]), "text": data["text"], # 如"晴""多云""小雨" "humidity": float(data["humidity"]), "windDir": data["windDir"], "windScale": data["windScale"] } return None注意:免费开发者配额每天只有一定次数,写代码时一定要加缓存。我用了个简单技巧——以“城市+当天日期”做key,每天第一次请求时存到本地JSON文件,后续直接读缓存,既不浪费配额也提高响应速度。
4.3 Flask路由与渲染的关键代码
核心路由不复杂,重点是逻辑要清晰:
from flask import Flask, render_template, request, redirect, url_for from recommend import get_recommendations app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/recommend/<int:user_id>") def recommend(user_id): city = request.args.get("city", "101010100") weather = fetch_weather(city) if not weather: weather = {"temp": 20, "text": "晴", "humidity": 50, "windDir": "东南风", "windScale": "2"} recs = get_recommendations(user_id, weather) return render_template("recommend.html", weather=weather, outfits=recs, user_id=user_id) @app.route("/preference/<int:user_id>", methods=["GET", "POST"]) def preference(user_id): if request.method == "POST": hot_tolerance = request.form.get("hot_tolerance") style_pref = request.form.get("style_pref") # 更新数据库里的用户偏好 update_preference(user_id, hot_tolerance, style_pref) return redirect(url_for("recommend", user_id=user_id)) return render_template("preference.html", user_id=user_id) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)4.4 ECharts图表展示技巧
为了让答辩时界面看起来有说服力,我在“个人中心”页放了两个图表:
一是近7天穿搭风格分布柱状图:统计用户当前天气推荐里出现最多的风格标签,比如“简约风占40%”“通勤风占25%”。这样评委一眼就能看出系统确实在学习用户偏好。
二是候选集分数Top10条形图:展示推荐物品在推荐排序中的计算分数。ECharts的配置不算复杂,只需准备好json数据就能渲染:
var chartData = {{ chart_data | safe }}; var myChart = echarts.init(document.getElementById('chart')); var option = { xAxis: { type: 'category', data: chartData.names }, yAxis: { type: 'value' }, series: [{ type: 'bar', data: chartData.scores }] }; myChart.setOption(option);这里的chart_data是从Flask视图函数通过json.dumps传过来的。前端模板里用| safe就是为了防止Jinja2转义把JSON搞坏掉键值结构。
5. 开发过程中的高频报错与排查要点
5.1 相似度矩阵稀疏全零
这是我调试时看到过最多的问题:运行协同过滤时matrix里全是0,导致相似度计算毫无意义。原因往往是ratings表里要么没数据,要么数据太过稀疏(比如每个用户只有一两条评分)。解决办法很简单,先检查一下数据规模:
SELECT COUNT(*) FROM ratings; SELECT COUNT(DISTINCT user_id) FROM ratings; SELECT COUNT(DISTINCT outfit_id) FROM ratings;如果ratings少于50条记录,就别指望协同过滤能算出好东西。宁可在初始化脚本里多造一些带倾向性的数据。另外在计算cosine_similarity之前要对矩阵做一下非零检查。
5.2 天气筛选后候选集为空
用6月的数据跑冬季场景,初筛后候选集可能就空了。但这其实不是“bug”,而是数据分布问题。两个解决办法:
第一,初始化衣物数据时保证覆盖各种温度段和天气场景,至少每个温度段裹七八件单品,从短袖短裤到羽绒服都有。
第二,在代码里做降级:候选集若为空,就把温度条件放宽到相邻几度。比如:
if weather_filtered.empty: weather_filtered = outfit_df[(outfit_df['min_temp'] <= current_temp + 5) & (outfit_df['max_temp'] >= current_temp - 5)]5.3 中文乱码问题
又是老生常谈但实在重要:MySQL建表时字符集必须是utf8mb4,连接加charset="utf8mb4",PyMySQL连接时也要显式传入:
conn = pymysql.connect( host='localhost', user='root', password='yourpass', database='recommend_db', charset='utf8mb4' )文本显示乱码时,优先检查数据库字符集,别急着改代码。以及,JSON返回时中文字符不乱码,记得在Flask里设置app.config['JSON_AS_ASCII'] = False。
5.4 协同过滤算出来的结果没有区分度
如果每个用户收到的推荐结果是同一批衣服,十之八九是因为用户间的评分模式太一致了。我在初始化数据时就刻意加入了随机偏移:同一个物品对于不同用户的评分并非全同,而是围绕5、4、3等档位有小幅波动。这样用户相似度才拉开差距。如果你用的公开源码里数据是同一个模板复制的,那就会出现这个问题。
5.5 推荐结果重复
协同过滤返回Top5时,如果相似物品列表里反复出现同一件衣服,或者相似度矩阵里的对角元素(自己和自己的相似度)被重复累加,就会出现推荐里全是同一件衣服的情况。
处理方式:在聚合分数时加个if sim_item not in candidate_scores检测,或者返回前用set去重。另外一提到截取相似物品列表时,记得跳过自己:[1:10]而不是[:10]。第0个item就是自己,这是新手最容易犯的错误。
6. 答辩演示与项目经验总结
6.1 演示脚本该怎么设计
答辩时的现场演示别打无准备之仗。我建议把演示流程固定成五步走:
- 画漫画式开场:用一张架构图讲清楚用户、天气API、推荐算法、数据库之间的关系。
- 造一个用户:现场注册一个新用户,选好体感偏好为“怕冷”,风格偏好“通勤”。
- 选一个冷天气城市:比如哈尔滨,展示系统推荐结果包含羽绒服和厚外套,同时展示用户A怕冷程度如何影响排序。
- 切换一个温暖的城市:比如三亚,让系统推荐短袖衬衫、薄裙,展示天气筛选生效。
- 对比另一个用户:选一个“偏好运动风”的用户登录同一城市,展示两屏推荐结果的差异。这个对比是整个演示的高光时刻,“个性化”三个字不需要多说。
6.2 老师爱问的几个问题
提前准备这几个高频答辩问题,现场就不慌:
为什么用基于物品的协同过滤而不用基于用户?衣物数量远小于用户数量,且物品相似度相对稳定,不用每次登录重新计算整个用户相似矩阵,实时性更好。
冷启动怎么解决?新用户没有行为数据时,用个人偏好设置(体感、风格)生成初始推荐,积累到一定评分数据后再切协同过滤。
天气数据对推荐结果的影响如何体现?天气作为硬过滤条件先圈出候选集,再进入协同过滤排序,所以任何推荐结果都物理符合当天天气,这是和纯推荐算法的根本区别。
协同过滤的缺点有哪些?方案是什么?稀疏性问题和冷启动是两大短板,我通过种子数据填充和偏好设置兜底双向处理。
6.3 论文的写作支点
这份毕设写论文时,四个章节取舍很关键:
- 需求分析章:重点写天气场景下的用户痛点,以及现有“一刀切”穿衣指数的问题。
- 系统设计章:以三张表和“筛选-排序”两阶段框架为核心,画好架构图。
- 算法实现章:写出相似度公式和Python伪代码,结合具体天气案例演算。
- 系统测试章:分别测试规则筛选、协同过滤准确率以及不同天气下的推荐比较。
6.4 关于“远程调试+讲解+定制”这类关键词的提醒
现在市面上很多毕设源码带远程调试和定制服务,用得好能省时间,但也有些坑:一是远程调试时尽量问对方要完整的SQL文件,否则连数据库都要一个个手敲;二是“定制”内容得提前把需求列成清单,别模糊地说“帮我改一下”;三是拿到代码先自己跑环境中走一遍。
我见过太多同学拿到一套源码,连Flask配置都没改就跑不起来,最后疯狂@卖家。其实多数跑不起来的原因都在虚拟环境依赖版本上,先执行pip install -r requirements.txt也不一定够,有时需要手动装某个特定版本的包。
7. 写在最后的心得
这个项目我调试了好几轮,有一个体会想分享:毕设的意义不在于做出一个性能碾压大厂的推荐系统,那本来也不现实;真正有价值的是,你能把一个有应用场景的问题,用一套完整的技术方案论证清楚,并诚实地说出它的局限性和可优化的方向。
如果后续你想在这个系统上继续扩展,我建议的方向是:把协同过滤的相似度计算换成“矩阵分解”,或引入隐语义模型(LFM)来做特征提取;再要么就是加入“衣柜管理”功能,让用户先“拥有”衣服,系统只从已有衣橱里做搭配推荐。这一下子就从“推荐单品”进化到了“推荐穿搭方案”,实用性强了一大截。
最后再分享一个实际调试的小窍门:跑Flask程序时别开着Debug模式去演示,万一报错页面弹出一堆红色堆栈信息,答辩观感会非常差。预先把可能出现的异常都try-except兜住,给用户展示友好的提示页面,系统“稳不稳”在演示时比“酷不酷”重要得多。
无论你是拿着这套源码二次开发,还是准备完全自己写,先把上面的表结构建好,再跑通一个最小链路:城市→天气→初筛→协同过滤→结果展示,后面的所有功能都是在这条链路上做深做细。祝你的毕设顺利,答辩稳过。