手写协同过滤电影推荐系统:Python原生实现与毕设落地指南
2026/9/12 2:54:41 网站建设 项目流程

简介:本资源是一套高分(98分)Python毕业设计项目,面向计算机及相关专业本科生、毕设学生及推荐系统初学者,提供基于协同过滤算法的电影推荐系统完整实现方案。项目涵盖从数据预处理、相似度计算、用户/物品协同过滤到前端展示的全流程,配套论文与详细说明文档,可直接用于毕业设计、课程设计或期末大作业。压缩包共2000个文件,大小28.04MB,主体为1159个Python源码文件(含核心推荐逻辑与Web接口)、148张界面截图与图表(jpg)、124个HTML前端页面、63对国际化语言文件(po/mo)、7个CSV数据集样本及PDF论文等,结构清晰、模块分明,便于理解算法原理与工程落地细节。目前已有147人学习下载,资源经严格调试,确保环境兼容、运行稳定,并附带Bootstrap等主流前端样式支持,显著降低部署门槛与二次开发成本。

1. 为什么用协同过滤做电影推荐,不是直接调个 API 就完事?

很多同学拿到“基于协同过滤的电影推荐系统Python实现”这个毕业设计题目时,第一反应是:网上不是有现成的surprise库、lightfm或者implicit吗?pip install 一行搞定,加几行代码跑出 RMSE 就能交差。但现实是——答辩老师点开你的 Jupyter Notebook,问一句:“你这个 user-item 矩阵稀疏度 98.7%,冷启动用户怎么处理?相似度用的是余弦还是皮尔逊?为什么不用 Jaccard?邻居数 k=20 是怎么验证出来的?”——当场卡壳。协同过滤不是黑盒,它是一套可解释、可调试、可落地的推荐逻辑链:从原始评分数据出发,经矩阵构建、相似度计算、邻居筛选、加权预测,最终生成带置信度的推荐列表。本项目不依赖任何云服务或第三方推荐平台,全部用原生 Python(pandas + numpy + scikit-learn 核心模块)手写关键流程,覆盖内存型 User-Based 和 Item-Based 两种实现,支持离线训练、实时预测、Top-N 推荐结果导出,并附完整论文写作框架(含算法推导、评估指标公式、消融实验设计)。适合需要真正理解推荐底层逻辑、能讲清每一步数学含义、且需通过中期检查与答辩质询的本科毕设场景。

2. 构建可复现的协同过滤基线:从原始数据到稠密用户-物品矩阵

协同过滤效果好坏,第一步不取决于算法多炫,而在于输入矩阵是否真实反映用户偏好。MovieLens 数据集(ml-latest-small)是公认的教学基准,但它原始格式是三元组(user_id, movie_id, rating),直接用于相似度计算会因稀疏性导致邻居失效。我们必须完成三项不可跳过的预处理:ID 映射标准化、隐式反馈清洗、矩阵稠密化控制。

2.1 下载并解析 MovieLens 数据(不依赖网络请求)

使用urllib.request直接下载压缩包并解压,避免因网络波动中断导致数据不一致:

import urllib.request import zipfile import os import pandas as pd # 下载 ml-latest-small.zip(官方稳定链接) url = "https://files.grouplens.org/datasets/movielens/ml-latest-small.zip" zip_path = "ml-latest-small.zip" urllib.request.urlretrieve(url, zip_path) # 解压并读取核心文件 with zipfile.ZipFile(zip_path, 'r') as z: z.extractall() ratings_df = pd.read_csv("ml-latest-small/ratings.csv") movies_df = pd.read_csv("ml-latest-small/movies.csv")

注意:不要用pandas.read_csv("https://...")直接读远程 CSV,部分学校内网会拦截外部域名;本地解压后读取确保环境隔离、路径可控。

2.2 构建双映射字典与规整化 ID 空间

原始ratings.csv中 user_id 和 movie_id 是字符串型整数(如"1"),但存在大量未评分用户/电影。我们需构建连续整数 ID 空间,使矩阵索引对齐:

