Kaggleで生まれて初めて銅メダルを取った話【IEEE-CIS Fraud Detection 】

概要

・Kaggleコンペ「IEEE-CIS Fraud Detection」で初めて銅メダルをとりました(554位6385チーム)。

・下記にやったことをまとめます。後で詳しく説明します。

 -----------

【CV】

・month区切りのgruop k-fold

【データ加工】

・文字分割、表記ゆれ、値丸めなど

・相関が高い列同士での値の補間

・hihg-cardinarityな要素の削減

【特徴量生成】

・TransactionDTから、時間系の特徴量取得

・全体,各月毎のCountEncoding

・「M4」「ProductCD」のみTargetEncoding

・各特徴量の平均、標準偏差

・月毎の各特徴量最大-最小、平均、標準偏差

・最新ブラウザを用いた取引には、1のフラグを立てる。

・Dカラムについて、各月の平均を引いた特徴量を生成

・TransactionAmtの小数点以下の値のみ抜き出し

【変数選択】

・Permutation Importance

・コルモゴロフ-スミルノフ検定

・Vカラムに対するPCA

【ダウンサンプリング】

・実施。精度の向上には繋がらない。検証の高速化に活用。

【AdversalialValidation】

・実施。精度の向上には繋がらない。

【ハイパラ決定】

カーネルのハイパラの値をベースに、正則化のパラメータを追加

・optuna

【アンサンブル】

・幾何平均で

0.6(LightGBM×4+XGBoost+CatBoostのスタッキング)+0.3(kernelのLightGBM)+0.1(kernelのNeuralNetwork)

・Rank-Blending

-----------

 

・下記のようなことに気付きました。詳細は後で。今後の礎にします。

-----------

◆コンペ開始直後はValidtionとLeadderBoardの相関が取れるまで、Validationの方法を検討する。

◆特徴量追加の前に、グラフで目的変数に対して効果があるかどうか確認すべき

◆特徴量を追加し分析した後、精度が向上しなかった場合、なぜ効かなかったのかを考えること

◆他の方のkernelをそのまま流用してはいけない。

◆Kaggleのコンペティションにおいては、

時系列データだからといって、Hold-outやTime-series splitに限定する必要はない。

◆kernel,DIscussionを見ることの重要性

-----------

 

・自身の備忘録と今後のための反省も兼ねているため、印象に残った部分を詳しく書きます。

 

自身について

sinchir0と言います。

一年ほど前にSIerのSEから、データサイエンティストにキャリアを変えました。

その前までデータサイエンスやプログラムに一切触れたことがありませんでした。

今は仕事でデータサイエンス案件のコンサル+実際の分析みたいなことをしています。

Kaggleへの真剣参加は今回が初の初心者です。

 

順位について

銅メダルでした。素直に嬉しい。

f:id:sinchir0:20191005011038p:plain 

submit回数、費やした金額と時間

・submit回数:145

GCP使用料:約4万円(3か月合計)

 

 感覚として、コンペ開催期間3か月の仕事終わりと休日は可能な限りKaggleに充ててました。また、kernelでsubmitする際の計算し直しの時間が煩わしかったため、初めて個人でCloud(GCP)に契約しました。

 

 同じ時間を費やせば僕よりも良い成績を納める方が多いと思います。あくまで凡人の指標です。

 

本題

コンペについて

IEEE-CIS Fraud Detectionはクレジットカードの不正利用を見つけるコンペティションです。

 

https://www.kaggle.com/c/ieee-fraud-detection

 

 データはテーブルデータで与えられており、各取引の情報が一行に与えられています。不正があったかどうかが「isFraud」の列で与えられており、testデータのisFraudが1か0を予測するコンペとなっています。評価指標はAUCが指定されています。

f:id:sinchir0:20191005003544p:plain

学習データの一部

isFraudが0のデータが約57万件に対し、1のデータが約2万件の不均衡データです。

 

f:id:sinchir0:20191005004315p:plain

isFraudのヒストグラム

 

また、非常に欠損値が多いのも特徴的なコンペでした。

f:id:sinchir0:20191005013919p:plain

train,testのデータにおける、各columnの欠損値の数

 

