资讯动态

基于机器学习的MOBA游戏胜率预测:从特征工程到模型部署

发布时间:2026/10/21 6:32:27 来源:尧图企业网站定制
简介LeaguePredictor 是一套面向电子竞技数据分析初学者与机器学习实践者的 Python 项目源码聚焦英雄联盟比赛胜率预测这一具体场景帮助读者理解如何把分类模型落地到真实赛事数据上。压缩包共 24 个文件以 22 个 py 脚本为主体覆盖数据生成、特征结构处理、分类器训练与测试等模块另含 1 个 gitignore 与 1 个 txt 说明文件整体约 31KB体量轻便、便于本地运行与二次修改。项目围绕数据预处理、特征工程、模型选择、训练评估与部署更新五个环节展开涉及 Pandas、Numpy、Scikit-learn 等常用库并给出逻辑回归、随机森林等模型的对比思路。目前已有 235 人学习下载适合想通过完整小项目熟悉监督学习流程、积累特征构造与模型调参经验的读者参考。1. 从一局排位说起这个预测胜率的项目到底能干什么你有没有过这种体验选人阶段刚结束看一眼双方阵容心里就大概有数了——这把是碾压还是坐牢。但“大概有数”终究是玄学能不能用代码把这个直觉量化出来LeaguePredictor 就是干这个的输入双方阵容、段位、近期战绩等特征输出一个 0 到 1 之间的胜率预测值。它不是那种“赢了就是模型准、输了就是队友菜”的玩具而是一条完整的机器学习流水线从数据清洗、特征工程到模型训练和推理接口都给你搭好了。这个项目适合两类人一类是想拿一个真实场景练手 Python 机器学习的新手另一类是打算把它接进自己工具链、做赛前分析或直播辅助的开发者。技术栈上核心是 Python pandas scikit-learn数据层用 SQLite 或 CSV 落盘推理部分封装成一个可调用的函数方便你后面接 Flask 或 FastAPI 做服务。整份代码没有花哨的深度学习框架全是能跑、能读、能改的工程代码这一点对二次开发特别友好。2. 项目骨架拆解数据从哪来、模型往哪走2.1 目录结构与模块职责拿到源码先别急着跑花两分钟把目录扫一遍后面改代码会顺很多。这个项目的结构是典型的“数据-特征-模型-接口”四层分离常见做法是长这样LeaguePredictor/ ├── data/ │ ├── raw/ # 原始对局数据CSV 或 JSON │ └── processed/ # 清洗后的特征表 ├── src/ │ ├── data_loader.py # 读取原始数据做基础校验 │ ├── feature_engineer.py # 特征构造与拼接 │ ├── train.py # 训练入口输出模型文件 │ ├── predict.py # 推理入口加载模型出胜率 │ └── utils.py # 通用工具比如路径管理、日志 ├── models/ │ └── lgbm_model.pkl # 训练好的模型持久化文件 ├── config.yaml # 路径、超参数、特征开关 └── requirements.txtdata_loader.py只负责把原始数据读进来不做复杂变换feature_engineer.py才是真正干活的地方把英雄、位置、段位这些原始字段转成模型能吃的数值train.py和predict.py一进一出边界清晰。这种拆法的好处是你想换模型只动train.py想加特征只动feature_engineer.py不会牵一发动全身。2.2 数据加载与清洗的关键代码原始对局数据通常来自公开数据集或自己抓的接口字段包括双方五个位置的英雄 ID、段位、胜负标签等。下面这段是data_loader.py里的核心逻辑import pandas as pd from pathlib import Path def load_raw_data(data_dir: str) - pd.DataFrame: 读取 raw 目录下所有 CSV合并成一张大表 data_path Path(data_dir) # 常见做法是按赛季或月份分文件这里统一 glob 进来 files sorted(data_path.glob(*.csv)) if not files: raise FileNotFoundError(f在 {data_dir} 下没找到任何 CSV 文件) frames [] for f in files: df pd.read_csv(f) # 只保留有胜负标签的完整对局缺标签的丢掉 df df.dropna(subset[win]) frames.append(df) merged pd.concat(frames, ignore_indexTrue) # 去重防止同一局被重复采集 merged merged.drop_duplicates(subset[match_id]) return merged这里有几个参数值得注意dropna(subset[win])只检查标签列不会因为某个英雄字段缺失就把整局丢掉drop_duplicates的subset指定match_id避免同一场比赛因为采集时间不同被算两次。如果你拿到的数据没有match_id常见做法是用双方十个英雄 ID 加时间戳拼一个哈希出来当唯一键。2.3 特征工程把阵容变成模型能读的数字这是整个项目最需要动脑子的地方。英雄 ID 本身是类别变量直接扔给模型会引入错误的序关系所以要做编码。同时双方阵容之间存在克制关系单看一边不够得构造交叉特征。import pandas as pd from sklearn.preprocessing import OneHotEncoder def build_features(df: pd.DataFrame, encoder: OneHotEncoder None): 把原始对局表转成特征矩阵返回特征和编码器 # 双方各五个位置的英雄 ID blue_cols [fblue_hero_{i} for i in range(1, 6)] red_cols [fred_hero_{i} for i in range(1, 6)] # 把两边英雄拼成一个列表列方便统一编码 df[blue_team] df[blue_cols].astype(str).agg(,.join, axis1) df[red_team] df[red_cols].astype(str).agg(,.join, axis1) # 段位做有序编码青铜到王者映射成 0-8 rank_map {Iron: 0, Bronze: 1, Silver: 2, Gold: 3, Platinum: 4, Diamond: 5, Master: 6, Grandmaster: 7, Challenger: 8} df[blue_rank] df[blue_rank].map(rank_map).fillna(3) df[red_rank] df[red_rank].map(rank_map).fillna(3) df[rank_diff] df[blue_rank] - df[red_rank] # 英雄组合做 one-hotfit 和 transform 分开方便推理时复用 if encoder is None: encoder OneHotEncoder(handle_unknownignore, sparse_outputFalse) hero_matrix encoder.fit_transform(df[[blue_team, red_team]]) else: hero_matrix encoder.transform(df[[blue_team, red_team]]) feature_df pd.DataFrame(hero_matrix) feature_df[rank_diff] df[rank_diff].values return feature_df, encoderhandle_unknownignore这个参数很关键推理时如果遇到训练集里没出现过的英雄组合编码器不会报错而是输出全零向量保证服务不崩。rank_diff是我自己加的一个特征段位差往往比绝对段位更有信息量这个在实战里比单纯堆段位数值效果好不少。3. 训练与推理模型怎么练、胜率怎么算3.1 训练脚本的完整流程train.py是整个项目的入口它把前面几步串起来最后落一个模型文件。下面这段是主干逻辑import joblib import pandas as pd from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score from lightgbm import LGBMClassifier from src.data_loader import load_raw_data from src.feature_engineer import build_features def main(): # 1. 读数据 raw load_raw_data(data/raw) # 2. 构造特征 X, encoder build_features(raw) y raw[win].astype(int) # 3. 切分stratify 保证正负样本比例一致 X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 4. 训练参数先给一组保守值 model LGBMClassifier( n_estimators300, learning_rate0.05, max_depth6, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_train, y_train) # 5. 验证看 AUC 而不是准确率 pred_prob model.predict_proba(X_val)[:, 1] auc roc_auc_score(y_val, pred_prob) print(f验证集 AUC: {auc:.4f}) # 6. 持久化模型和编码器推理时两个都要用 joblib.dump({model: model, encoder: encoder}, models/lgbm_model.pkl) if __name__ __main__: main()为什么用 AUC 而不是准确率因为胜负标签大致是 1:1准确率看着高不代表模型有区分度AUC 更能反映排序能力。stratifyy是为了防止切分后某一类样本过少导致验证结果抖动。模型选 LightGBM 而不是逻辑回归是因为英雄组合的 one-hot 维度很高树模型对稀疏特征的处理更稳。3.2 推理接口给一个阵容返回胜率predict.py要做的事情和训练相反加载模型和编码器把用户输入的阵容转成同样的特征格式然后输出概率。import joblib import pandas as pd from src.feature_engineer import build_features class WinPredictor: def __init__(self, model_path: str models/lgbm_model.pkl): bundle joblib.load(model_path) self.model bundle[model] self.encoder bundle[encoder] def predict(self, blue_heroes: list, red_heroes: list, blue_rank: str Gold, red_rank: str Gold) - float: 输入双方英雄 ID 列表和段位返回蓝方胜率 row {} for i, h in enumerate(blue_heroes, start1): row[fblue_hero_{i}] h for i, h in enumerate(red_heroes, start1): row[fred_hero_{i}] h row[blue_rank] blue_rank row[red_rank] red_rank row[win] 0 # 占位build_features 不读这一列 df pd.DataFrame([row]) X, _ build_features(df, encoderself.encoder) prob self.model.predict_proba(X)[0, 1] return float(prob)注意build_features在推理时传入了训练好的encoder这样英雄编码的维度才能和训练时对齐。如果你忘了传编码器会重新 fit维度对不上模型直接报错。这个坑我在第一次接服务的时候踩过排查了半天才发现是编码器没复用。3.3 配置与依赖管理config.yaml里放路径和超参数好处是换数据集不用改代码data: raw_dir: data/raw processed_dir: data/processed model: n_estimators: 300 learning_rate: 0.05 max_depth: 6 test_size: 0.2 random_state: 42requirements.txt里核心依赖就四个pandas、scikit-learn、lightgbm、joblib。版本上建议锁一下LightGBM 不同版本对sparse_output参数的支持有差异不锁容易在别人机器上翻车。4. 避坑与排查跑不起来时先看这几条4.1 编码器维度不匹配现象推理时报ValueError: X has n features, but LGBMClassifier is expecting m features。原因推理时没有复用训练阶段的OneHotEncoder而是重新 fit 了一个导致英雄组合的编码维度变了。解决把 encoder 和 model 一起用joblib.dump存成一个字典推理时一起加载。上面WinPredictor的写法就是标准做法别偷懒只存模型。4.2 英雄 ID 类型不一致现象训练时英雄 ID 是整数推理时传进来的是字符串编码后特征全对不上。原因CSV 读取时 pandas 可能把英雄 ID 推断成 int而接口传参是 str。解决在build_features里统一astype(str)训练和推理走同一条路径。这个改动很小但能省掉大量“为什么线上线下结果不一样”的排查时间。4.3 数据泄漏导致 AUC 虚高现象验证集 AUC 冲到 0.95 以上上线后预测一塌糊涂。原因特征里混入了对局结束后的信息比如击杀数、经济差。这些字段在选人阶段根本拿不到。解决严格区分“赛前特征”和“赛后特征”build_features里只保留选人阶段能获取的字段。我一般会在特征表里加一列available_at标注每个特征在什么时间点可用训练前过滤一遍。4.4 样本不均衡被忽略现象模型永远预测蓝方赢因为蓝方胜率略高于 50%。原因没有做分层切分也没有在训练时调整类别权重。解决train_test_split加stratifyyLightGBM 里加class_weightbalanced。如果蓝红胜率差距很大还可以对少数类做上采样但要注意别在验证集上做。4.5 模型文件路径写死现象换台机器跑报FileNotFoundError: models/lgbm_model.pkl。原因代码里用了相对路径工作目录一变就找不到。解决用pathlib.Path(__file__).parent拼绝对路径或者统一从config.yaml读。这个属于工程习惯问题早改早省心。5. 二次开发换模型、加特征、接服务5.1 换模型从 LightGBM 切到 XGBoost 或神经网络项目现在的训练入口是写死的 LightGBM但结构上留了口子。你只需要在train.py里把模型实例换掉特征部分完全不用动from xgboost import XGBClassifier model XGBClassifier( n_estimators300, learning_rate0.05, max_depth6, subsample0.8, colsample_bytree0.8, use_label_encoderFalse, eval_metriclogloss, random_state42 )如果想上神经网络把build_features的输出接一个StandardScaler再搭个几层的 MLP 就行。但要注意树模型对 one-hot 稀疏特征天然友好神经网络反而需要更细致的归一化和嵌入层设计别指望换个模型就无脑涨点。5.2 加特征从“阵容胜率”到“阵容选手”胜率现在的特征只有英雄和段位信息量有限。一个自然的扩展是加入选手近期战绩def add_player_features(df, player_stats): 把选手近 20 局的 KDA、胜率拼进来 for side in [blue, red]: for i in range(1, 6): pid_col f{side}_player_{i} df df.merge( player_stats, left_onpid_col, right_onplayer_id, howleft, suffixes(, f_{side}_{i}) ) return df合并之后记得处理缺失值新选手没有历史数据是常态用段位均值填充比直接丢样本更合理。特征变多之后建议跑一次特征重要性排序把贡献低的字段砍掉不然训练时间涨了、效果没动纯属给自己找事。5.3 接服务用 FastAPI 包一层推理类直接调用太原始包一个 HTTP 接口才能被其他系统用from fastapi import FastAPI from pydantic import BaseModel from src.predict import WinPredictor app FastAPI() predictor WinPredictor() class MatchInput(BaseModel): blue_heroes: list red_heroes: list blue_rank: str Gold red_rank: str Gold app.post(/predict) def predict_win_rate(match: MatchInput): prob predictor.predict( match.blue_heroes, match.red_heroes, match.blue_rank, match.red_rank ) return {blue_win_rate: round(prob, 4)}启动命令是uvicorn main:app --host 0.0.0.0 --port 8000。注意WinPredictor要在模块加载时初始化一次别每次请求都重新加载模型那样延迟会高得没法用。5.4 验证改造效果别只看 AUC改完模型或特征后除了 AUC我还会看两个指标一是校准曲线看预测概率和实际胜率是否吻合二是分段的准确率比如预测胜率在 0.6 到 0.7 区间的对局实际胜率是不是也在这个范围。只看 AUC 容易忽略概率校准问题而胜率预测恰恰对校准很敏感。从那以后我每次改完特征或换模型都强制走一遍“AUC 校准 分段”三件套不再单看一个数字就下结论。希望这个项目的拆解能帮到你拿到源码后先跑通训练再按自己的数据改特征最后接上服务整条链路走一遍比读十篇教程都管用。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