☰
基于Python的协同过滤商品推荐系统:从源码到实战的完整指南
2026/10/1 17:57:05 网站建设 项目流程

简介:这是一套面向计算机相关专业在校生与项目实战学习者的协同过滤商品推荐系统完整资料,源自大四毕业设计,经导师指导并获98.5分评审认可,适合作为毕设、课程设计、期末大作业或比赛初期立项演示的参考方案。压缩包共712个文件,约30.94MB,涵盖39个Python源码文件、36个Vue前端组件、51个CSS样式、29个HTML页面、2个SQL数据库脚本,以及配套的docx论文、mp4演示视频和bat一键安装运行脚本,前后端与数据库结构完整。资源已通过本地运行与功能测试,包含协同过滤推荐算法实现、用户与商品管理模块及界面资源,便于读者理解推荐流程与工程组织方式。目前已有147人学习下载,既适合入门者对照源码梳理项目结构,也可供有基础者在此基础上进行二次开发与功能扩展。

1. 从一份毕设压缩包说起:协同过滤商品推荐系统到底该怎么跑起来

你拿到手的可能是一个名为「基于python的协同过滤商品推荐系统源码+数据库+论文+演示视频.zip」的压缩包,解压之后大概率是几个文件夹:源码、SQL 文件、论文文档、录屏。很多人第一反应是双击运行,然后被环境报错劝退。这个标题背后其实是一条完整的工程链路:用 Python 实现协同过滤算法,用数据库存用户行为数据,最后跑出一个能根据历史评分或点击给用户推商品的系统。它适合两类人——一类是要交课程设计或毕设的学生,需要能跑通、能讲清原理、能改参数;另一类是想快速验证推荐逻辑的开发者,想拿一套可复现的最小系统做二次开发。我见过太多人卡在「python安装教程」这一步就放弃了,其实真正难的不是装环境,而是理解数据怎么进、相似度怎么算、推荐结果怎么解释。这一篇就按「能复现」的标准,把这条链路拆开讲透。

2. 协同过滤的两条路线:UserCF 和 ItemCF 到底选哪个

2.1 原理差异与适用场景

协同过滤的核心思想很朴素:如果两个人过去对商品的喜好相似,那么一个人喜欢的商品,另一个人大概率也喜欢;或者如果两个商品被同一批人喜欢,那么喜欢其中一个的人大概率也喜欢另一个。前者叫 UserCF(基于用户的协同过滤),后者叫 ItemCF(基于物品的协同过滤)。

UserCF 的计算对象是「用户之间的相似度」。它先找到和目标用户兴趣最接近的 K 个邻居,然后把邻居喜欢但目标用户没见过的商品推荐过来。这个路线适合用户数量不大、但商品更新频繁的场景,比如新闻推荐、短视频推荐——因为新商品一出来,只要有用户行为,就能通过用户相似度扩散出去。

ItemCF 的计算对象是「商品之间的相似度」。它先算商品两两之间的相似度矩阵,然后根据用户历史喜欢的商品,找相似商品推荐。这个路线适合商品数量相对稳定、用户数量庞大的场景,比如电商平台——因为商品相似度矩阵可以离线算好,线上只需要查表,响应快。

提示:如果你拿到的源码里只有一个recommend.py,先看它算的是user_similarity还是item_similarity,这决定了你后面调参的方向。

2.2 相似度计算的三种常见实现

不管选哪条路线,都要算相似度。常见的有三种:余弦相似度、皮尔逊相关系数、调整余弦相似度。余弦相似度只看方向不看数值大小,适合评分数据稀疏的情况;皮尔逊会减去用户平均分,能消除用户打分偏严或偏松的影响;调整余弦则是在余弦基础上减去商品平均分,适合商品质量差异大的场景。

下面是一个用 Python 计算用户余弦相似度的最小代码块,可以直接抄进你的项目里替换原有实现:

import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 用户-商品评分矩阵,行是用户,列是商品,0 表示未评分 ratings = np.array([ [5, 3, 0, 1], [4, 0, 0, 1], [1, 1, 0, 5], [1, 0, 0, 4], [0, 1, 5, 4], ]) # 计算用户之间的余弦相似度 user_sim = cosine_similarity(ratings) print("用户相似度矩阵:") print(np.round(user_sim, 2)) # 找目标用户(索引 0)最相似的 2 个邻居 target_user = 0 sim_scores = list(enumerate(user_sim[target_user])) sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True) neighbors = [i for i, _ in sim_scores[1:3]] print("目标用户的邻居:", neighbors)

这段代码的逻辑是:先把评分矩阵转成 numpy 数组,用cosine_similarity一次性算出所有用户两两之间的相似度,然后对目标用户那一行排序,跳过自己取前 K 个。参数上要注意,ratings里的 0 表示缺失值,实际项目中不要用 0 填充后直接算,否则会把「没买过」当成「评分为 0」,导致相似度失真。常见做法是先做均值中心化,或者用掩码矩阵只计算共同评分过的商品。

