世界中のデータサイエンティストが参加するデータコンペサイトKaggleでは、
日々様々な社会問題や企業課題に関連したテーマのデータ分析コンぺが開催されています。

その中でも、2019年1月31日まで開催されていた”PUBG Finish Placement Prediction”は、
かなり異色の、そして独特の熱狂で取り組まれたものであったと思われます。

このコンペは、大人気オンラインゲーム、PUBGの数万試合のプレイデータの解析コンペとなります。

PUBGとは、いわゆる、ここ数年人気のバトルロイヤル型のゲームとなりまして、
100人のプレイヤーが、フィールド内を移動しながら、武器や防具、回復アイテムなどを入手して、
敵をキルし、最後のひとりになった人が勝ち、というものとなります。
※PUBG以外では、Fortniteも有名ですし、最近ではApex legendsが話題です。

PUBG Mobile日本版TVCM

[バトルロイヤル型ゲームとは、広大なエリアに降り立ち、
アイテムを探しながら最後の1人になるまで、敵を倒すゲーム]

さて、このコンペは、kill数や移動距離、アイテム入手数など28パラメータを解析して、最終的な順位を予測するものとなります。
実際、賞金付きや、メダル授与コンペではない、playground扱いのものであったものの、
1,534チームもの、多数の参加となりました。
※2019年2月12日public leader board時点

本投稿では、私がKaggleのPUBGコンペに参加し、
順位予測精度が、1,534チーム中、338位(上位22%)となった、データ分析過程を振り返りたいと思います。

ちなみに、私は、PUBGはmobile版のみですが、業務とプライベートに
大きく支障がでる程度にはプレイしており、
ドン勝(ゲームクリア。最後の1人まで生き残り)もしております。

バトルロイヤル型以外では、スプラトゥーン2はプレイ時間1,500時間超え、
Youtubeでゲーム実況している程度には、ゲーマーです。

データサイエンティストである以前に、ゲーマーであることが、特徴量生成に大きく役立ったものの、
正直、PUBGをプレイする時間を減らせば、Kaggleの順位をもっとあげられたのでは、と思います。

[目次]
(1)与えられたプレイデータ概要。ゲーマー以外は大混乱。
(2)分析スケジュールの整理。PUBGプレイ時間をいかに減らすか。
(3)ドメイン知識からの独自特徴量生成と機械学習。自分がゲーマーであることに感謝。
(4)総括、ドン勝ならずも上位入り。

※PUBGユーザの方で、先に結論のみ知りたい方は、(3)がご参考になると思います。



| (1)与えられたプレイデータ概要。ゲーマー以外は大混乱。


下記がデータ概要となります。47,965試合分の440万IDのtrain dataのパラメータを解析し、
test dataの20,556試合分の各プライヤーの最終順位を予測する、というコンペとなります。

 レコード数データ中の試合数
train data4,446,966 47,965
test data1,934,174 20,556



与えられた28個のパラメータは下記となります。

winPlacePerc予測する目的変数。勝利順位
matchId試合ID
matchType試合タイプ(“solo”、”duo”、”squad”など)
numGroupsその試合におけるグループ数
maxPlaceその試合における最下位順位
matchDurationその試合が終わるまでの秒数
groupIdグループID
IdプレイヤーID
killsキル数
killStreaks連続キル数
killPointsキルベースのランキング
killPlaceその試合におけるキル数順位
longestKillキルしたプレイヤーとの最長距離
DBNOsノックアウトした敵の数
assistsキルアシスト数(自分がダメージを与えてチームメイトがキルした数)
headshotKillsヘッドショットでのキル数
damageDealt与えたダメージ数(自傷ダメージは含めず)
boostsブーストアイテム使用数
heals回復アイテム利用数
weaponsAcquired武器入手数
revivesノックアウトされたチームメイトを蘇生した数
walkDistance歩行移動距離
rideDistance乗り物に乗って移動した距離
roadKills乗り物からキルした数
swimDistance水泳距離
teamKillsチームメイトをキルした数
vehicleDestroys乗り物を破壊した数
rankPointsプライヤーランキング
winPoints勝利数ベースのランキング