不正検知について

 不正検知は「同じユーザーが短い時間で同じ買い物をした場合」に起きることが多いそうです。これは日本語のクレジットカードのサイトにも知見が沢山ありました。そのため、今回のコンペで上位に入賞した方は「匿名化された特徴量が何かを推定し、匿名化変数を基に同じユーザーを特定する変数」を上手く作っている傾向にある気がします。

 -----------------------

f:id:sinchir0:20191005114109p:plain

不正利用パターンについて

 

https://34.73.0.7:8888/login?next=%2Ftree%3F

 -----------------------

評価方法(Validationの方法)について

 今回与えられたデータは時系列データです。「TransactionDT」のカラムが時間を表しています。

f:id:sinchir0:20191005011749p:plain

今回僕は、month区切りのgroup k-fold(k=6)でvalidationを行いました。

具体的には下記となります。

学習データの時刻は隠されていたのですが、 調査により2017年12月開始とする意見が多かったため、学習データは2017年12月、2018年1-5月の合計6か月、テストデータは2018年7-12月の合計6か月あるのに対し、

 

fold1:学習 2018年1,2,3,4,5月 評価:2017年12月

fold2:学習 2017年12月,2018年2,3,4,5月 評価:2018年1月

fold3:学習 2017年12月,2018年1,3,4,5月 評価:2018年2月

fold4:学習 2017年12月,2018年1,2,4,5月 評価:2018年3月

fold5:学習 2017年12月,2018年1,2,3,5月 評価:2018年4月

fold6:学習 2017年12月,2018年1,2,3,4月 評価:2018年5月

という感じです。

 

(実運用を想定したデータサイエンス案件においては、Holdout or Time-series splitなど時間関係を意識したValidationを実施すべきだと思います。あくまでコンペでの話です。)

 

HoldOut,k-fold,Time-series splitも試しましたが、最終的には下記4点の理由からgroup k-foldを評価の方法として採用しました。

 

①isFraudは一度不正利用されてから短い時間で、同じユーザーによる不正利用が発生するパターンが多い。

もし不正にあっているユーザーAさんがいたとして、Aさんが学習にも評価にも出てきてしまうと、「学習データで不正にあっているAが評価データにも出てきた!どうせまた不正利用されてるでしょ!」と学習モデルが判断して、isFraudを1にしてしまうリークが起きる。

そのため、もしfoldを組むのであればある程度時間幅を持つことで、同じユーザーの一連のisFraudが同じ学習モデルに入るようなfoldを組む必要があると考えました。k=6のgroup k-foldなら、分断されるタイミングが5回しかないため、リークがほぼ起きないと考えました。

 

②isFraud自身は時間に依存したデータではない。

 

③もしhold-outやTime-series splitを用いた場合、最後のvalidationに用いたデータは学習に使えず、学習データの量について減ることになり、勿体ない。

 

④学習データの12月を評価する場合のみ、精度が何故か下がる。

これはコンペ終了まで理由が分かりませんでしたが、この情報から「月毎に何か特性がある。」と考えました。

#WinnerSolutionの質疑応答に記載がありましたが、12月は学習データの開始の月であり、事前にユーザーがisFraudになったどうかの情報が少ないため精度が落ちるようです。

 

上記4点+実際の検証によるCV,LBの上がり下がりを踏まえて、ある程度時間幅を確保出来て、学習データを無駄にもしないgroup k-foldが最適だと考えました。

使用モデル

 基本的にはLightGBMを使用し、アンサンブル時だけXGBoost,CatBoostを使用しました。またKernelモデルからLightGBM、NeuralNetworkモデルをお借りしてます。

 

メモリ削減

 今回のデータは下記のように巨大データです。

 

f:id:sinchir0:20191005132816p:plain


そのため、普通にやるとメモリエラーでブラウザ落ちるわ、カーネル死ぬわでとても悲しい気持ちになるコンペでした。下記のようなdataframeの型を最適化(int64→int32など)を使っている方が非常に多かったと思います。

 

f:id:sinchir0:20191005114454p:plain

memory reduce

 

https://www.kaggle.com/rinnqd/reduce-memory-usage

 

データ加工

トランザクションに使用したデバイス情報「DeviceInfo」のsplit、表記揺れ修正。

・時間関連のカラムであるD系の列において、マイナスの値は理解できないため0に丸める。#Solutionで知りましたが、マイナスの値にも意味があったそうです・・・

・全カテゴリ変数について、tarin,testの片方にしか存在しない値はNaNに変換