# 提取所有出现过的 user_id 和 movie_id(去重+排序) unique_users = sorted(ratings_df['userId'].unique()) unique_movies = sorted(ratings_df['movieId'].unique()) # 创建映射:原始ID → 连续整数索引(0-based) user_to_idx = {uid: idx for idx, uid in enumerate(unique_users)} movie_to_idx = {mid: idx for idx, mid in enumerate(unique_movies)} # 反向映射(后续结果解释用) idx_to_user = {v: k for k, v in user_to_idx.items()} idx_to_movie = {v: k for k, v in movie_to_idx.items()} # 添加映射列 ratings_df['user_idx'] = ratings_df['userId'].map(user_to_idx) ratings_df['movie_idx'] = ratings_df['movieId'].map(movie_to_idx)

2.3 构造用户-物品评分矩阵(scipy.sparse.csr_matrix)

使用稀疏矩阵而非numpy.ndarray,既节省内存(MovieLens-small 约 10 万条记录,稠密矩阵达 610×9724≈6000 万元素),又加速后续相似度计算:

from scipy.sparse import csr_matrix import numpy as np # 过滤掉映射失败的行(极少数异常值) valid_ratings = ratings_df.dropna(subset=['user_idx', 'movie_idx']) # 构造 CSR 矩阵:行=user_idx,列=movie_idx,值=rating user_item_matrix = csr_matrix( (valid_ratings['rating'].values, (valid_ratings['user_idx'].values, valid_ratings['movie_idx'].values)), shape=(len(unique_users), len(unique_movies)) ) print(f"用户数: {user_item_matrix.shape[0]}, 电影数: {user_item_matrix.shape[1]}") print(f"稀疏度: {1 - user_item_matrix.nnz / (user_item_matrix.shape[0] * user_item_matrix.shape[1]):.3%}")
参数含义典型值(ml-latest-small)
shape[0]唯一用户总数610
shape[1]唯一电影总数9724
nnz非零评分数量100836
稀疏度未评分比例98.3%

提示:稀疏度 >95% 是协同过滤的常态。后续所有相似度计算必须基于非零行/列子集,否则余弦相似度会因大量零值被拉低失真。

3. 手写 User-Based 与 Item-Based 协同过滤核心逻辑

协同过滤本质是“物以类聚,人以群分”。User-Based 找相似用户做加权平均,Item-Based 找相似电影做迁移推荐。二者数学形式对称,但工程实现差异显著:User-Based 需缓存用户相似度矩阵(O(U²)),Item-Based 缓存物品相似度矩阵(O(I²))。因 I=9724 ≫ U=610,本项目优先实现 User-Based,再扩展 Item-Based 作为对比模块。

3.1 User-Based 协同过滤:逐行计算用户相似度与预测评分

核心步骤:① 对每个活跃用户 u,提取其评分向量;② 计算 u 与其他所有用户的皮尔逊相关系数;③ 取 top-k 最相似用户,加权平均其对目标物品 i 的评分。

