
我当初接下“基于协同过滤算法的家居选购系统”这个毕设题目时觉得它跟普通电商推荐没什么区别无非是算相似度、取Top-N、套个Web壳子。直到真正动手才发现家居品类是所有推荐场景里最难啃的一类——低频、高客单、强主观偏好用户可能一年才买两件家具但每件都决定了整个空间的风格走向。这个系统我用了Python Flask做后端MySQL存业务数据前端用Bootstrap加jQuery搭建算法部分自己实现了UserCF和ItemCF两套协同过滤逻辑并附带了完整的源码、SQL脚本和一份模拟家居交互数据集。如果你是准备做推荐系统方向毕设的同学或者想在真实电商环境里落地协同过滤这篇文章基本能帮你把整条链路串起来从数据表设计、相似度矩阵构建、推荐接口开发到冷启动和多样性调优都讲清楚绝对比网上那些只贴公式的博客实在得多。1. 为什么家居选购场景需要协同过滤1.1 家居推荐的难点低频、高客单、强偏好做家居推荐前我先盘过它的业务特点结果发现跟电影、图书推荐完全不是一个套路。电影用户一天能看两部浏览行为多、评分密度高协同过滤可以轻松找到相似兴趣群体家居用户是“低频高客单”沙发可能三五年才换一次床垫、餐桌更是慎重决策。用户不会天天逛但一旦进入选购流程决策周期可能长达两周而且偏好极其主观有人只爱原木风有人偏爱意式极简有人对皮质材质过敏有人只看尺寸能不能塞进电梯。传统电商那种“猜你喜欢”的首页推荐用销量和热度过一遍出来的东西基本都是大路货根本接不住这种强个性化需求。在这个场景里用户没有足够的显式评分也不会有大量浏览历史但并不意味着没有信号。收藏夹、购物车、下单记录、甚至同一品类的反复搜索都是隐式偏好。协同过滤的核心假设就是“相似的人有相似偏好”它不依赖于理解商品本身的风格标签而是通过用户行为矩阵发现“买过实木床的人大概率也会喜欢同风格的床头柜”。这种发现方式恰好补足了家居场景里的“隐性品味”盲区。1.2 为什么是协同过滤而不是内容推荐做方案选型时我也对比过基于内容的推荐。内容推荐需要给每件家具打大量标签风格、材质、颜色、尺寸、品牌、价格带然后按用户历史偏好做特征匹配。听起来很合理但有两个现实问题第一标注成本高1000件商品就要编辑上千条标签第二内容推荐永远只能推荐“和之前喜欢的相似的东西”很难跨类目推荐。用户收藏了北欧风茶几系统只会推荐更多茶几很难发现他还需要同风格的落地灯、挂画甚至地毯。协同过滤不需要人工标签只需要用户与物品的交互矩阵就能自动挖掘出“喜欢这款茶几的人还喜欢那盏落地灯”这类跨类目关联。它本质上是人群智慧的复用——让用户自己定义商品之间的隐性关系。这在家居这种需要整体搭配的场景里尤其有价值因为用户往往不是单品购买而是整套空间一起买跨类目推荐的需求非常强烈。所以我最终选择协同过滤作为核心算法同时用内容规则做冷启动补充两条腿走路。1.3 系统整体设计思路整个系统我分成了三层用户行为采集层、推荐引擎层、Web展示层。用户在前台浏览、收藏、加购、下单这些行为通过AJAX接口写进数据库推荐引擎层跑两个离线任务一个构建用户相似度矩阵一个构建物品相似度矩阵生成结果写入Redis或MySQL推荐结果表Web展示层提供一个/api/recommend接口根据当前用户ID实时返回Top-N商品列表。为什么不把所有计算都放在线做因为协同过滤的相似度矩阵构建是O(N^2)级别用户量过千、商品量过千之后实时算会把人等崩溃。我的方案是离线算好相似度矩阵和预测评分在线阶段只做“查表排序”接口响应时间基本在100ms以内。这也符合工业界推荐系统的常规做法重计算放离线轻查询放在线。整体架构不复杂但足够清晰老师答辩时一眼就能看出你对系统设计有思考。2. 协同过滤算法核心原理与工程选型2.1 基于用户的协同过滤UserCF怎么算基于用户的协同过滤简单说就是“找邻居抄作业”。第一步构建用户-物品评分矩阵行是用户列是商品值是用户对商品的评分或行为权重第二步用余弦相似度或皮尔逊相关系数计算任意两个用户之间的相似度第三步找到与目标用户最相似的K个邻居第四步把邻居们评过、但目标用户没有行为的商品用相似度加权汇总得到每个候选商品的预测分第五步按预测分排序取Top-N。拿家居场景举个例子用户A收藏了“北欧白橡木餐桌”用户B同时收藏了“北欧白橡木餐桌”和“同系列餐椅”那么A和B在餐桌上产生了交集相似度高。系统就会把B收藏过的餐椅按权重推荐给A理由就是“与您品味相似的用户也喜欢”。这小逻辑很直观我用Python验证的时候用50个用户、200件商品就能跑出不错的推荐效果但用户量到几千之后用户相似度矩阵会变得特别大这也是我后来在工程上主推ItemCF的原因之一。2.2 基于物品的协同过滤ItemCF更适合家居基于物品的协同过滤是“买过A的人还买了B”。它的出发点是用户对良好推荐结果的期望更多是“这个推荐对我口味”而不是“推荐我朋友喜欢的东西”。物品之间的相似度比用户之间的相似度稳定得多因为商品不会频繁变化今天相似的茶几明天大概率还是相似而用户偏好会随时间漂移。所以在真实电商系统里ItemCF是更常用的方案。在家居场景里ItemCF还有一个隐藏优点——推荐可解释性强。给用户推荐一顶“北欧布艺单人沙发”的同时系统可以自然显示“因为您浏览过同风格的懒人沙发”这种理由用户看得懂也更容易转化。我在项目里把ItemCF作为主力算法UserCF作为辅助算法最后在结果融合阶段做加权排序。计算物品相似度的公式用的是改进版余弦相似度def build_item_similarity(interact_matrix, item_user_count): item_sim {} # interact_matrix: 行为矩阵的稀疏表示key为物品idvalue为对该物品有行为的用户id集合 for item_i, users_i in interact_matrix.items(): sim_row {} for item_j, users_j in interact_matrix.items(): if item_i item_j: continue # 计算同时购买/收藏物品i和j的用户数 common_users len(users_i users_j) if common_users 0: continue # 经典ItemCF公式|N(i)∩N(j)| / sqrt(|N(i)|*|N(j)|) sim common_users / (len(users_i) ** 0.5 * len(users_j) ** 0.5) # 加入热门物品惩罚降低超热门商品的相似度权重 sim sim * (1 - 0.5 * len(users_j) / max_user_count) sim_row[item_j] sim item_sim[item_i] sim_row return item_sim这里对热门物品做了惩罚否则像“北欧简约床垫”这种人人都看的热门款会跟所有物品都产生高相似度把推荐结果拉成热门榜失去个性化意义。2.3 相似度度量与评分预测的细节相似度度量我实际对比过三种余弦相似度、皮尔逊相关系数、Jaccard相似度。对于家居这种稀疏评分矩阵行为数据大量缺失Jaccard只看交集数很容易把只有一次共同交互的两个用户判成极度相似噪声很大余弦相似度受评分尺度影响喜欢打低分和喜欢打高分的用户会被强行掰开皮尔逊相关系数会先减去用户平均分能消除评分尺度差异在评分制数据上更准。但皮尔逊在极端稀疏下也有问题两个用户只有一个共同商品时相关系数不是1就是-1完全没有区分度。所以我的做法是结合计算用户相似度时用皮尔逊但只保留至少有3个共同评分的用户对小于3的直接按0处理。预测评分时采用加权平均def predict_score(user_id, item_id, user_sim, user_ratings): total_weight 0 weighted_score 0 for neighbor_id, sim in user_sim[user_id].items(): if item_id in user_ratings[neighbor_id]: neighbor_rating user_ratings[neighbor_id][item_id] weighted_score sim * neighbor_rating total_weight sim if total_weight 0: return 0 return weighted_score / total_weight这属于最经典的协同过滤实现没有用SVD、没有用神经网络但效果在毕设级别完全够用。把矩阵中心化之后再算评分分布明显更合理。3. 数据表设计与隐式反馈矩阵构建3.1 核心数据表结构数据表是整个系统的地基。我建了六张核心表用户表users、商品表products、类目表categories、行为日志表behavior_logs、订单表orders、推荐结果表recommendations。其中最重要的就是behavior_logs它是算法矩阵的数据来源。表名关键字段说明usersuser_id, username, style_preference, create_timestyle_preference是注册时可选的家居风格偏好用于冷启动productsproduct_id, product_name, category_id, price, style, material, image_urlstyle和material都是算法辅助特征categoriescategory_id, category_name家具类目如床、沙发、灯具、装饰behavior_logslog_id, user_id, product_id, behavior_type, log_timebehavior_type取值为view/favorite/cart/purchaseordersorder_id, user_id, product_id, quantity, order_time下单数据代表最高置信度行为recommendationsuser_id, product_id, score, reason, update_time离线生成推荐在线直接查表展示行为日志表必须加索引字段组合建议(user_id, product_id)。我在毕设中一开始没加索引几千行数据查起来无所谓但Web端每点一次就查一遍页面卡得不行加完索引之后秒开。索引看似不起眼但在线上系统里这是第一道性能门槛。3.2 从行为日志到评分矩阵的转化用户没有直接打分我需要把行为日志变成伪评分。我的权重规则是浏览记1分、收藏记2分、加购记3分、下单记5分。同时引入时间衰减越久远的行为对当前偏好影响越小。计算公式是score action_weight * math.exp(-0.02 * (now - log_time).days)时间衰减系数0.02可以调取大了衰减太快取小了跟没衰减一样。我调了一下取0.02时三个月前的行为权重约为0.165半年前的约为0.027刚好把陈旧行为压下去。这样每个用户对每个物品得到一个浮点分数构造成标准的用户-物品矩阵。矩阵规模是“用户数 × 商品数”家居平台商品按千计用户按万计矩阵里绝大部分是0。如果直接存成Python二维列表几十万用户加几千商品就是几亿个元素内存直接爆。所以我用scipy.sparse.csr_matrix存稀疏矩阵只记录非零位置内存占用能压缩到原来的百分之几。这一步是我项目里最关键的工程优化也建议所有做推荐系统毕设的同学务必掌握。3.3 数据增强与模拟数据构造没有现成的家居交互数据怎么办我做了两个事情第一用公开的MovieLens数据集验证算法正确性但没有直接拿MovieLens做演示因为场景太出戏第二我自己构造了一份“家居交互数据”包含120个虚拟商品、300个虚拟用户、约4000条交互记录。构造方式是先设定用户的风格偏好北欧、日式、工业、轻奢等再按照风格概率生成浏览、收藏、加购、下单行为这样数据里天然存在“同风格用户行为相似”的结构方便算法跑出合理结果。构造数据时要注意保证稀疏度用户平均交互量控制在总商品数的3%到5%这样既不会太密不然随便算都准也不会太稀不然没有推荐空间。这份模拟数据我打包进了源码的data/目录下载后可以直接运行不需要额外准备数据集对毕设演示特别友好。4. 核心代码实现与推荐接口搭建4.1 技术栈选择与代码结构技术栈我选的是Python Flask MySQL Redis Bootstrap/jQuery。没有用Django因为Flask更轻单文件能起服务适合把算法逻辑独立出来。如果你的学校要求Java也可以把算法用Java重写但Python在数据处理上确实方便太多尤其是scipy、numpy一两行代码搞定稀疏矩阵运算。项目目录结构如下home-recommend-system/ ├── app.py # Flask主入口所有Web接口 ├── recommend.py # 协同过滤算法核心相似度计算、Top-N推荐 ├── build_matrix.py # 离线构建相似度矩阵并缓存到Redis ├── db.sql # 数据库建表SQL ├── data/ │ └── interactions.csv # 模拟用户行为数据 ├── static/ # JS、CSS、图片 ├── templates/ # Flask页面模板 └── requirements.txt # 依赖列表我特意把recommend.py独立出来意味着你可以直接在命令行跑算法验证不依赖Web环境。这在调参的时候非常高效——我没爬起来多少次Flask大部分时间都在build_matrix.py里看指标。4.2 相似度矩阵构建代码物品相似度矩阵是ItemCF的地基。我封装了一个函数输入是交互字典输出是物品相似度字典。为了控制内存我只保留每个物品Top-50相似物品不存储全量相似矩阵这样即使商品过万也能轻松装进内存。from collections import defaultdict import math def build_item_sim(interactions, top_k50): # interactions: dict {user_id: {product_id: score}} item_users defaultdict(set) for user_id, items in interactions.items(): for item_id in items.keys(): item_users[item_id].add(user_id) item_sim defaultdict(dict) for item_i, users_i in item_users.items(): sim_row {} for item_j, users_j in item_users.items(): if item_i item_j: continue common len(users_i users_j) if common 0: continue sim common / math.sqrt(len(users_i) * len(users_j)) sim_row[item_j] sim # 只保留相似度最高的top_k个 sorted_row sorted(sim_row.items(), keylambda x: x[1], reverseTrue)[:top_k] item_sim[item_i] dict(sorted_row) return item_sim这段代码的复杂度是O(M^2)M为物品数在120个商品时跑起来飞快但如果是上万商品就需要用矩阵运算并行化了。毕设阶段不建议过度设计但你在论文里要写清楚“复杂度高未来可优化”这是答辩加分项。4.3 推荐服务与Web接口实现推荐接口是整个系统的门面。它先从Redis缓存里查当前用户的推荐结果如果Redis没有就查MySQL里离线计算好的recommendations表再查不到就返回热门兜底。同时给每个推荐结果带上reason字段比如“因为您收藏了北欧风边几所以推荐同风格两人位沙发”。接口返回JSON前端拿JSON渲染卡片。app.route(/api/recommend, methods[GET]) def api_recommend(): user_id request.args.get(user_id, typeint) topn request.args.get(topn, default10, typeint) cache_key frecommend:{user_id}:{topn} cached redis_client.get(cache_key) if cached: return jsonify(json.loads(cached)) result recommend_for_user(user_id, topn) redis_client.setex(cache_key, 300, json.dumps(result)) # 缓存5分钟 return jsonify({code: 0, data: result})这个接口做得很“现实”缓存机制让高并发场景下不会每秒都打数据库。302的响应时间在我的笔记本上平均70ms加上前端渲染也不到200ms老师演示的时候体验很流畅。4.4 前端展示与推荐理由前端虽然只是辅助但评分和展示直接影响演示效果。我用Bootstrap做了商品卡片墙每张卡片展示商品图、名称、价格和推荐理由右下角有一个“换一批”按钮点击会请求下一个Top-N批次。推荐理由我用“表现出用户偏好”的语义生成如果是ItemCF就取用户最近行为里相似度最高的那个物品拼句子如果是UserCF就写“与您偏好相似的用户也喜欢”。有个小细节首页顶部加了一个风格偏好选择器未登录或冷启动用户可以选择“北欧/日式/工业/轻奢”中的一种系统会先用风格作为先验用内容匹配生成初步结果等行为数据积累后再切到协同过滤。这个设计在答辩时被老师专门表扬了因为很多同学做的推荐系统完全不管冷启动而我用很小的代价把问题解决了。前端代码不复杂但配上真实图片后整体视觉效果上升了不止一个档次建议准备毕设的同学不要忽略UI好马要配好鞍。5. 冷启动、稀疏性与多样性问题的实战调优5.1 冷启动新用户没有任何行为怎么办冷启动是推荐系统里绕不开的话题。新用户注册后行为表是空的协同过滤算不了。我的方案是“三阶段推荐”注册1小时内用注册时选择的风格偏好做基于内容的召回只推荐该风格下评分最高的商品行为量达到3条后开始混合协同过滤结果但协同过滤权重只占30%风格规则占70%行为量超过10条后完全切到协同过滤。这套策略在代码里就是简单的规则判断但实际效果立竿见影。实现上我写了一个solution字段cold_content、mixed、cf。前端拿到solution之后会在页面上显示“发现您的独特品味后推荐会更精准”这既缓解了冷启动推荐不理想的尴尬又引导用户多产生行为。不要小看这种业务上的小包装它让整个系统显得完成度很高。5.2 矩阵稀疏导致的推荐头部化早期我跑ItemCF时发现推荐结果太集中了——高销量的热门商品占了绝大多数席位每个用户拿到的列表都差不多。原因是热门商品被大量用户交互过与任意物品的共现概率都高相似度容易被“捧高”。我做了两个惩罚一个是公式里的热门惩罚项另一个是生成推荐候选时限定同一个三级类目最多出现3件商品防止满屏都是“北欧风沙发”的变形款。经过这两种处理排序列表的个性化程度明显提升用户A和用户B拿到的前3名开始出现差异。5.3 推荐结果多样性不足与去重策略只看Top-N准确率还不够如果首页10件推荐里有6件都是沙发用户会觉得系统“魔怔了”。我去重的策略是“槽位配额”把推荐结果先按相似度分成两组——强相关组相似度0.3和弱相关组相似度0.1-0.3强相关组占6个槽位弱相关组占4个槽位。然后在每个槽位内部进行类目轮换让床、沙发、灯具、装饰都能露脸。这个思路也叫MMR的简化版即最大化相关性的同时尽量增加候选集的边际多样性。没上复杂算法但很实用你可以直接把我的思路拿过去改。5.4 推荐效果怎么评估做推荐系统不能“我觉得效果好”就到头了。我用离线评估的方式把用户最新的一次购买行为当作测试集历史行为当训练集跑一遍推荐然后看测试集商品是否出现在推荐列表里。指标上采用PrecisionK、RecallK、Coverage三个指标。我调参时用了一组对照邻居数KPrecision10Recall10多样性类目覆盖率50.210.130.61100.330.210.63200.290.240.58300.220.200.52从我的模拟数据看K10时精确率最高K20时召回略有提升但精确率下降最终选了K10。这个结果不是万能答案但提供了一套调优方法。你可以自己跑一遍观察指标变化选择最适合自己数据的参数。6. 源码复现步骤与遇到的问题6.1 项目目录与依赖环境拿到源码包后先看我整理好的requirements.txt。主要依赖是flask、pymysql、scipy、numpy、redis、pandas。推荐用Python 3.8及以上版本3.6太老3.12偶尔有依赖兼容问题3.9最稳。我踩过的第一个坑是MySQL版本本地MySQL 8.0的密码加密方式和老代码不兼容连接时报Authentication plugin caching_sha2_password cannot be loaded后来在db.sql和配置里都改成mysql_native_password才解决。你如果用Docker跑MySQL务必选mysql:5.7或mysql:8.0同时注意字符集要设为utf8mb4否则前端传中文会乱码。源码里我放了启动脚本start.sh它会依次执行导入数据库、启动Redis、运行build_matrix.py、启动Flask。如果你没有Redis代码里也做了降级处理直接查MySQL只是响应稍慢。6.2 从0到1跑起来的完整操作没有过高的门槛跟着做就能跑通安装依赖pip install -r requirements.txt。导入数据库mysql -u root -p db.sql创建好home_recommend库。修改app.py和build_matrix.py顶部配置里的数据库账号密码、Redis地址。运行build_matrix.py会打印“物品相似度矩阵构建完成共120个物品稀疏度xx%”。运行python app.py浏览器访问http://127.0.0.1:5000。用测试用户ID 10001、10002、10003分别登录观察推荐结果差异。如果想看算法效果运行evaluate.py会输出Precision和Recall。整个过程大概20分钟。如果卡在某个坑里优先看日志里的报错信息基本都是配置问题很少是代码逻辑问题。6.3 复现过程中最容易踩的坑我把复现过程中遇到的高频问题整理成了一张速查表很多同学问过贴在这里问题现象可能原因解决思路首页商品图片不显示图片路径是绝对路径换机后路径变了改成相对路径或用默认占位图推荐接口返回空列表Redis缓存里是旧数据没新用户行为清缓存或重新跑build_matrix.py运行build_matrix.py内存爆掉矩阵用稠密ndarray存储换成scipy.sparse.csr_matrix中文乱码数据库连接字符集不是utf8mb4在MySQL连接URL里加charsetutf8mb4find_user_sim计算极慢两层for循环遍历全部用户对只计算有过共同物品的用户对减少空转最后一个问题最值得展开。一开始我按照教科书逻辑两层for循环遍历所有用户对300个用户时觉得没问题但模拟到2000用户就开始卡顿。后来我改成“倒排索引”先找到每个物品被哪些用户交互过然后只对这些有交集物品的用户对计算相似度把O(N^2)的计算砍掉大半。这是书上不常写、但实战必须懂的优化细节。7. 一点个人体会和避坑建议整个项目做完我最深的感受是协同过滤的公式并不难难的是把公式放到真实业务里权衡。家居这个品类评比电影、图书都依赖行为密度但密度天然不足所以一定不能只靠一个算法硬扛要结合业务规则做冷启动、做降权、做多样性。我一开始天真地想拿纯矩阵分解一步到位最后发现离线指标和在线观感完全是两码事——离线Precision高了0.05不如一个推荐理由文案带来的演示效果提升明显。还有个小技巧分享给准备答辩的同学演示的时候别只展示“推荐出了什么”要展示“为什么推荐它”。我特意在系统里加了一个“查看推荐理由”的交互按钮点开会显示“因为您浏览了XXX相似的用户还购买了XXX”。这个设计被老师反复问了两轮也是我认为整个项目最值得保留的产品细节。如果你准备做类似毕设建议把这套“行为逻辑解释层”加上它不复杂但能瞬间让你的系统看起来比同组毕设高一个段位。至于具体代码源码里都有注释跑起来再对着改比看十篇文章都有效。