2.3 预测评分的公式与参数含义

找到邻居之后,下一步是预测目标用户对未评分商品的分数。UserCF 的预测公式是:

[ \hat{r}{ui} = \bar{r}u + \frac{\sum{v \in N(u)} sim(u,v) \cdot (r{vi} - \bar{r}v)}{\sum{v \in N(u)} |sim(u,v)|} ]

其中 (\bar{r}_u) 是用户 u 的平均评分,(N(u)) 是邻居集合,(sim(u,v)) 是用户相似度。这个公式的意思是:用邻居的评分偏差加权平均,再加上目标用户自己的平均分。参数 K(邻居数量)一般取 10 到 50,太小容易受个别邻居影响,太大则引入不相关用户。另一个隐藏参数是相似度阈值,低于阈值的邻居直接丢弃,通常设 0.5 左右。

如果你拿到的源码里预测部分写得很简单,比如直接sum(sim * rating) / sum(sim),没有减均值,那在评分尺度不一致的数据集上会翻车。我一般会先检查数据里每个用户的平均分差异大不大,如果差异超过 1 分,就必须做中心化。

3. 数据库设计与数据流转:从 SQL 文件到推荐结果

3.1 三张核心表的结构与字段说明

一套能跑的推荐系统,数据库里至少要有三张表:用户表、商品表、评分表。用户表存user_id和基本信息;商品表存item_id、名称、类别;评分表是核心,存user_id、item_id、rating、timestamp。下面是一个可以直接在 MySQL 里执行的建表语句:

CREATE TABLE users ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE items ( item_id INT PRIMARY KEY AUTO_INCREMENT, item_name VARCHAR(100) NOT NULL, category VARCHAR(50), price DECIMAL(10,2) ); CREATE TABLE ratings ( user_id INT NOT NULL, item_id INT NOT NULL, rating FLOAT NOT NULL, ts BIGINT NOT NULL, PRIMARY KEY (user_id, item_id), FOREIGN KEY (user_id) REFERENCES users(user_id), FOREIGN KEY (item_id) REFERENCES items(item_id) );

ratings表用(user_id, item_id)做联合主键,保证一个用户对一个商品只有一条评分记录。ts字段存时间戳,用来做时间衰减——越早的行为权重越低。如果你拿到的 SQL 文件里没有ts字段,建议手动加上,否则后面想优化都没抓手。

3.2 用 Python 读取数据库并构建评分矩阵

数据在数据库里是长表格式,一行一条评分,但协同过滤需要的是矩阵格式。下面这段代码用pandas和sqlalchemy把数据读出来并透视成矩阵:

import pandas as pd from sqlalchemy import create_engine # 替换成你自己的数据库连接信息 engine = create_engine('mysql+pymysql://root:password@localhost:3306/recommend_db') # 读取评分数据 sql = "SELECT user_id, item_id, rating FROM ratings" df = pd.read_sql(sql, engine) # 透视成用户-商品矩阵,缺失值填 0 matrix = df.pivot_table(index='user_id', columns='item_id', values='rating').fillna(0) print("矩阵形状:", matrix.shape) print(matrix.head())

这里的关键参数是fillna(0),它把没有评分的格子填成 0。但前面说过,0 在余弦相似度里会被当成真实评分,所以更稳妥的做法是填 NaN 后在计算时用掩码。如果你只是想快速跑通,填 0 也能出结果,只是精度会打折扣。pivot_table默认对重复的(user_id, item_id)取均值,如果你的数据里有重复评分,这一步会自动聚合。

3.3 从矩阵到推荐列表的完整链路

拿到矩阵后,计算相似度、找邻居、预测评分、排序取 TopN,这四步串起来就是一条完整的推荐链路。下面是一个把前面代码串起来的函数:

def recommend_for_user(matrix, user_id, top_n=5, k=10): user_sim = cosine_similarity(matrix) user_index = list(matrix.index).index(user_id) sim_scores = list(enumerate(user_sim[user_index])) sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True) neighbors = [i for i, _ in sim_scores[1:k+1]] scores = {} for item in matrix.columns: if matrix.loc[user_id, item] != 0: continue weighted_sum = 0 sim_sum = 0 for n in neighbors: rating = matrix.iloc[n][item] if rating != 0: weighted_sum += user_sim[user_index][n] * rating sim_sum += abs(user_sim[user_index][n]) if sim_sum > 0: scores[item] = weighted_sum / sim_sum return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_n]

这个函数的参数k控制邻居数量,top_n控制返回几个推荐。注意里面用matrix.loc[user_id, item] != 0来过滤已经评过分的商品,避免重复推荐。实际跑的时候,如果sim_sum为 0,说明邻居都没评过这个商品,直接跳过。这套逻辑在几千个用户、几百个商品的数据集上跑,几秒钟就能出结果。

4. 避坑与排查:跑不通的时候先看这几个地方

4.1 现象:运行报错ModuleNotFoundError: No module named 'sklearn'

