news 2026/9/30 12:33:51

飞桨MLP实战:英雄联盟段位预测中的特征工程与多分类调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
飞桨MLP实战:英雄联盟段位预测中的特征工程与多分类调优

1. 为什么选这道题:赛事背景与赛题拆解

飞桨学习赛是百度AI Studio平台上很经典的一类入门实战赛事,我这次报的是“英雄联盟大师预测”。说白了,赛题给了一批英雄联盟玩家的对局统计特征,要求参赛者构建模型,预测玩家最可能达到的段位。段位标签里包含大师这个高段位,所以赛题叫“英雄联盟大师预测”。

我选这道题,主要基于三个判断:第一,这是一道典型的表格数据分类问题,特征全部是数值型统计量,和很多工业场景里的用户画像、风险评分任务同构,做一遍能沉淀一套可复用的方法论;第二,数据集规模不大,不像CV、NLP赛道那样动辄几十个G的数据和昂贵的训练资源,普通笔记本的CPU也能跑通全流程;第三,段位预测天然带多分类、样本不均衡、特征相关性高等特点,很适合用来练手深度学习框架下的调优基本功。

先说赛题的具体目标。训练集里每行是一个玩家的行为快照,列包含类似击杀数、死亡数、助攻数、总场次、胜场、常用位置、每局平均经济等在内的统计指标。标签是玩家的当前段位,从黑铁到王者共9个级别。这里的难点在于:特征本身是高度冗余的(比如击杀数和场均击杀存在强线性关系),段位分布又严重不平衡,王者、大师这种头部段位样本占比可能不到1%。如果直接用朴素的多分类交叉熵训练,模型很容易把所有样本都预测成占比最高的“黄金”“铂金”,得到一个看似很高、实则无意义的准确率。

一句话概括我在这道题里的解题主线:先用EDA把数据分布和特征关系摸清楚,再围绕游戏机制设计出有区分度的人工特征,最后落到飞桨框架里用MLP类模型做多分类,并在验证集上反复验证每一处改动是否真的有效。下面我把每个环节的细节和踩过的坑展开讲。

2. 拿到数据第一件事:数据初探与预处理细节

2.1 先别急着训练,把数据读进来看5分钟

很多新手拿到CSV后第一反应是直接丢给模型训练,这是最容易走弯路的地方。我习惯先做一轮快速探查,主要看三样东西:字段类型是否合理、缺失值多不多、标签分布是否极度倾斜。

import pandas as pd train_df = pd.read_csv("train.csv") test_df = pd.read_csv("test.csv") print(train_df.shape, test_df.shape) print(train_df.dtypes.value_counts()) print(train_df.isnull().sum().sort_values(ascending=False).head(10)) print(train_df["rank"].value_counts(normalize=True))

我这次跑出来的结果有几个值得注意的点:

  • 特征列有四十多个,绝大多数是float64,部分是int64,没有object型的离散文本列,但有一个“most_played_position”字段是典型的低频类别特征,后面需要单独处理;
  • 缺失值整体不多,集中在三四个特征里,缺失比例在1%到5%之间,处理策略不需要太复杂;
  • 标签分布确认是长尾形态,“黄金”“铂金”两个段位占了将近一半样本,“大师”“王者”合计不足2%。

2.2 缺失值处理:均值填充未必是最优解

缺失值处理看似简单,实则会直接影响最终效果。我的原则是:数值型连续特征用中位数填充,而不是均值。原因是这些统计量大多呈右偏分布,个别“大号”玩家的击杀数可能远超中位数,均值会被极端值拉高,用均值填充相当于给缺失样本注入了偏高的信号。

num_cols = train_df.select_dtypes(include=["float64", "int64"]).columns for col in num_cols: med = train_df[col].median() train_df[col] = train_df[col].fillna(med) test_df[col] = test_df[col].fillna(med)

对于“most_played_position”这种低频类别特征,我采用众数填充,然后做序数编码或独热编码。不要小看这个字段,在英雄联盟里,打野和中单的游走节奏、参团率差异非常大,后续特征工程里它经常和KDA、击杀参与率产生有意义的交互。

2.3 标签编码的正确姿势

段位是有序的,0到8分别对应黑铁到王者。这里有两种编码思路:一是当作无序类别做普通Softmax多分类,二是利用段位的天然顺序做回归或有序分类。我实测下来,直接回归段位等级的效果并不理想,因为段位之间的“距离”并不均匀——钻石到大师的跨度远大于白银到黄金的跨度。所以最终方案还是多分类,但我会在损失函数上做文章,这部分放到模型章节细说。