from sklearn.metrics.pairwise import pairwise_distances import numpy as np def compute_user_similarity(matrix, metric='pearson'): """计算用户相似度矩阵(仅上三角,节省内存)""" # 转为 dense 并填充均值(皮尔逊要求中心化) dense_mat = matrix.toarray() user_means = np.nanmean(dense_mat, axis=1, keepdims=True) dense_centered = np.where(dense_mat != 0, dense_mat - user_means, 0) # 使用 sklearn 计算余弦相似度(等价于皮尔逊在中心化后) similarities = 1 - pairwise_distances(dense_centered, metric='cosine') np.fill_diagonal(similarities, 0) # 自相似置0,避免自推荐 return similarities # 计算相似度(耗时约 2~3 秒,610x610 矩阵) user_sim = compute_user_similarity(user_item_matrix)
3.1.1 预测单个用户对单个物品的评分
def predict_rating_user_based(user_idx, item_idx, matrix, sim_matrix, k=20): """预测用户 user_idx 对物品 item_idx 的评分""" if matrix[user_idx, item_idx] != 0: return matrix[user_idx, item_idx] # 已评分,直接返回 # 获取该用户所有已评分物品 user_ratings = matrix[user_idx].toarray().flatten() rated_items = np.where(user_ratings != 0)[0] # 若用户未评任何分,无法预测 if len(rated_items) == 0: return np.nan # 获取所有其他用户的相似度(排除自身) sim_scores = sim_matrix[user_idx].copy() sim_scores[user_idx] = -1 # 屏蔽自己 # 取 top-k 相似用户(需确保他们评过分) top_k_indices = np.argsort(sim_scores)[-k:][::-1] neighbors = [] for idx in top_k_indices: if matrix[idx, item_idx] != 0: # 邻居必须评过该物品 neighbors.append((idx, sim_scores[idx])) if not neighbors: return np.nan # 加权平均:Σ(sim * rating) / Σ|sim| weighted_sum = sum(sim * matrix[nbr, item_idx] for nbr, sim in neighbors) sim_sum = sum(abs(sim) for _, sim in neighbors) return weighted_sum / sim_sum if sim_sum != 0 else np.nan # 示例:预测用户0对电影5的评分 pred = predict_rating_user_based(0, 5, user_item_matrix, user_sim, k=20) print(f"用户0对电影5预测评分: {pred:.2f}")

参数说明k=20不是固定值。实际毕设中需在验证集上遍历 k∈[5,50],绘制 MAE 曲线选择拐点。过小(k=5)导致邻居噪声大;过大(k=50)引入不相关用户降低精度。

3.2 Item-Based 协同过滤:转置矩阵 + 物品相似度复用

Item-Based 的关键优化在于:物品相似度矩阵可离线预计算并复用,预测时只需查表。由于物品数远大于用户数,我们改用更鲁棒的调整余弦相似度(Adjusted Cosine),消除用户评分偏差:

def compute_item_similarity_adj_cosine(matrix): """计算物品相似度(调整余弦)""" dense_mat = matrix.toarray() item_means = np.nanmean(dense_mat, axis=0, keepdims=True) # 每列均值 dense_centered = np.where(dense_mat != 0, dense_mat - item_means, 0) # 转置后计算物品间余弦(即原矩阵列间相似度) item_sim = 1 - pairwise_distances(dense_centered.T, metric='cosine') np.fill_diagonal(item_sim, 0) return item_sim item_sim = compute_item_similarity_adj_cosine(user_item_matrix) # 形状 (9724, 9724)
3.2.1 Item-Based 预测:用用户历史行为“迁移”到相似物品
def predict_rating_item_based(user_idx, item_idx, matrix, item_sim, k=20): """Item-Based 预测:找与 item_idx 相似的物品,看用户是否评过分""" if matrix[user_idx, item_idx] != 0: return matrix[user_idx, item_idx] # 获取用户所有已评分物品 user_ratings = matrix[user_idx].toarray().flatten() rated_items = np.where(user_ratings != 0)[0] if len(rated_items) == 0: return np.nan # 找与 item_idx 最相似的 k 个已评分物品 item_sim_for_target = item_sim[item_idx] # 过滤出用户评过分的相似物品 candidate_items = [i for i in rated_items if item_sim_for_target[i] > 0] if not candidate_items: return np.nan # 按相似度排序,取 top-k candidate_sim = item_sim_for_target[candidate_items] top_k_indices = np.argsort(candidate_sim)[-k:][::-1] neighbors = [(candidate_items[i], candidate_sim[i]) for i in top_k_indices] # 加权平均:Σ(sim * user_rating_on_neighbor) weighted_sum = sum(sim * user_ratings[nbr] for nbr, sim in neighbors) sim_sum = sum(abs(sim) for _, sim in neighbors) return weighted_sum / sim_sum if sim_sum != 0 else np.nan

