☰
机器学习实现电影票房预测:完整数据清洗与模型训练链路
2026/9/27 23:11:22 网站建设 项目流程

简介:这是一套基于机器学习算法实现电影票房预测的毕业设计完整项目,内含可运行的Python源码和PDF版项目报告,适合正在筹备毕业设计、课程设计或期末大作业的计算机相关专业学生,也适合需要通过项目实战巩固算法知识的开发者。项目围绕票房预测场景,完整覆盖数据读取与清洗、特征工程、模型训练和结果可视化等环节,代码中融合了KNN、SVD等经典机器学习算法,并配有实验数据集与可视化图表,报告则对数据分析、算法选型与效果评估做了详细阐述,可帮助读者打通理论到实战的完整链路。资源压缩包共59个文件,主要包括16个Python脚本、16个CSV数据文件、23张PNG格式可视化图表及Markdown/PDF文档,整体大小约30.95MB,目录按功能模块划分,便于对照报告和源码逐步学习。目前已有267人学习,该项目曾以98.5分通过毕业设计评审,适合作为项目参考、二次开发或算法练手。

1. 机器学习算法实现电影票房预测:一份能直接落地的毕设完整链路

电影票房预测这个选题,几乎是为 Python 毕业设计量身定做的:目标明确、评估指标清楚、数据量适中,算法还能从线性回归一路写到 XGBoost,答辩时既有对比又有深度。这份资源给的是一个能直接落地的完整链路,大数据清洗、特征工程、模型训练到评估出图都有现成代码,报告 PDF 可以对照着改数据、换参数、补图表,最后拼成自己的论文。它适合两类人:一类是急着把毕业设计落地的同学,另一类是刚学完机器学习入门、想找完整项目练手的工程师。

2. 数据清洗与特征工程:票房预测先解决 80% 的脏数据问题

拿到源码后第一步不是调参,而是把数据看明白。原始票房表里常见的问题我拆包时几乎都遇到过:类别特征里同一个导演名字带空格、日期字段是字符串、成本列一半是 None,票房分布从几十万到几十亿右偏严重。这一章我会按「字段体检 → 缺失与异常 → 特征构造 → 相关性初筛」的顺序走,这也是这套源码里data_preprocess.py的实际执行路径。

2.1 拿到数据集先做字段体检

第一步永远是info()和isnull(),先搞清楚每一列的用途和缺失比例,再去想怎么填。

import pandas as pd import numpy as np df = pd.read_csv('movie_data.csv', encoding='utf-8') print(df.info()) print(df.isnull().sum()) print(df.describe())

这段代码做完,你就能看到哪些字段名带特殊符号、哪些数值列的量纲差异巨大。我的习惯是:缺失比例超过 40% 的字段直接丢掉,硬猜出来的均值比真实噪声危害更大;纯 ID 列(如movie_id)也删掉,它对预测没有任何贡献。票房、成本这类数值字段,后面会观察它们的分布形状,确认是否右偏。

2.2 缺失值、异常值与类型转换

这一节是数据清洗的核心。数值列缺失我一般用中位数填充,而不是均值,因为票房特征容易被个别大片拉偏;类别列缺失用「未知」占位,避免 scikit-learn 在编码时直接报错;日期列则转成datetime后拆出「上映月份」这个特征。

# 数值列缺失用中位数填充,避免被离群值带偏 num_cols = ['budget', 'marketing', 'opening_screens'] df[num_cols] = df[num_cols].fillna(df[num_cols].median()) # 类别列缺失用 "未知" 占位,防止 scikit-learn 编码时报错 cat_cols = ['genre', 'director', 'rating_source'] for col in cat_cols: df[col] = df[col].fillna('未知') # 日期转成 datetime,再切出档期月份特征 df['release_date'] = pd.to_datetime(df['release_date']) df['release_month'] = df['release_date'].dt.month

fillna的参数按列语义区分:数值列用中位数是保守策略,类别列用「未知」则保留了「缺失」本身的信息,某些树模型反而能把这个当成一个独立分类。to_datetime之后不要直接拿日期当特征,拆成月份、星期几、是否节假日才有预测意义。

