首页 / 资讯中心 / 文章详情

基于决策树的Python电影票房预测系统:原理、实现与避坑指南

基于决策树的Python电影票房预测系统:原理、实现与避坑指南 ★ FEATURED ARTICLE
简介基于决策树算法的Python电影票房预测系统实现包含完整项目文件面向计算机、电子信息、应用数学等专业学生的课程设计或毕设参考。压缩包共62个文件、约4.59MB主要包含Python源码py、数据集xlsx/dat、模型缓存与备份pyc/zbak及说明文档md其中涉及ID3、CART、GBDT等多种决策树变体的迭代版本和训练脚本。包内按数据预处理、模型构建、训练测试、可视化绘图等模块组织便于对比不同树模型在票房预测任务上的表现。已有47人学习下载。资源为技术学习参考而非定制方案需具备基础Python能力能独立分析代码结构、排查错误并做功能拓展。因维护者全职工作除资源文件完整性缺失外不作其他问题受理请知悉。来源于网络分享仅用于学习交流请勿商业使用。1. 先把决策树票房预测跑起来这份资源里有什么做毕业设计选“基于决策树算法的Python电影票房预测系统”的人多半不是真想研究机器学习而是想要一个能演示、能写进论文、答辩时能讲清楚逻辑的完整项目。这份资源正好卡在这个需求上——它不是一份理论讲义而是一套从数据清洗、特征构建、决策树训练到预测结果可视化的可运行代码核心入口是train.py和predict.py配套的还有电影样本数据集和几张训练产出的决策树图。你拿到的成品应该是一个能读入 CSV、自动切分训练集和测试集、输出准确率并画出树的 Python 项目。适合两类人一类是正在做毕设、需要快速把系统跑通并知道每一行在干什么的学生另一类是刚入门 Python 数据分析、想用一个真实场景练手决策树算法的开发者。先说结论这套系统的代码风格偏教学向性能不是它的强项但胜在链路完整、容易二次开发——你完全可以把它当作一个能跑通的骨架再往里面换数据、加特征、换算法。2. 决策树为什么适合票房预测原理、选型与数据准备2.1 决策树的核心机制与票房场景的匹配逻辑决策树是机器学习里少有的“白盒”算法它的训练结果是一棵可以被直接观察的树形规则集。以票房预测为例树根可能是“是否是大制作”向左走是“主演是否有一线明星”每一层都是一个if-then规则。相比神经网络的黑匣子决策树的每个分裂点都对应一个实际业务含义这在毕业设计答辩时是最实用的优势——你不需要说“模型自动学习到了特征”你可以直接指着树图说“这里按预算分叉了左侧 70% 的样本票房低于 2 亿”。从算法机制上看决策树通过计算信息增益或基尼系数来选择最优分裂特征。在票房预测这个任务里特征通常是混合类型的题材是离散型投资额是连续型档期可以编码成离散型。决策树对离散型特征有天然优势对连续型特征也能通过阈值切分处理不需要做复杂的归一化这比 SVM 或 kNN 省去了一大步预处理工作。另外票房数据中常存在缺失值和异常值决策树对缺失值有一定容忍度异常值也不容易像线性回归那样直接拉偏拟合结果。# 读取数据集并查看基础统计信息 import pandas as pd df pd.read_csv(movie_data.csv, encodingutf-8) print(df.shape) print(df.info()) print(df.describe())这段代码的作用是建立对数据的第一印象。df.shape告诉你样本数和特征数df.info()能看到每列是否有缺失值及数据类型df.describe()给出连续特征的均值、标准差、最小值、最大值。参数说明encodingutf-8是处理中文字段的常用编码如果你的 CSV 是从 Excel 另存的可能需要换成gbk这一点在后面踩坑部分会细说。2.2 特征工程的取舍与票房预测的特征体系决策树虽然不需要归一化但特征工程的优劣直接决定树的质量。票房预测常用特征可以分成三类影片自身属性、市场环境、时间效应。影片属性包括预算、时长、题材、是否系列电影、主演知名度市场环境包括档期类型、同期竞争强度时间效应包括上映月份、是否节假日。这套系统的基础版本通常只用到预算、主演热度、题材、档期这几个核心变量因为数据好采集编码不复杂。# 特征选择与标签切分 feature_cols [budget, actor_score, genre, release_month, is_holiday] X df[feature_cols] y df[box_office] # 检查特征值范围避免极端值影响树分裂 for col in feature_cols: print(f{col}: min{df[col].min()}, max{df[col].max()})这里单独把特征和标签分离feature_cols明确定义了模型要学习的变量。有一个容易忽略的细节是genre和release_month虽然是数字编码但它们是名义变量不是有序变量直接用数值进决策树会让树以为“题材 6 比题材 3 大”产生无意义的分裂。我一般会建议用pd.get_dummies()把它转成独热编码或者至少在选择特征时留意这个问题。is_holiday是 0/1 二值变量不需要额外处理。2.3 数据集的模拟生成与真实数据获取路径很多初学者卡在最前面没有真实的票房数据集。这套资源里自带的数据集如果是模拟生成的你就要明白它的局限性——模型在模拟数据上准确率高不代表真实数据表现好。常见的做法是从公开的电影资料网站爬取票房数据或者用 TMDB 的 API 获取影片信息和预算、收入字段。# 生成模拟数据的参考思路仅用于开发和调试 import numpy as np from sklearn.model_selection import train_test_split np.random.seed(42) n_samples 500 budget np.random.randint(50, 300, n_samples) # 预算单位千万 actor_score np.random.uniform(1, 10, n_samples) # 主演热度 1-10 genre np.random.randint(0, 6, n_samples) # 6 种题材 release_month np.random.randint(1, 13, n_samples) is_holiday np.random.randint(0, 2, n_samples) # 模拟票房预算影响最大叠加噪声 box_office budget * 0.8 actor_score * 200 genre * 30 \ (release_month 2) * 40 is_holiday * 100参数说明n_samples500是样本量对决策树来说这个量级足够训练但容易过拟合np.random.seed(42)固定随机种子保证每次生成的数据一致这个习惯能让你在调参时排除数据变异带来的干扰。模拟数据的box_office公式中budget * 0.8是主要贡献项后面几项是弱信号这样设计是为了让决策树能学到“预算对票房影响最大”这一规则训练时准确率会比较好看。3. 跑通决策树训练与可视化从训练集切分到树图导出3.1 训练集和测试集切分中的常见误操作训练集和测试集切分是整套系统里最容易翻车的环节尤其当你的样本量只有几百条时。最常见的问题是忘记设置random_state导致每次运行切分结果不同模型评估指标忽高忽低你根本判断不了是自己的调参有效还是数据随机性好。另一个问题是切分前没有对特征做编码处理直接拿字符串类型的题材列喂给 sklearn会直接报ValueError。# 训练集与测试集切分固定随机种子保证可复现 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifypd.cut(y, bins5, labelsFalse) ) print(f训练集样本数: {X_train.shape[0]}) print(f测试集样本数: {X_test.shape[0]})这段代码里有两个关键参数。test_size0.3表示 30% 的样本做测试对 500 条数据来说就是 150 条测试样本完全够用。stratifypd.cut(y, bins5, labelsFalse)是一个容易被忽略的细节——票房变量通常严重右偏少数高票房影片会集中出现在测试集里导致训练集学不到极值特征。pd.cut把票房分成 5 层再按层比例切分这样就保证了训练集和测试集里都有不同票房区间的样本。3.2 用 scikit-learn 训练决策树参数选择的实际考量训练决策树的核心就一个类DecisionTreeClassifier。但这里的标签box_office是连续值严格来说应该用DecisionTreeRegressor。不过很多毕设系统会先把票房离散化成几档高、中、低再用分类树预测档位答辩时更容易解释。两种方案各有取舍分类树输出的准确率直观但丢失了票房的具体数值回归树预测的是连续票房评估用 R²但指标不够“好看”。# 使用决策树分类器训练票房档位预测模型 from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score # 把票房离散化为三档低(100)、中(100-300)、高(300) y_class pd.cut(y, bins[0, 100, 300, np.inf], labels[0, 1, 2]) X_train, X_test, y_train, y_test train_test_split( X, y_class, test_size0.3, random_state42, stratifyy_class ) clf DecisionTreeClassifier( criterionentropy, max_depth5, min_samples_split10, min_samples_leaf5, random_state42 ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(f测试集准确率: {accuracy_score(y_test, y_pred):.4f})criterionentropy使用信息熵作为分裂质量的度量相比默认的gini信息熵对多分类问题的区分更细腻但计算开销略大数据量小时差别不大。max_depth5限制树的最大深度这是最直接的防过拟合手段——不限制的话树会分裂到每个叶节点只剩一个样本训练集准确率 100%测试集却一塌糊涂。min_samples_split10和min_samples_leaf5是两个配套约束前者要求内部节点至少有 10 个样本才允许继续分裂后者要求叶节点至少包含 5 个样本。这三个参数组合对 500 条数据的场景是比较合理的起点。3.3 决策树可视化把规则画出来给答辩看决策树可视化是毕业设计的加分项评委看到一张清晰的树形图比看准确率数字更有印象。scikit-learn 本身不带绘图功能需要借助matplotlib和sklearn.tree.plot_tree。这里有一个常见的坑直接用plot_tree输出中文特征名时中文字体会变成方块需要在绘图前设置字体。# 导出决策树结构图支持中文标签显示 import matplotlib.pyplot as plt from sklearn.tree import plot_tree plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(20, 10)) plot_tree( clf, feature_namesfeature_cols, class_names[低票房, 中票房, 高票房], filledTrue, roundedTrue, fontsize10, axax ) plt.savefig(decision_tree.png, dpi300, bbox_inchestight)这段代码里有几个为答辩而优化的参数。filledTrue会让每个节点的背景色随类别比例变化视觉上直观体现分类纯度roundedTrue让节点圆角化看起来更专业。figsize(20, 10)很重要——树深度为 5 时叶子数量可能超过 32 个默认画布根本放不下。dpi300保证导出图的清晰度打印到论文里不会糊。注意font.sans-serif要放在plot_tree之前否则中文字体设置不会生效。4. 系统模块拆解与参数边界从训练到预测的完整链路4.1 训练模块的内部结构与持久化输出一个合格的预测系统不能只有训练脚本还得有保存模型和加载模型的机制。真实的毕设项目里训练是一次性的预测是多次的如果把训练和预测写在一个函数里每次预测都要重新训练既浪费时间又无法脱离训练数据运行。# 训练完成后保存模型供 predict 模块加载 import joblib model_path box_office_model.pkl joblib.dump(clf, model_path, compress3) # 同时保存特征名称列表预测时需要保持一致 joblib.dump(feature_cols, feature_cols.pkl) print(f模型已保存至 {model_path})joblib.dump的compress3参数控制压缩级别数字越大文件越小但保存越慢对一个小型模型来说 3 是性价比最高的选择。这里把feature_cols一并保存是为了防止预测阶段特征顺序不一致——这是决策树模型的另一个潜在风险训练时特征是[budget, actor_score, genre, release_month, is_holiday]预测时如果传成[budget, genre, actor_score, release_month, is_holiday]树的分裂规则就全部失效了但你不会收到任何报错。4.2 预测模块的输入处理与输出格式化预测模块要面对的是全新数据不能假设用户会手动调整好格式。真实场景里用户输入的可能是一个字符串“动作, 2.5亿预算, 五一档”而不是模型期望的数字数组。因此预测模块的第一层职责是格式转换和特征对齐。# 预测模块输入新电影特征输出票房档位 import numpy as np import joblib def predict_new_movie(budget_million, actor_score, genre_code, release_month, is_holiday): # 加载训练时保存的模型和特征列表 clf joblib.load(box_office_model.pkl) feature_cols_saved joblib.load(feature_cols.pkl) # 构造输入数组顺序必须与训练时完全一致 features np.array([[budget_million, actor_score, genre_code, release_month, int(is_holiday)]]) # 预测类别和各类别概率 pred_class clf.predict(features)[0] pred_proba clf.predict_proba(features)[0] # 概率值保留两位小数 proba_formatted [{:.2f}.format(p) for p in pred_proba] result_map {0: 低票房1亿, 1: 中票房1-3亿, 2: 高票房3亿} return { 档位: result_map[pred_class], 各档位概率: proba_formatted, 规则深度: clf.get_depth() } # 示例预算 1.5 亿主演热度 8.5动作片春节档是节假日 print(predict_new_movie(150, 8.5, 1, 2, 1))predict返回的是类别编码需要手动映射回中文档位predict_proba返回的是每个类别的概率数组这里用列表推导式统一格式化成两位小数。clf.get_depth()是一个容易被忽略的调试信息——如果你发现所有预测结果都集中在同一个档位可以先看看树的实际深度如果深度是 1说明欠拟合严重树只在根节点分裂了一次就停了。4.3 评估指标的局限性准确率之外还要看什么很多毕设系统的评估部分只有一行“准确率 85%”这其实是不够的。当票房档位分布严重不均衡时比如 500 条样本中 70% 是中等票房模型只要全部预测为中等票房准确率就是 70%。你需要同时输出混淆矩阵和分类报告来证明模型是真的学到了规律而不是策略性偷懒。# 输出混淆矩阵与分类报告 from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns cm confusion_matrix(y_test, y_pred) print(混淆矩阵:) print(cm) # 用热力图可视化混淆矩阵 sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[低, 中, 高], yticklabels[低, 中, 高]) plt.title(决策树分类混淆矩阵) plt.xlabel(预测值) plt.ylabel(真实值) plt.show() print(classification_report(y_test, y_pred, target_names[低票房, 中票房, 高票房]))混淆矩阵对角线上的数字越大说明模型区分能力越强。classification_report输出的precision、recall、f1-score三个指标分别对应“预测为该类别的样本中真实占比”“真实为该类别的样本中被找出的占比”“两者的调和平均”。对一个票房预测系统来说高票房档的召回率尤其值得关注——如果模型总是把高票房电影预测成中等票房说明树的分裂规则还没有抓住“高票房”的特征区间。5. 决策树票房预测的避坑记录五个高频翻车现场5.1 中文编码问题导致的训练直接报错现象运行时提示UnicodeDecodeError: utf-8 codec cant decode byte 0xc4 in position 0CSV 完全读不进来。原因数据文件里含有中文字段文件实际保存编码是 GBK 或 GB2312但你用utf-8去读。这种情况在 Windows 环境下尤其常见因为 Excel 另存为 CSV 时默认使用本地化编码。解决读文件时指定编码pd.read_csv(movie_data.csv, encodinggbk)。还有一种更稳妥的做法是先用chardet库自动检测文件编码再读取适合数据源多样化的场景。import chardet with open(movie_data.csv, rb) as f: result chardet.detect(f.read(10000)) print(result[encoding]) df pd.read_csv(movie_data.csv, encodingresult[encoding])5.2 特征列包含字符串导致模型训练报 ValueError现象训练时报ValueError: could not convert string to float: action明明数据看着没问题数值都在。原因特征矩阵X中有一列是字符串类型的题材比如action、comedy而DecisionTreeClassifier在底层把特征矩阵转为float类型时无法转换字符串。解决先检查每列的数据类型df.dtypes然后用pd.get_dummies对字符串列做独热编码或者手动映射成整数编码。# 把题材字符串映射为数值编码并检查数据类型 genre_map {action: 0, comedy: 1, drama: 2, sci_fi: 3, romance: 4} df[genre_code] df[genre].map(genre_map) assert df[genre_code].isna().sum() 0, 存在未映射的题材5.3 数据泄漏用测试集信息做了特征编码现象训练集准确率不错测试集准确率也高但换了真实新数据预测时效果断崖式下跌。原因你用全量数据计算的均值、最大值、最小值来填充缺失值或创建新特征测试集的信息在训练阶段已经被模型间接知道了。比如用全量数据的平均预算填充缺失预算这就是典型的数据泄漏。解决先切分训练集和测试集再在训练集上计算填充值把填充值保存下来应用到测试集和未来新数据上。# 正确做法只在训练集上计算填充值 X_train, X_test, y_train, y_test train_test_split( X, y_class, test_size0.3, random_state42, stratifyy_class ) budget_median X_train[budget].median() X_train.loc[:, budget] X_train[budget].fillna(budget_median) X_test.loc[:, budget] X_test[budget].fillna(budget_median)5.4 决策树画图乱码中文全变成方块现象导出到decision_tree.png后所有中文标签和特征名都显示为方块。原因matplotlib 默认的英文字体不支持中文字符需要手动指定系统中文字体。不同操作系统的可用字体不同只设置一种很可能在你的环境下不存在。解决使用字体列表逐个尝试直到找到可用字体。from matplotlib.font_manager import FontProperties available [SimHei, Microsoft YaHei, Malgun Gothic, Noto Sans CJK SC] for font in available: try: plt.rcParams[font.sans-serif] [font] plt.rcParams[axes.unicode_minus] False # 尝试创建一个文本对象验证字体是否可用 fig_test, ax_test plt.subplots() ax_test.text(0.5, 0.5, 测试中文) plt.close(fig_test) print(f使用字体: {font}) break except Exception: continue5.5 模型文件丢失特征顺序导致预测结果沉默出错现象预测模块运行不报错但所有新电影都返回同一个档位且概率分布几乎一致。原因预测时传入的特征顺序和训练时不一致比如训练时genre_code是第 3 列预测时genre_code变成了第 2 列。决策树的分裂规则依赖特征索引顺序变了就是不同的判定路径而这个错误不会有任何提示。解决训练时把feature_cols一起序列化保存预测时严格按保存的列表顺序构造输入矩阵。查这个问题时可以打印clf.tree_.feature看看树节点引用的特征索引再和你传入的数据列对齐。# 检查树节点使用的特征索引确定模型内部特征顺序 tree_feature_indices clf.tree_.feature print(f树节点引用的特征索引: {sorted(set(tree_feature_indices))}) print(f训练时特征列表: {feature_cols})6. 进阶优化与验证方法剪枝、集成和新数据扩展决策树训练完准确率可能停在 75% 左右这时可以从两个方向继续优化。第一个方向是剪枝调参scikit-learn 提供了代价复杂度剪枝路径cost_complexity_pruning_path会返回一串不同ccp_alpha值以及对应的树复杂度你可以据此画出“树的节点数 vs 测试集准确率”的曲线找到准确率曲线开始下滑的拐点选取那个位置的ccp_alpha重训模型。# 使用代价复杂度剪枝路径选择最优剪枝参数 from sklearn.tree import DecisionTreeClassifier path clf.cost_complexity_pruning_path(X_train, y_train) ccp_alphas path.ccp_alphas # 遍历不同 alpha记录测试集准确率 results [] for alpha in ccp_alphas: model DecisionTreeClassifier( criterionentropy, max_depth10, random_state42, ccp_alphaalpha ) model.fit(X_train, y_train) acc model.score(X_test, y_test) results.append((alpha, acc)) best_alpha, best_acc max(results, keylambda x: x[1]) print(f最优 alpha: {best_alpha:.4f}, 最优准确率: {best_acc:.4f})第二个方向是把决策树换成随机森林对比多棵树投票的结果。随机森林通过自助采样和随机特征选择降低方差在 500 条数据上的效果通常比单棵树好 3~5 个百分点。这是答辩时最稳妥的“包装升级”——树结构还是原来的树结构只是在横向和纵向上都加了随机性。# 对比随机森林与单棵决策树的效果 from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators100, max_depth5, min_samples_split10, min_samples_leaf5, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) rf_acc rf.score(X_test, y_test) print(f随机森林准确率: {rf_acc:.4f}决策树准确率: {best_acc:.4f})n_estimators100是随机森林中树的数量不是越多越好超过 200 之后准确率增长趋于平缓训练时间却线性增长。n_jobs-1让 sklearn 自动使用所有 CPU 核心对 100 棵树的训练来说能明显提速。判断随机森林的改进是否有意义不能只看准确率提升了多少还需要观察几次运行之间的波动范围——如果单棵决策树多次运行准确率在 75%~78% 之间波动而随机森林稳定在 80%那这个提升是真实的。最后提一下验证方法的盲点如果你发现模型在测试集上准确率很高但在预测“国庆档大制作”这类极端样本时仍然漂移严重可以用clf.tree_.threshold查看所有分裂点的阈值反推模型学到的业务规则边界。比如你看到根节点是budget 120.5说明模型认为预算超过 1.2 亿是高票房的强信号这个阈值和你的行业直觉是否一致是判断模型是否学到真实规律的一个重要参考。我自己做这类系统时每次跑完都会强制走一遍sklearn.model_selection.cross_val_score做五折交叉验证交叉验证结果和单次切分的准确率相差超过 5 个百分点就说明模型对数据切分方式过于敏感需要回去处理特征和调参而不是急于出图。这个习惯帮我排掉过不少伪高准确率的坑希望能帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站