☰
电影票房预测平台实战:从数据清洗到Flask部署的毕业设计全流程
2026/9/24 22:14:02 网站建设 项目流程

简介:这是一套面向计算机相关专业学生的机器学习实战项目资料,以电影票房预测为主题,适合正在做毕业设计、课程设计或期末大作业的学习者参考。项目经导师指导并通过评审,获得98分成绩,可作为完整的高分毕设范例。资源包共59个文件,包含16个Python源码文件、16个CSV数据集、23张PNG图表、2份Markdown说明、1份PDF文档及1份TXT文件,压缩包约30.95MB。源码覆盖数据预处理、特征工程、KNN与SVD协同过滤、集成推荐及多模型对比等模块,数据集包含tmdb_5000电影与演职员信息,图表与PDF文档则记录了特征分析与实验结论。目前已有371人学习下载。读者可从中获得完整的赛题实现方案、可复现的训练与测试数据、模块化的代码结构以及数据分析报告,便于快速理解机器学习预测流程并迁移到自己的课题中。

1. 电影票房预测平台:从数据集到可运行系统的落地路径

很多同学做毕业设计时,第一反应是找个现成模板改改界面,结果答辩时被问“你的模型为什么用随机森林而不是 XGBoost”就卡住了。电影票房预测这个题目之所以每年都有人选,是因为它同时踩中了机器学习、数据清洗、特征工程和 Web 可视化四个可展示的模块,而且数据集相对好找。但真正能跑通并讲清楚逻辑的项目并不多,大部分卡在数据泄漏、特征维度爆炸和前后端联调上。这篇笔记按“数据怎么处理 → 模型怎么选 → 平台怎么搭 → 坑怎么避”的顺序,把一套可复现的 Python 毕业设计方案拆开讲。适合正在做机器学习方向毕业设计、需要完整源码和数据集支撑的同学,也适合想快速验证票房预测可行性的从业者。

2. 票房预测的数据集处理与特征工程:从原始表格到模型输入

2.1 数据集来源与字段取舍

常见做法是组合 TMDB 或 Kaggle 上的电影元数据,加上国内票房平台的公开榜单。我一般会保留这几类字段:预算、类型、上映档期、制片公司、导演和主演的历史票房均值、预告片热度、社交媒体讨论量。注意,上映后的票房数据绝对不能作为特征,这是最典型的标签泄漏。很多网上的“高精度”方案就是把首周票房当输入去预测总票房,答辩时一问就露馅。

处理流程分三步:先做缺失值统计,预算缺失超过 30% 的样本直接丢弃;再把多值字段(如类型、演员)做 one-hot 或 target encoding;最后按时间切分训练集和测试集,而不是随机切分。时间切分能模拟真实预测场景——用 2018 年之前的电影训练,预测 2019 年之后的票房。

import pandas as pd from sklearn.model_selection import train_test_split # 读取原始数据 df = pd.read_csv('movie_box_office.csv') # 丢弃预算缺失过多的样本 df = df[df['budget'].notna()] df = df[df['budget'] > 0] # 按上映年份做时间切分 train = df[df['release_year'] < 2018] test = df[df['release_year'] >= 2018] # 选择数值特征和类别特征 num_features = ['budget', 'popularity', 'runtime', 'release_month'] cat_features = ['genre', 'production_company'] # 对类别特征做 one-hot train = pd.get_dummies(train, columns=cat_features) test = pd.get_dummies(test, columns=cat_features) # 对齐列,防止训练集和测试集特征维度不一致 train, test = train.align(test, join='left', axis=1, fill_value=0) print(f'训练集样本数:{len(train)},测试集样本数:{len(test)}')

这段代码的关键在align那一步。如果不做列对齐,测试集可能缺少训练集里某些类型的 one-hot 列,导致模型输入维度对不上。参数上,release_month从日期字段拆出来,因为档期对票房影响很大——春节档和暑期档的票房基数明显不同。

2.2 特征工程中的三个必调参数

第一个是目标变量变换。票房分布严重右偏,直接回归会让模型偏向预测高票房。我一般对票房取对数:np.log1p(box_office),预测后再用np.expm1还原。这样评估指标(如 RMSE)会更稳定。

第二个是类别特征的编码方式。类型字段用 one-hot 没问题,但制片公司有几百个取值,one-hot 会导致维度爆炸。常见做法是只保留出现次数前 20 的公司,其余归为 “Other”,或者用 target encoding 把公司替换成其历史电影的平均票房。注意 target encoding 必须在训练集上计算均值,再映射到测试集,否则会泄漏。

第三个是数值特征的标准化。预算和热度量纲差异大,树模型虽然不敏感,但如果你用线性回归或神经网络,必须做 StandardScaler 或 MinMaxScaler。我一般统一做标准化,方便后续换模型对比。