然后是异常值检查。票房数据里那些个位数亿级的大片不是脏数据,是长尾本身,处理时和普通异常值要区别对待:

# 用分位数区间判断票房离群点,而不是只看 mean ± 3std gross_q1 = df['gross'].quantile(0.25) gross_q3 = df['gross'].quantile(0.75) iqr = gross_q3 - gross_q1 outlier_mask = (df['gross'] < gross_q1 - 1.5 * iqr) | (df['gross'] > gross_q3 + 1.5 * iqr) print(df.loc[outlier_mask, ['movie_name', 'gross']])

这段代码只负责「看」异常值,不要直接删。票房预测的目标就是预测长尾,把大片删掉等于把问题本身删掉了。

2.3 目标对数化与特征构造

票房从几百万到几十亿,直接回归会让误差集中在少数大片上。我一般会把目标做一次log1p变换,让分布接近正态后再训练,预测出来再expm1还原。这一步做不做,直接影响后面所有模型的 RMSE。

# 目标对数化:大幅压缩量纲,避免训练被头部大片主导 df['log_gross'] = np.log1p(df['gross'])

log1p是log(1+x),好处是x=0时依然有定义,逆变换对应expm1,评估时再换算回真实票房。

特征构造方面,这套源码里最有价值的是「导演历史平均票房」和「档期 one-hot」。导演历史均值体现的是导演口碑的惯性,档期 one-hot 则把贺岁档、国庆档和普通周末区分开:

# 导演历史平均票房:体现"导演口碑"的惯性 df['director_avg_gross'] = df.groupby('director')['gross'].transform('mean') \ .fillna(df['gross'].median()) # 档期 one-hot,过年与国庆档显著高于普通周末 df = pd.get_dummies(df, columns=['release_month'], prefix='month')

注意director_avg_gross这里有一个隐患:如果直接在全量数据上算均值,会用法院已上映的片子的票房去预测还没有上映的片子,这就带了「未来信息」。第 5 章我会专门讲这个时间泄漏坑,这里先埋个伏笔。

最后做一次相关性初筛,把和log_gross几乎无关的字段挑出来删掉。相关性接近 0 不代表没价值,但在这类小数据量项目里,删掉它们能显著降低过拟合风险。

numeric_features = df.select_dtypes(include=[np.number]).columns corr = df[numeric_features].corr()['log_gross'].sort_values() print(corr)

3. 算法选型:线性回归、随机森林与 XGBoost 怎么选怎么跑

模型这块我建议的路线是「先线性、再树模型、最后梯度提升」,每上一个模型都保留上一轮的对比结果。毕业设计答辩时老师几乎必问「为什么选这几个模型」,这套源码给的对比逻辑能让这个问题答得很从容:线性回归做基准,随机森林验证非线性提升,XGBoost 验证带正则化的梯度提升有没有进一步收益。

3.1 线性回归:先跑一个能当基线的模型

先跑线性回归不是为了拿高分,是为了拿到一个「基准线」。如果后续模型不比基线好多少,说明特征工程有问题,而不是模型不够强。对于特征之间关系比较简单的情况,Ridge 比普通线性回归更稳,alpha=1.0是我常用的起始值:

from sklearn.linear_model import Ridge from sklearn.metrics import mean_squared_error, r2_score ridge = Ridge(alpha=1.0) ridge.fit(X_train, y_train) y_pred = ridge.predict(X_test) print('R²:', r2_score(y_test, y_pred)) print('RMSE:', np.sqrt(mean_squared_error(y_test, y_pred)))

alpha是正则化强度,越大系数越往 0 收缩。如果发现某些特征系数被压得接近 0,说明这个特征本身对预测贡献有限,可以回第 2 章把它删掉。初始化阶段alpha=1.0是不用思考的默认值,等后面调参再放开。

3.2 随机森林:非线性建模和特征重要度一次搞定

随机森林是这套源码里的主力模型,原因有三:一是能天然处理特征之间的非线性交互,比如「大成本 + 贺岁档」的组合效应;二是不用过多做特征缩放;三是可以输出特征重要度,直接生成报告里的图表素材。