原因:环境里没装 scikit-learn,或者装到了另一个 Python 版本下。很多人用pip install装完,但 IDE 里选的解释器是系统自带的,不是同一个。

解决:先确认当前 Python 版本,python --version,然后用python -m pip install scikit-learn装到当前解释器下。如果用的是 PyCharm,去 Settings 里把 Project Interpreter 切到你装包的那个环境。我一般会直接建一个虚拟环境,python -m venv venv,激活后再装依赖,省得后面乱。

4.2 现象:推荐结果全是同一个商品,或者推荐列表为空

原因:评分矩阵太稀疏,或者相似度计算时用了填 0 的矩阵,导致大部分用户之间相似度都是 0 或负数。另一个可能是k设得太小,邻居里没有对目标商品评过分的人。

解决:先打印矩阵的稀疏度,(matrix == 0).sum().sum() / matrix.size,如果超过 95%,说明数据量不够,要么补数据,要么换 ItemCF 试试。然后把k从 10 调到 30,看结果有没有变化。如果还是空,检查预测公式里sim_sum是不是一直为 0,那说明邻居和商品没有交集,需要放宽相似度阈值。

4.3 现象:数据库连接报Access denied或Unknown database

原因:连接字符串里的用户名、密码、数据库名不对,或者 MySQL 没启动。还有一种情况是用了localhost但 MySQL 只监听127.0.0.1,解析出问题。

解决:先用命令行mysql -u root -p能登进去,确认数据库存在。然后把连接串里的localhost换成127.0.0.1,密码里如果有特殊字符要 URL 编码。如果还是不行,检查 MySQL 的bind-address配置。我踩过的坑是密码里有个@,直接写进连接串会被当成主机分隔符,改成%40就好了。

4.4 现象:论文里的算法描述和源码对不上

原因:很多毕设压缩包里的论文是通用模板,源码是另一套,两者可能来自不同项目拼凑。论文里写的是矩阵分解,源码里却是 UserCF,这种情况不少见。

解决:以源码为准,先跑通源码,再根据源码的实际逻辑去改论文里的算法描述。如果源码质量太差,比如相似度计算写错了,那就按本文第 2 章的公式自己重写核心函数。别硬着头皮对着论文改源码,那样只会越改越乱。

4.5 现象:演示视频里跑得飞快,自己跑却卡死

原因:视频里的数据集可能只有几百条,你导入的是几万条。协同过滤的相似度矩阵是 O(n²) 复杂度,用户数一多,内存和 CPU 都扛不住。

解决:先在小数据集上验证逻辑,确认没问题后再上全量。如果全量跑不动,换 ItemCF,因为商品数量通常比用户少,矩阵更小。或者用稀疏矩阵scipy.sparse来存评分数据,cosine_similarity对稀疏矩阵有优化。再不行就上离线计算,把相似度矩阵存到数据库或文件里,线上只查表。

5. 让推荐结果更稳的几个进阶技巧

5.1 用时间衰减给旧行为降权

用户三个月前买的奶粉和昨天买的手机,对当前推荐的价值完全不同。可以在评分表里用ts字段算一个衰减因子:

import time def time_decay(ts, half_life=30*24*3600): """half_life 单位秒,默认 30 天""" delta = time.time() - ts return 0.5 ** (delta / half_life)

把这个因子乘到评分上,再参与相似度计算,推荐结果会更贴近用户近期兴趣。half_life这个参数根据业务调整,快消品可以设 7 天,耐用品设 90 天。

5.2 用混合推荐兜底冷启动

新用户没有历史评分,协同过滤直接失效。常见做法是混合一个热门推荐:当用户评分记录少于 5 条时,直接返回销量最高的商品列表。等行为积累够了,再切回协同过滤。这个阈值我一般设 5 到 10 条,具体看商品丰富度。

5.3 离线评估指标:命中率和覆盖率

跑出推荐列表后,怎么知道好不好?可以用留一法:从每个用户的评分里抽一条藏起来,用剩下的数据训练,看推荐列表里有没有藏起来的那条。命中率(Hit Rate)就是命中的用户数除以总用户数。另一个指标是覆盖率,推荐出来的商品去重后占总商品的比例,太低说明推荐太集中,多样性差。

def hit_rate(recommendations, ground_truth): hits = 0 for user, items in ground_truth.items(): if user in recommendations: if any(item in recommendations[user] for item in items): hits += 1 return hits / len(ground_truth)

这个函数里recommendations是字典,键是用户 ID,值是推荐商品列表;ground_truth是藏起来的那条记录。跑一遍就能对当前参数下的效果有个数。

5.4 我自己的习惯

每次改完相似度公式或参数,我一定会先跑一遍离线评估,把命中率记下来,再和上一次对比。没有这个数,调参就是玄学。另外,数据库里的评分数据我习惯保留原始时间戳,哪怕当前用不上,后面想加时间衰减或者做序列推荐时,这就是后悔药。这套系统本身不复杂,难的是数据质量和参数耐心。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询