Signate MUFGコン参加記(10位)



初めまして、misaizuです。
SignateのMUFGコンに出場したら10位を取ってしまったので、解法共有がてら参加記でも書いていこうと思います。Signateのコンペに出るのは今回が初です。
解説をはさみながら紹介していくので、解法だけ知りたいという方はちょっと面倒かもしれませんが、ご了承ください。

# 経緯
最近はKaggleだと画像・音声・自然言語系ばっかりで、テーブルデータを扱う系のコンペがなかったので様子を見てたんですが、学生限定・テーブルデータ系のコンペが開催されてるのを見て参入することに。なんと初回提出で7位に入ってしまったので本格的に参入することにしました。

ちなみに参加者はインターンシップで優遇特典とか、1~5位入賞者にはさらに優遇特典とかあるらしいですが、26卒の僕には関係ない話でした(血涙)

# コンペ概要

決済情報のcsvファイル`train.csv`が与えられるので、そこからクレジットカードの不正利用を予測するコンペです。いわゆる二値分類タスクというやつです。`train.csv`には決済額や決済された場所(オンライン含む)、業種コードなど様々な項目があります。欠損値がかなり少なく、データの質がかなりいいです(まあ決済情報なので当たり前っちゃ当たり前ですが)。決済情報のほかに、ユーザーに関する情報(年齢・性別・年収など)`user.csv`と、ユーザーが使用したクレジットカード自体の情報`card.csv`も与えられます。

今回のコンペは学生限定ということで、つよつよ社会人がいない分それなりに難易度は下がってる気がします(それでも上位は魔境でしたが...)。

# やったこと
機械学習コンペとしては人生で2回目なのでセオリーとか全然知らないんですが、とりあえず知らないなりに工夫してみた点を書いてみます。なお、**時系列順に並んでるわけではない**のでご注意ください。

各工夫点の採用の有無・評価は以下の通りです。

- ★★★ - 採用、影響が多大
- ★★☆ - 採用、影響が微妙・不明
- ★☆☆ - 不採用、影響が微妙
- ☆☆☆ - 不採用、デメリットしかない

## 特徴量エンジニアリング関連
- #### ユーザーごとに分けて学習(★★★)
恐らくこれが一番大きいです。決済情報のデータは様々なユーザーの情報がバラバラになっており、ユーザーごとに購入品の傾向は微妙に異なるだろうということで一番最初に試したものですが、初回提出で当時7位に食い込みました。LBスコアは0.642でした。

- #### 特徴が近いユーザーを階層的クラスタリングでまとめて、クラスターごとに学習(☆☆☆)
これは「ユーザーごとに分ける」やり方の欠点であるデータ量の少なさを補えるのでアリかなーと思ってたんですが、0.640→0.591と大幅な減少となりました。もっとうまいクラスタリングの手法があるのかもしれませんが、このやり方が「ユーザーごとに分ける」やり方より高いスコアを出すことはなかったです。

- #### One-Hot Encoding(★★★)
例えば色というカテゴリ変数が`赤, 青, 黄`の3値をとるときに、これを`赤であるかどうか`, `青であるかどうか`というダミー変数で置き換える手法です。ここで、`黄であるかどうか`は前者二つが`0`であることで表現できるため、一つ数を落とすのが一般的ですが、実は今回落とすのを完全に忘れてました。

`use_chip(決済方法)`という項目のとる値が`Swipe Transaction, Chip Transaction, Online Transaction`の3値だけだったのでやってみました。正直これはOne-Hot Encoding自体よりも`Onlineであるかどうか`という特徴量を追加できたのが大きかった気がします。改善幅は記録し忘れたので覚えてないのですが(すみません)、これ消して回したらCVスコアが0.03くらい下がった記憶があります。

- #### ラベルカウントエンコーディング(★★★)
カテゴリ変数を、その出現回数の順位で置き換えるエンコーディング手法です。これをやったうえでLightGBMとCatBoostのcategorical_features(CatBoostはcat_features)に設定したらスコアが上がりました。謎です。改善幅は0.6720→0.6739と大幅な改善となり、最終評価は0.6742で僕の最終提出となりました。LightGBMとCatBoostのカテゴリ変数の取り扱いをあまり理解していないので、ここはもうちょっと研究の余地がありそうです。