from sklearn.ensemble import RandomForestRegressor rf = RandomForestRegressor( n_estimators=300, max_depth=8, min_samples_leaf=5, random_state=42, n_jobs=-1 ) rf.fit(X_train, y_train) importance = pd.Series(rf.feature_importances_, index=feature_names).sort_values(ascending=False) print(importance.head(10))

n_estimators=300对于千行级的数据基本够用,加到 500 收益递减;max_depth=8限制单棵树深度,防止训练集被完全记下来;min_samples_leaf=5强制每个叶子至少 5 个样本,这是对过拟合最直接的压制。random_state=42保证两次运行结果一致,答辩现场复现不翻车。n_jobs=-1表示用满所有 CPU 核心。

特征重要度这一项在论文里非常好用:画一张横向条形图,告诉老师「排片场次和宣传指数贡献了超过 60% 的预测能力」,比空谈模型原理有说服力得多。

3.3 XGBoost:梯度提升带来的边际收益

XGBoost 的核心优势是带正则的梯度提升,对这类特征不多、样本量也不大的表格数据,收敛速度快,而且不容易过拟合。它的参数比随机森林多,但真正要调的也就四五个:

from xgboost import XGBRegressor xgb = XGBRegressor( n_estimators=500, max_depth=5, learning_rate=0.05, subsample=0.8, random_state=42, eval_metric='rmse' ) xgb.fit(X_train, y_train)

learning_rate=0.05是步长,越小越稳但需要更多树;subsample=0.8表示每轮只用 80% 的样本训练,类似随机森林的采样思想,能抑制过拟合。eval_metric='rmse'不是参数而是一个监控指标,训练过程中打印它,可以观察验证误差是否在下降。

三个模型的横向对比,我习惯写成一个循环:

models = {'Ridge': ridge, 'RandomForest': rf, 'XGBoost': xgb} for name, model in models.items(): pred = model.predict(X_test) print(name, 'RMSE:', np.sqrt(mean_squared_error(y_test, pred)), 'R²:', r2_score(y_test, pred))

下面是这三个模型在这个场景下的选型对照,可以直接用在报告里:

模型非线性建模特征工程依赖千行级训练速度报告友好度
Ridge 线性回归弱高极快系数可解释
随机森林强低快特征重要度
XGBoost很强低中等参数对比可展开

4. 模型训练与调参:特征标准化、网格搜索与评估指标

这一章解决三个具体问题:训练集怎么切、标准化怎么做不泄露信息、参数怎么搜不玄学。源码里train_model.py的执行顺序和我下面写的一致。

4.1 按时间切分而不是随机切分

票房预测本质是在预测「未来」,如果随机打乱样本,测试集会混入较早年份的片子,和真实场景不符。我一般会按时间排序后取前 80% 做训练、后 20% 做测试:

cut_idx = int(len(df) * 0.8) df = df.sort_values('release_date') # 先按上映日期排序 train = df.iloc[:cut_idx].copy() test = df.iloc[cut_idx:].copy() X_train = train[feature_cols] y_train = train['log_gross'] X_test = test[feature_cols] y_test = test['log_gross']

这里有个细节:sort_values之后索引是乱的,用iloc切分就避开了索引问题。如果用train_test_split默认的随机切分,测试集里既有 2015 年的老片又有 2020 年的新片,看似分数高,但答辩时只要被问一句「你的模型对接下来上映的片子怎么预测」,就答不上来了。

4.2 标准化只 fit 训练集

线性回归和部分后续分析需要特征标准化,但很多人会在这翻车:先把全量数据标准化,再切训练测试集。这会无意中把测试集的分布信息泄露给训练过程,导致验证分数虚高。

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)

fit_transform只在训练集上计算均值和标准差,transform对测试集只做转换,不再重新计算。这两行的顺序错不得,先是fit后是transform。随机森林和 XGBoost 不需要这步,它们对特征尺度不敏感,但如果做 Stacking 集成,标准化能显著加快线性元模型的收敛。

4.3 网格搜索:用 GridSearchCV 把手动调参变可复现

