Kaggle体験記:IEEE CIS Fraud Detectionで19位/6381

はじめに

こんにちは。くるぴー(@kurupical)です。

このたび、IEEE CIS Fraud Detectionコンペに、@pondelion1783さん、@HighGradeToppoさん、@TaTakoihirokazuさんと参加し、19位/6381の成績を残すことができました。
f:id:kurupical:20191005092325p:plain:w600

チームのみなさまはとても優秀で、コンペに参加した2ヶ月の間とても刺激的な時間を過ごすことができ、いい経験になりました。
チーム目標であった「金メダル」も達成できてとても嬉しいです。本当にありがとうございました!

このブログでは、これからKaggleなどのデータ分析コンペ参加しようとしている方向けに、どのようにコンペに取り組んだのかという経緯を残しておきたいと思います。
何かのお役に立てれば幸いです。

もしよろしければ、1年前に書いたkaggle体験記もあわせてご覧ください。
kurupical.hatenablog.com


1. 参加のきっかけ

きっかけは、8/3(土)に開催されたatmaCupの懇親会でした。*1

atma.connpass.com

「関西のexpert以下のチームで金を取る」というお酒の勢いだけで4人チームを集め、結成しました。
「みんなテーブルコンペが得意」という理由だけで、そのとき唯一開催されていたIEEE CIS Fraud Detectionコンペに参加することを決めました。
この時点では、全員、ゼロサブどころか、コンペの概要を読んだ程度でした。

また、このオフラインコンペで@takuokoさんに公開いただいた上位ソリューション(PetFinder 2ndの知見の集合体)に感銘を受け、
何かテーブルコンペで自分でも試してみたいという気持ちがありました。

2. コンペ取り組み

2-1. コンペ説明

2-1-1. 課題設定

  • ユーザーの取引データから、「不正取引」を検出したい

2-1-2. データセット

  • train, testそれぞれ約50万行、約450列
  • うち「不正利用」とされるデータは3%。残り97%は不正利用ではない。
  • すべての項目名は匿名化されている。が、特徴の属性(カテゴリなのか日付なのかカウントなのか等)は項目名である程度わかるようになっている。
    例:項目名の頭にD→日付、C→カウント 等

詳しくは以下EDAカーネルをご参照ください

www.kaggle.com

2-2. 序盤

2-2-1. まずは1サブミット

機械学習させるコードを持っていない人にとって、何もない状態から1サブミットするのはかなりハードルが高く、ここでめんどくさくなって挫折してしまうケースが多いです。
逆に1サブミットさえしてしまえば、そこからスコアを伸ばすモチベーションが湧いてきて、2サブ目、3サブ目以降と続けやすくなると感じています。
この挫折を回避するために、自分は以下ルーチンを実施して「とりあえず1サブミット」を目指しています。(あくまで個人的な意見です)

  • 以下手順で良さそうなNotebookを探す
    f:id:kurupical:20191005094917p:plain
  • Notebookをコピーする
  • 1行でもいいので自分のオリジナルを加える(コピペではなく自分でやった感を出す)
  • サブミットする

2-2-2. EDA

まずは全特徴量の分布を、train, testでvisualizationしました。
f:id:kurupical:20191005161238p:plain:w500
上のvisualizationは、ProductCDという項目をtrain/testをそれぞれ時系列に3分割してヒストグラムを出したものです。
例えばこのグラフだと、ProductCD=Hが、trainの頭・testの末に多いことがわかるので、それを深堀していきました。
この例だと、Hは12月に多く、HはHobbyのHだと仮説を立てました。分析の結果12月は明らかに分布が違うこと、不正利用が少ないことがわかったので、12月フラグという特徴量を作りました。

2-2-3. 総当りで特徴量作成(Public:0.9423, 3000位/6381)

序盤は特徴が匿名化されているため、思考停止して特徴を作成しました。
「すべての特徴(400項目ほど)から2つを取って差・比を取る」総当り。12万ほどの特徴が作成されるので、
1000個ずつモデルに投入→top10項目を取る*120回 = 特に効いていると思われる1200特徴量を突っ込んでどうなるか、試しました。
全然効きませんでした。

2-3. 中盤

2-3-1. 仮説を立てた

総当り特徴の作成はリソースを無限に食うし、こういうやり方ではAutoML系サービスに明らかに分があります。
「50万行の中には、同一のユーザーがいるのではないか」「同一のユーザーを特定すれば、スコアがかなり上昇するのではないか」という仮説を立て、EDAを実施しました。

2-3-2. ユーザー特定のIDを見つけた(Public:0.9435, 2750位/6381)

同一ユーザー特定のヒントを得るため、同じユーザーと思われる行をまずは手作業で抜き出そうと考えました。
方法としては、カード会社等の属性などが入っている項目card1〜card6を結合して仮ユーザーIDとして、同じ仮ユーザーIDを持つものをまとめてcsv出力し、
Excelで縦横に流しながらざっと眺めました。

