nykergoto’s blog

機械学習とpythonをメインに

Kaggle Days Tokyo のオンサイトコンペに参加しました! #kaggledaystokyo

Kaggle Days Tokyo で開催されたオンサイトコンペに参加してきました!! 結果としては全体 88 チーム中 private で 56 位という悔しさの残る結果になりました。が同時に反省点と学びもとても多い素晴らしいコンペだったので、感想兼反省文を書いていこうと思います。

f:id:dette:20191214155803p:plain

Kaggle Days Tokyo は2日間ありましたが、僕は体調不良のため2日目しか参加できませんでしたので1日目のことに関しては他の方の記事を参照していただければと思います。

どんなコンペだったか

日経電子版のログデータをもとにして、閲覧しているユーザーの年齢を当てるというタスクです。メインのテーブルには

  • ユーザーid
  • 見ているデバイス情報
  • 記事のid

などがありこの記事 id が記事データと紐づくような構造になっています。記事データには

  • タイトル (1/2/3)
  • キーワード
  • 本文
  • 記事の発行された日時

などがありました。

基本的な戦略

僕は弊社でインターンをしてくれている もーぐりくん と一緒にチームで参加しました。 僕はオレオレのフレームワーク vivid があり初速がある程度出せるだろうという見積もりがあったので、僕が基本的なモデリングを行い feature importance など特徴量の重要な話などは共有しつつ、最後にマージしましょうという作戦を取りました。

NYK510 TimeLine

ML Bearさん が当日の timeline を書かれていて、後で反省するのに良いと思ったので僕も真似します。 正直かなり切羽詰まっていたのでほぼ覚えていないのですが git の message とともに振り返っていこうと思います。

  • 10:35 initial commit
    • atma-cup #2 のリポジトリをコピペしてスタートしました。
  • 11:22 [update] version-1 feature
    • データの構造と submission すべき情報を見て submission までの雛形を作成していました。このときにつかっているのはメインのテーブルだけでした。
  • 11:50 [update] first submit
    • 最初のサブミット. 単体モデルでは Objective="poisson" の LightGBM が一番良かったためそれで submit しました. この日で一番余裕があったタイミングだったと思います。
    • 一旦 10 モデルぐらい作成して ridge 回帰も作っていたのですがそれぞれのモデルのチューニングが適当すぎたのか CV と LB の差が激しく撃沈
  • 12:01: FastText のモデル作成完了
  • 13:10: [update] add pseudo labeling
    • pseudo labeling をやってみたかったので実装してみていました。盛大にバグって精度が出ませんでした。このあたりから焦り始めます。
  • ~ 14:30 [fix] bug
    • NN を無理やり入れようと苦戦して bug と戦う羽目になりました。また optuna での tuning を回しながら特徴を作ろう、であったり記事のテキストを整形しよう、みたいな欲張りをしてそちらでも bug と戦っていました。この間の進捗は虚無でした。
    • このあたりで一回もーぐりくんとMTGをして正気を若干取り戻し、記事情報を少し入れました。またもーぐりくんアイディアの特徴も入れてスコアは良くなりました。
  • 15:17: [update] swem enbedding
    • FastText で作った特徴量で記事情報の SWEM をしました。この時点でかなり上位陣と差をつけられていて差分がわからず混乱に陥っていました。(後で判明しましたがこのときロジックのミスでちゃんと本文情報を使えていなかったみたいです)
  • 16:47 [update] genre
    • 記事のジャンル情報を入れてみて若干のスコア改善。もすぐに抜かされまくるという状況。
  • ~ 18:30 気づくとコンペはおわっていた

ふりかえり

悪かったこと

チームのメリットを活かせなかった

最初はまだしも、途中からは自分のことでいっぱいになり、全くチームとして機能していませんでした。

基本的に今回僕は独創的な特徴量は考えられませんでしたが、すこしの議論しかできませんでした。 一方でもーぐりくんはユーザーがどういう気持で閲覧するかなどを考慮した集計方法などを提案してくれていて、非常に頼もしかったです。 そのアイディアのパワーを生かしきれなかったのは反省です。

実装の正確性

今回は家のPCに ssh 接続して分析を行っていたのですが noetbook のバグ? かなにかで jupyter 上のブラウザからささっとファイル閲覧ができなくなり、最終的な output の特徴量をエクセルで確認するような作業をふっとばしていました。 これによって、よくある「概ねはあっているが、1行違うために生成される特徴量がおかしい」というパターンに陥って、性能がでないという自体になっていました。

次の日コードを見て気づいて修正すると Private で 11.7036611.41330 (13位相当) になりました。悔しい。

知っている内容を使えなかったこと

今回の solution ではほとんどすべてのチームが TargetEncoding を使ってカテゴリの埋め込みを行っていて、どのチームもとても良く効いたとのことでした。 僕はリークを恐れて最後まで「TargetEncodingを使う」ことが頭の中の選択の一つにも上がっておらず、ここで大きな差をつけられてしまった感があります。

ではこの情報に僕が到達できなかったかというと否で、なんなら atmaCup#2 でも target encoding の使い方が肝でしたし分析コンペLT会のハクビシンさんの発表も、なんなら前日の Jack さんの発表でも TargetEncoding 大事だよーということは言われていて twitter などでも耳にしていましたから「TargetEncodingの実力を過小評価していた」自分の落ち度です。