・欠損値や無限大の値は全て-999に変換。

 

 Discussionの中で、「LightGBMはNaNの値を過大評価する傾向にあるため、-999に置き換えて上げた方が良い」という文言があったため実施。精度は微減しました。

 

f:id:sinchir0:20191005105723p:plain

NaNの扱いについてのDiscussion

 

https://www.kaggle.com/c/ieee-fraud-detection/discussion/108575


・使用したクレジットカード情報である「card」について、

欠損値が存在しないcard1の値と関連する最頻値で、card2-6の欠損値を補間。

 

 個人的に「こんな埋め方が!」と驚いていたのは、欠損値について、「1対1での相関が高いカラム」の最頻値を取る際の値で欠損値を埋める方法です。

 

 具体的に説明すると、card1とcard2について、これらのカテゴリの値は、80%以上が1対1の関係を取ります。例えば、card1が'13926'という値だったとき、同じ行のcard2の値は全て'327'など。

 

 このような「1対1での相関が深いカラム」の値における最頻値にて、欠損の補間を行います。

 

流れとしては

①card2の欠損値を埋めたい!

②card2と1対1での相関が高いカラムを調べ、card1が80%以上一致することを調査

 具体的なコードは下記を参考にしました。

 https://www.kaggle.com/gunesevitan/ieee-cis-fraud-detection-dependency-check

③card2がNaNのときのcard1の値を見に行く、仮に'13926'だったとする

④card1が'13926'のときのcard2の値は何が最も多いか計算する。今回は'327'というカードだったとする。

➄card2を'327'で埋める。

となります。

 

この補間でスコアが微増しました。

f:id:sinchir0:20191005014701p:plain

左:欠損値埋め前 右:欠損値埋め後

 

 コードは下記で公開されています。

https://www.kaggle.com/kyakovlev/ieee-card1-card6


・取引で支払った金額であるTransactionAmtについて、5000以上を全て5000に丸める。


 下記カーネルにおいて、TransactionAmtをノイズに置き換えても、LightGBMの変数重要度は大きく変化しないことが説明されていました。

https://www.kaggle.com/kyakovlev/ieee-check-noise

 

 このカーネルより「TransactionAmtは大小関係に依存しない、カテゴリ変数であること」が察せられます。

#不正利用はユーザーにばれないことが重要なので、大金のみで多く生じるとは考えにくい。

 

 カテゴリ値と考えた場合、下記のグラフで表現されるTransactionAmtの外れ値は全て、一つの「大きい値」のカテゴリにまとめた方が、決定木モデルが「値が大きいのはTransactionAmt=5000で分ければ良いのね。」と理解が出来ます。

f:id:sinchir0:20191005021645p:plain

x軸:時間 y軸:TransactionAmtの値

 丸めるコードは下記となります。簡単。

>train_df['TransactionAmt'] = train_df['TransactionAmt'].clip(0,5000)
>test_df['TransactionAmt'] = test_df['TransactionAmt'].clip(0,5000)

 

 反省点としては、この丸めの加工実施を他の多くの施策と合わせて実行したため、単体での効果があったかどうかを見ていないことです。

 

・high-cardinarity(要素が多い)な「card1」について、登場回数が少ない要素はNaNに置換

 

 card1は下記のように非常にhigh-cardinarityです。

 

f:id:sinchir0:20191005022520p:plain

card1の要素数

 

 しっかりとした根拠を理解していないのですが、「あまりにも要素が多い変数については似たような要素でまとめて要素数を減らすと、決定木モデルにとっては良い」ということを経験的に知っていたため、登場回数が10回以下のcard1の要素は"rarecard"と捉え、全てNaNに置き換えました。変換後の要素数は下記です。

 

f:id:sinchir0:20191005023351p:plain

card1の変換後の要素数


 一応この施策単体では精度は微増しましたが、WinnerSolutionなどの記載から、本コンペにおいてはユーザーの特定が重要な要素となり、card1のカウントが少ない情報はユーザー特定の重要な情報となるため、結果的には悪手だったかもしれません。

 

特徴量生成

・TransactionDTの変数から、月・日・時間・祝日、12月かどうかなどの特徴量取得
・カテゴリ型変数のCountEncoding
・変数「M4」、「ProductCD」のTargetEncoding
・各特徴量の平均、標準偏差
・月毎の各特徴量最大-最小、平均、標準偏差