すると、明らかに特徴が似ている行たちがいました。
この「特徴が似ている行たち」だけを切り取って眺めていると、「D1」がカード作成からの経過日数?、
「D5」が前回取引からの経過日数らしきものであることがわかりました。
f:id:kurupical:20191005162613p:plain
目視で「多分同一人物だろうな」という人を黄色塗しました。
赤枠1+赤枠2=赤枠3。赤枠1は基準日から15日、赤枠3は19日経過した時点のデータ。赤枠2は、赤枠1の行から何日経過したか、ではないか?
※項目「DAY」はTransactionDTという経過秒を60×60×24で割って経過日にしたもの

これをチームメンバーに共有したところ、TransactionDay(取引した日)とD1(カード作成からの経過日数)の差を取れば、「カード作成日」という特徴が作れ、
ユーザーがかなり厳密に特定できるのでは?
と新たに示唆をいただきました。
さらに、カード作成日を加味し、カード情報+カード作成日というユーザーIDを使って、data.groupby("userID").mean()、data.groupby("userID").std()のような、
ユーザーIDごとの特徴量平均・標準偏差のような特徴を作って実験したところ、大幅にLBが向上した(Public0.9564, 85位/6381)という報告がありました。*2
同じことを自分の手元で試したのですがスコア伸びませんでした(Public0.9435止まり)。

2-3-3. ハイパーパラメータ調整(Public0.9477, 2195位/6381)

ハイパーパラメータを調整するだけで、Publicが0.9435→0.9477に向上しました。
もともと、HomeCreditコンペで使っていたmax_leaves=60, depth=10というパラメータで実験していましたが、一番強いNotebookに記載されているハイパーパラメータを一部参考にしました。
私のかってな推察なのですが、card1、card2の特徴量はユニーク数がかなり大きく(10000くらい?)、決定木の葉っぱの数であるnum_leavesが少ないと、枝分かれが十分にできないためにLBが低かったのではないかと思っています。
Notebookのハイパーパラメータはこういったところも加味してちゃんと設定されているのだなあと感動しました。*3

2-3-4. さらなる特徴作成(Public:0.9584, 64位/6381)

いろいろな特徴量を作りました。

  • userIDをベースに全特徴量の平均、標準偏差
  • userIDをベースに月ごと・週ごと・日ごと等で取引回数や取引金額の平均、標準偏差
  • あらたなuserIDを試す
  • 特徴量をいくつかのグループに分け、それぞれのグループの「最大/最小値」、「最大/最小をとった項目名」「欠損パターン」。
    例えば、以下V95,V101,V143,V167,V177,V279,V293あたりは挙動が似てるので一つのグループとしました。パターンはエクセル目視で確認しました。今思うと、特徴ごとのクラスタリングとか使ってスマートにやればよかったのですが…
    f:id:kurupical:20191005164423p:plain

2-4. 終盤

2-4-1. Negative Samplingによる実験の高速化

「不正利用」のデータ全部と「正常利用」のデータ2割で学習させても精度はそこまで変わらないので、実験が高速にできるよ!というDiscussionを読み、実践しました。
実験時間がかなり少なく済む、特徴量をたくさん入れてもMemoryErrorにならないなどかなりメリットが大きかったです。

2-4-2. 特徴選択

Negative Samplingをしたうえでfeature importanceが高い項目を採用しました。

2-4-3. 予測できていないデータの分析

自分のモデルが予測できていないデータを分析しました。結果、ProductCDがWの不正利用が検知されていないケースが多いことが判明しました。
この結果から、ProductCD=W、Cだけのモデルを作り、アンサンブルに加えました。(LB +0.001)
ProductCD=W, D1=0が不正利用検知できていないケースが多かったためこれもアンサンブルしたかったのですが、実験が間に合いませんでした...。

2-4-4. CatBoost

カテゴリがかなり多いのでCatBoostも効くのでは?という考えで、試しました。LBは低かったですが、アンサンブルでよく効きました。
パラメータ調整はdepthのみ行い、6,8,10,11あたりを試しました。結局depth=6が一番強かったです。
あと、変な特徴を入れるとCV0.960→LB0.951と出たり、挙動がよくわかりませんでした…
結局、CV0.955, LB0.955くらいまで伸ばしてアンサンブルしました。

2-4-5. seed averaging

モデルのseedを変えて学習させ、平均を取りました。

2-4-6. postprocess

厳しめに絞ったユーザーID(card1-6 + (D1-Day) + (D10-Day))を使って、以下を実施しました。

  • trainデータですべて不正利用、取引回数2回以上のID → 予想を1に書き換え
  • trainデータですべて不正利用ではない、取引回数5回以上のID → 予想を0に置き換え

Public+0.0007, Private+0.0003でした。

2-4-7. アンサンブル

StratifiedKFold(5), Timeseries(6)で全員のモデルを予測させ、最終foldのoofを使ってNelder-Mead法で重み付けしました。(全Foldのoofを使ったほうがPublicは低かったもののPrivateは0.001くらい高かったです。)
qiita.com