- #### `in_US(アメリカ国内で使われたか)`を追加(★★☆)
`user.csv`を見てみると、居住州がすべて2文字だったのでこれらはすべてアメリカの州のコードだと気づき全員がアメリカ在住だったので追加してみました。影響はよくわかっていません。

- #### `used_in_home(居住州内で使われたか)`を追加(★★☆)
そのままです。これも影響はよくわかっていません。

- #### `in_Italy(イタリアで使われたか)`を追加(★★☆)
不正利用された決済情報のみ抜き取って分析すると、なぜかイタリアでの決済がそれなりに多かったので入れてみました。あんまりスコア上昇には寄与していない感じがします

- #### 差分の追加(★★☆)
「GBDTは差を明示してあげるとスコアが上がる場合が多い」的なことをどこかで見かけたので試してみましたが、影響はよくわかってないです。追加した差分は以下です。
    - `amount(決済額)`とその中央値
    - `credit_limit(利用可能額上限)`と`amount`
    - `acct_open_date(口座開設日時)`と`expires(有効期限)`
    - `year_pin_last_changed(最後に暗証コードを変更した日)`と`acct_open_date`


- #### `merchant_city`の欠損値補完(★★★)
`train.csv`には取引の場所を示す`merchant_city(取引市町村))`の項目がありました。オンライン決済されたものはすべてこれが欠損値ですが、不正利用されたものに絞って分析してみるとどうやら`merchant_city`の欠損値の割合が多いようだったので、これは欠損値であること自体に意味があると思い、オンライン決済の方には`100000`、それ以外の欠損値はすべて`110000`で埋めるということをしました。

- #### `zip(郵便番号)`, `merchant_state(取引州)`を削除(★★☆)
特徴量重要度が両方とも1程度しかなかったので削除しました。`zip`に関しては値が結構バラバラ、`merchant_state`についてはすでに`in_US`を追加していたのであまり使われなかったのかなーという感じです。`merchant_city`だけ重要度が高かったのは上の処理が要因かなと思っています。

- #### カード情報を結合(★★☆)
`train.csv`には使用したカードのidがあったので、これと`card.csv`を結合しました。単に番号が振られているよりかはカードの種類、発行カード会社、上限額などがあるのでマシだとは思いますが、どれほど影響があったかはよくわかってません。

## モデル・チューニング関連
- #### LightBGM(★★★)
強いです。軽いうえに強い最強のGBDTです。下手なモデルよりこっちのほうがいいとかそういうレベルじゃなくて、単純に強いです。GPUの恩恵はそんなにないです。

- #### CatBoost(★★★)
動作はちょっと重いですがこれも強いです。多分GPUの恩恵がそれなりにあります。ユーザーによってはこっちのほうが精度がよかったりしました。

- #### XGBoost(☆☆☆)
単純に弱いです。ハイパーパラメータもっと真面目にいじれば強いかもしれませんが、CVスコアでは何をやってもLightGBMに全く勝ててませんでした。後述するバギングをLightGBM + XGBoostで試したらLBスコアが0.64→0.51に激減するくらいには弱いです(多分ちょっとやり方が悪かったのかな?とは思いますが...)。