・最新ブラウザを用いた取引だった場合、1のフラグを立てる。

 今回のコンペの場合、取引に用いたブラウザが最新かどうかの変数を入れると精度が向上するとDiscussionに記載がありました。(単体で精度が上昇したかどうかを検証していないため、本当に上がったかどうかが分かりません。)ブラウザの脆弱性起因の不正取引があるということですね・・・、実生活でも気をつけなければですね。


・Dカラムについて、各月の平均を引いた特徴量

 

 Dカラムは時系列要素であり、下記のように時間が経つにつれてベースが増加していく変数が多いです。

f:id:sinchir0:20191005030427p:plain

x軸:時刻 y軸:D3の値

 このようなデータを決定木で学習させる場合に「学習データでは500以上で不正が多いな!」と決定木に理解して頂いても、ベースの値が変化してると、分岐すべき点もベースの値に合わせて変化してしまうため意味がないと考えました。そのため、ベースの値が各月の平均の値で表せると考え、各Dカラムから各月の平均の値を引き算した変数を追加しています。

f:id:sinchir0:20191005032607p:plain

D3から、D3の各月の平均を引いた変数

 ただ、今ブログ用にグラフを出力し直して気づきましたが、平均を引いてもベースの値の上昇を全く取り除けてないですね・・・、D3の値自体が0のカラムが多いため、平均が小さい方に引っ張られるためだと思われます。ちゃんと可視化してベースが取り除けたかどうかを確認するべきでした。

 

・TransactionAmtの小数点以下の値のみ抜き出し

 

 Discussionで話題になった話として、「TransactionAmtの小数点以下の値は、各国の通貨と紐づいているのでは」という話です。TransactionAmtのカラム自体がどこかの国の通貨(おそらくセント?)で統一されており、小数点以下は、各国の通貨を統一された通貨への変換の際に付くもの、という認識のようです。下記のDisucusionを見る限り明確にisFraudの差は出ますが、なぜ差が出るのかをまだ理解できていません。

f:id:sinchir0:20191005110157p:plain

TransactionAmtの小数点以下の値 左:isFraud=0 右:isFraud=1

https://www.kaggle.com/c/ieee-fraud-detection/discussion/110696#latest-639066


・カード1-6、TransactionAmt、P-email domain,R- email domain,addr1、addr2の組み合わせを用いた個人idを生成し、平均・標準偏差を取る。

 

 このコンペの非常に重要な点である「各ユーザーをどう特定するか」についてです。ここら辺は僕は余り手出しできなかったため上位のソリューションの解法を参考にして改めて勉強します。

 

 やったことは、下記となります。

・カード1-6、TransactionAmt、P-email domain,R- email domain,addr1、addr2の組み合わせを用いたユーザーIDをuid1,uid2,uid3,uid4,uid,5,uid6として生成

・カテゴリ変数について、各ユーザーID毎の平均、標準偏差

・カテゴリ変数について、各ユーザーID毎の月毎の平均、標準偏差

・TransactionDTの値を用いて、前回のTransactionからどの程度時間が経過したか

 

・Target Encodingについて

 TargetEncodingでは、「card1」などのhigh-cardinarityな変数に対して用いるとleakし、全くLightGBMの精度が上がらない事態に陥ってしまいました。これは、例えばcard1の変数が'100'で、その時にisFraudが1であり、その変数の行が学習データの中で1つしか無かった場合、決定木としては「card1が'100'のときのTarget Encodingの値が1だからisFraudも1だ!」と一発で答えが分かってしまいます。これがleakに繋がり、碌に精度向上をしなくなります。

 

下記ツイートが参考になります。

 

 

これを防ぐためにsmoothingなどの方法が存在します。

https://mikebird28.hatenablog.jp/entry/2018/06/14/172132

ただし、スムージングの度合いが悪かったのか、今回は良い精度を得られなかったため諦めました。

 

変数選択

・Permutation Importance

Permutation Importanceと呼ばれる、ランダムフォレストを用いて各特徴量の値をランダムに入れ替えた前後で、特徴量重要度にどの程度差が出るか、で変数の重要性を評価する手法を用いて変数選択を行いました。詳しい方法は下記に載っています。最低評価の変数10個を削ったら精度が微増しました。

 