目的変数である「winPlacePerc」(最終順位の%表示)含む、各変数とそれ以外の値との相関は下記となります。



最終順位に対して、単純に相関が高そうな「kills(キル数)」よりも、
「walkDistance(歩行距離)」、「boosts(ブーストアイテム使用数)」、「weaponAcquired(武器入手数)」の方が
相関が高いのは、ゲーマー心をくすぐりますね。

[序盤はとにかく、素早く動きながら、アイテム入手することが重要]

ちなみに、単純にロジスティック回帰を用いて、ドン勝予測(今回はあくまで全体の順位を予測するコンペですが)に
重要な変数を解析したところ、下記となりました。

[ロジスティック回帰によるドン勝予測に重要な上位特徴量]

 特徴量偏回帰係数オッズ比
上位1rankPoints 1.7026 5.4883
上位2winPoints 1.666 5.2911
上位3walkDistance 0.8638 2.3722
上位4kills 0.3583 1.4309
上位5rideDistance 0.2599 1.2968
上位6boosts 0.2419 1.2737
上位7assists 0.2061 1.2289
上位8headshotKills 0.0448 1.0459
上位9longestKill 0.0439 1.0448



こちらは、ランキング関連、ついでキル関連、や
移動関連が、予測に重要な変数として上位にきました。
ドン勝を狙うユーザは、上記の指標を高める行動を心がけると良いかもしれません。
※ただし、ランキングは、あくまで該当の試合までの累積の結果の指標となることと、
 すべてのユーザデータに付与されているわけではなく注意が必要です。
※また、上記はあくまでドン勝予想に重要な変数のため、
 2〜100位のユーザの違いに上記が重要かは、検証する必要があります。

さて、最初にデータ探索を行う際に、概要を眺めて、
欠損値や異常値をチェックすることはよくあることですが、今回、その過程で、いきなりドメイン知識を要求されます。
それは、オンラインゲームではあるあるなのですが、
「チーター(Cheater)」と「回線落ち(Zombi)」の存在です。

「チーター(Cheater)」とは、不正なコードを用いて、改造データでプレイするユーザのことでして、
たとえば、「一歩も動かずに敵をキル」するような改造や、
「異様な速さで移動する」、「攻撃の命中率が異様に高い」改造などが、あげられます。
実際、今回のデータでも、「攻撃のヘッドショット命中率(頭を狙うとダメージが大きい)が(常識的な上限である)80%よりも高い」ユーザデータが見られます。

一方で、「回線落ち(Zombi)」ですが、試合にマッチングしたものの、
プレイ開始後に、回線が途切れ、ゲーム内で全く動かなくなるユーザとなります。
※スプラトゥーン2などでは、回線落ちユーザはゲームから消失しますが、
 PUBGでは、通称ゾンビと呼ばれるように、回線落ちしても動かくなるだけで、残ります。
今回のデータの中でも、「一歩も動かず、キルもせず、武器も入手していない」ユーザデータが見られます。

チーター、回線落ち、それぞれ、今回のデータセット中の数、および、
ドン勝(順位1位。最終的な生き残り)率への影響は下記となりました。

タイプtrain_data内
該当数
test_data内
該当数
ドン勝率
全体 4,446,966 1,934,174 47.28%
チーター 1,630 332 49.68%
回線落ち 78,384 34,120 2.5%



チーターは規模が小さい上に、勝率がそこまで劇的によくもないという結果ですが、
回線落ちは規模が大きい上に、勝率に非常に大きく影響する結果となりました。
これらの存在をいかに扱うか(あるいは気にしないのか)が、ひとつの判断となります。
私は、最終的に、チーターの存在は無視し、
回線落ちについてはフラグを立てる、という処理をしました。