提示:标签编码时一定要保证训练集和测试集使用同一套映射表,不要在测试集上重新fit,否则极容易在提交阶段踩到“类别对不上”的坑。

3. 特征工程:从通用统计量到带机制的特征设计

3.1 原始特征里的信息密度其实很低

赛题给的特征虽然数量不少,但信息密度并不高。比如“总场次”“胜场”和“胜率”三个特征,本质上就是同一个信息的三种表达;又比如“总击杀”“总死亡”“总助攻”和“总场次”组合起来,才能算出真正有意义的KDA。直接把这些原始列喂给模型,模型要花很多层才能隐式学到这些比值关系,不如我们直接替它算好。

我第一版特征工程的主线是“机制驱动”,也就是围绕英雄联盟的游戏逻辑构造特征,而不是盲目堆砌。英雄联盟胜负的核心变量有三类:个人操作(击杀、死亡、助攻)、资源获取能力(补刀、经济、等级)、团队协作能力(参团率、视野得分)。所以我的特征设计也按这三类展开。

3.2 亲手算出来的核心特征组

第一组是个体效率特征,包括KDA、(击杀+助攻)/死亡、(击杀+助攻)/场次等。这里有个小技巧:KDA公式里的死亡数要加一个极小值平滑项,防止出现除以0的inf。

eps = 1e-5 train_df["kda"] = (train_df["kills"] + train_df["assists"]) / (train_df["deaths"] + eps) train_df["kill_participation"] = (train_df["kills"] + train_df["assists"]) / (train_df["team_total_kills"] + eps)

第二组是位置与风格特征,做法是把“最常玩位置”进行独热编码后,再与KDA、场均经济等指标做交叉。比如中单玩家的KDA与段位的相关性,明显强于辅助玩家的KDA与段位的相关性,如果不做交叉,模型很难捕捉这种条件关系。

第三组是稳定性和成长性特征,比如场次等级变动率、胜率与KDA的比值。用胜率除以KDA可以刻画“混子型玩家”——胜率高但KDA低的玩家,说明他更偏团队型打法;反过来KDA高但胜率低,说明是“独狼型”玩家。这类特征对段位预测的区分度非常可观。

3.3 特征筛选:不是越多越好

特征工程做完后,维度大概到了一百多个。我不建议直接把所有特征丢进模型,因为部分交叉特征之间相关性极高,既拖慢训练速度,也增加过拟合风险。我习惯用两步筛选:第一步看相关系数矩阵,把相关系数超过0.95的特征合并或剔除;第二步用LightGBM的feature_importance做一轮快速初筛,保留重要性排序前80%的特征,再落到飞桨模型里训练。

这样做的逻辑很简单:不同模型对特征的敏感度不同,树模型擅长处理非线性且对特征尺度不敏感,而深度学习模型对方差大的特征更敏感。先用树模型筛一轮,再用网络训练,可以在减少维度的同时保留绝大多数有效信号。

4. 飞桨模型搭建:从基准MLP到调优路径

4.1 飞桨的建模方式和我为什么选MLP

飞桨(PaddlePaddle)在AI Studio上支持动态图模式,API设计和PyTorch非常接近,上手成本很低。这道题的特征全部是结构化表格数据,没有空间结构也没有时序依赖,所以不需要上CNN、RNN、Transformer这类重型结构,一个精心调过的多层感知机就能达到相当好的效果。

我第一版基准模型用的是三层MLP:输入层维度接特征数量,中间两层每层256个神经元,激活函数用ReLU,每个全连接层后接Dropout,输出层用Softmax输出9个段位的概率。评估指标以准确率为主,同时参考Macro F1,避免被头部段位的大样本主导。

import paddle import paddle.nn as nn class RankModel(nn.Layer): def __init__(self, feat_dim, num_classes=9): super().__init__() self.fc1 = nn.Linear(feat_dim, 256) self.fc2 = nn.Linear(256, 256) self.fc3 = nn.Linear(256, 64) self.out = nn.Linear(64, num_classes) self.drop = nn.Dropout(0.3) self.relu = nn.ReLU() def forward(self, x): x = self.relu(self.fc1(x)) x = self.drop(x) x = self.relu(self.fc2(x)) x = self.drop(x) x = self.relu(self.fc3(x)) return self.out(x)

4.2 损失函数的针对性设计:加权交叉熵