https://eli5.readthedocs.io/en/latest/blackbox/permutation_importance.html

 

コルモゴロフ-スミルノフ検定

略してK-S法と呼ばれる手法を用いました。trainデータとtestデータに分布が特徴量を予測に使ってしまうと、testデータに対する精度が悪くなってしまう問題を回避するための手法です。各特徴量がtrainデータとtestデータで分布に差がある」という帰無仮説を立て、「差がある」という仮説が棄却された変数のみを学習に使います。実装は下記。

 

https://upura.hatenablog.com/entry/2019/03/03/233534

 

ただし、この手法は「各特徴量がtrainデータとtestデータで分布に差がある」という帰無仮説の棄却を行うだけなので、棄却された変数が「trainデータとtestデータで分布に差がない」とは言えず、「trainデータとtestデータで分布に差があるかどうか分からない」状態までしか断言できないという説もあり、誤用の可能性があるそうです。

 

下記に色々載っています。

https://qiita.com/goodclues/items/0c91f6ac6df6a081fe86

https://asaip.psu.edu/Articles/beware-the-kolmogorov-smirnov-test

 

個人的には、「各特徴量がtrainデータとtestデータで分布に差がある」という帰無仮説が棄却出来なかった変数を落とすと考えれば問題ないように思います。

 

・PCA

300個あるVカラムの欠損値を-1で埋めた後、PCA(主成分分析)にて変数を10個まで削減しましたが、精度は落ちました。Solutionから判断するに、Vの変数の影響が大きかったことが原因かと思われます。

 

ダウンサンプリング

不均衡データのため、「多数派のisFraudが0の行数を、少数派の1の行数に合うよう減らすNegative Down Samplingで精度が上がるのでは?」と期待しましたが、多数派:少数派 = 2:1にしたダウンサンプリングでは精度向上には繋がりませんでした。ただし、計算時間が短縮が可能となります。

 

基本ダウンサンプリングは素早く検証するためのデータ削減方法の意味合いが強いようです。下記Disucussionで詳しく話がされています。

 

https://www.kaggle.com/c/ieee-fraud-detection/discussion/108616#latest-634925

 

ハイパーパラメータ

下記カーネルの値をベースに、正則化パラメータであるreg_alpha,reg_lambdaを追加して使っていました。

https://www.kaggle.com/kyakovlev/ieee-lgbm-with-groupkfold-cv

 

またコンペ終盤にハイパーパラメータ自動調整ライブラリのoptunaを使いました。

使い方はカレーちゃんさんの下記kernelが一番分かりやすいと思います。

https://www.kaggle.com/currypurin/tutorial-of-kaggle-ver4-optuna

 

hyperoptも同様なのですが、ハイパラ自動調整のライブラリを用いて精度が向上したことがありません。自身がモデルを作る際、最初に使用したパラメータに特徴量を最適化させてモデル構築してしまうため、最後にハイパラ調整しても意味がないのでは疑っています。やるんなら特徴量生成の前にやった方が良いかもしれません。

 

Adversalial Validation

Kaggle特有の「TrainとTestの分布に差があるデータを使ってしまうと精度が落ちる。」という問題を解決するための手法です。主に行方向への手法と、列方向へのアプローチがあるかと考えています。

 

「行方向についてのアプローチ」

Trainデータに1,Testデータに0をつけて、trainかtestかを判別する予測を行います。その精度が極めて高いor低い = Train or Testって判別しやすいデータを出力します。

該当の行は、Train,test間の判別が簡単に出来る=Train,Test間で分布が異なると判断できるため削除する、もしくは該当行のweightを低くするなどの対応を行います。

 

今回のコンペでは精度が減少したため、採用しませんでした。

 

「列方向について」

Trainデータに1,Testデータに0をつけて、trainかtestかを判別する予測を行います。予測の結果から、trainとtestを見分けるために極めて有効な特徴量がどれかを出力します。その特徴量は”TrainとTestを見分けるのに役に立つ”="Train,Test間で分布が異なっている"と考えられるため、削除します。

 

今回のコンペでは精度が減少したため、採用しませんでした。

 

実装は下記など。(列方向の例)

https://www.kaggle.com/tunguz/adversarial-ieee

 

アンサンブル

・LightGBMのパラメータを変えたモデル×4