- #### 損失関数としてPRAUCを採用(★★★)
0.640→0.647程の改善となりました。今回のコンペは予測する`is_fraud?(不正利用されたかどうか)`の偏りがひどく、不正利用なしが約430000なのに対し不正利用ありが30000ほどしかありません。よって普通のaccuracy等の損失関数ではあまりあてになりません。詳しくはこちらが分かりやすいです→[二値、多値、多ラベル分類タスクの評価指標](https://qiita.com/jyori112/items/110596b4f04e4e1a3c9b)

PRAUCは偽陽性・偽陰性に敏感に反応しスコアが大幅に下がるので、こういう偏りが酷いときにはオススメです。

- #### Stratified k-Fold(★★★)
正解ラベルの割合が等しくなるように分割する交差検証の一手法です。`is_fraud`の偏りが酷い分、これは結構重要です。

- #### バギング(★★★)
強いです。
アンサンブルの一種で、複数のモデルによる予測値を多数決や平均で統合する手法です。改善幅は0.643→0.655と大幅にアップしました。

- #### スタッキング(★★★)
バギングの上位互換です。めちゃめちゃ強いです。バギングでは単純に多数決・平均をとるだけで、そのモデルの得意・不得意を考慮しての統合はできませんでしたが、スタッキングではモデルごとの重みづけをモデルに学習させたうえで統合します。具体的には一度学習させ出力されたtrain dataの予測値を、train dataに加えたうえでもう一度学習・予測させます。勘のいい方はお気づきかもしれませんが、注意点としてこの2つの学習で同じデータを使ってしまうと過学習が起きてしまいます。交差検証と併用するのがおすすめです。

改善幅は0.655→0.665でこれまた大幅なアップとなりました。僕も最初はアンサンブルについては疑心暗鬼でしたが、これは時間の許す限りやったほうがいいです。

また、seed値の違うモデルを複数用意してスタッキングするだけでもかなりの効果があります。スタッキングを2回するのも強いです。
最終的にはアンサンブルだけで0.643→0.670となり、やはり侮れないなという感じです。


- #### Optunaによるハイパーパラメータチューニング(★★★)
みんな大好きベイズ推定でハイパーパラメータのチューニングを自動でやってくれるアレです。強いです。これも時間の許す限りいくらでもやったほうがいいです。

- #### 閾値の設定(★★★)
今回のコンペは評価関数がF値なので、予測を0-1の連続値で行った上で適切な閾値を設定するとスコアが上がる可能性があります。詳しくはここに載ってます→[F値とPrecisionとRecallのトレードオフを理解する(超重要!!)【機械学習入門21】](https://datawokagaku.com/f1score/)

改善幅は0.667→0.672と大幅に上昇し、この提出の最終評価は0.675と最高スコアでした(設定し忘れたのでこの提出は使用されてませんが...)。

## 反省点
- #### `mcc(加盟店業種コード)`について
`train.csv`には`mcc`の項目があったのですが、これは全く生かすことができませんでした...
どのコードの業種が近いなどが分かれば活用のしようはあったかもです。

- #### 取引州どうしの距離について
ユーザーの居住州と取引州の距離を特徴量として追加したかったのですが、あまりにも時間がかかりすぎる&ポリシー的にアリかわからなかったので断念しました。

- #### NN導入
最後の方はもう何も浮かばず、スコアも伸び悩んでいたので最後の手段としてNNを導入してスタッキングさせようかなと考えてました。ただNNの構築方法がよくわからなかったのと、テーブルデータではNNよりGBDTのほうが強いと聞いたことがありあまり試す気になれず、そのまま終わってしまいました。

- #### 特徴量重要度について
これは`zip`, `merchant_city`を落としたあたりまではいいんですが、スタッキングを導入したあたりから面倒くさくなってサボってしまいました。重要度が極端に低い(0~2程度)特徴量は、落とすとスコアが上がったり、計算量削減につながったりするのでここはサボらないほうがいいです(自戒)。

## その他やっておいたほうがいい(かもしれない)こと
- #### その時点での最高スコアのコードを保存しておく
これ結構重要です。色々変更を加えてもスコアが下がることの方が多いんですが、改善案がポンポン浮かんできて色々変更を加えてくと、どの時点でのコードが分からなくなって最悪の場合最高スコアが再現できなくなります(2敗)。コピペして名前変えるなどして保存しておきましょう。

- #### メモリ増設
実機でJupyter Notebookでやっていたのですが、改めてこれの重要性を感じました。スタッキングまで実装すると学習に1時間とかかかるようになってきて暇なんですが、Jupyter Notebookのほうが普通に5~10GBとか食ってくるので何もできなくなります。16GBじゃ結構カツカツになります。メモリのせいかわかりませんが学習途中でカーネルがクラッシュすることが数回あって、1時間弱学習回したのが全部おじゃんになったりもしました。
まあ16GBだと学習すらできないというわけではないので、ここは財布と自分のPCの性能と相談です。Google Colabを使う手もありますが、普通に遅いのでつよつよPCを持ってる方にはお勧めしません。

# 感想
今回のコンペは学生限定なので幾分か難易度は下がってましたが、不正利用されたケースが極端に少なく、傾向をつかむのが難しいという印象でした。決済日時が与えられれば時系列順に並べることで、もうちょっと簡単にはなったかもしれません。10位をとれたのは最初に試したユーザーごとに分けるやり方が一番大きかった気がします。というかそれ以外でまともに特徴量エンジニアリングができた気がしないです。なんというかスタッキングと自分のPCの計算力でゴリ押した感じがすごいので、kaggleのコンペとかにも出て色々勉強していきたいと思ってます。