2-4-8. 最終サブの選定

2サブのうち1サブは安全にという気持ちで、Train/Testで分布が異なる特徴量を削除したモデルにしました。

2-5. 結果

全員のモデルをアンサンブルしてPublic0.9634(29位), Private 19位になりました。
チームメンバー全員、Publicで0.96を超えるSingle Modelはありませんでした。一番よくて0.9588くらいだった気がします。
そういう意味ではチームで勝つことができたのかなと思います!!

3. 振り返り

3-1. 良かったこと

  • 丁寧にEDAができ、EDAから得られた知見を特徴に落とし込めた。
    統計量やサマリーを見るのも大事ですが、同じくらい個々のデータを見るのも大事。ただ、いたずらに全部見るのではなく、いろいろな条件で絞り込む(IDとか、予測うまく行かなかったデータとか)とスマートにEDAできると思いました。Excel Masterになりましょう!
  • 新しいライブラリ(CatBoost)に触れることができた。
    新しいコンペに参加するごとに、公開されているNotebookからたくさん学んでいます。Masterになったら僕も少しずつ貢献していきたいです。
  • 行き詰まったら特徴作成コード全捨て
    強い人がやっているのを聞いて自分もやってみましたがかなりよかったです。
  • 酒の勢いで言った「チーム組みましょう」を、ノリで終わらせず、その場でちゃんとチームマージした。笑
    「チーム組みましょう。また明日以降対応しますね」じゃなくて、「とりあえずチーム組みましょう、その代わり後で気が変わってやる気なくなっても文句なしで!」という方針にしました笑
  • バージョン管理でgithubを使った。
    管理は超適当でしたが、無いよりマシでした。
    f:id:kurupical:20191005170442p:plain:w500

3-2. 反省点

  • 実験の効率化をするのが後回しになってしまった。
    コンペが進んでいくと技術的負債がすごい勢いで積み上がっていきますが、短期間だからと負債を抱えたまま走り切るより、途中途中でリファクタリングしたほうがよいと感じました。
  • 検証が十分でない実験結果をむやみに発言しない。
    チーム全体が誤った実験結果に引っ張られてしまい、時間が無駄になる。(終盤、脳が働かなくなって、発言を推敲する能力が著しく低下しました。。)
  • ログをあまり残さなかった。
    この実験なんだったっけ…どういうソースコードで、どういう特徴量を使ったんだっけ?とならないようにしたいです。(と思っていたが、結局最後まで着手せず、最低限のログしか残しませんでした…。)
  • 途中までテストコードを書かなかった。
    ガッツリ書く必要はありませんが、最低限確認したいことをassert文で書いたらよかった。バグで1週間進捗が止まったりして、結構しんどかったです。
  • 睡眠時間を削った結果、集中力が低下した。
    学習経過をぼーっと眺める、学習中のモデルを応援する(学習経緯を眺めながらauc上がれ!上がれ!と声をかけ始める等)ようになったら、集中力低下のサインです。kaggleやるときはkaggleやる、やらないときはやらない、メリハリをつけるのが大事だなと痛感しました。

3-3. かかった費用

16000円でした。他に趣味もないしこれくらいいいかな。

  • GCP: 9000円(CatBoostの深いdepth実験)
  • PCのメモリ32GB→48GBに増強 8000円。コスパめっちゃいいのでオススメです。50万件×2000特徴くらいまでは実験できました。

3-4. 所感

  • ユーザーIDを特定できた人とできなかった人で勝敗がわかれたコンペでした。個人情報の縛りがありユーザーID公開できなかったそうですが、この特定作業は若干不毛な作業でした。
    (そういう意味ではHomeCreditは良コンペだなと…)
  • 今回使ったモデルが実務で活きるのか不明。ユーザーIDを特徴に入れてしまっているので。
    Public/PrivateでPrivateスコアがかなり下がってしまっていることからも、Train/Publicにoverfitしていると思います。
    モデル作成が社会貢献につながってほしいので、次はそういうコンペを選びたい。

4. まとめ

IEEE CIS Fraud Detectionコンペの活動記録をまとめました。
少しでもみなさまのお役に立てたなら幸いです。
長文となりましたが、最後まで読んでいただきありがとうございました。

*1:第2回も11/23に開催されるようです。初心者の方でも、開催者が手取り足取り教えてくださるので、初心者から上級者のみなさまにおすすめのイベントです! atma.connpass.com

*2:このキーを発見した方の名前にちなんで、弊チームではtakoiIDと読んでいました笑

*3:ハイパーパラメータの意味は以下ブログを参考にしました。nykergoto.hatenablog.jp
ハイパーパラメータ調整方法は以下ブログを参考にしました。(IEEEコンペ5位の@MLBear2さんのブログです。終盤MLBear2さんのチームと競ってたので、「敵に塩を送っちゃって大丈夫か〜〜〜?」と思いながら読んでいたのですが、結局大敗しました…)
naotaka1128.hatenadiary.jp