手动调参最大的问题是「凭感觉」,调完之后说不清为什么选这个值。网格搜索的好处是穷举参数组合,并给出每个组合的交叉验证得分,答辩时可以直接说「我在 5 折交叉验证下搜索了 n_estimators 和 max_depth 的组合,最优结果是……」。

from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [200, 400], 'max_depth': [6, 10], 'min_samples_leaf': [3, 6] } gs = GridSearchCV( RandomForestRegressor(random_state=42), param_grid, cv=5, scoring='neg_root_mean_squared_error', n_jobs=-1 ) gs.fit(X_train_scaled, y_train) print(gs.best_params_, gs.best_score_)

scoring='neg_root_mean_squared_error'的负号是因为 sklearn 要求所有分数「越大越好」,所以把 RMSE 取负,让最优值对应最小 RMSE;cv=5表示 5 折交叉验证,对千行级数据计算量完全可以接受;verbose=1可以加,能看到每一组参数的实时进度。搜完拿到best_params_之后,再用这个参数重新初始化模型去 fit 全量训练数据。

4.4 评估指标与图表输出

这套源码最后的评估输出包含三个指标:MAE(平均绝对误差)、RMSE(均方根误差)和 R²(决定系数)。我在报告里是这么解释它们的:MAE 告诉你平均预测偏了多少,单位是万元;RMSE 因为做了平方,对个别偏差大的样本更敏感,所以 RMSE 一定大于 MAE,两者差距越大说明误差越集中在个别片上;R² 则直接给出「模型解释了百分之多少的票房变化」。

指标含义什么时候看它
MAE平均绝对误差,单位与票房一致关心平均偏差时
RMSE对大偏差更敏感想惩罚极端预测错误时
R²模型解释的方差比例横向对比模型时

预测结果的可视化是报告里必放的图。真实票房和预测票房的散点图越贴近对角线,说明模型越准:

import matplotlib.pyplot as plt plt.figure(figsize=(6, 6)) plt.scatter(y_test, y_pred, alpha=0.5) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--') plt.xlabel('真实票房 (log)') plt.ylabel('预测票房 (log)') plt.savefig('prediction.png', dpi=150)

注意这里的横纵坐标都是log尺度,因为训练目标是log_gross。savefig会把图存到本地,后续写报告直接插入。如果想让图更专业,再加一句plt.title标明模型名和 R² 值,答辩 PPT 里这张图就是门面。

5. 避坑排查:五个最容易被答辩老师抓到的低级错误

这一章写的是我拆这个项目时实际踩过的坑,每一条都按「现象 → 原因 → 解决」整理。答辩前把这几条过一遍,能挡掉大部分提问。

5.1 时间泄漏:R² 虚高到 0.95 的第一嫌疑人

现象:模型在测试集上 R² 高达 0.95,看起来完美,但一到真实档期预测就崩。

原因:特征里混进了「未来信息」。最常见的是第 2.3 节提到的director_avg_gross,如果它在全量数据上计算均值,等于用「这部电影上映之后的其他电影票房」去预测这部电影,模型实际上是在抄答案。另一个常见来源是评分类特征——上映首周后才有的豆瓣评分,怎么能在上映前用来预测首周票房?

解决:任何聚合类特征都要按「截止该片上映之前」的数据计算。代码上可以做一次按时间排序的累积均值,或者直接给特征加时间约束,比如只用上映日前 90 天内的宣传指数。R² 超过 0.9 先别高兴,查一遍特征里有没有未来量。

5.2 先标准化再切分,等于自家考试先看答案

现象:训练集和测试集全部标准化后训练,验证分数明显高于用新数据测试的结果。

原因:StandardScaler在全量数据上计算均值和标准差时,已经「看」了测试集的分布。测试集的中心化偏移和缩放尺度被带进了训练过程,这属于标签泄漏的一种变体。

解决:严格按第 4.2 节的写法,fit_transform只用于训练集,transform单独用于测试集。检验方法也很简单:跑两遍,一遍先切分再标准化,一遍先标准化再切分,对比 RMSE,后者通常虚高 5% 到 15%。

5.3 直接回归原始票房,误差被头部大片拉爆

