この記事は『CRESCO Advent Calender 2022』17日目の記事になります。

 

はじめまして。データテクノロジーセンターのおばんです。

部署の方々に「Pythonでデータ加工できるようになっとけ!」と言われる日々を過ごしています。

というわけで、データ加工の修行がてらデータ分析のコンペにチャレンジすることに……

勉強は身近で楽しい方が面白いですよね。
そんなわけで、某人気色塗り対戦ゲームの試合データを用いて、勝敗予測をするコンペに参加してみました
(※すでに終了済みのコンペです)。

実践的にデータ分析をすることが初めてだったので、
「pandas中心に、データの加工方法を手を動かしながら学んでいく」
これを意識して取り組みました。

対象読者

データ分析初学者
基本的なデータ加工方法に興味がある人
データ分析コンペに興味がある人

コンペ概要

データ分析のコンペにはKaggle等様々なプラットフォームがありますが、
今回はProbspaceで開催されていた対戦ゲームデータ分析甲子園というコンペに参加しました。
(※現在は終了済み)

コンペの内容をざっくりと説明すると、某有名色塗り対戦ゲームの勝敗を対戦開始時点で予測する、という課題になります。

以下のような各試合の特徴量が与えられ、これを用いてAチーム/Bチームのどちらが勝つかを予測します。いわゆる二値分類ですね。

period 試合開始時の時刻
lobby-mode レギュラーマッチ/ガチマッチ ※1
mode 試合ルール※2
stage ステージ
A1-weapon プレイヤーA1のブキ
A1-rank プレイヤーA1のウデマエ※3
A1-level プレイヤーA1のランク※3

※1 ガチマッチがいわゆるレート戦に値します。
※2 ガチアサリ、ガチエリア、ガチホコ、ガチヤグラ、ナワバリの5種類
※3 ウデマエがレート、ランクが経験値を指します。

trainデータ(学習用データ)には試合結果を表すデータyも格納されています。
1であればAチームの勝利、0であればBチームの勝利を示します。

実際にやってみる

前準備

インポート

import numpy as np
import pandas as pd
import lightgbm as lgb
import csv
#訓練データ・テストデータ分割
from sklearn.model_selection import train_test_split
#正規表現
import re

データの読み込み

#訓練データ・テストデータの読み込み
train = pd.read_csv('../**/train_data.csv')
test = pd.read_csv('../**/test_data.csv')

データの前処理・加工

外部データの追加

ブキのデータを取り込みます。ここでは、ブキのカテゴリ情報のみ取り込んでいます。

weapon_data = pd.read_csv('../**/weapon_data.csv')
with open('../**/weapon_data.csv', newline='',encoding="utf-8") as csvfile:
reader = csv.reader(csvfile)
weapon_category_dict = {rows[2]:rows[1] for rows in reader}
#ブキをブキ種で分け、置換
train = train.replace(weapon_category_dict)
test = test.replace(weapon_category_dict)

不要な特徴量を落とす

train = train.drop(['period','game-ver','id'], axis = 1)
test = test.drop(['period','game-ver','id'], axis = 1)

ダミー変数化

データの中身に文字列が含まれていると分析ができないため、すべて数値に置き換えます。

#ダミー変数に変換
train = pd.get_dummies(train)
test = pd.get_dummies(test)
#不正文字列を正規表現を利用して削除
train = train.rename(columns = lambda x:re.sub('[^A-Za-z0-9_\+\-]+', '', x))
test = test.rename(columns = lambda x:re.sub('[^A-Za-z0-9_\+\-]+', '', x))

機械学習

データ分割、LightGBM用データ作成

ここで使用するtestデータは以下で作成するモデルの検証用データになります。
そのためコンペサイトからDLしたtestデータではなく、trainデータの一部をランダムに取り分けて検証用データとします(このデータは学習には使われません)。

#trainデータを分割
train_set, test_set = train_test_split(train, test_size = 0.2, random_state = 123)
x_train = train_set.drop('y', axis = 1)
y_train = train_set['y']
x_test = test_set.drop('y', axis = 1)
y_test = test_set['y']
#LightGBM用にデータセットを作成
lgb_train = lgb.Dataset(x_train, y_train)
lgb_test = lgb.Dataset(x_test, y_test)

