1. 选题背景:为什么用协同过滤算法做家居选购
1.1 家居选购的决策特点
做毕设选题时,很多人第一反应是做一个常规的电商系统,商品管理、购物车、订单结算一套流程走完,但这样的系统技术含量有限,答辩时很难讲出亮点。我最后定的是“基于协同过滤算法的家居选购系统”,核心思路不是再做一个电商Demo,而是把重点放在推荐算法上:用户进入系统后,不再靠自己去分类列表里翻找,而是由系统根据历史行为数据,主动推算出“这个用户可能喜欢什么家居商品”。
家居选购这个场景很有意思。它和买书、买零食完全不同——低频、高客单价、决策周期长。用户不会像买日用品那样隔三差五下单,可能一年就买一两次,每次都要货比三家。这个特点直接影响推荐算法的设计:如果用基于用户的协同过滤,会遇到用户行为数据稀疏的问题;如果单纯用基于物品的协同过滤,又需要更精细地刻画物品之间的搭配关联。系统做得怎么样,很大程度上取决于你针对这些痛点做了哪些处理,这就是项目真正的价值所在。
1.2 协同过滤算法为什么契合这个场景
协同过滤算法在推荐系统里属于经典中的经典,核心假设是:如果用户A和用户B在历史行为上相似,那么A喜欢的物品B大概率也喜欢;反过来,如果物品X和物品Y经常被同一批用户购买或收藏,那么当用户买了X时,系统就应该把Y也推给用户。
家居选购恰好有很强的内容关联性。举个很直白的例子:一个用户买了北欧风原木色餐桌,大概率也需要同风格的餐椅、桌布或者吊灯。这种关联不是商品分类层面能解决的,因为分类只是一种标签,而协同过滤挖掘的是用户行为背后真实的共同出现规律。相比规则推荐(比如“买了餐桌的人也可能买桌布”这种人工设定的关联),协同过滤的优势是规律自己从数据里长出来,不需要人工一条条维护规则。对于一个毕设项目来说,算法原理清楚、实现路径明确、效果可量化,这个选题的完成度和展示空间都很高。
1.3 毕设的系统定位与技术选型背景
这个项目定位是一个带Web前端、后端服务、数据库和推荐引擎的完整系统。基础功能包括用户注册登录、商品浏览、收藏、加入购物车、模拟下单,核心亮点是独立的推荐模块:用户登录后首页展示“猜你喜欢”,商品详情页展示“相关搭配推荐”,用户个人中心展示“历史浏览相似推荐”。
技术栈的选择上,我倾向于Python做推荐算法部分,因为NumPy和Pandas处理相似度矩阵非常方便,代码量比Java少一截。Web端可以用Flask或者Django做后端,前端用普通HTML/CSS/JavaScript或者Vue按需选择。数据库用MySQL,存用户表、商品表、行为表、推荐结果表。推荐引擎单独抽成一个模块,加载用户行为数据后离线计算相似度矩阵,再通过接口返回推荐列表。这套结构清晰、演示方便,也符合毕设评审老师对“系统完整性”的期待。
2. 算法设计:家居场景下协同过滤的两种核心路线
2.1 基于用户的协同过滤(UserCF)
基于用户的协同过滤思路很直观:先找到和你兴趣最像的一批人,然后把这些人喜欢的、但你没见过的家居物品推荐给你。
实现上分三步。第一步,构建用户-物品评分矩阵,行是用户,列是物品,值可以是显式评分,也可以用收藏、点击、加购等隐式行为量化。第二步,用余弦相似度或皮尔逊相关系数计算用户之间的相似度,找到当前用户的TopK相似邻居。第三步,汇总邻居们对候选物品的评分,加权计算出当前用户对物品的预测评分,取TopN生成推荐列表。
在毕设里我用的评分量化方式是这样:浏览计1分,收藏计3分,加购计5分,下单计8分。这种人工量化的好处是简单直接,不需要做复杂的隐式反馈建模,演示时效果也比较直观。但UserCF有个明显问题:家居选购低频,新用户几乎没有历史行为,用户相似度根本算不准。而且在线用户量不够大时,相似用户的计算结果波动很明显,这是我在实验阶段真实碰到过的现象。
2.2 基于物品的协同过滤(ItemCF)
基于物品的协同过滤解决的是另外一类问题:不用找相似用户,而是找相似物品。核心逻辑是——计算物品i和物品j之间的相似度,依据是“有多少用户同时喜欢这两件物品”。当用户喜欢过某个物品时,系统就把和它最相似的物品推荐出去。
家居选购场景下单品的关联购买行为非常常见:一个用户同时下单了床和床头柜,同时收藏了沙发和茶几,这些行为天然地构建了物品之间的关联。ItemCF的计算公式在学术上常用余弦相似度:
sim(i, j) = |N(i) ∩ N(j)| / sqrt(|N(i)| * |N(j)|)N(i) 是对物品i产生过行为的用户集合,分子是两个物品共同被行为的用户数,分母是两个物品各自行为用户数的几何平均。这个公式的直观理解是:两件物品被同一批用户喜欢的程度越高,它们就越相似。实现时我直接用Pandas的透视表把评分矩阵转换成物品-用户矩阵,然后用矩阵乘法一步算出所有物品对的相似度,效率非常高。
实际测试下来,ItemCF在家居场景中的推荐效果明显好于UserCF。原因也好理解:物品的共现规律比用户的相似性更容易在稀疏数据下被捕捉到。一个用户可能只有3条行为记录,但每一条都能贡献给物品关联,UserCF却可能需要十几个行为记录才能跟别人对上号。
2.3 家居场景下的算法选择与混合策略
我的最终方案不是二选一,而是组合使用。
首页的“猜你喜欢”用基于物品的协同过滤做主推荐,因为它稳定、可解释性强。商品详情页的“相关搭配推荐”用基于物品的协同过滤计算相似商品,但会加上过滤条件:同一品牌或同类风格的商品权重适当提高。“相似用户还买了什么”则用UserCF计算,展示的是社交性的推荐结果,这部分虽然命中率不如ItemCF,但演示价值高,能让评审老师直观看到两种算法的区别。
混合策略上我用的是加权融合:最终得分 = 0.7 × ItemCF得分 + 0.3 × UserCF得分。权重是在实验基础上调的,这个比例在我的测试数据上综合表现最好——ItemCF保证了推荐的相关性,UserCF带来了意外发现的多样性。调权重的过程是真实做项目最花时间的地方,但这个参数没有标准答案,数据不同最优值就不同,建议拿到自己的数据后跑几组对比,而不是直接抄网上参数。
3. 从评分矩阵到推荐列表:核心模块实现细节
3.1 数据来源与评分矩阵构造
毕设项目的数据是很多人头疼的地方。真实电商数据不公开,自己造数据又怕不真实。我的做法是两条腿走路:基础商品库用爬虫采集的公开家居电商数据(只保留商品名、图片、分类、价格这些公开字段,外链一律去掉),用户行为数据用脚本模拟生成。
行为数据生成要符合真实规律,不能均匀随机乱来。我设定了几种用户画像:偏好北欧风的用户会更倾向于浏览和收藏原木色、简约款商品;偏好中式风的用户对深色实木家具兴趣更高。生成时按照“用户画像 -> 候选商品池 -> 行为类型加权”的流程构造记录,这样数据里自带规律,协同过滤算法才能学出有价值的模式。我用这个方式生成了50个用户、近2000条行为记录,对毕设演示来说足够。
评分矩阵最终用Pandas来做,行为用户ID,列为商品ID,值为行为量化得分,空缺填0。这里有一个细节:矩阵千万不要用稠密矩阵存,20个用户800个商品就是16000格,数据量小还能接受,但毕设演示如果数据扩到上万条记录,稠密矩阵的内存和计算开销会直接爆炸。用稀疏矩阵(SciPy的CSR格式)是正确做法,矩阵乘法的速度也会快很多。
3.2 相似度计算的选取与实现
相似度算法我对比过余弦相似度、皮尔逊相关系数和Jaccard相似度。
余弦相似度适合处理评分向量,因为评分向量天然是数字,可以把用户或物品看作向量,计算向量夹角。皮尔逊相关系数在评分标准化后有优势,能降低用户打分习惯差异的影响——但家居选购的评分大多是我自己量化的行为得分,不存在“某个用户习惯打高分”的问题,所以皮尔逊的优势体现不明显。Jaccard相似度只看有没有行为,不看行为多少,更适合二值化的场景。
最终我主力用余弦相似度,核心代码很少,经常用的写法是用矩阵乘法实现。假设行为矩阵是user_items,物品相似度矩阵可以这样算:
import numpy as np from scipy.sparse import csr_matrix def calc_item_similarity(user_item_matrix): # user_item_matrix: 用户-物品稀疏矩阵,行是用户,列是物品 user_item_csr = csr_matrix(user_item_matrix) # 物品-物品同现矩阵 = 矩阵转置乘以矩阵 item_sim = user_item_csr.T @ user_item_csr # 计算每个物品被行为的用户数,用于归一化 item_count = np.array(user_item_csr.sum(axis=0)).flatten() # 分母:sqrt(N(i)) * sqrt(N(j)) denominator = np.sqrt(item_count[:, None] @ item_count[None, :]) # 处理分母为0的情况 denominator[denominator == 0] = 1e-10 return item_sim.toarray() / denominator这里用稀疏矩阵转置乘自己,一步得到同现矩阵,再逐项除以几何平均做归一化,就是标准的物品余弦相似度。跑上千件商品只需要秒级时间,不管是离线预计算还是答辩现场演示,效率都没压力。
3.3 Top-N推荐生成与过滤策略
相似度矩阵算完,推荐生成就从矩阵运算变成了取数和排序的问题。对当前用户,把他产生过行为的物品ID取出来,遍历每个物品的相似商品列表,累加得到候选物品的得分,再排序取前N个。这个过程的伪代码思路是:
- 获取用户行为过的物品列表及其得分;
- 对每个行为物品,取相似度矩阵中相似度最高的K个物品;
- 候选物品综合得分 = 物品相似度 × 用户行为原始得分,然后按物品汇总;
- 过滤掉用户已经购买过的商品,避免推荐已购物品;
- 按综合得分排序,取TopN作为推荐列表。
这里有一个很关键的过滤:已经产生过行为的物品必须挡在推荐结果之外,否则系统会把你刚收藏过的沙发继续推给你,演示效果非常尴尬。另一个细节是候选物品的得分要排除两个来源不同的物品对同一候选物品的简单叠加——统一在汇总阶段按用户ID加总,否则重复计算会让结果发生偏斜。举个例子,用户行为物品A(得分5)和物品B(得分8),A和B都相似于候选物品C,相似度分别为0.5和0.2,那么C的得分应该是5×0.5+8×0.2=4.1,而不是分别算完再加总时出现浮点误差累积导致的异常值。
4. 系统架构与代码工程结构
4.1 整体架构与数据流
整个系统我按经典的三层结构拆:表现层、业务逻辑层、数据层。表现层是Web页面,负责向用户展示推荐结果;业务逻辑层管理用户操作和推荐调用;数据层存商品、用户、行为、推荐快照。推荐引擎独立存在于业务逻辑层下面,不跟具体业务耦合,这样换数据集或换算法时不用动Web部分的代码。
数据流向是这样的:用户在前端浏览或点击商品,前端把行为事件异步提交到后端接口;后端把行为写入MySQL;推荐引擎定时或按需从MySQL里拉取行为数据,重新计算相似度矩阵,生成推荐列表存回数据库;用户打开首页时,Web后端直接从推荐结果表中查出推荐商品,拼接商品信息后返回前端展示。
这个设计有一个好处,答辩演示的时候即使网络不稳定,推荐结果也不会丢——因为推荐结果已经缓存到数据库了,画面上依然能正常展示,不需要现场临时跑算法。
4.2 数据库设计要点
数据库表我设计了五张:用户表、商品表、行为表、相似度结果表、推荐结果表。
- 用户表:用户ID、用户名、密码哈希、注册时间。
- 商品表:商品ID、标题、图片URL、分类、价格、风格标签、品牌。
- 行为表:行为ID、用户ID、商品ID、行为类型(浏览/收藏/加购/下单)、行为得分、时间戳。
- 相似度结果表:物品A、物品B、相似度值。这张表是核心,离线算完直接写入,查询TopK相似物品时一条SQL搞定。
- 推荐结果表:用户ID、推荐物品ID、推荐得分、排序位置、推荐类型(猜你喜欢/相关搭配/相似用户)。
商品表里的风格标签对推荐有辅助作用——算法算相似度做主判断,风格标签用于推荐后的二次排序。比如在相似度得分接近的情况下,风格匹配的商品排前面。这个细节在实际测试中很有用,因为物品相似度只考虑了行为共现,而家居商品天然有风格属性,辅助排序能让推荐结果更“像人挑的”。
4.3 推荐引擎与Web端的衔接
后端我用Flask写REST接口,推荐引擎单独封装成一个Python类。初始化时加载相似度矩阵,对外暴露三个方法:recommend_for_user(user_id, top_n)、similar_items(item_id, top_n)、user_cf_recommend(user_id, top_n)。Web接口只管接收请求、调方法、返回JSON,不关心算法内部怎么算。
一个实操建议:推荐引擎不要做成每次请求都实时计算相似度矩阵。哪怕Python矩阵运算很快,连续多次请求时重复计算也是浪费,而且在线计算会拖慢页面响应。我的做法是写了一个定时任务,每5分钟增量拉取一次新行为数据,重新计算相似度后更新数据库里的相似度结果表。Web接口只查表,不碰矩阵。这个方案在答辩演示时特别稳,页面秒开。
5. 毕设附带的源码如何运行与二次开发
5.1 环境准备与工程导入
源码工程我会打包成一个完整目录,包含后端Python代码、前端静态文件、SQL初始化脚本、预处理好的CSV数据和README说明文档。拿到后的第一步是准备环境,依赖项我用requirements.txt管理,核心依赖包括:Flask、Flask-CORS、NumPy、Pandas、SciPy、PyMySQL、scikit-learn(用于数据划分和评估指标计算)。执行安装:
pip install -r requirements.txt然后是初始化数据库,我提供了init.sql,里面包含建表和预置商品数据。在MySQL里执行如下命令即可:
mysql -u root -p < init.sql接下来启动推荐预计算脚本,脚本会读取行为数据,算相似度矩阵,把推荐结果写入数据库:
python recompute.py最后启动Web服务:
python app.py浏览器访问http://127.0.0.1:5000,用README里预置的测试账号登录,就能看到首页的“猜你喜欢”推荐。整个流程做成了一键式操作,保证不在项目调试上浪费答辩准备时间。
5.2 数据集测试与效果观察
代码库里附带了一份演示数据集,60个用户、800多件家居商品、约5000条行为记录。数据不是纯随机生成的,而是按照用户画像和行为规律构造,所以跑完算法后你能看到比较明显的推荐效果。
我建议拿到后先做三个直观验证。第一,注册一个新账号,不做任何操作,首页推荐是默认的热门商品;浏览收藏几件北欧风商品后刷新,推荐结果会快速向北欧风偏移——这是基于物品的协同过滤在起作用。第二,打开一个商品详情页,看“相关搭配推荐”,应该能看到和当前商品风格接近或常被一起购买的商品,比如看沙发时推荐茶几、边几。第三,找一个有较多历史行为的老账号,对比首页推荐的Top5和普通热门榜单,看差异有多大——推荐应该明显带个人偏好色彩。
这三个验证都是在代码层面能真实跑通的,如果测试时发现结果不理想,优先排查行为表里是否有足够多的共现数据,这是推荐效果的最大变量。
5.3 二次开发:替换成自己的家居数据
很多同学拿到源码后第一件事是想换自己的数据集。替换方式并不复杂,关键是格式要对。
商品表里每行要包含:商品ID(唯一)、标题、图片URL、分类、价格、风格标签。风格标签这里强烈建议认真填写,一个商品可以有多标签,比如“现代简约、原木、北欧”,因为辅助排序阶段会用到。行为表里需要的是:用户ID、商品ID、行为类型、时间戳。行为类型可以是任何你自定义的字符串,但要在配置文件里把行为类型到行为得分的映射关系同步修改,不然浏览、收藏、加购、下单的权重还是默认值。
我遇到过有人直接把自己爬的数据导进去,但用户ID对不上,行为记录里有大量“同一个用户反复操作同一商品”的记录,导致相似度矩阵出现严重的偏差。处理方法是导入前对行为数据做去重和清洗:同一用户同一商品只保留一次最高行为类型,避免重复数据干扰共现计算。
6. 踩坑记录与优化方向
6.1 冷启动问题的触底与缓解
冷启动是这个项目里我踩得最深的一个坑。新用户没有任何历史行为,协同过滤矩阵里他对应的向量全是0,算相似度时Cosine会得到一个无效值(分母为0),推荐结果直接变成空列表。
第一版我直接用了一个笨办法:新用户返回热门商品Top10。后来发现这个策略过于粗糙,改进版是这样处理的:当用户产生至少1条行为后,先用ItemCF针对这个行为商品找到相似商品,立刻就能给出个性化推荐;只有完全没有行为的用户才返回热门榜。这个改进只用了很少的代码量,但推荐从“完全无用”变成了“跟用户行为直接相关”,效果提升非常明显。
6.2 评分稀疏导致相似度失真
家居选购的低频特性导致行为矩阵稀疏度极高,我的演示数据里矩阵稀疏度超过95%。在稀疏矩阵下,物品相似度的分母两个集合交叉很小,会出现“只被同一个人买过的两件家具相似度=1”的假象,因为分子是1,分母也是1。
解决办法是加频率惩罚,推荐结果里对共现用户数小于阈值的相似物品对做降权或过滤。我在代码里给相似度结果加上一个字段common_count,只有共同行为用户数不低于3的相似关系才参与推荐候选。这个阈值可以根据数据量调整,数据量大的时候可以考虑提高到5。这个细节在论文里也值得写一笔——它是展示你对算法有过深入思考的重要证据。
6.3 推荐结果的多样性不足
做实验时发现,纯用ItemCF会出现“推荐结果过于集中在某个风格”的问题。一个用户浏览了一件北欧风沙发后,首页推荐全是北欧风的家具,椅子、桌子、灯具无一例外。这在真实使用体验中不好——用户确实偏好北欧风,但也不代表完全不想看其他风格。
我的改进方案是引入多样性重排:从推荐候选中取Top50,然后按风格分类,保证最终Top10里最多3件同风格商品,其余的风格尽量错开。重排的条件是推荐得分和风格多样性之间的权衡,我用了简单贪心策略:先取得分最高的商品,后续商品在保持同一风格不超过3件的前提下,优先取得分次高的。这个策略在代码上只有十几行,但推荐结果的可接受度提高了非常多。
6.4 在线与离线推荐的取舍
最后说一个关于系统设计层面的问题。很多推荐系统课程会强调实时性,但家居选购这个场景真的不需要实时推荐。用户一天内产生的新行为量很小,而且决策周期长,30分钟级别乃至小时级别的更新频率完全足够。
项目里我做的是离线计算、在线查询的架构,定期把相似度计算和推荐结果生成完,缓存到数据库。这样的好处是系统在线部分极其简单,稳定性高,并发请求几乎没有压力;代价是实时性差,但在这个场景下这个代价可以接受。如果你后续想加实时性,升级路线也很清晰:加上一层Redis缓存,行为产生时增量更新物品相似度,再配合消息队列异步计算,就能把更新延迟降到秒级。
整个项目从选题、到算法实现、再到系统开发和数据构造,完整走下来,我的体会是:推荐系统项目最容易出彩的地方不一定是最复杂的模型,而是你对场景特点的理解和针对性的处理。毕设代码本身不复杂,复杂的是让推荐结果在一个具体场景里真正“像个懂行的导购”,这些经验在数据分析和推荐相关的岗位上,都是实打实的加分项。