今天和大家分享的是前不久老肥参加的腾讯游戏安全技术竞赛,这也是我第一次参加这样刺激的比赛。怎么个刺激法呢,总的来说就是一发入魂(也可称作摸奖),每个人只有一次提交机会,并且不论是初赛还是决赛都无法查看自己的线上得分,并且每轮比赛都是限时两天半。
本次竞赛机器学习赛道是FPS朝向异常检测,第一人称射击游戏(First Person Shooting,简称FPS游戏)是最为经典的游戏类型之一,也是当下玩家最多,最受欢迎的游戏类型之一。由于射击游戏很多关键逻辑计算放在了客户端,导致普遍安全性不是很高,朝向异常就是其中比较典型外挂功能。朝向异常可帮助作弊玩家实现自动瞄准甚至自动开枪击杀,极大影响游戏的公平竞技性。
某FPS游戏截图
赛题给出了某款FPS游戏的2020.05.09当天全量玩家的瞄准击杀行为数据,以及当天游戏的白用户名单(玩家历史上清白没有处罚记录也没有可疑行为,大概率不作弊)和当天有朝向异常外挂的用户名单(有检测朝向异常外挂进程,大概率作弊),同时提供2020.5.12~2020.5.14部分未标注的2000个账号的瞄准击杀行为数据作为测试集数据,需要我们根据历史数据表现来预测测试集中的账号是否存在朝向异常行为。
这些描述一下就勾勒出了本赛题的两大难点:
标签不纯
的问题,简单来说就是黑样本未必开挂,白样本也未必不开挂。在有限机器的条件下,我采用了分块的方法将数据读入内存,在读入的时候修改数据类型以减少内存消耗,同时只选取部分特征列读入(即将不同数据列分开处理)。
dtype={
1:"category",
2:"float32",
4:"uint16",
5:"int32",
6:"int32",
7:"uint32"
}
def get_df(f):
files = []
for chunk in tqdm(pd.read_csv(f, sep='\|', header=None, usecols=[1, 2, 4, 5, 6, 7], chunksize=1000000, dtype=dtype)):
files.append(chunk)
return files
chunk = get_df('cp_rawdata_0509.txt')
df = pd.concat(chunk,axis=0)
本方案特征的思路非常简单直接,以每一次击杀作为样本,对每个样本数据特征(包括deltaX、deltaY、yaw、pitch等)做统计,diff后进行统计,包括max、min、mean、std等等,同时采用Word2Vec
的思想对button序列以及type序列进行建模来刻画用户完成击杀时的情形,最后还使用rolling
的方法对type标识前后的数据做滑窗统计,同样是受限于机器,没有时间做更多更加全面的特征工程。
因为存在标签不纯的困难问题,我采用两阶段的训练模式,第一阶段选取所有出现在黑白名单中的用户, 每个uin使用所有的击杀数据为基础建模(每600条为一个样本),此阶段使用auc作为评价指标,旨在尽可能地划分黑白样本;第二阶段选取每个黑名单用户的击杀样本oof概率最大的一次击杀样本以及每个白名单用户的概率最小的一次击杀样本形成每个用户作为一个样本的训练数据,希望模型能够准确地区分黑白样本,此阶段使用binary error作为评价指标,旨在得到更高的f1分数。
最后,我还使用了所给的黑白名单做了规则处理,即白名单预测修改为0,黑名单预测修改为1,当然这部分也没法保证规则是百分百正确,本次比赛的所有代码已上传,在后台回复「游戏安全」即可。
从此次比赛以及最近的一些比赛(芒果、微信等等)来看,要想取得良好成绩,kaggle和colab的免费GPU已无法满足竞赛人日益增长的硬件需求,一台或者多台内存充裕、算力出众的机器应是不可或缺的。
期待明年获得更好的名次!