最初、誤って学習データ全体でモデル構築+答え合わせをしてしまいました……
テストの本番問題を使ってテスト勉強するようなものです。

パラメータの設定

params = {
'boosting_type':'gbdt',#ブースティングの種類。ここでは勾配ブースティング
'objective':'binary',#機械学習タスクの種類。ここでは二値分類
'metric':'auc',#モデルの評価指標
'num_leaves':16,#決定木の葉(ノード)の数。大きいほど複雑になる
'learning_rate':0.1,#学習率
'n_estimators':100000,#作成する木の数
'random_state':0 #乱数のシード値
}

モデル作成

先ほど作成したデータセットやパラメータをもとにモデルを作成します。

#モデル作成
bst = lgb.train(params = params,
train_set = lgb_train,
valid_sets = lgb_test,
num_boost_round = 100, #ブースティングの回数
early_stopping_rounds = 100)# 100回ごとに検証精度の改善を検討→精度が改善しないなら学習を終了(過学習に陥るのを防ぐ)

モデルの評価

#あらかじめ切り分けておいた検証用データを予測
test_predicted = bst.predict(x_test)

予測を実行すると、以下のようなログが表示されます。

[LightGBM] [Info] Start training from score 0.098605
[1] valid_0's auc: 0.528582
Training until validation scores don't improve for 100 rounds
[2] valid_0's auc: 0.546332
[3] valid_0's auc: 0.548256
[4] valid_0's auc: 0.548496
[5] valid_0's auc: 0.548267
[6] valid_0's auc: 0.549871
[7] valid_0's auc: 0.550842
...
[207] valid_0's auc: 0.566587
[208] valid_0's auc: 0.566348
Early stopping, best iteration is:
[108] valid_0's auc: 0.569808

 ログで確認すると、このモデルのAUCは0.569808でした。

AUCはモデルの評価指標の一つで、
 0.5(完全ランダム)~1(完璧な予測)
の値となります。

提出

提出用ファイル作成

作成したモデルと配布されたtestデータを利用して、提出用のデータを作成します。
今回はしきい値を0.5に設定し、予測結果を
1 or 0に置換します。

#配布されたtestデータを予測
y_pred = models[0].predict(test,num_iteration=bst.best_iteration)
#四捨五入
y_pred = np.where(y_pred < 0.5, 0, 1)
#提出用ファイルの作成
pred_df =pd.DataFrame({"id":test.index,"y":y_pred})
pred_df.to_csv("submission.csv",index=False)

結果の確認

コンペの提出ページからファイルを提出すると、結果が表示されます。

PublicスコアとPrivateスコアの違いをざっくり説明すると、予測している対象の違いです。どちらもtestデータから得られた予測の結果ではあるのですが、privateとpublicでは抜粋箇所が異なります。このコンペは終了済みなのでどちらも確認可能なのですが、開催中はpublicしか見られません。最終的な結果としてはPrivateスコアが採択されます。

ということで、今回の結果は0.542884でした!
うーん、ほぼ五分五分ですね。

特徴量を変更して比較してみた

五分五分はちょっと悔しいので、特徴量を変化させて再度予測を行ってみました(結果はどちらもPrivateスコアになります)。

ウデマエ(rank)をダミー変数に置換 0.534651↓
ブキを種類でカテゴライズしない 0.533663↓
ランク(level)を特徴量から落とす 0.534415↓
サブウェポン・スペシャルのデータを特徴量として追加 0.540626↓

感想

完璧に予測できたとは言い難いですが、
・実践的なデータ加工ができた
・機械学習の一通りの手順は試せた
ということで、初回にしては良い勉強になりました。

もう少し突き詰めるとしたら、
・さらなる特徴量の生成(ステージ・ブキの組み合わせ等)
・モデルの修正(パラメータのチューニングなど)
あたりに挑戦してみたいです。

また、今回は終了済みのコンペで腕試しをするという内容でしたが、
そのうち開催中のコンペにも参加してみたいです。