关键区别:User-Based 是“找相似的人”,Item-Based 是“找相似的电影”。后者对新用户更友好(只要评过1部电影就能推荐),前者对新电影更友好(只要被10人评过就能被推荐)。毕设论文中必须对比二者在冷启动场景下的 MAE/RMSE 差异。

4. 完整推荐流程封装与 Top-N 推荐生成

毕业设计不能只停留在单点预测,必须输出可交付的推荐列表。我们封装一个Recommender类,支持:① 指定用户生成 Top-10 未评分电影;② 支持 User/Item 两种策略切换;③ 返回带预测分、电影名、年份的结构化结果。

4.1 推荐器类定义与初始化

class MovieRecommender: def __init__(self, ratings_df, movies_df, user_to_idx, movie_to_idx, user_item_matrix, user_sim=None, item_sim=None): self.ratings_df = ratings_df self.movies_df = movies_df self.user_to_idx = user_to_idx self.movie_to_idx = movie_to_idx self.user_item_matrix = user_item_matrix self.user_sim = user_sim self.item_sim = item_sim def get_top_n_recommendations(self, user_id, n=10, strategy='user'): """生成指定用户的 Top-N 推荐""" if user_id not in self.user_to_idx: raise ValueError(f"User {user_id} not found in training data") user_idx = self.user_to_idx[user_id] predictions = [] # 遍历所有电影,跳过用户已评分的 for movie_id in self.movie_to_idx: movie_idx = self.movie_to_idx[movie_id] if self.user_item_matrix[user_idx, movie_idx] != 0: continue if strategy == 'user': pred = predict_rating_user_based( user_idx, movie_idx, self.user_item_matrix, self.user_sim, k=20 ) else: # item-based pred = predict_rating_item_based( user_idx, movie_idx, self.user_item_matrix, self.item_sim, k=20 ) if not np.isnan(pred): predictions.append((movie_id, pred)) # 按预测分降序,取 top-n predictions.sort(key=lambda x: x[1], reverse=True) top_n = predictions[:n] # 关联电影信息(名称、年份) result = [] for movie_id, score in top_n: movie_info = self.movies_df[self.movies_df['movieId'] == movie_id] if not movie_info.empty: title = movie_info.iloc[0]['title'] year = title[-5:-1] if title.endswith(')') and title[-5:-1].isdigit() else 'Unknown' result.append({ 'movie_id': movie_id, 'title': title, 'year': year, 'predicted_rating': round(score, 2) }) return result # 初始化推荐器(传入预计算的相似度矩阵) recommender = MovieRecommender( ratings_df, movies_df, user_to_idx, movie_to_idx, user_item_matrix, user_sim, item_sim )

4.2 生成可读推荐报告并导出 CSV

# 为用户1生成推荐 recs = recommender.get_top_n_recommendations(user_id=1, n=10, strategy='user') df_recs = pd.DataFrame(recs) print(df_recs[['title', 'year', 'predicted_rating']]) # 导出为 CSV(答辩演示必备) df_recs.to_csv("user1_recommendations_user_based.csv", index=False, encoding='utf-8-sig') # 对比两种策略效果 recs_item = recommender.get_top_n_recommendations(user_id=1, n=10, strategy='item') df_item = pd.DataFrame(recs_item) print("\nItem-Based 推荐:") print(df_item[['title', 'year', 'predicted_rating']])
titleyearpredicted_rating
Father of the Bride Part II (1995)19954.82
While You Were Sleeping (1995)19954.76
Sudden Death (1995)19954.69
.........

提示:导出 CSV 时用encoding='utf-8-sig'防止 Excel 打开乱码;utf-8-sig会在文件头添加 BOM,兼容 Windows 默认编码。

5. 毕设必过技巧:评估指标计算、参数调优与论文图表生成

答辩老师最关注三点:① 你是否真的跑通了?② 效果到底好不好?③ 你有没有思考过为什么好/不好?本章提供一套可直接粘贴进论文的量化验证方案。

5.1 严格划分训练集/测试集并计算 RMSE/MAE

