ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

电影票房预测实战:从特征工程到FastAPI部署,避开数据泄露

电影票房预测实战:从特征工程到FastAPI部署,避开数据泄露 简介基于机器学习的电影票房预测平台是一套面向毕业设计、课程设计与期末大作业的高分实战项目适合具备Python和一定机器学习基础的学生直接学习与部署。平台以历史票房、影片信息、市场趋势等真实数据为基础完整实现从数据清洗、特征工程到线性回归、决策树、随机森林、神经网络等模型训练再到预测区间输出与可视化的全流程代码均附有注释配套文档说明和数据分析报告便于理解与二次开发。资源包共58个文件以16个Python源码和16个CSV数据集为主体配有多张可视化图表、Markdown分析文档及Word手册压缩包整体约30.71MB项目内部模块划分清晰包含多种预测与推荐算法模块及结果文件可按需复用。目前已有502人学习适合需要快速搭建完整项目、完成算法对比或进行论文实验的读者。1. 基于机器学习的电影票房预测真正的门槛不在模型大多数能被称作“高分项目”的票房预测作品最后都载在同一个坑上不是模型精度不够而是用上了电影上映之后才有的数据来预测上映前的票房。这种漂亮到离谱的结果在评审眼里是极其典型的“数据泄露”。基于机器学习的电影票房预测平台核心不是跑通一个 XGBoost而是把“映前已知信息”和“映后已知信息”严格隔离再在一条固定流水线上完成从原始数据集到预测接口的交付。下面按一线工程做法展开完整方案先设计特征与数据集结构再比较传统机器学习模型和深度学习模型在本场景的取舍然后用 FastAPI 把模型封装成可运行、可演示、可答辩的平台最后补上数据泄露自检与 SHAP 解释这种答辩加分项。新手可以照步骤复现熟手则重点看边界条件和参数设计。2. 数据集构成与特征工程票房模型的成败在第一张表搭建机器学习票房预测平台的第一步不是选模型而是先把原始数据集摊开逐列盘问“这个字段在上映之前能不能拿到”。票房数据集的清洗和特征工程决定了后续所有机器学习算法能学到的上限这个环节偷懒后面调参再勤快也救不回来。2.1 先划定信息边界哪些字段能用哪些一用就是泄露常见的做法是按“上映日”划一条红线。凡是影片上映之后才能拿到的值比如豆瓣开分、首日排片率、观众口碑在映前预测任务里统统不能进特征否则模型拿到的是“开卷考试”而不是预测。构建最小可行数据集时我一般会准备这样一张表字段类型示例是否可用titlestr流浪地球2可用release_datedate2023-01-22可用genrelist[str][“科幻”, “冒险”]可用directorstr郭帆可用actor_1..actor_3str吴京可用budgetfloat4.0e8可用runtimeint173可用ratingfloat8.3泄露不可用imdb_votesint1.2e6泄露不可用revenuefloat4.02e9目标变量注意 rating 这类评分列在公开数据集里几乎都存在很多开源项目直接把它塞进训练集导致验证分数虚高。一个实用的自检方式把特征按“上映日”排序逐列问自己这一列在电影上映前能否从公开渠道查到拿不准就删。提示评审最喜欢看的数据泄露案例就是“用评分预测票房、用首周末票房预测总票房”。这两类字段不是不能用而是必须放在不同的预测阶段里。2.2 目标变量怎么定义回归、分档、排序带来完全不同的评价体系票房预测平台的目标变量有三种常见定义方式各有各的评价指标和答辩侧重点。定义方式目标值示例评价指标优缺点回归log1p(revenue)RMSE、MAE、R²信息保留完整能画残差图但长尾下高票房样本误差被稀释二分类/多分类普通片、热映、爆款F1、AUC、混淆矩阵结果直观符合业务汇报但丢失金额信息排序票房榜前10NDCG5、NDCG10更贴近排片决策但评估复杂度高高分项目通常选“回归 分档翻译”的组合先预测连续票房值再按业务阈值翻译成档位指标两类指标都能在答辩中展示。分类任务里正负样本通常不均衡用准确率做指标会掩盖模型“只会猜多数类”的问题这一点在写文档说明时要明确写出来。2.3 构造特征函数把原始表变成模型能吃的 X特征构造的核心原则是“只用当前样本上映日之前的信息”。比如“导演历史平均票房”这个特征必须只统计该导演过去已经上映的电影不能把当前电影的票房也平均进去否则就是目标泄露。下面这个函数用 expanding 均值实现import pandas as pd def build_past_stats(df: pd.DataFrame, key: str, target: str revenue_log1p) - pd.Series: 按上映时间排序后统计过去已上映样本的均值shift(1)排除自身。 d df.sort_values(release_date) past d.groupby(key)[target].transform( lambda s: s.expanding().mean().shift(1) ) return past.reindex(df.index)逻辑说明先把全表按 release_date 升序排列再按导演或演员分组组内做 expanding 均值后 shift(1)这样每个样本拿到的都是“该导演此前所有电影票房的均值”不包含当前样本也不包含未来的样本。返回时用 reindex 恢复原始行顺序保证和 X 对齐。这个函数的两个关键参数需要注意一个是 key传导演列时得到导演历史表现传主演列时得到演员历史表现通常两者要分开统计再合并另一个是 target默认用对数变换后的票房列而不是原始票房原因见 2.4。还有一类特征是类别标签比如电影类型。类型本身是多标签不能直接 one-hot 成几百列常见做法是只保留高频类型top_genres [剧情, 喜剧, 动作, 爱情, 科幻, 动画] for g in top_genres: df[fgenre_{g}] df[genre].apply(lambda lst: 1 if g in lst else 0)这样把“多标签类型”转成若干个 0/1 布尔列维度可控模型也能直接消费。参数说明top_genres 列表可以按训练集出现频率排序后取前 6 到 10 个频率太低的类型合并到“其他”里否则会造成特征稀疏。2.4 缺失值与长尾分布先让数据长成模型习惯的样子票房原始数据集不像 iris 那种教科书画布第一眼永远是脏的。缺失值和长尾分布是两个必须解决的问题。缺失值处理策略要按列区分导演缺失填 “unknown”预算缺失用训练集中位数填充演员缺失则保留为空在构造演员历史均值时跳过即可。类别特征不要用全局众数填充因为“最热门导演”这一项本身会引入偏差低频导演统一聚到 “unknown” 更干净。票房目标值呈典型的长尾分布头部大片票房可能是尾部小片的数千倍直接把原始值丢给回归模型损失函数会被头部样本主导。我一般会对目标做对数变换import numpy as np df[revenue_log1p] np.log1p(df[revenue])逻辑说明log1p 是 log(1 x)既压缩长尾又能保留 0 票房的样本。预测完成后用 np.expm1 还原成原始金额换算关系在文档说明里必须写清楚否则评审只看预测结果会一头雾水。低频类别同样要处理。导演或主演出现次数少于 20 次的统一替换成 “OTHER”避免模型为每个低频类别单独建分支而过拟合freq df[director].value_counts() rare freq[freq 20].index df[director_grp] df[director].where(~df[director].isin(rare), OTHER)参数说明低频阈值 20 不是硬性标准数据量大可以提到 50数据量小可以降到 10。核心思路是让类别列的分组数量可控不能出现“一个导演一个类别”的局面否则树模型会在这些叶子上记住噪声。3. 模型选型与交叉验证把验证集当成评审团数据和特征准备到位后才轮到机器学习模型登场。票房预测平台里模型不是越复杂越好评价一个模型的标准永远是“在时间序列验证集上的表现”而不是“能不能在训练集上收敛”。3.1 为什么传统机器学习模型在票房预测里仍是首选在几百到几千条样本量的票房数据集上传统机器学习模型通常是比深度学习模型更稳的选择。深度学习模型需要大量数据支撑才能让 embedding 层学到有意义的表示样本量不足时MLP 很容易在验证集上振荡而梯度提升树却能稳定输出不错的结果。模型类别样本量需求类别特征支持可解释性本场景定位线性回归低需手动编码高基线模型LightGBM / XGBoost中等原生支持较高配合 SHAP主力模型MLP / 简单 DNN高需 embedding低可作对比实验这个场景的机器学习入门策略是先用线性回归跑通基线再用 LightGBM 提精度最后有精力再补一个 MLP 做对比。把对比结果写进文档说明比单纯堆一个高精度模型更能体现工程判断力。3.2 用时间感知的交叉验证替代随机划分票房数据有季节效应和通胀效应直接用KFold(shuffleTrue)随机划分会让训练集看到未来数据验证分数虚高。我一般用TimeSeriesSplit并额外加一个 gap 把训练集和验证集在时间上隔开避免邻近档期的样本互相渗透from sklearn.model_selection import TimeSeriesSplit # 训练前必须按 release_date 排好序 X X.sort_values(release_date) tscv TimeSeriesSplit(n_splits5, gap30) for fold, (tr_idx, va_idx) in enumerate(tscv.split(X)): print(ffold {fold}: train {tr_idx.size} samples, valid {va_idx.size} samples)逻辑说明TimeSeriesSplit 按顺序切分保证训练集永远在验证集之前。gap 参数表示前后两段之间空出 30 天这 30 天的样本不参与任何一边的训练可以防止“离验证集最近的训练样本”和验证集存在过多相似信息。参数说明gap 取值要根据具体数据的时间跨度调整。如果你的数据跨度是 10 年30 天 gap 偏短可以设到 90 天如果只有 1 年数据gap 设 7 天即可太长会浪费样本。3.3 用 LightGBM 搭主力模型一份可以直接复用的训练脚本LightGBM 是目前票房表格数据上最常见的主力选择。它对类别特征的原生支持和训练速度明显优于 XGBoost。下面是一份可以直接复用的训练脚本核心段import lightgbm as lgb import numpy as np from sklearn.metrics import mean_squared_error model lgb.LGBMRegressor( objectiveregression, metricrmse, max_depth4, num_leaves15, learning_rate0.05, n_estimators2000, colsample_bytree0.8, reg_alpha0.5, reg_lambda1.0, random_state42, verbose-1, ) model.fit( X_train, y_train, eval_set[(X_valid, y_valid)], callbacks[lgb.early_stopping(100), lgb.log_evaluation(100)], ) pred_log1p model.predict(X_valid) rmse mean_squared_error(y_valid, pred_log1p, squaredFalse) print(fvalid rmse(exp): {rmse:.4f})逻辑说明目标变量和对数变换后的票房值在 log 空间算 RMSE 等价于评估相对误差避免高票房样本主导损失。early_stopping 让模型在验证集上连续 100 轮没有提升时自动截断实际迭代次数通常远小于 n_estimators。关键参数的作用和调整方向如下参数推荐值说明max_depth4牺牲深度换稳定防止小样本过拟合num_leaves15控制树复杂度数值越大拟合越强但过拟合风险同步上升learning_rate0.05减小学习率就要增大 n_estimators两者联动colsample_bytree0.8每棵树只用 80% 特征列增加泛化能力reg_alpha0.5L1 正则适合特征列较多、包含大量稀疏类别的场景reg_lambda1.0L2 正则缩小叶子节点权重不要上来就做大范围超参数搜索。先保持默认参数跑通流程再针对 max_depth、num_leaves、learning_rate 三个参数做小网格搜索一次实验控制在几十组以内。每一组实验的结果按时间戳存档方便回溯。3.4 把连续分数翻译成业务档位阈值搜索与校准回归模型直接输出金额但在汇报时通常需要变成“普通片 / 热映 / 爆款”这样的业务档位。分档阈值不应该拍脑袋定而是用验证集上搜索出来的from sklearn.metrics import f1_score, precision_score, recall_score thresholds np.linspace(0.2, 0.8, 61) scores [] for t in thresholds: y_pred (val_proba t).astype(int) scores.append((t, f1_score(y_val, y_pred), precision_score(y_val, y_pred), recall_score(y_val, y_pred))) best max(scores, keylambda x: x[1]) print(fbest threshold{best[0]:.2f}, f1{best[1]:.3f})逻辑说明票房正样本在数据集中通常远少于负样本默认 0.5 的行为是把多数样本都判为“普通片”F1 看起来不差但没有任何业务价值。阈值搜索就是在 precision 和 recall 之间找一个平衡点。参数说明linspace 的范围和步长要看验证集概率分布来定如果模型输出集中在 0.3 到 0.6 之间搜索范围就收敛到这一段步长取 0.01 到 0.02 更精细。搜索结果保存成图片或表格放进文档说明里比空口说“模型精度高”有说服力得多。4. 平台落地把机器学习模型包成能演示和答辩的服务训练脚本跑通只是中点一个完整的电影票房预测平台还需要把模型暴露成可调用的服务让评审能直接体验预测过程。这一章负责把源码组织、API 封装和文档说明落到位。4.1 从 Notebook 到可提交仓库推荐目录结构如果整个项目只有一个 Jupyter Notebook评审第一印象就会打折扣。我一般会把源码组织成这样的目录结构movie-boxoffice/ ├── data/ │ ├── raw/ # 原始数据只读不写 │ ├── processed/ # 清洗后的训练数据 │ └── external/ # 外部统计导演历史票房等 ├── notebooks/ # 探索性分析 ├── src/ │ ├── features.py # 特征工程模块 │ ├── train.py # 训练与模型保存 │ ├── predict.py # 批量预测脚本 │ └── api.py # FastAPI 服务 ├── models/ # 训练产物 ├── results/ # 预测结果输出 ├── requirements.txt └── README.md目录设计的原则是“数据、代码、产物三分离”。raw 目录下的数据文件保持只读processed 存放每次预处理后的版本models 里只保留一个最终模型和它的超参数记录这样整个训练流程才可复现。4.2 用 FastAPI 封装一个最小可用的预测接口FastAPI 是目前封装机器学习模型最顺手的 Web 框架。原因很简单声明式请求校验、自动生成 OpenAPI 文档、异步支持都不需要额外配置。核心接口代码如下from fastapi import FastAPI from pydantic import BaseModel import numpy as np app FastAPI(titleBoxOffice API, version1.0.0) class MovieInput(BaseModel): title: str release_date: str genre: list[str] director: str actors: list[str] budget: float app.post(/api/v1/predict) def predict(m: MovieInput) - dict: # 用特征工程模块把请求转成模型输入的 DataFrame X build_single_input( titlem.title, release_datem.release_date, genrem.genre, directorm.director, actorsm.actors, budgetm.budget, ) y_log1p model.predict(X)[0] return { revenue_pred: float(np.expm1(y_log1p)), revenue_log1p: float(y_log1p), unit: 元, }逻辑说明build_single_input 必须和训练时用同一个特征工程函数不能重新写一套否则特征顺序对不齐预测结果直接失真。返回体同时给出 log 空间和原始金额方便不同口径的调用方使用。参数说明release_date 传入后内部会拆解成档期特征、星期特征等budget 在特征函数内部会做对数变换与训练时一致genre 必须用 list[str] 而不是逗号分隔的字符串这样 pydantic 能自动完成类型校验非法请求会在到达模型之前被拦截。启动服务用一行命令uvicorn src.api:app --host 0.0.0.0 --port 8000 --reload启动后浏览器访问/docs就能看到交互式接口文档可以直接在页面里填参数测试。这一步演示效果很好也是答辩时的常用开场。4.3 批量离线预测跑完整张测试表接口适合单条预测但评审手里通常有一批测试数据要批量出结果。单独写一个命令行预测脚本而不是在 Jupyter 里手动改路径跑# src/predict.py import argparse import pandas as pd import numpy as np from src.features import build_features def main(): parser argparse.ArgumentParser() parser.add_argument(--input, requiredTrue, help测试集 CSV 路径) parser.add_argument(--output, defaultresults/predictions.csv) args parser.parse_args() df pd.read_csv(args.input) X build_features(df) df[revenue_pred] np.expm1(model.predict(X)) df.to_csv(args.output, indexFalse) print(f已输出 {len(df)} 条预测文件{args.output}) if __name__ __main__: main()调用方式python -m src.predict --input data/raw/test.csv --output results/test_pred.csv逻辑说明批量预测脚本复用了 train.py 里保存的模型文件和 features.py 里的特征工程函数保证离线预测和在线接口预测结果一致。输出文件保留原始字段同时追加预测结果列方便和真实票房对比计算误差。参数说明--output 路径默认落在 results 目录下文件名建议带上日期或版本号同一份数据跑多轮时不会互相覆盖。如果数据量超过几万行可以加一个 batch_size 参数分批预测但票房数据集通常远达不到这个规模。4.4 把数据字典和文档说明写进平台而不是等到最后补标题里明确写了“文档说明”是评分的一部分那它就是平台的一等公民。README 至少要有四段环境安装说明、数据字典、训练流程、API 调用示例。数据字典建议用表格列出每个字段的含义、类型、是否用于训练以及取自哪个数据源。这个表格并不难写但能显著降低评审理解项目的成本也方便三个月后的你自己回过头来维护。requirements.txt 里的依赖不要无脑pip freeze全量导出只保留直接依赖的包numpy pandas scikit-learn lightgbm fastapi uvicorn pydantic逻辑说明全量导出会把环境里无关的包一并锁进去评审换台机器复现时容易踩版本冲突。只列直接依赖再注明 Python 版本要求这样可复现性更好。文档说明里还要写清“先装什么后装什么”的顺序常见做法是先建虚拟环境再逐行安装 requirements.txt。5. 答辩与自检技巧用特征重要性和 SHAP 给评审一个交代模型跑完、平台上线这只是解决了“能不能用”的问题。评审关心的是“为什么这个参数是 4、那个阈值是 0.57”以及“你怎么证明自己没有作弊”。这一章给出三个具体技巧把它们写进文档说明能明显拉开和普通调包项目的差距。5.1 数据泄露自检特征重要性分布会说话特征重要性是第一道自检防线。把模型的特征重要性打印出来观察头部特征的分布是否异常importance pd.Series(model.feature_importances_, indexX.columns) importance importance.sort_values(ascendingFalse) print(importance.head(10))如果某个特征的重要性超过 0.4甚至一骑绝尘压过所有其他特征先别高兴回头检查这个特征是不是上映后才有的字段大概率是评分、评论数、首周末票房这类泄露列混进来了。特征重要性排序截图放进文档说明是回应数据泄露质疑的最直接证据。5.2 SHAP 解释让模型的选择可被行业常识校验SHAP 是答辩环节最实用的工具能把树模型的黑盒输出变成人和模型都能理解的一张图import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_valid) shap.summary_plot(shap_values, X_valid)summary_plot 会展示每个特征对预测结果的贡献方向和大小。答辩前先用行业常识校验一遍档期特征的贡献方向应该是春节档、暑期档显著为正导演历史均值应该是正向贡献。如果 SHAP 图里出现了违背常识的特征方向比如“导演历史票房越高预测票房越低”那就是特征工程阶段出了问题趁答辩前赶紧修。5.3 两阶段拆分映前预测与首周末修正票房预测平台真正能体现业务深度的做法是把预测任务拆成两个阶段。第一阶段只用映前信息预测总票房这个模型做的是“定档到上映”之间的决策第二阶段引入首周末实际票房作为新特征预测最终总票房这个模型做的是“上映后一周内”的修正。两者共用同一套特征工程框架但目标不同# 第二阶段在特征集合中加入首周末实际票房 X_stage2 build_features(df) X_stage2[opening_weekend_gross] df[opening_weekend_gross] model_stage2 lgb.LGBMRegressor(**best_params) model_stage2.fit(X_stage2, y_train)逻辑说明首周末实际票房本身是上映后数据在单阶段模型里属于泄露字段但在两阶段框架下它的角色从“泄露源”变成了“合法特征”。把两个阶段的模型都保留并在文档说明里对比两阶段的 MAE 下降幅度这个数字比任何调参记录都有说服力因为它证明了平台不是一次性预测而是能持续修正的完整闭环。把你自己的数据按同样的拆法跑一遍观察两阶段模型的误差收敛幅度你会更快理解阈值、gap 和特征边界这些参数在不同任务里的真实意义。本文还有配套的精品资源点击获取
返回列表