import numpy as np from sklearn.preprocessing import StandardScaler # 目标变量取对数 train['log_box'] = np.log1p(train['box_office']) test['log_box'] = np.log1p(test['box_office']) # 数值特征标准化 scaler = StandardScaler() train[num_features] = scaler.fit_transform(train[num_features]) test[num_features] = scaler.transform(test[num_features]) # 制片公司 target encoding company_mean = train.groupby('production_company')['log_box'].mean() train['company_encoded'] = train['production_company'].map(company_mean) test['company_encoded'] = test['production_company'].map(company_mean) test['company_encoded'].fillna(train['log_box'].mean(), inplace=True)

这里fit_transform只在训练集上调用,测试集用transform,这是避免数据泄漏的基本功。company_encoded的缺失值用训练集均值填充,防止测试集出现训练集没见过的公司时产生 NaN。

3. 票房预测模型选型与训练:随机森林、XGBoost 和 LightGBM 的对比

3.1 为什么树模型是票房预测的基线首选

电影票房和特征之间大量是非线性关系——预算增加到一定程度后边际收益递减,档期和类型的交互效应也很明显。线性回归在这类数据上表现通常很差,而树模型能自动捕捉这些非线性。随机森林作为基线,训练快、调参少、不容易过拟合;XGBoost 和 LightGBM 在结构化数据上通常是精度最高的。

我一般会同时跑三个模型,用交叉验证对比 RMSE 和 R²。如果时间紧,直接上 LightGBM,它的训练速度比 XGBoost 快不少,而且对小数据集更友好。注意,不要用深度学习模型,电影票房数据集通常只有几千条样本,神经网络很容易过拟合,答辩时也讲不清楚为什么用 MLP 而不是 GBDT。

import lightgbm as lgb from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, r2_score # 准备特征矩阵 feature_cols = [c for c in train.columns if c not in ['box_office', 'log_box', 'release_year', 'production_company']] X_train, y_train = train[feature_cols], train['log_box'] X_test, y_test = test[feature_cols], test['log_box'] # 随机森林基线 rf = RandomForestRegressor(n_estimators=200, max_depth=8, random_state=42) rf.fit(X_train, y_train) rf_pred = rf.predict(X_test) # LightGBM lgbm = lgb.LGBMRegressor(n_estimators=500, learning_rate=0.05, max_depth=6, random_state=42) lgbm.fit(X_train, y_train) lgbm_pred = lgbm.predict(X_test) # 评估 for name, pred in [('RandomForest', rf_pred), ('LightGBM', lgbm_pred)]: rmse = mean_squared_error(y_test, pred, squared=False) r2 = r2_score(y_test, pred) print(f'{name} — RMSE: {rmse:.4f}, R²: {r2:.4f}')

参数上,n_estimators从 200 到 500 足够,再大收益很小。max_depth控制在 6 到 8,防止单棵树太深导致过拟合。learning_rate设 0.05 配合 500 棵树,是 LightGBM 的常用组合。如果 RMSE 在训练集上远低于测试集,说明过拟合了,优先降max_depth或加min_child_samples。

3.2 交叉验证与超参数搜索的实操细节

单次切分评估波动大,我一般用 5 折交叉验证。注意,时间序列数据不能随机 K 折,要用TimeSeriesSplit,保证每折的训练集都在测试集之前。超参数搜索用GridSearchCV或Optuna,但毕业设计时间有限,手动调几组关键参数就够了。

from sklearn.model_selection import TimeSeriesSplit, GridSearchCV tscv = TimeSeriesSplit(n_splits=5) param_grid = { 'n_estimators': [300, 500], 'max_depth': [5, 7], 'learning_rate': [0.03, 0.05] } grid = GridSearchCV( lgb.LGBMRegressor(random_state=42), param_grid, cv=tscv, scoring='neg_root_mean_squared_error', n_jobs=-1 ) grid.fit(X_train, y_train) print(f'最佳参数:{grid.best_params_}')

TimeSeriesSplit的每一折都保证训练集时间早于测试集,这比随机 K 折更贴近真实预测场景。scoring用负 RMSE,因为 sklearn 的 GridSearchCV 默认最大化分数,所以取负值。n_jobs=-1用满 CPU 核数加速搜索。

4. 预测平台的后端搭建:Flask 接口与模型加载

4.1 用 Flask 暴露预测接口的最小实现

模型训练完要能对外提供服务,最常见的是 Flask 写一个/predict接口。前端传 JSON 格式的电影特征,后端加载训练好的模型文件,返回预测票房。注意,模型文件用joblib或pickle保存,不要每次请求都重新训练。

from flask import Flask, request, jsonify import joblib import numpy as np app = Flask(__name__) # 启动时加载模型和标准化器 model = joblib.load('lgbm_model.pkl') scaler = joblib.load('scaler.pkl') feature_cols = joblib.load('feature_cols.pkl') @app.route('/predict', methods=['POST']) def predict(): data = request.get_json() # 构造特征向量 features = [] for col in feature_cols: features.append(data.get(col, 0)) features = np.array(features).reshape(1, -1) # 标准化 features = scaler.transform(features) # 预测并还原对数 log_pred = model.predict(features)[0] box_office = np.expm1(log_pred) return jsonify({'predicted_box_office': round(box_office, 2)}) if __name__ == '__main__': app.run(debug=True, port=5000)

