恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于机器学习的电影票房预测平台:从回归建模到工程化落地
首页
资讯中心
/
基于机器学习的电影票房预测平台:从回归建模到工程化落地
基于机器学习的电影票房预测平台:从回归建模到工程化落地
发布时间:2026/10/10 11:15:40
简介面向计算机专业期末大作业与课程设计场景这套基于机器学习的电影票房预测平台提供了可直接运行的完整项目。项目经导师指导评审98分源码均本地编译调试通过稳定可靠。压缩包共58个文件包含16个py源码、16个csv数据集、23个png可视化图表以及md和txt格式的详细说明文档整体大小约29.85MB。源码覆盖数据预处理、特征工程、票房预测逻辑并集成朴素推荐、KNN/SVD协同过滤、关键词推荐等模块tmdb_5000等真实数据集便于直接训练png图直观呈现EDA特征分布md报告完整讲解从数据加载到模型评估的步骤。目前已有95人学习下载按目录结构结合报告运行源码可快速掌握机器学习项目开发全流程也可作为高分课设模板复用。1. 基于机器学习的电影票房预测平台从回归问题到可交付的工程一部电影还在立项投资方就要估票房过去多数靠经验和胆量。基于机器学习的电影票房预测平台就是把这件“拍脑袋”的事变成一个可计算的回归问题从开源数据集里把预算、阵容、档期、宣发热度这些特征捞出来用机器学习模型在开画前给出票房预测区间。它同时涉及数据清洗、特征工程、模型调参和 API 封装正好是课程设计或简历项目里最完整的一条链路。我接到的很多咨询都来自想拿高分项目的学生也有影视行业做投资决策分析的从业者。这个方向值得做但要先建立预期票房数据噪声极大模型是辅助决策的黑匣子不是能精确到数字的金手指。这篇笔记按源码、数据集和文档的落地顺序把每一步可复现的做法和藏得深的坑一起讲完。2. 数据底座先搞清楚哪些字段真正影响票房再动手清洗2.1 字段选型预算、阵容、档期、声量哪些是强特征票房预测平台的数据集常见公开来源是 TMDB 5000、IMDB 和 Box Office Mojo 的抓取结果国内项目则多基于猫眼、豆瓣的爬虫数据。不管哪种核心字段基本一致。我在实际项目里会先把字段分成三类强特征、弱特征和“看起来能用但其实不能用”的特征。强特征里预算是成本体量最直接的度量通常和票房有正向关系但它也是缺失率最高的字段之一。演员、导演作为类别特征不能直接塞给回归模型需要先转换成号召力数值。上映日期本身是日期类型但真正有用的是它派生的档期属性——春节档、暑期档、国庆档对票房的加成是实打实的。弱特征包括片长、制作国家、语言、类型组合它们对票房有影响但单独拿出来解释力不强必须和其他特征组合使用。最容易被误用的是评分和评分人数它们在 TMDB 数据集里和票房相关性很高但那是上映后产生的信息做“开画前预测”时根本拿不到把它当特征就是在制造数据泄露。下面这个表格是我做字段选型时固定会画一张的清单区分信息可用时点能帮你在写清洗代码之前就拦掉一半坑字段类型特征作用可用时点budget数值成本体量强特征开拍前release_date日期派生档期特征定档时genres多标签类别题材受众范围立项时cast / director类别票房号召力聚合立项时popularity数值上映前宣发热度上映前vote_average / vote_count数值口碑信息仅上映后数据集的原始行数、列数、缺失分布直接影响后面的所有步骤。建议先跑一遍df.info()和df.describe()把缺失率超过 60% 的列直接降级处理不要勉强留用否则后面特征工程会不停被 NaN 打断。2.2 数据清洗与划分缺失值、极端值与时间序列切分拿到原始数据后我习惯先做一次“目标列体检”。票房收入是典型的右偏分布少数超级大片占据大部分数值区间直接用原始票房做回归模型会被几个极端样本拉走。常见做法是对目标取对数也就是log1p(revenue)让分布更接近正态。后面预测出的结果再用expm1还原成真实票房这个一进一出的操作会在模型配置里反复出现先记住它。import pandas as pd import numpy as np df pd.read_csv(movie_dataset.csv) print(df.shape, df.columns.tolist()) # 目标是票房收入右偏分布先取对数再做回归 df[revenue_log] np.log1p(df[revenue]) df df.dropna(subset[revenue]) # 预算缺失不要直接填 0先补一个标记列 df[has_budget] df[budget].notna().astype(int) df[budget] df[budget].fillna(df[budget].median()) # 时长缺失用中位数极端值先在 describe 里观察 df[runtime] df[runtime].fillna(df[runtime].median())这段代码里有个关键设计预算缺失时我先加了一列has_budget再把缺失值填成中位数。如果直接把缺失预算填 0模型会把一大批独立小成本电影和缺失项混为一谈预算特征的权重会失真。加上标记列后模型至少能区分“预算是真实已知”和“预算是补出来的”两种情况。log1p的作用是log(1x)即使票房恰好是 0 也不会出现负无穷。清洗到这里接下来是切的顺序。很多人在这一步会顺手train_test_split(random_state42)对票房预测来说这是标准的翻车姿势。票房有明显的年度趋势2019 年的市场体量和 2014 年完全不同随机切分会让训练集和验证集在时间上重叠验证集里相当于带着“未来答案”去考试。正确的做法是时间序列切分让模型始终用过去预测未来。from sklearn.model_selection import TimeSeriesSplit df df.sort_values(release_date).reset_index(dropTrue) tscv TimeSeriesSplit(n_splits5) for train_idx, valid_idx in tscv.split(df): train df.iloc[train_idx] valid df.iloc[valid_idx] print(ftrain: {train.release_year.min()}-{train.release_year.max()}, fvalid: {valid.release_year.min()}-{valid.release_year.max()})TimeSeriesSplit和普通 KFold 的区别是它不做随机 shuffle每次划分用前 80% 的时序数据做训练后 20% 做验证。第一次划分训练集可能是 2010-2016验证集 2017第二次就变成 2010-2018 对 2019依此类推。如果数据集年头跨度不够也可以手动按年份切train df[df[release_year] 2019]规则简单好解释适合高分项目的文档部分。提前切还是事后切会直接影响后面的特征工程如果先用全量数据算了某个统计量再去切就已经把验证集的信息偷渡进训练集了。这条线必须画在清洗后、做任何统计特征之前。3. 特征工程把演员咖位、档期和宣发热度变成模型能用的数值3.1 类别特征编码多标签类型、档期映射与低频国家原始数据里的电影类型是逗号分隔的字符串比如Action|Adventure|Sci-Fi不能直接进模型。常见做法是用 MultiLabelBinarizer 把一个电影的多类型展开成多列布尔值每个类型一列。档期特征埋得比类型深一点需要在清洗阶段就把release_date解析成 datetime再按节假日范围映射成档期编号。注意这不是简单的月份判断春节档的日期每年都漂移最好根据农历或手工维护的档期表来映射我给学生的经验是先用固定规则做一个粗糙版本再根据历史票房数据校正档期边界。from sklearn.preprocessing import MultiLabelBinarizer # genres 是逗号分隔的多标签先用 split 展开 mlb MultiLabelBinarizer() genre_matrix mlb.fit_transform(df[genres].str.split(, )) genre_cols [fgenre_{g} for g in mlb.classes_] df_genres pd.DataFrame(genre_matrix, columnsgenre_cols) # 档期映射用日期判断是否落在热门档期 def map_season(date): if date.month 1 or (date.month 2 and date.day 15): return 1 # 春节档 elif 6 date.month 8: return 2 # 暑期档 elif 9 date.month 10: return 3 # 国庆档 else: return 0 # 普通档 df[season] df[release_date].apply(map_season)genres多标签展开之后一个动作电影同时会有genre_Action1和genre_Adventure1模型可以同时利用这两个信号。档期特征用整数编码而不是独热编码是因为档期本身有强度梯度春节档和国庆档对票房的拉动逻辑相似都在 1、2、3 三个值附近。低频国家这条我通常处理得更粗只保留主要生产国的布尔列其余全部归为other因为一个国家类别的出现次数如果少于几十次模型很难学到稳定规律反而会引入噪声。特征工程做完后用pd.concat把原表、类型展开列、档期列拼在一起再做一次fillna兜底。3.2 构造咖位和声量特征历史票房均值、明星指数与宣发热度演员、导演对票房的影响是这类项目里最值得做的一类特征。直接对演员名字做独热编码没有意义几万个名字会让特征矩阵爆炸常见做法是构造“号召力评分”——把每个演员历史参演电影的平均票房当作他的咖位指数。这个特征做出来后模型的精度提升往往比调参还明显。但要非常小心这个统计量如果切分前后都用同一份全量数据去算就犯了目标编码泄露。正确顺序是先做 2.2 里的 TimeSeriesSplit或者至少先按年份划分训练集与验证集再只用训练集去groupby算均值。top_roles [director, actor_1, actor_2, actor_3] # 先用训练集构建号召力映射验证集只能 map不能参与计算 hist_map {} for role in top_roles: hist_map[role] train.groupby(role)[revenue_log].mean() for role in top_roles: train[fstar_power_{role}] train[role].map(hist_map[role]) valid[fstar_power_{role}] valid[role].map(hist_map[role]) # 新导演/新演员没有任何历史票房 - 用全体中位数兜底 fill_val train[fstar_power_{role}].median() train[fstar_power_{role}] train[fstar_power_{role}].fillna(fill_val) valid[fstar_power_{role}] valid[fstar_power_{role}].fillna(fill_val)注意这里我用的是revenue_log而非原始票房因为对数域的均值更抗极端值干扰。用中位数兜底比用均值更稳理由和新演员的不确定性有关一个新人没有任何历史信号把他当作“平均水平”是相对安全的先验。如果你手头的数据集里有“想看人数”或 TMDB 的popularity字段那是最接近市场声量的特征它在开画前就能拿到而且通常和首周票房拟合得不错。这类特征要注意单位差异比如部分电影的 popularity 是百分制部分则是百万级处理方式是做rank或StandardScaler归一化。特征列拼好后最后做一次train_X, train_y, valid_X, valid_y分离把要进模型的特征集中放在一个列表里后面训练调参就围绕这个列表展开。4. 模型训练与调参从线性回归到 XGBoost 的精度爬坡4.1 基线与评估指标为什么票房预测用 MAE 和 MAPE票房预测是个回归任务不能用准确率评估。我看过不少学生项目在文档里写“预测准确率达到 92%”那是把回归结果四舍五入到区间后算分类准确率分数好看但没实际意义。做票房预测定量指标用 MAE平均绝对误差和 MAPE平均绝对百分比误差就够用了。MAE 直观好解释单位是美元MAPE 更贴近业务表达比如“平均偏差 28%”。R² 只能参考因为票房分布极度不均衡R² 会被少数高票房电影带高。from sklearn.linear_model import Ridge from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error feature_cols [c for c in train.columns if c.startswith( (budget, has_budget, runtime, season, genre_, star_power_, popularity_) )] X_train train[feature_cols] y_train train[revenue_log] X_valid valid[feature_cols] y_valid valid[revenue_log] ridge Ridge(alpha1.0) ridge.fit(X_train, y_train) pred_log ridge.predict(X_valid) pred_revenue np.expm1(pred_log) true_revenue np.expm1(y_valid) print(MAE:, mean_absolute_error(true_revenue, pred_revenue)) print(MAPE:, mean_absolute_percentage_error(true_revenue, pred_revenue))Ridge 在这里是基线模型它的价值不是精度而是帮你确认特征工程做的方向对不对。如果基线 MAE 就很小说明特征里明显有泄露如果基线乱糟糟说明特征还不够。feature_cols里我用startswith按前缀批量捞列这样后面新增手工特征时不用改这一行。评估时先把预测结果从对数域还原成真实票房再算指标因为真正关心的是“差多少美元”不是“差多少 log”。MAPE 对低票房电影极其敏感一部 500 万的小成本片差 200 万MAPE 直接 40%所以要结合 MAE 一起看别只看一个数下结论。4.2 集成模型随机森林、XGBoost 与 LightGBM 的关键参数和早停基线跑通后我一般会先上 XGBoost因为它对缺失值有原生处理也支持早停在中小数据集上表现稳定。再跑随机森林做对照因为随机森林对特征重要性解释更友好适合写进文档。LightGBM 训练更快但叶子生长策略在小数据集上容易过拟合需要更激进的正则。三个模型不用都微调选一个主模型往里钻就行。调参路线我习惯先固定学习率和树的棵数再调深度和采样比例最后统一调正则参数。from xgboost import XGBRegressor from sklearn.ensemble import RandomForestRegressor xgb XGBRegressor( n_estimators800, learning_rate0.05, max_depth6, subsample0.8, colsample_bytree0.7, reg_alpha0.1, reg_lambda1.0, early_stopping_rounds50, eval_metricmae, random_state42 ) xgb.fit(X_train, y_train, eval_set[(X_valid, y_valid)], verbose100) rf RandomForestRegressor( n_estimators300, max_depth12, min_samples_leaf3, max_featuressqrt, n_jobs-1, random_state42 ) rf.fit(X_train, y_train)XGBoost 的early_stopping_rounds50意味着验证集 MAE 连续 50 轮不下降就提前停n_estimators800只是个上限实际训练常常在 200 轮以内就收敛。learning_rate0.05和max_depth6是一对搭配学习率越低越需要更多棵树深度限制在 6 是怕小数据集上长出过深的树。subsample0.8和colsample_bytree0.7分别是行采样和列采样相当于给每棵树换一批数据练能明显压低方差。随机森林的三个参数更直接min_samples_leaf3限制叶子最少样本避免纯靠一个样本决定预测值max_featuressqrt让每棵树只看特征总数的平方根个特征加大树间差异。参数选择上我习惯做一张对照表记在文档里方便答辩或复盘参数作用我的常用初始值learning_rate每棵树的贡献步长越小越稳0.05max_depth树深度过高容易过拟合6subsample每棵树使用的行采样比例0.8colsample_bytree每棵树使用的特征采样比例0.7reg_alpha / reg_lambdaL1/L2 正则压制极端分裂0.1 / 1.0min_samples_leaf叶子最少样本数RF/LightGBM3网格搜索在这类数据集上可以用但别一上来就全参数 GridSearchCV时间和算力都浪费。正确顺序是先用默认参数跑一遍看特征重要性筛特征再做一轮小范围调参。验证方式必须是 TimeSeriesSplit不能用普通 KFold这一点在第 5 章展开说。模型收敛后把最终模型的feature_importances_打出来存成 CSV这是写“详细文档说明”时最有用的素材也能反过来检查是不是有某个特征重要度高得离谱。5. 避坑与排查票房预测平台最容易翻车的 5 个场景5.1 数据泄露把上映后的评分当特征模型精度假得吓人现象验证集 MAPE 只有 8%用户觉得模型神了但一拿新片来预测就崩。原因把vote_average、vote_count甚至部分平台上的“首周票房”当成了特征这些信息在预测时点根本不存在。比如用 TMDB 数据做训练时没做时点标注所有字段都是默认带着“上映后最终值”的。解决给每个字段打上available_at标记凡是预测时点之后才会产生的数据一律丢弃。popularity如果用的是上映前快照就可以保留但普通数据集往往只有最终快照这种也要谨慎使用。这条是票房预测项目里最常见、也最致命的坑优先级排第一。5.2 随机切分导致的时间穿越现象交叉验证分数很高但模型上线后月度 MAE 波动巨大个别月份误差超过 60%。原因train_test_split默认随机切分票房有强烈年度趋势和档期规律随机切等于把未来的影片混进训练集模型顺手背下了年份分布。解决用TimeSeriesSplit或按年份手动切分。我在 2.2 里特意强调先排序再切就是为了避免这里翻车。已经用随机切分写好代码的改一行df.sort_values(release_date)再切就来得及。5.3 预算缺失直接填 0等于给模型灌错误样本现象特征重要性里budget排名垫底跟业务直觉不符。原因缺失预算填 0 后大量低预算独立片和缺失项混在一起模型学到的是“没填的就是没花钱”预算特征被污染。解决用一个has_budget二值标记区分缺失与否再对缺失项用中位数填充。更进一步可以用同类型电影的中位数填充比如动作片预算普遍偏高按类型分组填比全局中位数更合理。这个坑在 2.2 的代码里已经埋了钩子运行时记得把has_budget列保留在特征列表里。5.4 极端票房样本把模型整体带偏现象模型预测值整体偏高尤其是对小成本片的预测普遍大于实际。原因票房分布右偏MSE 损失被《复仇者联盟》这种超级样本拉爆模型为了降低那几部的误差牺牲了绝大多数普通影片。解决把目标值做log1p变换后再训练让损失函数在取对数后的空间里计算评估时再用expm1还原。这样超级大片和普通片的误差权重就均衡了。如果还原后的 MAE 还是偏高考虑对训练样本按票房分桶加权给低票房区间的样本更高权重。5.5 目标编码泄露号召力评分用全量数据计算现象验证集上模型表现极好但换到真正的新片数据预测效果骤降。原因在切分之前用整个数据集的票房均值去算演员号召力验证集里那些片子已经参与了自身特征的统计属于标准的 target encoding 泄露。解决严格在切分后用训练集groupby构建映射验证集只做map。对于新出现的导演或演员用训练集中的中位数兜底。这条和第 4 章 3.2 的代码是对应的文档里要专门写一段“为什么先切分再算特征”这是高分项目和普通项目拉开差距的地方。6. 模型落地把训练好的回归模型封装成可用的票房预测接口训练和评估结束后下一步是把模型从 Notebook 里搬出来交给平台使用。这个项目叫“平台”至少要提供一个能接收请求、返回预测结果的 API。常见做法是用 joblib 把模型和特征列名一起打包再用 Flask 写一个轻量接口。需要注意特征构造逻辑必须在接口里完整复现比如请求里传的是日期字符串接口要先解析再算档期否则训练和预测用的特征对不上。import joblib from flask import Flask, request, jsonify app Flask(__name__) model joblib.load(box_office_model.joblib) feature_cols joblib.load(feature_cols.joblib) def build_features(payload): # 这里必须和训练时的特征构造顺序保持一致 data {budget: payload[budget], runtime: payload[runtime], season: payload[season]} for g in genre_all: data[fgenre_{g}] 1 if g in payload[genres] else 0 return [data[c] for c in feature_cols] app.route(/predict, methods[POST]) def predict(): payload request.get_json() X [build_features(payload)] pred_log model.predict(X)[0] return jsonify({predicted_revenue: round(float(np.expm1(pred_log))), unit: USD}) if __name__ __main__: app.run(host0.0.0.0, port5000)上线之后不能只看单次预测值要按周或按月回流真实票房算实际误差。我习惯把预测值和真实值画成散点图如果在高票房区明显低估、低票房区明显高估说明模型已经被中间样本主导该考虑分桶建模了。平台是否值得继续投入看两个数字月度 MAPE 是否稳定在 30% 以内以及预测误差是否在可解释的合理范围内。票房受档期竞争、口碑发酵和突发事件影响模型不可能全知一个能给出区间的版本比我见过太多“精确到万”的预测方案都可靠。这个项目给我最大的教训是特征质量永远优先于模型复杂度先把数据泄露的源头堵住再去追求更花哨的算法。希望帮到你。本文还有配套的精品资源点击获取