资讯动态

Python实战英雄联盟胜率预测:从数据清洗到SHAP模型解释

发布时间:2026/10/21 6:30:44 来源:尧图企业网站定制
简介LeaguePredictor 是一套面向电子竞技数据分析初学者与机器学习实践者的 Python 项目源码聚焦英雄联盟比赛胜率预测这一具体场景帮助读者理解如何把分类模型落地到真实赛事数据上。资源包共 24 个文件以 22 个 py 脚本为主体覆盖数据生成、特征结构处理、分类器训练与测试等模块另含 1 个 gitignore 与 1 个 txt 目录说明文件压缩包约 31KB体量轻便、结构清晰。项目围绕数据预处理、特征工程、模型选择、训练评估与部署更新五个环节展开涉及 Pandas、Numpy、Scikit-learn 等常用库并给出逻辑回归、随机森林等模型的对比思路与交叉验证调参方法。目前已有 235 人学习下载适合希望用小型完整案例打通机器学习流程、积累特征构造与模型评估经验的开发者参考。1. 从一局排位说起这个胜率预测脚本到底能干什么昨晚开黑连跪三把第四把选人阶段队友秒锁亚索我盯着加载界面想这局到底几成胜算大多数人凭感觉但感觉经常骗人。LeaguePredictor 就是干这个的——用 Python 把英雄联盟对局数据喂进模型输出一个 0 到 1 之间的胜率数字。它不是那种“看阵容猜输赢”的玄学工具而是把经济差、击杀数、推塔进度、龙区控制这些硬指标量化后做概率推断。这个资源适合两类人一是想拿真实对局数据练手分类模型的 Python 开发者二是对 MOBA 游戏数据分析感兴趣、想自己跑一遍完整流程的从业者。它不要求你多懂游戏但要求你会用 pandas 读表、会用 scikit-learn 或类似库搭分类器。整条链路从数据清洗到特征工程再到模型评估麻雀虽小五脏俱全拿来当二分类项目的模板很顺手。2. 数据从哪来、特征怎么选把一局比赛拆成模型能吃的表2.1 对局数据的两种获取路径与字段结构做胜率预测第一步永远是数据。常见做法有两种一是用官方对局 API 拉取历史比赛记录二是用现成的对局数据集。前者需要处理分页、速率限制和字段嵌套后者省事但字段固定。不管走哪条路最终落到 DataFrame 里的核心字段大致是这些字段名含义类型是否参与建模gameId对局唯一标识string否win是否获胜bool标签kills / deaths / assists击杀/死亡/助攻int是goldEarned总经济int是totalMinionsKilled补刀数int是turretsDestroyed推塔数int是dragonKills击杀小龙数int是gameDuration对局时长秒int是这里有个容易翻车的地方对局时长和总经济高度相关如果两个都塞进去模型可能把“时长长”直接等同于“赢”但实际因果是反过来的——赢的队伍往往拖得久是因为优势方在推基地。我一般会做一次相关性筛查把相关系数超过 0.85 的特征对砍掉一个。2.2 特征工程从原始字段到模型输入原始字段不能直接扔进模型。比如击杀数10 分钟 5 杀和 30 分钟 5 杀意义完全不同所以要构造“每分钟击杀”这类归一化特征。下面这段代码是我常用的特征构造流程import pandas as pd import numpy as np def build_features(df): # 避免除以零时长至少为 60 秒 df[duration_min] df[gameDuration].clip(lower60) / 60.0 # 每分钟击杀、死亡、助攻 df[kills_per_min] df[kills] / df[duration_min] df[deaths_per_min] df[deaths] / df[duration_min] df[assists_per_min] df[assists] / df[duration_min] # KDA死亡为 0 时用 1 替代防止除零 df[kda] (df[kills] df[assists]) / df[deaths].replace(0, 1) # 经济效率每分钟经济 df[gold_per_min] df[goldEarned] / df[duration_min] # 补刀效率 df[cs_per_min] df[totalMinionsKilled] / df[duration_min] # 推塔贡献占比假设有团队总推塔数字段 team_turrets if team_turrets in df.columns: df[turret_share] df[turretsDestroyed] / df[team_turrets].replace(0, 1) # 删除原始字段避免共线性 drop_cols [gameId, gameDuration, kills, deaths, assists, goldEarned, totalMinionsKilled, turretsDestroyed] df df.drop(columns[c for c in drop_cols if c in df.columns]) return df逻辑说明clip(lower60)是防止极端短对局导致除零或爆炸值replace(0, 1)处理零死亡场景KDA 在零死亡时按满值处理是社区惯例删除原始字段是为了避免树模型把原始击杀数和每分钟击杀同时纳入导致特征重要性分散。参数方面duration_min的下限设 60 秒是经验值低于这个时长的对局通常是重开或异常局建议直接过滤掉而不是 clip。2.3 标签平衡与数据集划分英雄联盟对局胜率天然接近 50%所以标签不平衡问题不严重。但如果你的数据集里蓝方胜率明显偏高比如 55%那可能是数据采集偏差需要按时间或段位做分层抽样。划分训练集和测试集时我习惯用stratifyy保证两边胜率一致from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )random_state42是为了复现stratifyy保证训练集和测试集的胜率比例与原始数据一致。如果跳过 stratify小数据集上测试集胜率可能偏 3 到 5 个百分点评估结果就失真了。3. 模型选型与训练逻辑回归、随机森林还是梯度提升3.1 三个基线模型的适用场景对比胜率预测本质是二分类可选模型很多。我一般先跑三个基线看效果再决定要不要上更复杂的模型优势劣势适用场景逻辑回归可解释性强训练快拟合非线性关系弱特征少、需要看系数方向随机森林抗过拟合特征重要性直观模型文件大推理慢特征多、样本量中等梯度提升如 XGBoost/LightGBM精度通常最高调参成本高追求 AUC、有调参预算如果只是验证流程逻辑回归足够如果要发论文或做产品梯度提升更稳。我一般会先用随机森林跑一版看特征重要性排序是否符合游戏常识——如果“推塔数”排不进前五那特征工程大概率有问题。3.2 训练脚本与关键参数下面是一个随机森林的训练与评估脚本from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, roc_auc_score, classification_report # 初始化模型 rf RandomForestClassifier( n_estimators200, # 树的数量200 是精度和速度的折中 max_depth12, # 限制深度防过拟合 min_samples_leaf5, # 叶子最少样本数防止噪声被放大 class_weightbalanced, # 自动调整类别权重 random_state42, n_jobs-1 # 用满所有 CPU 核心 ) # 训练 rf.fit(X_train, y_train) # 预测 y_pred rf.predict(X_test) y_prob rf.predict_proba(X_test)[:, 1] # 评估 print(Accuracy:, accuracy_score(y_test, y_pred)) print(AUC:, roc_auc_score(y_test, y_prob)) print(classification_report(y_test, y_pred))参数说明n_estimators从 100 加到 200 通常有提升再加到 500 收益递减max_depth12是我在中等规模数据集上的常用值太深容易记住训练集噪声min_samples_leaf5保证每个叶子至少有 5 个样本避免模型对个别对局过拟合class_weightbalanced在胜率接近 50% 时影响不大但如果数据有偏它能自动补偿。3.3 特征重要性解读与模型可信度判断训练完别只看准确率特征重要性是判断模型是否“学到真东西”的关键import pandas as pd importance pd.DataFrame({ feature: X_train.columns, importance: rf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head(10))如果输出里gold_per_min、kda、turret_share排在前列说明模型抓住了经济、战斗、推塔三条主线可信度较高。如果某个你根本没业务含义的字段排第一比如某个 ID 类特征没删干净那就得回去查数据泄漏。我见过一次gameId没删模型 AUC 冲到 0.99后来发现是数据按时间排序后 ID 和标签意外相关纯属翻车。4. 避坑与排查胜率预测项目里最容易翻车的五件事4.1 数据泄漏特征里混入了未来信息现象模型在测试集上 AUC 超过 0.95但实际预测新对局时准确率掉到 0.6 以下。原因特征里包含了只有对局结束后才能拿到的字段比如“最终经济差”或“总击杀数”这些在选人阶段根本不可知。解决明确预测时间点——如果是赛前预测只能用阵容和段位如果是赛中预测只能用当前时间戳之前的数据。把特征按时间可用性分两批别混用。4.2 版本差异不同赛季的数据分布漂移现象用上赛季数据训练的模型新赛季预测准确率明显下降。原因游戏版本更新会调整英雄强度、装备属性和地图资源导致特征分布偏移。解决按版本号或时间段切分数据集训练时加入版本标识作为特征或者只保留最近两个赛季的数据。如果跨版本使用至少做一次分布检验比如对比训练集和测试集的经济均值。4.3 样本偏差高段位对局和低段位对局不能混现象模型在整体测试集上表现不错但单独看低段位对局时准确率很差。原因高段位玩家补刀、推塔、控龙节奏和低段位差异巨大混在一起训练会让模型偏向多数样本。解决按段位分层建模或者把段位作为特征加入。如果数据量不够分层至少要在评估时按段位分组看指标别只看总体。4.4 特征缩放树模型不需要但线性模型必须做现象逻辑回归训练后系数方向反直觉比如“经济越高胜率越低”。原因不同特征量纲差异大经济是万级、KDA 是个位数未标准化时梯度下降会被大量纲特征主导。解决逻辑回归和 SVM 必须做 StandardScaler 或 MinMaxScaler树模型可以跳过。我一般把缩放步骤放进 Pipeline避免训练集和测试集分别 fit 导致数据泄漏。4.5 过拟合训练集 AUC 0.99 测试集 0.7现象训练集表现完美测试集一塌糊涂。原因模型太复杂、特征太多、样本太少三者必居其一。解决先降模型复杂度——减少树深度、增加叶子最小样本数、减少特征数量再考虑增加数据。如果数据实在少用交叉验证代替单次划分看多折方差。方差大说明模型不稳定不是调参能救的。5. 进阶技巧用 SHAP 解释单局预测与概率校准模型跑通只是开始真正让胜率预测有价值的是“为什么是这个数”。SHAP 是我常用的解释工具它能告诉你每个特征对单局预测的贡献方向。比如某局预测胜率 0.72SHAP 会显示“经济领先 0.15、推塔领先 0.08、死亡偏多 -0.05”这样输出就不是一个黑匣子数字而是一份可读的局势分析。import shap # 用树模型解释器 explainer shap.TreeExplainer(rf) shap_values explainer.shap_values(X_test) # 单局解释取第 0 个测试样本 shap.initjs() shap.force_plot( explainer.expected_value[1], shap_values[1][0], X_test.iloc[0], matplotlibTrue )explainer.expected_value[1]是基准胜率shap_values[1][0]是第一个样本对正类获胜的贡献值。正贡献推高胜率负贡献拉低。如果不想画图直接打印shap_values的排序也能看。另一个容易被忽略的点是概率校准。随机森林输出的predict_proba不一定是校准过的概率可能集中在 0.4 到 0.6 之间导致“预测 0.7 胜率”实际只有 0.6。用CalibratedClassifierCV包一层from sklearn.calibration import CalibratedClassifierCV calibrated CalibratedClassifierCV(rf, methodisotonic, cv5) calibrated.fit(X_train, y_train) y_prob_cal calibrated.predict_proba(X_test)[:, 1]methodisotonic适合样本量较大的情况样本少时用sigmoid。校准后概率更接近真实频率做决策时更可靠。从那以后我每次跑分类项目都会在训练完先看特征重要性、再做概率校准、最后用 SHAP 抽几个样本人工核对这三步走完才敢把模型交出去。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