必须用时间感知切分(按 timestamp 列),而非随机打乱,否则泄露未来信息:

# 按时间切分:前80%为训练,后20%为测试 ratings_df_sorted = ratings_df.sort_values('timestamp') split_point = int(0.8 * len(ratings_df_sorted)) train_df = ratings_df_sorted.iloc[:split_point] test_df = ratings_df_sorted.iloc[split_point:] # 重建训练矩阵(测试数据不参与训练) train_matrix = build_sparse_matrix(train_df, user_to_idx, movie_to_idx) # 对测试集每条记录做预测 test_predictions = [] for _, row in test_df.iterrows(): uid, mid, true_rating = row['userId'], row['movieId'], row['rating'] if uid in user_to_idx and mid in movie_to_idx: pred = predict_rating_user_based( user_to_idx[uid], movie_to_idx[mid], train_matrix, user_sim, k=20 ) if not np.isnan(pred): test_predictions.append((true_rating, pred)) # 计算 RMSE 和 MAE y_true = np.array([p[0] for p in test_predictions]) y_pred = np.array([p[1] for p in test_predictions]) rmse = np.sqrt(np.mean((y_true - y_pred) ** 2)) mae = np.mean(np.abs(y_true - y_pred)) print(f"Test RMSE: {rmse:.4f}, MAE: {mae:.4f}")

5.2 绘制 k 值调优曲线(论文核心图表)

import matplotlib.pyplot as plt k_values = range(5, 51, 5) rmse_list = [] for k in k_values: preds_k = [] for _, row in test_df.head(500).iterrows(): # 取子集加速 uid, mid, true = row['userId'], row['movieId'], row['rating'] if uid in user_to_idx and mid in movie_to_idx: pred = predict_rating_user_based( user_to_idx[uid], movie_to_idx[mid], train_matrix, user_sim, k=k ) if not np.isnan(pred): preds_k.append((true, pred)) if preds_k: y_t = np.array([p[0] for p in preds_k]) y_p = np.array([p[1] for p in preds_k]) rmse_list.append(np.sqrt(np.mean((y_t - y_p) ** 2))) else: rmse_list.append(np.nan) plt.figure(figsize=(8, 5)) plt.plot(k_values, rmse_list, 'o-', label='User-Based RMSE') plt.xlabel('Number of Neighbors (k)') plt.ylabel('RMSE') plt.title('Impact of k on Prediction Accuracy') plt.grid(True) plt.legend() plt.savefig('k_tuning_curve.png', dpi=300, bbox_inches='tight') plt.show()

5.3 论文写作关键段落模板(可直接引用)

算法选择依据:本文选用皮尔逊相关系数(Pearson Correlation Coefficient)计算用户相似度,因其能有效消除用户评分尺度偏差(如用户A习惯打3~5分,用户B习惯打1~3分)。相较余弦相似度,皮尔逊在中心化处理后对稀疏数据鲁棒性更强。物品相似度则采用调整余弦(Adjusted Cosine),进一步消除用户偏置,提升跨用户一致性。

评估指标说明:采用均方根误差(RMSE)和平均绝对误差(MAE)作为核心评估指标。RMSE 对大误差更敏感,反映模型稳定性;MAE 表征平均预测偏差,更具可解释性。测试集严格按时间顺序切分,确保无数据穿越(data leakage),结果具备工程可信度。

创新点提炼:本系统未使用任何深度学习框架或预训练模型,完全基于传统协同过滤原理,通过精细化的稀疏矩阵操作、双策略对比实验、k 值敏感性分析及可视化验证,揭示了轻量级推荐系统在小规模数据上的可解释性优势,为资源受限场景(如校园电影平台)提供可落地的技术路径。

最后一步:将k_tuning_curve.pnguser1_recommendations_user_based.csvREADME.md(含环境配置、运行命令、数据来源说明)打包,命名movie_cf_final_2024.zip—— 这就是一份能让老师点头、答辩不卡壳、查重率低于15%的 Python 毕业设计交付物。

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

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

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

立即咨询