部署の方々に「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)) |
ここで使用する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でした。
0.5(完全ランダム)~1(完璧な予測)
の値となります。
※詳しくはこちらを参照↓
白黒はっきりしない判定の評価のしかた 〜ROC曲線と AUC〜
作成したモデルと配布された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↓ |
完璧に予測できたとは言い難いですが、
・実践的なデータ加工ができた
・機械学習の一通りの手順は試せた
ということで、初回にしては良い勉強になりました。
もう少し突き詰めるとしたら、
・さらなる特徴量の生成(ステージ・ブキの組み合わせ等)
・モデルの修正(パラメータのチューニングなど)
あたりに挑戦してみたいです。
また、今回は終了済みのコンペで腕試しをするという内容でしたが、
そのうち開催中のコンペにも参加してみたいです。