前面提到,段位分布严重不均衡,直接用nn.CrossEntropyLoss训练,模型会偏向预测大类别。针对这个赛题,我用了带类别权重的交叉熵损失。权重怎么算?采用“1 / 类别频率开根号”的方式做平滑,避免权重被头部段位压得过小。

import numpy as np label_counts = train_df["rank"].value_counts().sort_index().values weights = 1.0 / np.sqrt(label_counts) weights = weights / weights.sum() * len(weights) weight_tensor = paddle.to_tensor(weights, dtype="float32") criterion = nn.CrossEntropyLoss(weight=weight_tensor)

这里有个关键点:类别权重的调整要适度。权重如果拉得过大,模型会把预测结果推往小众段位,整体准确率下降,Macro F1却可能提升。比赛评价指标如果只看准确率,权重就不宜过猛;如果同时关注F1,则需要做一个折中。我在实际调参中是用验证集同时看两个指标,找到一个平衡点。

4.3 训练配置与监控方法

优化器我选Adam,初始学习率设3e-4,配合CosineAnnealingDecay做学习率衰减。批次大小64,训练轮数50轮。每轮结束后用验证集评估一次准确率,并保存验证集指标最优的那版模型,而不是最后一轮模型——深度学习训练后期容易过拟合,最后一轮往往不是泛化最好的点。

scheduler = paddle.optimizer.lr.CosineAnnealingDecay( learning_rate=3e-4, T_max=50, verbose=True) optimizer = paddle.optimizer.Adam( parameters=model.parameters(), learning_rate=scheduler)

训练日志里我除了看loss,还会打印准确率、Macro F1、以及每个段位的召回率。只看总loss容易产生“明明在下降但效果没变化”的错觉,细分到每个类别的召回率能帮助定位模型是否放弃了某些段位。

5. 踩坑实录:训练阶段最耗时间的三个问题

5.1 特征拼接没对齐,训练集和测试集维度不一致

这是我这次比赛踩的第一个坑。计数类特征在构造时,如果训练集和测试集分开处理,某些特征的值域范围不同,导致fillna时填充值不一致,甚至拼接后列数都对不上。最后训练时报了个“shape mismatch”的错,定位半天才发现是预处理阶段的问题。

后来我把特征工程封装成了一个函数,先对训练集和测试集做concat处理,整体算填充值、整体做编码,再按原索引切分回两部分。这个习惯现在已经成为我所有表格赛事的标准流程。特征工程必须保证训练和推理走同一套逻辑,否则线上分数和本地验证结果完全是两回事。

5.2 Dropout过大导致验证集指标震荡

第二版模型为了压制过拟合,我把Dropout提到了0.5,结果训练loss下降变慢,验证集指标出现明显的震荡。原因是表格数据本身不是超大容量模型,Dropout过大相当于每轮都在训练一个残缺的子网络,在样本量不够大的时候,反而损害了模型的稳定性。

我把Dropout从0.5调回0.3,同时把隐藏层从256缩减到128,验证集指标立刻稳定下来。这里想说明一个原则:正则化手段不是加得越多越好,而是要在“模型容量”和“数据量”之间找平衡。飞桨里还有其它正则化工具,比如Weight Decay和Label Smoothing,我最后只用了轻量Weight Decay,没有再叠加Label Smoothing,避免改动过多导致无法判断哪项改动起效。

5.3 验证集划分不当导致复现结果虚高

另一个隐蔽的坑是验证集划分。赛题数据按玩家分组,特征里没有显式的时间戳,但不同行之间可能存在同一个玩家的重复采样。如果随机切分,同一个玩家的相似对局会同时出现在训练集和验证集里,造成验证集分数虚高。

我处理的办法是按玩家ID分组划分。如果赛题没有提供玩家ID,就退而求其次用特征聚类先找出可能重复的样本,把同一类的样本放进同一个折里,再分别划分训练和验证。这一步做完后,验证集准确率比随机划分低了大概两个百分点,但线下分数和线上提交结果的一致性明显提高。

提示:在任何表格类比赛中,划分验证集前先确认样本之间是否存在分组相关,是决定你调参方向是否可信的关键一环。宁可线下分低一点,也要保证验证集能真实反映线上情况。

6. 复现要点与我的赛后复盘

6.1 完整流程串起来是什么样