・XGBoost

・catBoost

の6つの予測結果を学習データにして、更にロジスティック回帰を用いて予測をしています。

 

僕はスクラッチで組みましたが、ライブラリを使うなら下記。ただし、Validationの方法が指定できないといった問題がありそうです。

http://rasbt.github.io/mlxtend/user_guide/classifier/StackingClassifier/

 

上記作成したモデルに加え、kernelのLightGBMのモデルと、Neural Networkのモデルをそれぞれ0.6,0.3,0.1の重みで幾何平均を取っています。幾何平均を使う理由は、Predictの値が確率であるため、足し算が出来ないためです。詳しくは下記。

http://peng225.hatenablog.com/entry/2018/05/01/224358

 

Rank-Blending

一言でいうと、「AUCの値は予測確率の大小関係のみで決まるのだから、予測結果をモデル間でオーダーに差がある確率ではなく、順番に変換してからアンサンブルしよう!」という内容のものです。

 

今回のような判別の予測確率を出力すると、特に決定木系のモデルとNeural Networkのモデル間で確率に差が出てきます。

f:id:sinchir0:20191005045059p:plain

左:決定木系アルゴリズムの確率出力結果 右:NN系アルゴリズムの確率出力結果

この状態で素直にアンサンブルしてしまうと、モデルが均等に扱われません。(この例の場合、おそらくNN系のモデルの方が影響力が強くなります。)

 

ここで、AUCを計算する際に関係するのは、該当の行が同じデータの中で何番目に来るかという順番のみという事実を思い出してみます。

 

すると、最も確率が小さい行=1,・・・、最も確率が大きい行=最終行と変換した後にアンサンブルすることで、モデル間を均等に扱うことが出来るようになることが分かるかと思います。

 

f:id:sinchir0:20191013103459p:plain

順番に変更した予測結果

上記の例でいうと、Tree based Algorithm rankedの最初の予測結果は、全体の行数である506691のうち、42371番目に小さい数字であったため、42371という数字に置換されています。この状態で予測結果をアンサンブルすることで、各モデルの予測結果を平等に扱うことが可能です。

 

詳細は下記。

 https://www.kaggle.com/c/ieee-fraud-detection/discussion/110326#latest-635475

https://docs.scipy.org/doc/scipy-0.16.0/reference/generated/scipy.stats.rankdata.html

 

その他、やったけど駄目だったこと

・今回のデータには、「不正利用にあっているけれども、本人が気づいていない場合は

 isFraudが0になる」という説明が開催者からありました。

 モデルによるValidationの精度が95%を超えていたため、「Validationの予測で、

 isFraudが実際は0なのに1と予測してしましまっている行は、全て上記パターンなの

 では?」と考え、正例データの増加のためにこれらのデータのisFraudを1に置き換え

 再度分析を行いましたが、LBの精度が減少したため却下しました。

 

・欠損値補間について、相関が高いカラムを全て出力し、80%を超えていたら相互に補

 間しあうことをやりましたが、精度が下がりました。欠損値がある位置にも理由が明

 確にありそうだと察しました。

 

・正例:不例=2:1でダウンサンプリングしたモデルをバギングしてみましたが、

 単体のダウンサンプリングによる予測モデルよりは精度が高く、全データを用いた際

 のモデルよりは精度が低いという結果になりました。

 

・TransactionAmtのグラフが右に尾を引いていたため、一応logを取った特徴量を追加し

 ましたが精度に変化はありませんでした。

 

・LightGBMのboosting_typeをgbdtとdartを試しましたが、gbdtの方が精度が良かったた

 め基本こちらを採用しています。(dartモデルはアンサンブルに使用)

 

やらなかったこと

・欠損値を予測するモデルを作成

 →意味はあるかと思いましたが、欠損値があること自体に意味があると途中で察した

  ため、優先度を下げました。

・TargetEncodingのleakを、smoothing以外の方法で補正

 →具体的な方法がすぐには思いつかず、後回しにしているうちにコンペが終わりまし

  た。

 

相関係数による変数選択

 →決定木の場合、あまり変数選択は精度に関係しないと思っていたので(計算時間・

  使用メモリにはもちろん影響する)優先度を下げました。

 

学んだこと

