简介: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(lower=60) / 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(lower=60)是防止极端短对局导致除零或爆炸值;replace(0, 1)处理零死亡场景,KDA 在零死亡时按满值处理是社区惯例;删除原始字段是为了避免树模型把原始击杀数和每分钟击杀同时纳入导致特征重要性分散。参数方面,duration_min的下限设 60 秒是经验值,低于这个时长的对局通常是重开或异常局,建议直接过滤掉而不是 clip。2.3 标签平衡与数据集划分英雄联盟对局胜率天然接近 50%,所以标签不平衡问题不严重。但如果你的数据集里蓝方胜率明显偏高(比如 55%),那可能是数据采集偏差,需要按时间或段位做分层抽样。划分训练集和测试集时,我习惯用stratify=y保证两边胜率一致:from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )random_state=42是为了复现,stratify=y保证训练集和测试集的胜率比例与原始数据一致。如果跳过 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_estimators=200, # 树的数量,200 是精度和速度的折中 max_depth=12, # 限制深度防过拟合 min_samples_leaf=5, # 叶子最少样本数,防止噪声被放大 class_weight='balanced', # 自动调整类别权重 random_state=42, 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_depth=12是我在中等规模数据集上的常用值,太深容易记住训练集噪声;min_samples_leaf=5保证每个叶子至少有 5 个样本,避免模型对个别对局过拟合;class_weight='balanced'在胜率接近 50% 时影响不大,但如果数据有偏它能自动补偿。3.3 特征重要性解读与模型可信度判断训练完别只看准确率,特征重要性是判断模型是否“学到真东西”的关键:import pandas as pd importance = pd.DataFrame({ 'feature': X_train.columns, 'importance': rf.feature_importances_ }).sort_values('importance', ascending=False) 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.72,SHAP 会显示“经济领先 +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], matplotlib=True )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, method='isotonic', cv=5) calibrated.fit(X_train, y_train) y_prob_cal = calibrated.predict_proba(X_test)[:, 1]method='isotonic'适合样本量较大的情况,样本少时用'sigmoid'。校准后概率更接近真实频率,做决策时更可靠。从那以后我每次跑分类项目,都会在训练完先看特征重要性、再做概率校准、最后用 SHAP 抽几个样本人工核对,这三步走完才敢把模型交出去。希望帮到你。本文还有配套的精品资源,点击获取
简介: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(lower=60) / 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(lower=60)是防止极端短对局导致除零或爆炸值;replace(0, 1)处理零死亡场景,KDA 在零死亡时按满值处理是社区惯例;删除原始字段是为了避免树模型把原始击杀数和每分钟击杀同时纳入导致特征重要性分散。参数方面,duration_min的下限设 60 秒是经验值,低于这个时长的对局通常是重开或异常局,建议直接过滤掉而不是 clip。2.3 标签平衡与数据集划分英雄联盟对局胜率天然接近 50%,所以标签不平衡问题不严重。但如果你的数据集里蓝方胜率明显偏高(比如 55%),那可能是数据采集偏差,需要按时间或段位做分层抽样。划分训练集和测试集时,我习惯用stratify=y保证两边胜率一致:from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )random_state=42是为了复现,stratify=y保证训练集和测试集的胜率比例与原始数据一致。如果跳过 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_estimators=200, # 树的数量,200 是精度和速度的折中 max_depth=12, # 限制深度防过拟合 min_samples_leaf=5, # 叶子最少样本数,防止噪声被放大 class_weight='balanced', # 自动调整类别权重 random_state=42, 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_depth=12是我在中等规模数据集上的常用值,太深容易记住训练集噪声;min_samples_leaf=5保证每个叶子至少有 5 个样本,避免模型对个别对局过拟合;class_weight='balanced'在胜率接近 50% 时影响不大,但如果数据有偏它能自动补偿。3.3 特征重要性解读与模型可信度判断训练完别只看准确率,特征重要性是判断模型是否“学到真东西”的关键:import pandas as pd importance = pd.DataFrame({ 'feature': X_train.columns, 'importance': rf.feature_importances_ }).sort_values('importance', ascending=False) 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.72,SHAP 会显示“经济领先 +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], matplotlib=True )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, method='isotonic', cv=5) calibrated.fit(X_train, y_train) y_prob_cal = calibrated.predict_proba(X_test)[:, 1]method='isotonic'适合样本量较大的情况,样本少时用'sigmoid'。校准后概率更接近真实频率,做决策时更可靠。从那以后我每次跑分类项目,都会在训练完先看特征重要性、再做概率校准、最后用 SHAP 抽几个样本人工核对,这三步走完才敢把模型交出去。希望帮到你。本文还有配套的精品资源,点击获取