ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python与混合推荐算法的服饰推荐系统设计与实现

基于Python与混合推荐算法的服饰推荐系统设计与实现 简介这是一套基于Python实现的服饰推荐系统完整项目面向毕业设计、课程设计与实际项目开发场景适合计算机相关专业学生与初中级开发者参考。项目采用前后端分离结构前端应用、服务端、脚本与图像数据集分目录组织附带项目文档覆盖数据采集、预处理、推荐逻辑与界面展示的完整链路。压缩包共2000个文件以1863张JPG图像样本为主辅以Python源码、Vue与JavaScript前端代码、XML/CSV/JSON配置数据以及BSON商品和搭配数据整体约223.8MB结构清晰便于按模块学习和二次开发。源码已经严格测试并配有说明文档可放心在此基础上延展使用。目前已有79人学习下载对需要完整项目参考、希望深入理解服饰推荐系统实现细节的读者具有较高参考价值。1. 服饰推荐系统的实际定位一个能直接拿去交差的完整工程做毕业设计或者课程设计的人最怕的不是题目难而是题目太泛。服饰推荐系统这个题目属于典型的「看着不新做起来事多」——它既要跑通数据、算法、接口又要有一整套能验收的文档。这套基于 Python 实现的服饰推荐系统把用户画像、服饰属性标签、协同过滤和内容推荐都串在了一起网页端能注册登录、选择风格偏好、看推荐结果后台有 Flask 接口支撑数据集和 SQL 脚本也都配好。它的价值不在于某个算法有多前沿而在于你拿到手之后改一改就能变成自己的毕设或者课设作品。适合谁正在选题的本科生、需要交课程设计的大三学生以及接了私活想快速交付的人。这篇文章会把整个工程的运行流程、关键代码、文档构成和最容易翻车的地方全部拆开讲。2. 数据与算法选型为什么这个系统用混合推荐而不是单一模型2.1 数据字段与用户交互表的搭建打开源码包之后第一件事是看dataset目录下的数据文件。这个系统用的不是公开的 MovieLens 那种电影评分数据而是自己构建的服饰数据。我拆过很多这样的项目服饰数据一般分两张核心表一张存服饰本身的属性一张存用户与服饰的交互记录。服饰表里常见的字段有clothing_id、category上衣/裤子/裙子/外套、color颜色标签、style休闲/通勤/运动/甜美、season春夏秋冬、material棉/麻/化纤、image_url。用户交互表则记录user_id、clothing_id、rating1到5分和timestamp。CREATE TABLE clothing ( clothing_id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, color TEXT NOT NULL, style TEXT NOT NULL, season TEXT NOT NULL, material TEXT, image_url TEXT ); CREATE TABLE user_clothing_rating ( user_id INTEGER NOT NULL, clothing_id INTEGER NOT NULL, rating INTEGER CHECK (rating BETWEEN 1 AND 5), timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, clothing_id) );这张user_clothing_rating表是整个推荐系统的核心。协同过滤算法全靠它来计算用户之间的相似度或者物品之间的相似度。为什么要单独建一张交互表而不是把评分字段塞进用户表因为一个用户会对多件衣服评分如果放在用户表里每加一条评分都要改用户表的记录后续查询和计算都变得极其低效。关系表的设计能让我们直接用 SQL 做聚合统计比如计算每个用户的平均评分、活跃度这些特征后面都要用到。2.2 协同过滤与内容推荐的取舍服饰推荐这个场景用单纯的基于用户的协同过滤会有一个很明显的毛病衣服不像电影用户不太可能给大量衣服打分大部分人只浏览、收藏、加购物车评分数据通常非常稀疏。所以这个系统里没有完全依赖协同过滤而是做了「基于标签内容推荐 协同过滤」的混合方案。基于内容推荐的逻辑很好理解——把用户点过、收藏过的服饰的属性标签提取出来统计用户对颜色、风格、类别的偏好然后按照这个偏好去匹配服饰表里没有看过的新衣服。这个方案在冷启动阶段特别重要因为新用户没有任何评分记录时协同过滤算不出任何相似用户。def compute_user_preference(user_id, ratings_df, clothing_df): user_ratings ratings_df[ratings_df[user_id] user_id] if user_ratings.empty: return None merged user_ratings.merge(clothing_df, onclothing_id) preference {} for attr in [style, color, season]: preference[attr] merged.groupby(attr)[rating].mean().to_dict() return preference这段代码的意思是把你评分过的每一件衣服的属性取出来按属性分组求平均分。比如你在休闲风格的服装上平均给了 4.8 分在运动风格上只给了 2.5 分那系统就知道你偏爱休闲风格。groupby之后返回的是一个字典键是具体属性值值是该属性下所有已评分衣服的平均分。这个偏好字典会作为后续内容推荐的特征输入。2.3 混合推荐的整体流程整个系统的推荐流程可以分成三步。第一步从数据库里读数据然后构建用户偏好画像和协同过滤的相似度矩阵第二步分别用内容推荐和协同过滤各自产生一份候选集这里注意各自都要做去重并且去掉用户已经评分过的衣服第三步按照一定权重把两份结果合并权重可以手动配默认是内容推荐 0.6、协同过滤 0.4。def hybrid_recommend(user_id, top_n10, content_weight0.6, cf_weight0.4): content_recs content_based_recommend(user_id, top_ntop_n * 2) cf_recs collaborative_filter_recommend(user_id, top_ntop_n * 2) score_dict {} for item, score in content_recs.items(): score_dict[item] score_dict.get(item, 0) content_weight * score for item, score in cf_recs.items(): score_dict[item] score_dict.get(item, 0) cf_weight * score sorted_items sorted(score_dict.items(), keylambda x: x[1], reverseTrue) return [item for item, score in sorted_items[:top_n]]这里有个细节值得注意两份候选列表都取了top_n * 2然后再合并排序取前top_n。原因是单模型给出的前 10 个结果里可能有不少是重复的或者用户已经看过的如果不扩大候选范围混合之后的结果会明显变少。我个人调试的时候更倾向于内容权重调高一点因为服饰类的属性标签相对明确用户对风格的偏好稳定性高于对某个具体用户的相似性。3. 把系统跑起来环境配置与核心代码复现3.1 环境依赖与项目目录结构拿到源码包后建议先用 Python 3.8 到 3.10 之间的版本太新的版本偶尔会出现一些第三方库还没适配的情况。项目根目录下的requirements.txt是这个系统能跑起来的关键里面主要有 Flask、pandas、numpy、scikit-learn 这几样。不要自己去 pip 乱装最新版严格按照这个文件装。pip install virtualenv virtualenv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install -r requirements.txt用虚拟环境是必须养成的习惯因为 Flask 和 scikit-learn 的版本之间偶尔会有依赖冲突不用虚拟环境的话你机器上原有的项目可能会被连带搞坏。装完依赖后项目根目录下应该能看到app.py、recommend/、models/、data/、static/、templates/这几个关键目录。recommend/里放的是推荐算法的实现models/里是数据库模型定义templates/里是网页模板。3.2 协同过滤相似度计算的复现系统里的协同过滤部分用的是基于物品的相似度计算也就是 ItemCF。ItemCF 的核心思想是如果两件衣服经常被同一批用户购买或评分那这两件衣服就是相似的用户喜欢其中一件就大概率喜欢另一件。相比基于用户的 UserCFItemCF 在电商场景的推荐里更稳定因为物品的相似关系相对固定不需要频繁重新计算。from sklearn.metrics.pairwise import cosine_similarity def build_item_similarity(ratings_df): user_item_matrix ratings_df.pivot_table( indexuser_id, columnsclothing_id, valuesrating, fill_value0 ) item_similarity cosine_similarity(user_item_matrix.T) item_ids list(user_item_matrix.columns) sim_df pd.DataFrame(item_similarity, indexitem_ids, columnsitem_ids) return sim_dfpivot_table的作用是把长表转成宽表每一行是一个用户每一列是一件衣服值是对应的评分。转置之后求余弦相似度得到的矩阵里第i行第j列的数字就是衣服i和衣服j的相似度范围在 0 到 1 之间。1 意味着两件衣服被完全相同的用户以相同的评分对待0 意味着完全没有交集。这个矩阵在数据量大起来之后会非常耗内存所以系统里默认只对评分次数超过阈值的衣服做计算这个阈值放在配置文件的MIN_RATING_COUNT参数里。3.3 基于内容的标签匹配代码内容推荐部分系统用了一个很直接的做法把用户偏好和每件衣服的属性做加权匹配。每匹配上一个属性就累加一次权重分最后按总分排序。def content_based_recommend(user_id, top_n10): preference compute_user_preference(user_id, ratings_df, clothing_df) if preference is None: return get_popular_clothing(top_n) scores {} unseen clothing_df[~clothing_df[clothing_id].isin( ratings_df[ratings_df[user_id] user_id][clothing_id] )] for _, item in unseen.iterrows(): score 0.0 for attr in [style, color, season]: attr_score preference[attr].get(item[attr], 0) score attr_score scores[item[clothing_id]] score return dict(sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n])第 3 行有个很关键的兜底逻辑如果compute_user_preference返回None说明这个用户一条评分都没有系统会直接返回热门衣服的列表而不是返回空结果。unseen变量筛选出了用户没看过的衣服这一步不能省否则推荐列表里会出现用户已经买过的衣服体验很糟糕。属性匹配的分数直接累加好处是代码简单、可解释性强——你可以跟用户说「因为您偏好休闲风格、喜欢深色系、主要在秋天穿所以推荐了这些」。3.4 API 接口与前端页面的联调后端服务用 Flask 起了两个核心接口一个是注册登录另一个是获取推荐结果。app.py里最核心的接口定义大概长这样app.route(/api/recommend, methods[GET]) def recommend_api(): user_id request.args.get(user_id, typeint) if not user_id: return jsonify({error: user_id is required}), 400 rec_list hybrid_recommend(user_id, top_n10) rec_clothing [ { clothing_id: i, name: clothing_map[i][name], image_url: clothing_map[i][image_url], reason: generate_reason(i, user_id) } for i in rec_list ] return jsonify({code: 0, data: rec_clothing})generate_reason函数是这套系统在毕设答辩时的一个亮点它会把推荐命中的属性标签拼成一句话比如「因为您在休闲风格上评分较高为您推荐这件休闲衬衫」。前端拿到这个字段后直接渲染在卡片下方用户就知道为什么推荐这件衣服而不是看到一个黑匣子一样的推荐列表。这段接口代码里我比较在意的是参数校验user_id如果不是合法的整数接口直接返回 400 而不是抛异常这一点在答辩演示时能省掉不少尴尬。4. 项目文档的构成从需求文档到答辩 PPT 的完整链路4.1 源码包里的文档清单很多人下载资源后只盯着代码跑其实这个包里最值钱的是文档。毕设和课设的评分标准里文档通常占 30% 到 40% 的比例。这套资源里包含了五类文档需求分析说明书、数据库设计文档、系统详细设计文档、测试报告、答辩 PPT 模板。每一份文档都是按本科毕设的格式要求写的章节编号、图目录、表目录都是齐全的。文档名称核心内容对应开发阶段需求分析说明书用户角色、用例图、非功能需求前期调研数据库设计文档ER 图、表结构、字段说明设计阶段系统详细设计文档架构图、算法流程、接口定义设计阶段测试报告测试用例、缺陷记录、结论测试阶段答辩 PPT 模板项目背景、技术架构、演示效果答辩准备我见过太多人在答辩前一周才开始写文档最后交上去的东西一看就是凑数的。到手这份文档之后建议你第一时间打开需求分析说明书把项目背景那一段用自己的话改写一遍因为答辩老师很可能问的就是「这个系统要解决什么问题」你如果答得和文档里一字不差老师马上就知道你没有参与真实项目。4.2 数据库设计与 ER 关系梳理数据库设计文档里给了一个完整的 ER 图核心实体有四个用户、服饰、服饰标签、评分记录。你和服饰之间是多对多的关系评分记录是它们的联系表。这套设计在很多电商推荐系统里都能复用换一个领域就能变成图书推荐、美食推荐。CREATE TABLE user ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, preferred_style TEXT, preferred_color TEXT, register_time DATETIME );运维上需要注意的一点是preferred_style和preferred_color这两个字段在用户注册时会要求选择但这只是最粗粒度的偏好真正的偏好还是从行为数据里算出来的。这意味着系统里存在两套偏好一套是用户主动声明的另一套是从数据中推断的。推荐算法的优先级上主动声明的偏好权重要高于推断偏好这个规则在详细设计文档里有单独一节说明答辩时如果被问到你可以直接引用这个设计决策。4.3 测试用例与验收标准测试报告里的内容不是那种「点开页面—没报错—通过」的水货而是把每个模块的接口都配了输入、输出、预期结果。比如推荐接口的测试用例可以复用下面的 JSON 格式{ 用例编号: TC-REC-001, 接口: /api/recommend, 前置条件: 用户 1001 已有至少 5 条评分记录, 输入: {user_id: 1001}, 预期输出: 返回 10 条服饰记录每条包含 clothing_id、name、reason, 实际结果: 返回 10 条reason 字段无空值 }写测试用例的时候有个潜在的坑很多人把「接口不报错」当成「测试通过」实际上要验证推荐列表里不能包含用户已经评分过的衣服这个用一条 SQL 就能查出来但大多数学生写的测试报告里根本没有这一项。你可以往测试报告里补一条「推荐结果中不包含已交互服饰」的用例这一条就能让答辩老师觉得你真的在系统层面思考过问题。5. 避坑指南服饰推荐系统开发中的典型问题排查5.1 冷启动新用户没有任何评分推荐列表为空现象注册一个新账号登录系统点击获取推荐前端页面一片空白接口返回data为空列表。原因hybrid_recommend函数里先调content_based_recommend这个函数在用户没有评分时返回None虽然写了兜底逻辑但兜底逻辑只出现在内容推荐内部如果协同过滤部分也直接按矩阵计算就会出现空结果。解决把兜底逻辑从函数内部上提到混合推荐的入口。在hybrid_recommend的开头判断用户评分数量如果为 0直接返回热门服饰列表不再进入任何推荐算法分支。同时在前端做一个判断如果data为空展示「先去逛逛并收藏喜欢的服饰吧」的提示页不要展示空白的推荐区域。5.2 中文路径和编码导致的数据库读取问题现象在 Windows 上运行init_db.py初始化数据库时报UnicodeDecodeErrorSQL 脚本里中文注释乱码服饰数据插进去之后显示成乱码。原因Windows 系统默认编码不是 UTF-8Python 打开 SQL 和 CSV 文件时如果没指定编码就会用系统默认编码去读中文数据直接坏掉。解决在所有打开文件的代码里显式指定编码不要依赖默认值。写 SQL 脚本时保存为 UTF-8 格式并在 Python 读取时加一条encodingutf-8参数。如果你用的是 PyCharm需要在 File Encoding 里把项目全局编码设为 UTF-8这个不设置会有很多隐藏的诡异问题。5.3 数据稀疏导致的相似度矩阵全为零现象协同过滤推荐出来的是随机衣服每次刷新结果都不一样而且推荐的衣服和用户历史偏好明显不符。原因服饰数据的评分数量太少比如总共只有几十个用户、几百条评分记录很多衣服只被一个人评过分计算余弦相似度时矩阵里大量是零向量相似度全部为零或极小排序时相当于在随机排。解决给 ItemCF 加一个「共现次数」的门槛也就是两件衣服至少要被同一个用户同时评分过 N 次以上才把相似度纳入计算。另一个办法是引入类目补全如果找不到相似的衣服退而匹配同风格同色系的其他衣服。这个策略在系统里叫做规则兜底虽然是土办法但在毕设场景里比上模型效果更可靠。5.4 Flask 前后端联调时的跨域问题现象前端页面单独用npm run dev起在 3000 端口后端 Flask 跑在 5000 端口浏览器里调用接口显示CORS policy: No Access-Control-Allow-Origin header is present。原因浏览器的同源策略不允许不同端口之间的跨域请求后端没有返回跨域响应头。解决用flask-cors扩展初始化时直接允许所有来源毕设阶段不需要把跨域配得太严格。from flask_cors import CORS CORS(app, resources{r/api/*: {origins: *}})这段代码放在app.py里创建 Flask 实例之后。资源的匹配规则只对/api/开头的接口生效避免把静态资源也暴露出去。加了flask-cors之后如果还报跨域多半是前端用了application/x-www-form-urlencoded的 POST 请求而后端只处理 JSON这时需要在请求头里加Content-Type: application/json。5.5 推荐结果不稳定每次点击刷新结果顺序都变现象同一个用户连续刷新三次推荐接口推荐的衣服集合一样但显示顺序不同每隔几秒排序就变一次。原因在这些代码里如果对评分相同的物品做sortedPython 的排序是稳定的但多个候选集的合并过程中字典的遍历顺序是基于插入顺序的而插入顺序来自不同的集合运算导致同分物品的前后顺序不确定。解决在sorted时同时传入两个排序键主键是推荐分数次键是固定不变的衣服 ID这样同分数的物品会按 ID 升序排列结果每次都一致。注意别把里层的外层的键搞混。6. 让推荐效果真正可用的三个进阶做法第一个进阶做法是召回和排序分离。当前系统的hybrid_recommend做的是「召回 20 条再混合排序取前 10」这其实已经是召回/排序的雏形。如果想要效果更好可以再加一层重排序前 10 条结果不要直接输出而是再用一条规则去重和打散——比如连续出现的必须是不同类别的衣服不能让五条裙子排在一起。第二个做法是加入时间衰减。服饰是一个季节性很强的品类冬季的羽绒服如果推荐给用户其他条件相同的情况下夏装的权重应该更高。实现方式也不复杂在计算用户偏好时给评分乘一个时间衰减系数越近的评分权重越大做的事情本质上是给timestamp增加一个指数衰减函数。import math def time_decay_weight(timestamp, half_life_days30): days_since (current_time - timestamp).days return 0.5 ** (days_since / half_life_days)half_life_days的含义是评分权重减半需要的天数默认 30 天意味着一个月前的评分权重只有现在的一半。这个函数可以直接嵌入之前写好的compute_user_preference里把原来的rating乘以权重系数。答辩时讲这个点老师会很容易理解你是考虑了业务场景的。第三个做法是输出可解释的推荐理由。整套推荐代码里最容易被忽略却又最容易被老师追问的就是「为什么推荐这条裙子」。我的习惯是在输出结果之前记录每一件被推荐衣服命中了哪些用户偏好标签再把标签组合成自然语言。比如用户的偏好字典里休闲风格是 4.8 分衣服的风格也是休闲那推荐理由就是「休闲风格是您评分较高的类型这件衣服也属于该风格」。这个逻辑不用写到多复杂的程度一个if嵌套就能实现但它能证明不推荐是拍脑袋的事。整套系统跑通之后我自己每次调试时都会强制走一遍三件事先清空数据库重新初始化确认数据没问题再注册一个新账号验证冷启动兜底最后测试推荐结果与已评分数据不能重复。这三步走完之后才会去动算法参数不然改了一堆参数分不清是算法提升了还是数据的随机波动。从那以后我接手的每个推荐类项目都保留这个习惯这套服饰推荐系统能让你在毕设答辩时少流很多汗。希望这篇拆解帮到你拿到想要的分数。本文还有配套的精品资源点击获取
返回列表