しかし、当然、上記のような存在は、ゲーマー以外は馴染みのない異常値となりまして、
実際、KaggleのDiscussionボードはじめ、ゲーマー・非ゲーマー間のデータサイエンティストのやりとりが散見されました。

[Kaggle内のデータサイエンティストたちのやりとり ※意訳]
非ゲーマー「たとえば非常に高い命中率のプレイヤーがいるのは、どのように考えたらよいのでしょう」
ゲーマー「それはね、チーターだよ」
非ゲーマー「一歩も動かず、キルもしていないユーザが途中まで生き残っているのはどのように考えたらよいのでしょう」
ゲーマー「それはね、回線落ちだよ」
非ゲーマー「Ummm….私は、非常に混乱してきました。

正直、少し同情しました。



| (2)分析スケジュールの整理。PUBGプレイ時間をいかに減らすか。


今回は年末年始を挟んでいることもあり、比較的時間がありましたので、
まずはPUBGをやり込むことにしました。結果、時間が切羽詰まってきました。

[惜しくも最後の一人を倒せず、2位となることもしばしば]

残り時間1ヶ月ほどを切ったところで、本格的に分析を始めました。
前回、Kaggleの別のコンペに参加したときは、新たな手法(CatBoostほか)を試すことを目的にしておりましたので、
今回は、特徴量生成を、まじめに取り組むことを目的にしました。

大雑把なスケジュールとしては、
・最初の1週間で、一通り、Kernel、Discussionをチェックし、前処理、後処理含む、全体の処理を作成
・次の3週間ほどで、特徴量をひたすら生成
・次の1週間ほどで、複数の機械学習手法を試しながら、パラメータチューニング
・最後の3日ほどで、アンサンブル

となりました。
機械学習手法は、DNNなども試したのですが、パラメータチューニングがうまくいかなかったため、
最後の最後まで粘ったのですが、途中で切り上げて、LightGBM一択にしました。(LightGBMのアンサンブル)
また、データ量が多いため、やたらに特徴量を生成すると、
オンラインKernel上での実行エラーとなることが多く、特徴量の取捨選択に思ったより時間をとられました。

Memory圧縮系の処理ふくむ前処理、各機械学習手法のチューニングについては、
ある程度ストックがあったため時間短縮にはなりましたが、
それでも、DNN系を中心にもっとストックを増やしておかねばと思いました。
真面目に順位を狙わないKaggleチャレンジについても、人通り手法などはチェックして
notebookに写経しておこうと思った次第です。



| (3)ドメイン知識からの独自特徴量生成と機械学習。自分がゲーマーであることに感謝。

さて、いよいよ本稿のメイントピックである特徴量生成について記載します。
すなわち、「何がドン勝(最終順位1位)のために重要なのか」の解明となります。
※実際には、ドン勝だけを予想するのではなく、試合ごとの全プレイヤーの順位を予測しなければならないのですが。

やみくもに、諸々の特徴量同士の組み合わせをするのではなく、
ドメイン知識から、下記のように大きな分類を整理していきました。

a) 方向性1:距離系。移動あたりの行動。総移動距離(時間)に対する行動割合。
b) 方向性2:時間系。時間あたりの行動。総試合時間に対する行動割合。
c) 方向性3:kill割合・kill頻度。1killに対して、何をしたか。
d) 方向性4:順位系。該当試合における全プレイヤーの中での
e) 方向性5:グループごとの最大、最小、平均
f) 方向性7:リスク系。(リスク回避行動、など。あるいは逆にリターンを狙うハイリスクな行動など)
g) 方向性8:仲間のサポート系。どれだけアシスト、カバーができていたか。
h) 方向性9:テクニック系。
i) 方向性10:プレイスピード、総アクティビティ系

※注意は、matchdurationは、すべての人でおなじ試合時間。
 当たり前ですが、ひとりひとりの試合(生存)時間は与えられていません。(それが分かれば順位を出せるので)