现象:RMSE 大得离谱,而且预测值普遍偏向中间,几部大片的误差贡献了总误差的一大半。

原因:票房分布极度右偏,几百万到几十亿跨了三个量级。模型为了降低平均误差,会把预测值往中位数方向收缩,导致头部大片全部低估。

解决:目标变量换成log1p(gross),训练结束后用expm1还原预测值再算指标。这一步对线性模型尤其重要,对树模型也有帮助。论文里记得写一句「因票房呈长尾分布,对目标进行对数变换以稳定训练」,这既是加分项,也是答辩时老师想听到的理由。

5.4 不固定随机种子,答辩现场跑两次结果不一样

现象:同一个模型代码,第一次跑 RMSE 是 1800 万,第二次变成 2000 万,答辩现场演示直接翻车。

原因:随机森林的样本采样、网格搜索的交叉验证划分、XGBoost 的随机特征选择都有随机性,不固定种子就无法复现。

解决:所有模型初始化都显式传random_state=42,训练前加一句np.random.seed(42),交叉验证时给GridSearchCV的cv参数传KFold(shuffle=True, random_state=42)。固定种子不代表调参更准,但能保证你在答辩现场跑出的数字和报告里写的数字一致,光是这一条就值回票价。

5.5 照着报告 PDF 复现不出图表

现象:报告里的图和表,按自己写的代码重跑一遍怎么都对不上。

原因:三个典型——依赖库版本不一致、数据文件被改动过、随机状态没固定。sklearn 和 xgboost 在版本升级后默认参数会有变化,比如旧版的RandomForestRegressor默认max_depth=None,新版同样;但部分评估行为会变。

解决:源码包里的requirements.txt逐个装上对应版本,不要用pip install sklearn这种偷懒写法,要pip install scikit-learn==版本号。数据文件保持原始 CSV 不要改动,每次跑之前先git diff检查。我自己的习惯是根目录放一个run_all.py,从数据加载到图表输出一条命令跑完,报告里所有数字直接从运行日志复制,杜绝手抄错数。

6. 答辩前最后一步:把验证闭环和两个升级点做扎实

模型跑通、报告写完,离答辩还差一个「验证闭环」:你凭什么说模型是有效的?这里分享两个不用大改就能补上的验证方法和两个扩展点,这套源码的结构已经留好了位置。

第一个验证方法是「随机基线对比」。拿一个只预测均值(或者随机数)的笨模型做对照,如果 XGBoost 只比随机基线好一点点,说明特征没建对。这个对比在报告里画一条水平线就能说清楚。

第二个验证方法是「按档期分组评估」。不要只看整体 R²,把测试集按贺岁档、暑期档、普通档切成三组分别算 MAE。这样能发现模型在哪个档期失效,答辩时说出来是加分项,老师会觉得你理解业务而不仅是调包。这套源码里release_month字段已经构造好了,按它groupby算指标即可。

两个扩展点:

第一个是把宣传指数做成滞后特征。微博热度、物料投放这类指标通常超前于票房,常见做法是取上映前三天、前七天的热度均值构造新列。写爬虫抓这部分数据是另一条线了,这里只改特征就已经有收益。

第二个是 Stacking 集成。源码里的三个模型已经训练完毕,可以拿 Ridge 做元模型,把随机森林和 XGBoost 的预测值拼起来再加一层线性回归。代码改动很小,但答辩时能多说五分钟的架构设计。

做完这四件事,报告里的结论就不再是「我用 XGBoost 得到了 R²=0.87」,而是「在三模型对比基础上,通过滞后特征与集成方法,将 RMSE 从 2100 万降到 1700 万」。后者明显更能站得住脚。

说句实话,这套源码本身能跑通不难,难的是把每一步为什么这么做讲圆。我以前做毕设时只顾着把 RMSE 调低,结果答辩被老师一问「你的特征有没有用到上映之后的数据」,当场就卡住了。从那以后我每次跑票房预测都强制走一遍「时间泄漏检查 → 先切分再标准化 → 目标对数化 → 固定随机种子」四连,顺序一步不乱。

希望帮到你。

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

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

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

立即咨询