最后把整个流程按可复现的方式整理一遍。第一步,读入数据,检查字段类型、缺失值和标签分布;第二步,缺失值用中位数/众数填充,类别特征做序数编码;第三步,围绕游戏机制构造三组特征:个体效率、位置风格、稳定性成长性,再做相关性筛选和树模型初筛;第四步,按玩家维度做分组划分,拆出验证集;第五步,搭建飞桨MLP模型,使用加权交叉熵损失,Adam优化器加余弦退火,训练50轮并保存验证集最优模型;第六步,在测试集上推理,按官方格式输出提交文件。

这套流程跑下来,我的最终成绩大约在0.912的准确率,Macro F1在0.74左右。作为对照,完全不做特征工程、直接用原始特征训同样结构的模型,准确率只有0.87左右,说明人工特征在表格竞赛里的增益依旧非常可观。后续再提升的空间主要在两个方向:一是用LightGBM和MLP做模型加权融合,树模型和神经网络在表格数据上的错误模式差异较大,融合后通常还能再涨一到两个点;二是针对小众段位做更精细的上采样或数据增强,比如用SMOTE合成了一些大师和王者样本,但效果提升有限,需要谨慎控制比例避免引入噪声。

6.2 我在这道题上悟到的几点经验

第一,表格类任务里“特征机制”比“模型结构”重要得多。我试过把MLP换成更深的残差网络,准确率几乎没有变化,但特征工程的两个小改动就可能带来两三个点的提升。原因在于,神经网络在表格数据里很难凭空学到特征之间的显式比值关系,把这些关系直接喂给模型,相当于替模型降低了拟合难度。

第二,样本不均衡问题的处理要跟随评价指标。如果评价指标是准确率,模型偏向多数类其实是合理的;如果评价指标是Macro F1,就必须通过类别权重或重采样来平衡。赛前先搞清楚评价指标,能省掉大量无用功。

第三,飞桨的动态图模式对调试非常友好,所有的中间张量都可以直接打印和观察。我在调损失函数时就用这个特性打印过每一批样本的预测概率分布,直观看到了模型初期把概率全部堆在“黄金”段位上的现象,然后才针对性引入类别权重。

最后再分享一个小技巧:每次改动特征或者模型结构后,固定随机种子,在同一个验证集上跑至少三遍取平均值,避免因为随机初始化导致的波动误判改动效果。这个习惯能帮你节省大量无效调参时间,尤其是在数据量不算大的学习赛里,单次验证结果的可信度真的很有限。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 12:33:07

hindsight架构实战:为LLM Agent构建主动防御的记忆系统

1. 从“hindsight”说起:为什么我们需要给Agent装上“后视镜”第一次看到“hindsight”这个词,我脑子里蹦出来的不是词典释义,而是每次调完一个Agent项目之后复盘时的那种感觉——当时要是早点知道某个工具调用会超时、某个上下文会被截断、某…

作者头像 李华
网站建设 2026/9/30 12:32:25

YOLOv8交通定制版:车辆检测、轨迹跟踪与违章识别实战

简介:本资源是一份面向计算机视觉工程师、智能交通系统开发者及深度学习初学者的实战型技术文档,聚焦YOLOv11在车辆轨迹跟踪与交通违章识别两大核心任务中的端到端落地实践。文档共48页PDF,结构完整、支持目录跳转与左侧大纲导航,…

作者头像 李华
网站建设 2026/9/30 12:30:21

利益链评估:从干系人分析到风险传导的工程化解决方案

1. 先说清楚:利益链评估到底在解决什么问题 做了十来年信息系统方案设计,我越发有一个感受:很多项目技术上没输过,落地却总栽在"人"和"利益"上。你方案画得再漂亮,算法推得再严谨,只要…

作者头像 李华
网站建设 2026/9/30 12:28:27

大数据在酒店行业的四类应用方向

随着数据量指数级增长与云计算普及,大数据正逐步渗透酒店行业,其核心价值在于挖掘数据中蕴藏的情报,而非简单的数据计算。有机构从四个方面总结了大数据在酒店行业的应用方向。 一是精确市场定位。 传统市场调研依赖统计年鉴、行业报告等&…

作者头像 李华
网站建设 2026/9/30 12:24:39

FreeRTOS每日健康清单:7项指标监控嵌入式系统亚健康

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 12:23:54

ECharts 3D 饼图实战:echarts-gl 参数曲面与伪3D方案

做数据大屏的人大概都遇到过这种需求:设计稿上明明白白画着一个带厚度、带透视的 3D 饼图,你打开 ECharts 官方文档, series.type 翻到 pie ,配置项从头看到尾, roseType 、 radius 、 itemStyle ……就是找…

作者头像 李华