・コンペ開始直後はValidtionとLeadderBoardの相関が取れるまで、Validationの方法を検討する。

 

 Validtionが間違っていると、どれだけよい特徴量を追加したとしても、LB(PublicLeaderBoeadのスコア)は上がりません。一番初めに、LocalCV(手元の精度)が上がればLBも上がる。LocalCVが下がればLBも下がることを念入りに確認することが必須と感じました。

 

今回はその確認をしなかったため、コンペ残り一週間辺りで「group k-foldはダメなんじゃ・・・、hold outなら正しく評価できるのかな?それともTime series splitが正しいのか?」という悩みを抱えながら、数回別のValidationを検討する時間を設けたため、ちょともったいなかったかなと思います。

 

・特徴量追加の前に、グラフでisFraudに対して効果があるかどうか確認すべき

 

 思いついた特徴量は、あまりisFraudとの関係も気にせずとりあえずモデルに追加して

精度の様子を見る、というパターンを繰り返していました。モデルの計算時間を考えると、初めはisFraudを判別できそうかどうか可視化して判断するべきだと今は考えています。 

 

・特徴量を追加し分析した後、精度が向上しなかった場合、なぜ効かなかったのかを考えること

 

 

 Jackさんの上記ツイートに全てが詰まっていますが、特徴量を追加して精度が上がらなかった場合、なぜ効かなかったのかの部分をより深く考えるべきだと気づきました。例えば、作成したけれど効かなかった特徴量とisFraudの分布を見比べ、1と0に差があるかどうかを確認するなど。データの理解に繋がるかと思います。

 

・他の方のkernelをそのまま流用してはいけない。

 他の方のkernelをそのまま流用すると、どの特徴量が効くのか、どの特徴量が効かないのか経験値が自身にたまりません。例えば、今回のコンペはユーザー特定の変数を作成する部分が非常に重要でしたが、僕はkernelの該当部分のコードをそのまま流用してしまい、他の特徴量と一緒に評価してしまったため、ユーザー特定の変数が効くということに気付くのがかなり終盤になってしましました。これは大きな反省点です。

 ベースラインモデルを作成後、丁寧に特徴量を追加していくことが必要なのだと思います。時間との兼ね合いもありますが。

 

・Kaggleのコンペティションにおいては、時系列データだからといって、Hold-outやTime-series splitに限定する必要はない。

 (もしかしたらこの見解は間違っているかもしれません。)

 

 今回のような時系列データのValidation=Holdout or Time-series splitと思い込んでいましたが、データ分析のコンペティションに限って言えばその限りではないことを知りました。

 

評価方法(Validationの方法)について」で詳しく記載しましたが、今回僕はユーザーが分断される回数が6回と少なく、trainデータを無駄にしないgroup k-lodを採用しました。これは、例えば2018年5月のデータを用いて2017年12月のデータを予測しているvalidationも含まれています。今回の場合はisFraudが時系列に依存するデータではなかったため、このような評価でも問題ないと考えています。

 

(ただし、Dカラムのような時系列データは何かしらの方法で決定木に正しく理解させる必要があるかと思います。)

 

仕事の実案件においては、時系列をk-foldしてはいけないのは「実運用時に未来のデータを使ってモデルを作成できない」という理由があります。しかし今回のようなコンペティションであれば一応ありなんだなぁというのが新たな気づきです。

 

・kernel,DIscussionを見ることの重要性

 重要な情報はほぼkernelとDisucussionに上がります。Kaggleの世界中のデータ分析大好きな方がこぞって参加しているため、開催期間中のkernelのアウトプットやDiscussionの議論がかなり活発です。誰かが投げた質問に対し、上位者がヒントを与える形で新しい変数が思いつくパターンもありましたし、カーネルでがっつり良い手法公開してくれるパターンもあります。これらの大量の情報に振り回されてはいけないのですが、kernelとDisucussionで得た情報に自分で重要度をつけて、実際に試してみる、という進め方は非常に勉強になりました。

 

最後に

 コンペを走り切ったのは生まれて初めてで、一応でもメダルが取れたのは非常に嬉しいです。ただし、kernelのモデルを使用している部分や、あまり独自の取り組みは出来なかったため、次のコンペでは独自な取り組みで金圏・銀圏を目指したいです。

 

 こんなやつでも別コンペでマージしてやるよ!って方いたら声かけて頂けると嬉しいです。https://twitter.com/sinchir0