hakubishin さんのスライド: Target Encoding はなぜ有効なのか

speakerdeck.com

Jack さんのスライド: How to encode categorical features for GBDT

speakerdeck.com

ちなみにメインテーブルのカテゴリ変数に対して TargetEncoding を投入すると Private で 11.7036611.25799 (3位相当) になりました。悔しいなあ。

良かったこと

もーぐりくんがチームメイトだったこと

多分僕が二人のチームだったらもっと悲惨でした。ありがとう。

vivid で完走できたこと

今回のコンペで与えられていたテーブルは予測する際に一度ユーザーで集計をする必要があるデータでした。 そのようなデータは vivid を作ったときには全く想定していなかったのですが特徴変換の部分とそれを pipeline 的に行う部分を分離して実装していたおかげで対応できたので構造化がある程度できていたのかなと思っています。 またある程度の初速を出せたのは(あとでめちゃ抜かされてはいますが)うりではあるのでそれも良かった点でしょうか。

使っているうちに出てきたダメポイントについても、体を張ったテストだと思って、全部 issue にしてより強くしていきたいと思います。

たくさんの Kaggler と一緒にオンサイトコンペに参加できたこと

これは何者にも変えられない体験でした。特に夕方のある程度形勢が定まってきたような段階でも皆黙々と作業に打ち込んでいる様子は流石だなあと思っていました。 同じ場に参加できたこと、大変嬉しく思います。

またコンペのタスク・データについても非常に噛みごたえのある面白いデータでした。提供いただいた日経さん、また設計を主に担当されたであろう u++ さんに感謝です。

学び

精神的なこと

いつもは開催者側にいるのでなんとなーく大変なことはわかっていましたが、時間制限付きのコンペの大変さは僕の想像を遥かに上回っていました。*1

もとから知っていはいましたが僕がとても予想外・いつもと違う状況に弱いことを改めて知ることができました(悲しいことにほとんど覚えていないのですが夕方からコンペ終了までの僕はかなり狼狽していたと思います)。 そもそも、そういう状況になることを思って行動していない & 腹をくくって思考を切り替えられないのは分析コンペだけではなく必要な能力だと思います。正直ちょっとどうやって鍛えたら良いのかがわかっていませんが、精進します。

追試験

勉強のため上記で触れたような WordEmbedding・Pseudo Labeling*2 まわりなどのバグの修正 & TargetEncoding の追加等の修正など行い LateSubmission をしてみました。 マシンパワーの関係で LightGBM の SingleModel しか試せていませんが 一番良かったのは Poisson の LightGBM で Private 11.17052 (2位相当) となりました(SeedAveraging や Stacking など行えばまだ上がるかもしれないです)。

五月雨ですがやったことで効いた・効かなかったはざっくり以下のような感じになりました。

  • 効いたこと
    • target encoding
    • pseudo labeling
      • やるごとにじわっと良くなる (private で 0.02程度?)イメージでした。3回ぐらいまでは有効でした。(それ以上はサチって止まってしまう感じ)
    • SWEM (simple word embedding) での記事タイトルや本文、タイトル、キーワードの埋め込み
    • Objective=RMSE 以外で解く
      • 自分の範囲内では Poisson が一番良かったです
      • いま Log 変換を試していないことに気が付きました。後でやる。
    • ユーザーと記事との関係から記事の embedding の作成
    • seed averaging
    • キーワード全体での count で置き換えて集計 (mean/sum/std)
    • ユーザーごとのアクセス時間ヒストグラム特徴量 (もーぐりくん案)
  • (自分の範囲内では)効かなかったこと
    • one-hot 化して多クラス分類として解いた後に期待値に直す
      • 画像からユーザーの年齢を当てるタスクの論文で上記の方法のほうが regression よりも有効だった旨が書いてあったのを思ってやってみましたが余り効果ありませんでした。*3
      • そもそも学習させたモデルが LightGBM だったので駄目だったのかも知れません
  • 効果が微妙だったこと
    • カテゴリ変数の one-hot encoding
      • 水準数がとても多いカテゴリも存在していたので Target Encoding のほうが効率もよくあえて one-hot する必要はなかったかなと思っています。

コードは以下においてありますのでもしよろしければ。vivid 使ってます 😌

github.com

やっていて、当日の8時間の中でこれをやってしまう上位のチームのパワーに圧倒されるばかりでした。しかも優勝チームの pocket さんに話を伺ったとき、全部スクラッチで書いてーという話をされていて、圧倒的な自力の差を感じました。

最後に

このような機会を頂いたKaggle Days Tokyoの運営の方々, データ提供頂いた日経様, スポンサード頂いている会社様, ありがとうございました。 次回以降も機会あれば是非参加したいです!

参考文献

naotaka1128.hatenadiary.jp

py2k4.hatenablog.com

*1:あらためてatmaCupもそうですがオンサイトで勝つ方のタフさを感じました

*2:限定的ですが Pseudo Labeling は効果がありました

*3:ちょっと読んだのが昔なのでうろ覚えですがたぶん DAGER: Deep Age, Gender and Emotion Recognition Using Convolutional Neural Network この論文