この中でも、特に最終順位予測に効果的だったのは、
f) 方向性7:リスク系
i) 方向性9:テクニック系

となります。

f) 方向性7:リスク系
これは、たとえばリスク回避の潜伏行動や、
ハイリスクの乗り物移動(敵に狙われやすく、かつ乗り物破壊されると即死)などが該当します。

特に、潜伏時間、は、上位プレイヤーと下位プレイヤーを分ける指標なのでは、と思い、
様々な角度から特徴量を生成しました。

[常に移動するのではなく、時には、うまく潜伏して
敵より早く位置確認してから対面することが重要]


その中で、最終的に採用した計算式は下記となります。



all_data[‘_hideDuration’] =
    all_data[‘matchDuration’] – (round(all_data[‘walkDistance’] / 6) + round(all_data[‘rideDistance’] / 277) + all_data[‘swimDistance’] / 1)

各歩行、乗り物、水泳、移動距離を、平均的な移動速度で割って、移動時間を算出し、
それらの合計を試合時間全体から引くことで、逆に「移動していない時間」を割り出しております。
※6m/sは歩行の平均移動速度、277m/sは乗り物の平均移動速度、1m/sは水泳の平均移動速度(ファンサイトその他からの概算です。)
※もちろん、前述の通り、試合時間、は、あくまで、プレイヤーごとに共通の試合時間となります。
 そのため、本来は、プレイヤーごとの生存時間、から総移動時間を引きたかったのですが、妥協案として上記の式としております。

i) 方向性9:テクニック系
これは、たとえば、killをただとるだけではなく、
ヘッドショットで、最大ダメージを与えることができたか、
あるいは、連killをとれたか、などの指標となります。
もちろん、kill合計は重要な指標でありながら、
できるだけスマートなkillをとれるユーザの方が、上位プレイヤーである可能性は高まります。

最終的に、下記の特徴量を採用しました。

all_data[‘_headshotKillRate’] = all_data[‘headshotKills’] / all_data[‘kills’]
all_data[‘_killStreakrate’] = all_data[‘killStreaks’]/all_data[‘kills’]

その他、scikit learnのPolynomialFeaturesを用いて、
変数の掛け合わせを自動生成することもしました。
※クロスフィーチャーの自動生成コード例はこちら

前述の通り、特徴量を増やすだけではなく、減らすことも重要であったため、
下記のように、管理票を作成して、ひとつづつ検証していきました。
※絶対読めないと思いますので、なんとなくの雰囲気を感じていただければ。クリックすると拡大されます。
※縦軸:アクション、横軸:特徴量。右端に、特徴量数と、solo、duo、squadごとの精度
※特徴量の重要さは他の指標と、意味合いとしての重複がなく、新しい軸であるかなどでもあり、
 importanceは、あまりあてにならなかったので。


機械学習は、Xgboost、lightGBM、CatBoost、リッジ回帰、DNNを試したのですが、
単体でlightGBMが一番精度が高く、かつ、計算時間も早かったため、
後半は、lightGBMのみでひたすらパラメータチューニングしていました。

最終的に、精度の高い、かつ、予測の異なる、3つのパラメータの組み合わせを算出し、
3つのlightGBMをアンサンブルしました。



| (4)総括、ドン勝ならずも上位入り。


今回のコンペは、ITmediaにスプラトゥーン2のデータ解析記事を寄稿した直後だったこともあり、
できるだけ、ゲーマーとしての実感を活かせるよう、
(そして、分析結果をプレイにフィードバックできるよう)
特徴量の生成に力を入れました。

しかし、最終的には、上位手法などを見ておりますと、
自分が良い精度を出せなかったDNNなどによるものが見られまして、
さらなる上位進出には、機械学習自体のスキルも高めていかねばと思った次第です。

次回のKaggleこそ、
「勝った!勝った!夕飯はドン勝だ!!」