简介:这份资源面向计算机相关专业的毕业生与课程设计学习者,提供一套基于Python与机器学习算法的电影推荐系统及票房预测系统完整方案,可用于毕业设计、期末大作业等高分场景。压缩包共59个文件,约30.95MB,包含16个py源码文件、16个csv数据集、23张png图表、2个md说明文档及1份pdf报告,覆盖数据预处理、特征工程、模型训练与结果可视化全流程。推荐系统部分涉及协同过滤、基于内容的推荐及KNN、SVD等混合模型,票房预测部分则采用线性回归与随机决策树,并配有特征可视化脚本与数据分析报告。目前已有343人学习,代码注释详尽,新手也能理解部署,目录按data、prediction、ensemble_recommender等模块划分,便于按需查阅与二次开发。
1. 电影推荐与票房预测:一个毕业设计为什么能同时踩中两个机器学习高频场景
做毕业设计选题时,很多同学会陷入两难:推荐系统看起来太烂大街,票房预测又怕数据拿不到。但把这两个场景放进同一个项目里,反而能形成一条完整的技术闭环——推荐系统解决“用户看什么”,票房预测解决“片方赚多少”,两者共享同一套数据清洗、特征工程和模型评估流程。这个标题下的项目,本质上是用 Python 和机器学习算法,把电影领域的两个核心问题串起来:一个基于用户行为做协同过滤或内容推荐,一个基于上映前可获取的特征做回归预测。适合谁?适合正在找毕业设计方向、想用 Python 把机器学习从课本推到可运行系统的本科生,也适合刚入门机器学习、想找一个有真实数据感、能写进简历的练手项目。热搜词里“python机器学习”“电影推荐系统”“票房预测系统”反复出现,说明这个组合的检索需求一直很稳,不是短期噱头。
2. 推荐系统选型:协同过滤、矩阵分解还是内容推荐,先看数据长什么样
2.1 三种主流推荐路线的适用边界
电影推荐系统在毕业设计里最常见的三种做法:基于用户的协同过滤(UserCF)、基于物品的协同过滤(ItemCF)、矩阵分解(SVD/NMF),以及基于内容(TF-IDF + 余弦相似度)。选哪个不取决于哪个“高级”,而取决于你手头有什么数据。
如果你拿到的是 MovieLens 这类标准数据集,用户-物品评分矩阵比较稠密,UserCF 和 ItemCF 都能跑出可解释的结果。UserCF 适合用户兴趣相似度高的场景,比如影迷社区;ItemCF 适合物品之间关联强的场景,比如“看了这部也看那部”的推荐位。矩阵分解在评分预测任务上通常比邻域方法更稳,因为它把稀疏矩阵拆成隐向量,能缓解数据稀疏问题。内容推荐则完全不依赖用户行为,只要有电影元数据(类型、导演、演员、简介),就能给新用户做推荐,冷启动友好,但精度上限低。
我一般会建议:主推矩阵分解做评分预测,辅以 ItemCF 做相似电影推荐,内容推荐作为冷启动兜底。这样论文里既有对比实验,系统里也有实际可用的推荐结果。
2.2 用 Surprise 库跑通矩阵分解的最小代码
# 安装:pip install scikit-surprise from surprise import Dataset, Reader, SVD from surprise.model_selection import train_test_split from surprise import accuracy # 加载 MovieLens-100k,格式:user item rating timestamp reader = Reader(line_format='user item rating timestamp', sep='\t') data = Dataset.load_from_file('ml-100k/u.data', reader=reader) # 划分训练集和测试集,random_state 固定保证可复现 trainset, testset = train_test_split(data, test_size=0.2, random_state=42) # 使用 SVD 矩阵分解,n_factors 是隐向量维度,n_epochs 是迭代轮数 algo = SVD(n_factors=50, n_epochs=20, lr_all=0.005, reg_all=0.02) algo.fit(trainset) # 预测并评估 predictions = algo.test(testset) accuracy.rmse(predictions, verbose=True)这段代码的核心逻辑是:把评分数据加载成 Surprise 能识别的格式,用 SVD 做矩阵分解,最后用 RMSE 衡量预测评分和真实评分的差距。参数方面,n_factors控制隐向量维度,太小欠拟合,太大容易过拟合,50 是常见起点;n_epochs是 SGD 迭代次数,20 到 50 之间看收敛情况;lr_all是学习率,0.005 比较稳;reg_all是正则化系数,防止过拟合。跑完如果 RMSE 在 0.9 左右,说明模型基本可用。失败时先看数据格式对不对,Surprise 对分隔符和列顺序很敏感,line_format写错会直接报错。
2.3 物品相似度计算与推荐结果生成
矩阵分解给出的是评分预测,但用户需要的是“推荐列表”。常见做法是用 ItemCF 计算物品相似度,然后对用户看过的电影找相似电影。
import pandas as pd from sklearn.metrics.pairwise import cosine_similarity # 构建用户-物品评分矩阵 ratings = pd.read_csv('ml-100k/u.data', sep='\t', names=['user', 'item', 'rating', 'ts']) matrix = ratings.pivot_table(index='user', columns='item', values='rating').fillna(0) # 计算物品之间的余弦相似度 item_sim = cosine_similarity(matrix.T) item_sim_df = pd.DataFrame(item_sim, index=matrix.columns, columns=matrix.columns) def recommend_similar(item_id, top_n=10): # 取相似度最高的 top_n 个物品,排除自身 sim_scores = item_sim_df[item_id].sort_values(ascending=False)[1:top_n+1] return sim_scores # 示例:找与电影 1 最相似的 10 部 print(recommend_similar(1))这里用余弦相似度衡量物品向量之间的夹角,值越接近 1 越相似。pivot_table把长表转成宽表,缺失值填 0 表示未评分。注意:填 0 会引入“未评分等于不喜欢”的偏差,更严谨的做法是用均值填充或只对共同评分用户计算相似度。毕业设计里如果只求跑通,填 0 可以接受,但论文里要说明这个简化。
3. 票房预测系统:从数据获取到特征工程,哪些字段真正影响预测
3.1 票房预测的数据来源与字段选择
票房预测比推荐系统更依赖外部数据。常见做法是从公开数据平台抓取电影元数据(预算、类型、导演、演员、上映时间、时长、制片公司)和上映后数据(首周票房、排片率、上座率)。但毕业设计里最容易踩的坑是:用了上映后才有的数据去预测上映前的结果,这叫数据泄漏,模型指标会虚高,答辩时容易被问住。
我一般会严格区分两类特征:上映前可获取的(预算、类型、导演历史票房、演员热度、档期、是否续集)和上映后才有的(首周票房、观众评分、社交媒体讨论量)。如果做“上映前预测”,只能用前者;如果做“上映后修正预测”,可以加入首周数据。论文里最好两种都做,对比一下加入首周数据后 RMSE 下降多少,这本身就是个有价值的实验。
3.2 用 Pandas 做特征工程与缺失值处理
import pandas as pd import numpy as np # 假设已有电影数据表 movies.csv df = pd.read_csv('movies.csv') # 处理预算:去掉货币符号,转成数值,缺失值用中位数填充 df['budget'] = df['budget'].replace('[\$,]', '', regex=True).astype(float) df['budget'] = df['budget'].fillna(df['budget'].median()) # 类型做 one-hot 编码,取前 10 个高频类型 genres = df['genres'].str.get_dummies(sep='|') top_genres = genres.sum().sort_values(ascending=False).head(10).index genres = genres[top_genres] # 上映月份提取,档期对票房影响很大 df['release_date'] = pd.to_datetime(df['release_date'], errors='coerce') df['release_month'] = df['release_date'].dt.month df['is_summer'] = df['release_month'].isin([6, 7, 8]).astype(int) df['is_holiday'] = df['release_month'].isin([1, 2, 12]).astype(int) # 合并特征 features = pd.concat([df[['budget', 'popularity', 'runtime']], genres, df[['is_summer', 'is_holiday']]], axis=1) features = features.fillna(0)这段代码做了四件事:预算字段清洗、类型 one-hot、档期特征提取、缺失值填充。budget里的货币符号和逗号必须去掉,否则astype(float)会报错。类型字段用str.get_dummies按|拆分,只保留高频类型避免维度爆炸。档期特征里,暑期档和贺岁档通常是票房高峰,做成 0/1 变量让模型更容易捕捉。popularity和runtime如果有缺失,填 0 或中位数都行,但要在论文里说明。
3.3 回归模型对比:线性回归、随机森林和 XGBoost 的 RMSE 差异
票房预测本质是回归任务。毕业设计里至少跑三个模型做对比:线性回归(基线)、随机森林(非线性)、XGBoost(集成学习)。评价指标用 RMSE 和 R²。
from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from xgboost import XGBRegressor from sklearn.metrics import mean_squared_error, r2_score import numpy as np X = features y = df['revenue'].fillna(df['revenue'].median()) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) models = { 'LinearRegression': LinearRegression(), 'RandomForest': RandomForestRegressor(n_estimators=100, random_state=42), 'XGBoost': XGBRegressor(n_estimators=100, learning_rate=0.1, random_state=42) } for name, model in models.items(): model.fit(X_train, y_train) pred = model.predict(X_test) rmse = np.sqrt(mean_squared_error(y_test, pred)) r2 = r2_score(y_test, pred) print(f'{name}: RMSE={rmse:.2f}, R2={r2:.4f}')参数上,随机森林的n_estimators从 100 起步,树越多越稳但计算越慢;XGBoost 的learning_rate控制每棵树的贡献,0.1 是常用值,调小到 0.05 通常能提升一点精度但需要更多树。如果 XGBoost 的 RMSE 明显低于线性回归,说明特征和票房之间确实存在非线性关系,论文里可以展开分析特征重要性。注意:票房数据通常右偏严重,取对数后再预测往往效果更好,np.log1p(y)是个实用技巧。
4. 避坑与排查:推荐和票房预测里最容易翻车的五个地方
4.1 评分矩阵稀疏导致推荐结果全是热门电影
现象:不管给哪个用户推荐,出来的都是那几部最热门的电影,个性化推荐形同虚设。原因:评分矩阵太稀疏,大部分用户只评了几部电影,相似度计算时共同评分项太少,模型退化成“推荐大家都看的”。解决:降低相似度计算的共同评分阈值,或者改用矩阵分解,隐向量能在低维空间捕捉用户偏好,不依赖共同评分。另外可以在推荐列表里加入多样性约束,比如强制每个类型至少出现一部。
4.2 票房预测用了上映后数据导致指标虚高
现象:模型 RMSE 很低,R² 接近 0.9,但答辩时被问“首周票房是上映后才知道的,你怎么预测?”原因:特征里混入了上映后才能获取的字段,比如首周票房、观众评分、社交媒体热度。解决:严格按时间切分特征,上映前预测只能用上映前可获取的数据。如果要做上映后修正,单独建一个模型,论文里分开报告指标。
4.3 类型字段 one-hot 后维度爆炸
现象:电影类型有几十种,one-hot 后特征维度从几十涨到几百,模型训练变慢,还容易过拟合。原因:低频类型出现次数太少,对模型没有泛化价值。解决:只保留高频类型,或者把低频类型合并成“其他”。常见做法是取累计出现次数前 80% 的类型,剩下的归为一类。这样既保留了主要信息,又控制了维度。
4.4 缺失值填充方式不一致导致训练和预测行为不同
现象:训练时用中位数填充,预测时用 0 填充,模型在测试集上表现正常,上线后预测结果离谱。原因:填充逻辑没有封装成统一的预处理管道,训练和推理走了两套代码。解决:用 sklearn 的Pipeline把填充、编码、模型串起来,训练和预测都走同一个管道。或者至少把填充值保存下来,预测时用同样的值。
4.5 随机种子不固定导致实验结果无法复现
现象:每次跑代码得到的 RMSE 都不一样,论文里写的结果和别人复现的对不上。原因:train_test_split和模型初始化没有固定random_state。解决:所有涉及随机的地方都设random_state=42,包括数据划分、模型初始化、交叉验证。这不是玄学,是保证实验可复现的基本操作。
5. 把两个系统串成一个可演示的毕业设计:接口设计、评估报告和答辩技巧
5.1 用 Flask 把推荐和预测封装成两个 API
毕业设计最终要能演示。推荐系统和票房预测系统可以各自封装成一个 REST API,前端用一个简单页面调用。推荐接口接收用户 ID,返回推荐电影列表;预测接口接收电影特征,返回预测票房。
from flask import Flask, request, jsonify import pickle app = Flask(__name__) # 加载训练好的模型和推荐器 with open('svd_model.pkl', 'rb') as f: svd_model = pickle.load(f) with open('xgb_model.pkl', 'rb') as f: xgb_model = pickle.load(f) @app.route('/recommend', methods=['GET']) def recommend(): user_id = int(request.args.get('user_id')) # 这里简化处理:取用户未看过的电影,按预测评分排序 # 实际代码需要维护用户已看列表和候选电影集 candidates = [1, 2, 3, 4, 5] # 示例候选 preds = [(i, svd_model.predict(user_id, i).est) for i in candidates] preds.sort(key=lambda x: x[1], reverse=True) return jsonify({'user_id': user_id, 'recommendations': [i for i, _ in preds[:5]]}) @app.route('/predict', methods=['POST']) def predict(): data = request.json features = [[data['budget'], data['popularity'], data['runtime'], data['is_summer'], data['is_holiday']]] pred = xgb_model.predict(features)[0] return jsonify({'predicted_revenue': float(pred)}) if __name__ == '__main__': app.run(debug=True)这个示例把两个模型加载进来,推荐接口返回 top-5 电影 ID,预测接口返回票房数值。实际项目中,推荐接口需要维护用户已看列表和候选电影集,预测接口需要和训练时的特征顺序完全一致。debug=True只用于开发,演示时关掉。
5.2 评估报告里必须写清楚的四个指标
论文里的评估部分不能只写“效果不错”。推荐系统至少写 RMSE(评分预测)、Precision@K、Recall@K、覆盖率。票房预测至少写 RMSE、MAE、R²、特征重要性排序。Precision@K 衡量推荐列表里有多少是用户真正喜欢的,Recall@K 衡量用户喜欢的电影有多少被推荐出来,覆盖率衡量系统能推荐出多少不同电影,避免全是热门。票房预测的特征重要性可以用 XGBoost 的feature_importances_直接输出,预算、档期、导演历史票房通常是前三。
5.3 答辩时被问“创新点在哪”怎么答
毕业设计的创新点不一定是算法创新。把两个场景串起来、做了完整的特征工程对比、实现了可演示的系统、分析了数据泄漏的影响,这些都是工程层面的创新。我一般会建议准备三个回答方向:第一,推荐和预测共享了同一套数据清洗和特征工程流程,减少了重复开发;第二,对比了多种模型并给出了选型依据,不是随便挑一个;第三,系统可演示、代码可复现,不是只跑了个 notebook。如果答辩老师追问算法细节,就回到 SVD 的隐向量含义和 XGBoost 的 boosting 原理,这两个讲清楚就够用了。
希望帮到你。
本文还有配套的精品资源,点击获取