这段代码的关键是feature_cols的顺序必须和训练时完全一致,否则预测结果会错乱。我一般把特征列名也保存下来,加载时按顺序构造向量。debug=True只在开发时用,部署时要关掉。

4.2 前端页面与接口联调的注意点

前端可以用 Vue 或 React 写一个表单,用户输入预算、类型、档期等,点击预测后调用/predict。注意跨域问题,Flask 需要加flask-cors,否则浏览器会拦截请求。另外,前端传的类别字段要和训练时的 one-hot 列对应,比如类型 “Action” 要转成genre_Action: 1,其他类型列置 0。这一步容易出错,建议在后端做一层映射,前端只传原始值。

from flask_cors import CORS CORS(app) # 类别映射示例 genre_map = {'Action': 'genre_Action', 'Comedy': 'genre_Comedy', 'Drama': 'genre_Drama'} @app.route('/predict', methods=['POST']) def predict(): data = request.get_json() features = {col: 0 for col in feature_cols} # 数值特征直接填 for col in ['budget', 'popularity', 'runtime', 'release_month']: features[col] = data.get(col, 0) # 类别特征做 one-hot genre = data.get('genre', '') if genre in genre_map: features[genre_map[genre]] = 1 # 后续预测逻辑同上

这样前端只需要传{"budget": 100000000, "genre": "Action", ...},后端自动展开成模型需要的特征向量。联调时先用 Postman 测接口,确认返回正常再接前端。

5. 票房预测平台避坑记录:数据泄漏、维度错位和部署翻车

5.1 现象:测试集 R² 高达 0.95,答辩时被问住了

原因:把上映后的票房相关字段(如首周票房、总观影人次)当成了特征。解决:检查特征列,确保所有字段在预测时点之前就能获取。我一般会列一个时间线,标注每个字段的可得时间,上映后的数据一律剔除。

5.2 现象:Flask 接口返回的预测值忽大忽小,和训练时完全对不上

原因:前端传的特征顺序和训练时的feature_cols不一致,或者标准化器没有正确加载。解决:把feature_cols保存成文件,加载时按顺序构造向量;标准化器用训练集拟合的那个,不要重新 fit。

5.3 现象:LightGBM 训练时报 “Input contains NaN”

原因:target encoding 后测试集出现训练集没见过的公司,映射后产生 NaN。解决:map之后用fillna填充训练集均值,或者直接丢弃这些样本。更稳妥的做法是用category_encoders库的 TargetEncoder,它自带平滑和缺失值处理。

5.4 现象:本地跑得好好的,部署到服务器后接口超时

原因:Flask 默认单线程,模型加载在每次请求时重复执行。解决:模型在应用启动时加载一次,用全局变量持有;生产环境用 gunicorn 加多 worker,gunicorn -w 4 -b 0.0.0.0:5000 app:app。

5.5 现象:前端页面预测按钮点击没反应

原因:跨域请求被浏览器拦截,或者接口返回的 JSON 字段名和前端读取的不一致。解决:后端加flask-cors,前端在fetch里检查response.ok,打印错误信息。字段名统一用下划线,避免大小写混淆。

6. 模型可解释性与预测结果验证:SHAP 值分析和残差检查

答辩时老师最常问“你这个模型为什么预测这个票房”,光说“LightGBM 精度高”是不够的。我一般用 SHAP 值做特征重要性分析,它能给出每个特征对单条预测的贡献度。比如某部电影预测票房高,SHAP 会告诉你主要是预算和档期拉高的,类型的影响很小。这样解释起来有理有据。

import shap explainer = shap.TreeExplainer(lgbm) shap_values = explainer.shap_values(X_test) # 全局特征重要性 shap.summary_plot(shap_values, X_test, plot_type='bar') # 单条预测解释 shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])

TreeExplainer对 LightGBM 和 XGBoost 都适用,计算速度快。summary_plot的 bar 图能看出哪些特征整体影响大,force_plot能解释单条预测。注意,SHAP 值反映的是特征对预测的贡献,不是因果关系,答辩时别说“预算导致票房高”,要说“预算对预测结果有正向贡献”。

残差检查也不能少。把测试集的预测值和真实值画散点图,如果残差在低票房区间偏大,说明模型对中小成本电影预测不准,可以考虑分档训练或加更多特征。我一般还会看残差是否随预测值变化,如果呈喇叭形,说明存在异方差,取对数那一步可能没做彻底。

最后说个血泪经验:毕业设计的代码要能一键跑通,别把路径写死。我习惯在项目根目录放一个config.py,所有路径用os.path.join拼接,数据集和模型文件放在data/和models/下。这样换台电脑也能直接运行,答辩现场不会翻车。希望帮到你。

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

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

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

立即咨询