【質疑応答付き】Kaggle Tokyo Meetup #6 に参加しました
DeNAで開催された、Kaggle Tokyo Meetup #6の参加記です。YouTube配信の視聴を含めるとMeetup参加は3回目ですが、回を増すごとに情報量も発表内容の多様性も増しているように思います。
本当にすばらしい発表を皆様ありがとうございました。僕も発表やLTでコミュニティに貢献していきたいと思います。
この記事では、各発表について僕自身が面白い・知らなかったと思ったポイントを中心にまとめ、可能な限り出典などのリンクをつけています。 内容を網羅しているわけでは必ずしもありませんので、もとの資料を併せてご覧になることを強くおすすめします。素晴らしい資料なので。
それでは、15000文字を超える長い記事ですが、最後までお読みくださると幸いです。
- Opening Talk (threecourseさん)
- Petfinder 2nd Place Solution (Wodoriチーム)
- iMet 7th Place Solution & 画像コンペのアプローチ (phalanxさん)
- Quora Insincere Questions 10th Place Solution & 昔話 (tksさん)
- PLAsTiCC 3rd Place Solution (nyanpさん)
- スポンサーセッション (hoxoshさん)
- Neural Network のご機嫌取りがしたい(tawatawaraさん)
- kagglerのためのAllenNLPチュートリアル (tamakiさん)
- Freesound Audio Tagging 2019 4th Place Solution (oumedチーム)
- Large-scale Landmark Retrieval / Recognition under a Noisy and Diverse Dataset (smlyさん)
- Santander Customer Transaction Prediction 2nd place solution (onoderaさん)
Opening Talk (threecourseさん)
- イキっている人もちゃんとバリデーションを切っている
- Kaggle Masterもまぁ間違える。カバーしている知識も違う
- いまはソリューションの発表が多いが、それはそちらのほうが準備が楽だから。Tipsなども歓迎。アウトプットしていきましょう
Petfinder 2nd Place Solution (Wodoriチーム)
- 特徴量作成から学習、予測まで2hに収めるkernel onlyコンペ
- 3種類の特徴量を作成し、4モデル(NN/LGBM×2/XGBoost)のridge stacking
- LGBMしか使っていない人が多かった。モデルの多様性が差別化ポイント
takuoko part
- テキストの処理
- 前処理を施したあと、BOW/tf-idfをして、それを行列分解にかける
- できるだけembeddingにおさめて、embeddingの平均を特徴量にした
- 画像の処理
- Kerasのdensenet121とInceptionResNetV2を採用
- KerasとPyTorchで精度が全然違う
- テーブルの処理
- 基本的なアグリゲーション
- 平均との比、平均との差などの特徴を作成
- カテゴリの共起を取り、行列分解
- 交差項の作成
- 絵文字の情報やマレーシアの州の情報などをexternal dataとして利用
- Kernelは間違っていることもあるので、自分で考えよう
- (これはほんとそう!)
- 特徴量は合計4000-5000ほど作成。importanceのTOP1000を利用
- NNはAdam/CLRを利用
kaerururu part
- CNNの出力、doc2vec、tfidfを利用した
- これらはすべて高次元。低次元にいろいろ潰してみて、スコアが伸びたものだけを利用
- 画像のサイズ、幅、高さを特徴量にする
- 「cute」で検索して出てきた犬猫画像データセットで学習したモデルで予測したcute特徴を入れたが、効かなかった
- クレンジングが足りなかったからでは?
- (アイディア自体は面白いので、外部データを利用できるコンペではやってみるといいかも?)
- (そこそこめんどくさそう)
gege part
- はじめてのコンペだった(!?)
- Kernelをベースに粛々と進めた
- testっぽくないtrainデータを10%程度削ったが、精度向上はしなかった
- 基本なにしてもダメだった
ynktk part
- 4モデルをstackingした
- LGBM < Average < Ridge だったのでRidgeを採用
- Ridgeに入れる前の前処理でランク変換を行った
- foldごとに予測値の分布が異なりがあったのでは?
- ランク変換によって予測値分布の差を吸収
- QWKは閾値に敏感。予測分布の差があると悪影響がある
- 後処理
- ImageHashを使って重複画像を探索→train/testで重複している画像はtargetで置き換える
u++ part
質問
- 1位との大きな差はなに?(smlyさん)
- まじでわからん
- 外部データが効いたらしいが、再現してみたら全然効かなかった
- ultimate model作ろうとしたらいまのtrainにoverfitするんじゃ?(smlyさん)
- 今はロバスト/lightweightなモデルを作ろうとするムーブはあんまりない
- あくまでボランティアとしてやっているので、他の参加者もそこまでモチベーション高くはない
- interaction featureは何を意識して作ったの? 普通はビジネスセンスが必要(smlyさん)
- 今回は総当たりで効きそうなのを試した。列が少ないからいける
- 全然効いていない特徴量は総当たりの対象から外した
- ods.aiに有用な情報はあった?(koukyoさん)
- petfinderのときには、ods.aiにしかない情報はなかった
- これが話題だから見よう、みたいな情報は取れた。取れてしまった
- テキストの情報をクラスタリングしたら効く、みたいな話があったが、やった? (pocketさん)
- やってないです
- 閾値が大事という話だが、どう決めた? (pocketさん)
- 問題をregressionで解き、CVの値が良くなるように閾値を決めている
- Discussionを見てください
- https://www.kaggle.com/c/petfinder-adoption-prediction/discussion/76107#latest-502207
- 実際閾値はprivateでも最適だったの?(onoderaさん)
- 検証はできていない
- QWKが0.4くらいになるので、それって全然あたってないよね。タスクとして難しかった
- 各FoldでQWKの閾値はどれくらい変わるのか? データの分布が違うと閾値はかなり敏感にスコアを変えるのでは? (maxwellさん)
- ログとしては取っているが、詳しくは調べていない
- RescuerIDを利用したGroupKFoldを利用した
- 最終サブミットはFoldの切り方を変えた2つを提出した
完全に記憶の彼方に飛んでましたが、チームではkappaとrmseを合わせて見てました!#kaggle_tokyo https://t.co/JyNmHFJ5gn
— u++ (@upura0) 2019年7月13日
iMet 7th Place Solution & 画像コンペのアプローチ (phalanxさん)
壺コンペソリューション
- 推論だけKernelで回すタイプのKernel Onlyコンペ
- 2nd stageでテストデータが5倍になるので、それを考慮して提出しなければいけない
- アノテーションがまぁまぁ雑。ラベルが欠けている画像が多くある
- class imbalanceがある
- モデルをひたすら積み続けた。ResNet34も最後まで生き残った
- ラベルごとに最適なモデルが違ったので、アンサンブルが効くと思った
- cultureとtagで根本から別々のモデルにした
- Grad-CAMを見ても見ている画像の部分が違う
- 全結合層だけ分けるのが普通だが、それだとあまり上がらなかった
- すべてのモデルにこれをやると時間が足りないので、強いモデルだけやった
- 蒸留を頑張っていたけど、効かなかった
- 蒸留をがんばればモデルが小さくなるので推論時間を稼げるのでは?
- attention transfer (activation-based method)を使った
- studentのattention mapをteacherのattention mapに近づける手法
- focal lossとlovasz lossを採用したが、ほんの少し良くなっただけ
- 頻度が少ないクラスはdropしたが、意味がなかった
- 過去にGibaがやっていた
- 長い画像には特に何もせず、resizeして突っ込んだ
- 上位の人を見ても対処している人はいなかった
- CNNでstackingした
- 過去コンペでKeepLearrningがやっていた
- 1000クラスあるので、全部ridgeはやってられない
- CNNでモデル間の相関を学習し、denseで特徴間の相関を学習する
- チームメイトの予測をスタッキングに載せた
- 1st place solutionの紹介
- batchsizeを1000-1500と大きい値を設定(ノイズの軽減?)
- 誤差が大きい画像を弾く
- ImageNetのpredictionや画像の長辺の長さなどの特徴をLightGBMにかける
- psuedo labelingとhard example miningをちゃんとやれば精度が上がった
画像コンペのアプローチ
- yamlを書いたら全部走るpipelineがある
- 壺コンペはyamlを書き換えつづけるだけ
- 初日
- ルールとかOverviewとかをちゃんと読む
- mean, std, min, maxをRGB, HSVのチャンネルごとに確認する
- めちゃくちゃ明るい/暗い画像のチェック
- 画像サイズのmean, std, min, maxなどを確認
- targetの分布を確認
- 画像を目で見る
- resnet18, 34などの小さいモデルを作った後、resnet101, 152に進む
- optimizerはAdamを使う。
3e-3 〜 1e-4
の範囲をコンペによって変える - image_sizeは
(mean/2, mean/2)
,(mean, mean)
,(mean*2, mean*2)
を試す
- ベースラインの予測を作ったら、予測を確認する
- CVとLBの違いや、attentionを可視化
- サーベイ
- ドメインの論文が効くことがある
- 読むだけじゃなくて実装もする
- パイプラインは作ろう
- 時間を効率的に使う。学習回している間に論文を読んだりコーディングをしよう
- Twitterとかじゃなくてね
- 終盤
- ドメイン特化の論文を読む(10%)
- アンサンブルに備えていく
質問
- ラベルが信用出来ないとき、ルールで許されていたらハンドラベリングしたりするか?(kaerururuさん)
- 1103クラスに対してはハンドラベリングはだるすぎるのでやってない
- 今回はpseudo-labelingをやれば十分だった
- optimierをAdam固定にはどんな意図があるか?(koukyoさん)
- そこは本質的な価値じゃないので、Kagglerとしては本質的なところで戦うべき
- 蒸留がうまくいかなかったのはなぜ?
- 予測に対してL2ノルムで最適化した。しかし、出力が小さいところまで合わせる必要はなかった
- 今回はpseudo-labelingで十分
- 早い段階からたくさんモデルを作るという戦略か?
- 最近のコンペはだいたい、多くのモデルを混ぜればうまくいく
- 今回は特にラベルごとに最適なモデルが違った。ちゃんとそれを確認している
- CNNでアンサンブルするとき、モデルの並べ順によって差が出るのでは?(Jackさん)
- 学習時に順番をぐちゃぐちゃにしても学習可能なのでよくわかりません
- パイプラインについて。1080TiとKaggle Kernelで両方実行可能にするのはどうする?(flowlightさん)
- Kernelで実行する時は、モジュールをべた張りしている
- pseudo-labelingについて工夫を教えてほしい(u++さん)
- LBが最も高くなるような閾値で切ってpseudo-labelとして使う
- もともとのラベルをhard-label、pseudo-labelをsoft-labelとする。soft-labelの方はlossの寄与を小さくする
- phalanx-pipelineはどうやって完成した?(koukyoさん)
- segmentation/detection/recognitionでそれぞれパイプラインがある
- detectionはmmdetectionというライブラリがあるので、それに似た形にしている
- コンペごとに進化していっている
- 作り始めたのはタンパク質コンペのとき。新しいタスクに挑戦する時は、最初はパイプラインが崩壊することもあるけど、そうしたら組み直す
Quora Insincere Questions 10th Place Solution & 昔話 (tksさん)
Quoraコンペの解法
- 外部データ利用不可のKernel-Only
- Embeddingが4種類与えられている
- これをどう使うかがひとつのポイント
- BERT発表後初めてのNLPコンペ
- モデル構造はかなりシンプル
- 層を複雑なモデルを作ったことはない
- 小さいモデルをアンサンブルさせてスコアを取っている
- Embeddingの組み合わせで
4C2=6
個のモデルをアンサンブルしている
- CVをきちんとすることが勝利の秘訣だった
- 最初からaveragingすることは決めていたので、averageでのスコアで評価
- モデル構成が定まるまではtrain/dev/valの3分割を5-foldで行う
- スペル修正はしなかった(面倒だったので)
- ラベルノイズも多かった
- モデルの安定化のため、Exponential Moving Averageを利用する
- Embeddingの扱い
- concatすると次元が大きくなってしまうので、重み付き平均を使う
- Projection Meta Embedding
- concat後低次元に射影して、ReLUする
- 実行時間制限が大切
- EMAの更新回数を減らした
- 長さが近いものを同じバッチに入れると早くなるが、トライしなかった
- そんなに気分が乗らなかったからだと思う
- 反省点
- 長さが近いものをバッチに入れる、は最初に実装すべき
- statistical feature (質問の長さ、大文字だけの単語数、など)を入れておけばよかった
- 早期に選択肢を狭めている
- Local CVを信じれば、たまに良い順位に入れる
tksさんのsolutionの最後にEMA使っている論文がまとめられてる https://t.co/z1ouC6QgsR #kaggle_tokyo
— tamaki (@tamaki_730) 2019年7月13日
昔話
- Kaggle歴5年以上の人どれくらいいる?→ほんの少し
- Submission Limitも変わっている。個人的な意見としてはFinal Submissionは1個にしてほしい
- なにか問題があったら、オープンなフォーラムで話し合う文化は昔からあった
- 要望・提案があればどんどん出していきましょう
質問
- QuoraのEMAについてもう一度説明してほしい
- 重みの指数加重移動平均を取る
- EMAは学習率を単純に下げるのとは違いがあるのか?(upuraさん)
- 自分はoptimizerは据え置きでやるようにしている
- BERTではEMAは全然効かなかったので、チューニングは必要
- 同じ長さの文を同じバッチに入れるとなぜ早くなるのか?
- 長さをまとめなかったら最大長でpaddingする必要がある
- 同じ長さでまとめたら、バッチ単位でpaddingすることができるので早くなる
- 精度はほとんど変わらないはず
- Freesoundでやったとき、paddingが長すぎると精度が下がりました(osciiartさん補足)
- 出るコンペはどういう基準で選んでいる?(koukyoさん)
- 基本、簡単そうなやつ
- 今と昔だと上位に行く難易度に違いはあるのか?(pocketさん)
- 昔の方が断然簡単だった
- 昔はいけたけど、今は悩んでいる人はたくさんいると思う
- 昔はツールを作ってるリサーチャーが多かったからそれに勝つにはツールを作るしかなかった(smlyさん補足)
(おまけ)僕の参加記
PLAsTiCC 3rd Place Solution (nyanpさん)
公開してくださっているGitHub
解法
- 特徴量エンジニアリングが好き
- 突発天体・変動天体のように、明るさが変わるような天体を見つけたい
- いままでは漏らしていたけど、LSSTの運用で発見できるようになるはず
- これまで人類が発見した天体は1万くらいしかないのに、LSSTは10年間で1000万程度発見してしまう
- 機械学習は必須
- 突発天体を早期検出して、後続の観測機器で精度良く観測する
- LSST自身でも自動分類したい
- Light Curveから天体を特定する多クラス分類
- かなりimbalanceなデータセットで、少数クラスは30件程度
- minority classほど1データの重みが大きいので、sample_weightなどで補正しないと良いスコアが出ない
- 超新星の爆発のときには観測ができていなくて、残り滓しか見えないようなケースもあるので難しい
- trainは既存の観測データ、testはLSSTが観測するもの、という問題設定だった
- ノイズの大きい観測データをどうするかが争点
- Gaussian Process
- NN
- Template Fit
- Class 99をどうするかもスコアを大きく左右したが、Kernelに準じても金メダルは取れた
- LSST Science Bookという文献を読むことで、ドメインのエキスパートがどこに注目して分類しているかがわかった
- (ドメインspecificな知見を調べるのが大事)
- マージした時点でnyanpさんは200特徴量だったが、mamasくんは80000個作っていた(!?)。yuvalは1個も作っていなかった(!?!?!?)
- CNN、LightGBM、CatBoostのアンサンブル
- CNN
- 欠損を線形補間/detected=1だけ抜き出して線形補間/最近傍の有効データとの時間差 をCNNに突っ込む
- 1DConvを数回行い、GlobalMaxPoolingにした後、メタデータの特徴量とくっつけてDenseに通す
- 時間方向のshiftについてクラスは変わらないはずなので、CNNが効きそうだし、augmentationとしても使える
- 特徴量なしでも金が取れた(!?)
- Template Fitting
- 遅い。テストデータに対しての計算は30日くらいかかった
- 並列化が効かなかったので、インスタンスを30個立てて30倍速にした
- コマンドは心をこめて手打ちした
- sncosmoというライブラリで試せるものだけで数十個ある
- 全部やった。CVであたりをつけてから、よいものを試した
- preemptible instanceの牧場管理をがんばった。最終的には60個まで増えた
- Gaussian Processも試した。時間がかかるのでやっぱり30インスタンス立てた
- tsfreshやfeetsといった時系列の特徴抽出ができるライブラリを使った
- ベースラインとしては使える
- pseudo-labeling
- class 90, class 42に絞ってラベルを付けた
- class 99はtestにしかなく、class 99に間違ったpseudo-labelをつけると大きくスコアを落としてしまう。class 99と遠いクラスのみラベルを付けた
- Adversarial Pseudo Labeling
- training dataに似ているサンプルを使ってtraining
- クラスごとに異なる重みでアンサンブルした
- OOFのpredictionの精度で決めた。サブミット数が残り少なかったため
- LB Probingでclass 99を決めた
- one-class SVMなどを利用した通常の異常検知はうまく行かない
- class 99はどんな天体なのか考えた
- class 99はすべてのクラスから均等に遠いことはないはず
- クラスごとに異なる重みを付けてみた
- モデルの方向性がみんな全然違ったのが良かった
質問
- Template Fittingのハイパーパラメータはどう決めた?(akiyamaさん)
- コードが見たい人はopenにしているkernelを見てほしい
- 時間がなかったので、あまり調整はしていないが、一部のパラメータはちゃんとlsstと設定しないといけない
- Fittingによって出てくるパラメータをそのまま特徴量として使う
- 1位との違いは何ですか?(u++さん)
- 1位はdata augmentationをやっていた。これが一番の差分
- train dataをtest dataの分布に合わせるため、遠方にシフトさせるaugmentation
- これにはかなりドメイン知識が必要
- これをやっていれば1位いけたかも
- 上位解法のばらつきが大きく、上位の答えをアンサンブルをするだけでスコアが上がる
- サーバー代はいくらですか?
- CatBoostはなぜ強かった?
- データにカテゴリ変数はぜんぜんなかった
- かなりoverfitしやすいデータだった。CatBoostに含まれているsymmetric treeとordered boostingという機構がコンペにマッチしていた
- ordered boosting
- t番目のデータに対する残差を計算するとき、t-1番目までのデータで学習したモデルを使う
- サンプルが少ないときに特に有効
- 比較した結果としても、ordered boostingが良かった
- 使う特徴量によってLightGBMとCatBoostでどちらが良いかは変わった
- 最近はCatBoostも使いやすくなっているので、スモールデータコンペだったら試す価値がありそう
- データ数が少ないとき、NNはoverfitしやすい。augmentationはどう選んでいる?
- そのクラスは何に対して不変なのかを考えるべき。cyclic shiftは自然な発想
- 観測ノイズが載っているので、ノイズを乗せても良さそう
- そんなにアクロバティックじゃないとは思う
- psuedo-labelingについて。コードは公開している? OOFでやるなど実装方法もいろいろあるが、どれを使った?(pocketさん)
- GitHubを公開しているので、どこかに埋まっているはず
- ラベルを付けた数がtrainの10倍になるくらいにした。基準は実験的に決めた
- Gaussian Processはどうした?(onoderaさん)
- fittingして、そのまま特徴量として突っ込んだ。そんなに寄与は大きくなかった
- 1位はGaussian Processでデータを補完してその波形上で集約して特徴量を作っていた
スポンサーセッション (hoxoshさん)
- DeNAの事業は多岐に渡っていて、Kagglerが各地で活躍している
- 合宿(magic camp)に行きました。今度も行きます
- @DeNAxAI_NEWSをフォローしてください
Neural Network のご機嫌取りがしたい(tawatawaraさん)
- テーブルより画像のほうがメダル取りやすいのでは?
- batchsizeが小さいと一部のサンプルに引っ張られがち。batchsizeを大きくしたらlrも大きくする
- 基本的にbatchsizeは大きく取りましょう
- lrはLR-RangeTestで決めよう
- fastaiのライブラリでlr_findとして実装されている
- 学習率のスケジューリングは正直好みでは? warmupは有効だとは思う
- 学習率が不変 or AdamならEarlyStopping
- initializerはscaleを小さめに
- TuningのためのCourseraもある
質問・コメント
- fastaiのfind_lrはモデルやデータで良いlrは変わる?(osciiartさん)
- chainerで自分で実装しているからfastaiのは実はわからない
- 実際モデルやデータで変わりそうではある
- scaleを小さくすると学習は安定するが、結果はscaleが大きい方が結果は良くなりそう。回数を打てるならscale大きいほうがよさそう(owrubyさん)
- batchsizeが大きすぎると汎化性能が下がるのでは? BERTだとそのためのoptimizerがある
kagglerのためのAllenNLPチュートリアル (tamakiさん)
資料
- GitHub: https://github.com/RyujiTamaki/kaggle_allennlp
- Kernel: https://www.kaggle.com/decoflight/allennlp-example
内容
- 最近はテキストが有害かどうかのコンペが多い
- かなり分割がうまくいっていて、書いていて気持ちいい
- DatasetReaderがテキストファイルからInstanceのリストを返してくれる
- Modelがtorch.nn.Moduleのサブクラスを返す
- AllenNLPはjsonから実験ができる
質問・コメント
- NLPコンペで初心者が詰まりがちなところを教えてほしい(u++さん)
- NNと同様で、学習率どうする?みたいなポイントはある
- ベースラインを作るまでにはハマりポイントはあんまりない。それ以降何をするかが大事
Freesound Audio Tagging 2019 4th Place Solution (oumedチーム)
- 医学部6年なので、実はKaggleしてる場合ではない
- domain adaptationも必要
- 人間の聴覚はlog-scaleなので、それを反映したlog-melspectrogramが主力
- スペクトル画像に変換したらほぼ画像分類として扱える
- 時間方向に圧縮すると意味が変わってしまう
- 波形とlogmelの両方使った
- 波形についてはEnvNet-v2を使った
- MixUp/BC learning
- 画像と違い、音声は2つの音声が混ざるのはかなり自然
- cropするより、全長を使ったほうが良かった
- 最初の方にクラスを決める要素が多かった
- noisyの使い方がポイントだった
- 多くの参加者はpretrainとして使ったが、OUMedではマルチタスク学習として使った
- soft pseudo-labeling
- マルチラベルに対応できないし、分布の差によって信頼性が高いラベルが得られない
- 疑似ラベルを0, 1にしない
- 長さの異なるモデルを複数用意してaveragingするのが有効だった
- 外部ラベルデータダメなのにハンドラベリングOKだった→1位はそれ
- 2位はlogmelの変換もlayerとして入れて、パラメータ探索した
- 2位は時間軸方向はGlobalMaxPooling、周波数方向はGlobalAveragePooling
- 意味合い的にも妥当
- Kaggleだと半教師付き学習はpseudo-labelingが最強だよね
- 論文で言ってるpseudo-labelingはKaggleのpseudo-labelingは全然違う
- biasがかかっていた瞬間に論文のpseudo-labelingは死ぬ
- Kaggleのpseudo-labelingは蒸留に近い
質問・コメント
- BC learningについて。もとはマルチラベル対応ではなかったのでは?(koukyoさん)
- このコンペはほぼシングルラベルみたいなものなので、えいやでやってしまう
- powerだとそのままmixupしていいけど、dbでmixupするとおかしくなる
- マルチタスク学習について。どういう学習方法を使っている?(koukyoさん)
- noisy/cleanはそれぞれに対応したheadを使っている
- 推定は別々でやって、backpropは同時にやっている
- lossの足し合わせは1:1で行っている
- 1次元側の工夫はあるか?
- 基本は論文の実装をそのまま使った
- 単体ではあまり精度は高くなかったので、あくまでアンサンブルの材料
- 得意なクラスが違ったかも
- 半教師付き学習について。本来は十分教師がない状況に使うためのものなのでは? 今年のlandmarkコンペでは半教師付き学習が使われている(smlyさん)
- 適切な目的のところでは半教師付き学習は使える
Large-scale Landmark Retrieval / Recognition under a Noisy and Diverse Dataset (smlyさん)
- 巨大なデータセットに対して学習
- 半分以上のクラスが10枚以下というimbalanceなデータセットで、しかもノイズが多い
- single modelでもbestfitting以上が出せるので、アンサンブルしないと勝てないのは甘え
- 画像検索で大事なのは2つしかない
- データセットをキレイにする
- 去年のデータセットはキレイだったので、それをベースにクレンジング
- (そもそも去年のコンペに参加していないと難しい)
- 近傍を取ってきた後、spatial verificationでキーポイントが保存されていることを確認する
- 去年のデータセットはキレイだったので、それをベースにクレンジング
- 1回recognitionのタスクを解き、ラベルが一致するものを上位に置く
質問・コメント
- 計算リソースはどれくらいですか?(osciiartさん)
- pairwiseの学習だとリソースがたくさん必要だが、ArcFaceなどならV100×1×1日くらいで済む
Santander Customer Transaction Prediction 2nd place solution (onoderaさん)
「結局、Kagglerは何を必死にやっているのか?」というLTをしました
弊社では毎週、新卒LT会が開催されています。 エンジニアがメインですが、同期が職種を問わず広く集まる場になっています。 僕もなにか話したいと思い、7/5(金)に発表しました。
タイトルは「結局、Kagglerは何を必死にやっているのか?」です。
スライドはSpeakerDeckにアップしていますので、何かに使いたい方がいらっしゃいましたらぜひどうぞ。「使ったよー」って言ってもらえるとめちゃめちゃ喜びます。
LTをしようと思ったきっかけ
「Kaggle」や「Kaggler」という単語はデータサイエンス界隈では通りが良くなってきており、Kaggleについて詳しく知っているという人も多いでしょう。
しかし、ビジネス職の方はKaggleについて知らないという方も多いですし、「Kaggle」という言葉を聞いたことがあっても、何が行われているのかきちんと理解している人は少ないのではないでしょうか。
Kagglerが適切に評価され、活躍していくためには「なんか内輪でワイワイやってる連中でしょ」という立ち位置から抜けていくことが大切だと考えています。 そのためには、Kagglerの得意なことや、どのような形で事業に貢献できるのか伝えていくことが重要なのではないでしょうか。
これがマジメな理由です。
マジメじゃない理由としては、「Kaggleってなんなの?」って聞かれたときに「ほらこれ見て」って言える資料を作っておきたかったという感じです。 Kagglerのみなさんはn回この質問をされているでしょうから、この資料が何かの助けになれば嬉しいです。
(おまけ)スライドを作るときに気をつけたこと
- 聴衆が誰かを意識する
- 話す目的が何かを意識する
- 太くて大きい文字を使う
- 真っ黒い文字は圧が強すぎるのであまり使わない。
#333333
くらいが個人的には好き - 線や矢印など、情報が少ないものは薄く表示する
- Unsplashでキレイな画像を取る
- 「laptop」「business」「mountain」とかはかなりそれっぽい
- icooon-monoのアイコンを使う
- アイコンの色は文字色に合わせる
ちょっと設定するとM PLUS 1pっていうフォントをGoogle Slideで使うことができるんだけど、これが程よくポップでかわいいのでLTにおすすめです。 pic.twitter.com/eSjcbEwAYH
— えじ|Amane Suzuki (@SakuEji) 2019年6月23日
なぜn_estimatorsやepochsをパラメータサーチしてはいけないのか
ハイパーパラメータを探索するため、グリッドサーチやOptunaなどを利用することがあると思います。
しかし、「ハイパーパラメータ探索してみた」のようなQiita記事などでは間違って書かれていることも多いのですが、XGBoostやLightGBMの n_estimators
( num_boosting_rounds
)やKerasの epochs
をパラメータサーチの対象にしてはいけません。
いけません、というか、すごく無駄なことをしています。
なぜ、n_estimatorsやepochsを探索すると無駄なのか
理由はシンプルで、これらのパラメータは「大きい値で精度確認する過程で小さい値の結果も分かる」からです。
LightGBMのn_estimators
は構築する決定木の数を表しています。
例として、n_estimators=5
(こんな小さい値で学習することはないですが、簡単のため)で学習を行ったとします。学習の過程で、n_estimators=1
のスコアも、n_estimators=2
のスコアも確認することができます。
1, 2, ..., 5とn_estimators
を振って確認した場合、合計15個の木を構築する必要がありますが、正しくやれば5個の木で十分です。単純計算で3倍、探索を高速化することができるでしょう。
n_estimatorsは学習率によっては5000程度になることもざらにあります。500, 1000, 1500, ... , 5000と500刻みに探索した場合、合計で27500個の木を構築することになります。本当なら5000個で済むのに。しかも、500刻みで探索していた場合、最適な点を見逃す可能性もあります。
Kerasのepochs
に代表される、ニューラルネットワークのepoch数も同様です。
epochs=100
で学習した場合、その過程で1epochから100epochまでのすべての学習状態を取得することができます。
では、どうすればいいのか?
一般的なのはEarlyStoppingを使用することでしょう。
テストデータとは別にバリデーションデータを用意し、バリデーションで対しての精度がサチったら(一定epochのあいだ向上が見られなかったら)終わりにするという方法です。
LightGBMのscikit-learn interfaceを使っているなら、eval_set
とearly_stopping_rounds
を指定すれば動きます。n_estimators
は大きめに取っておくとよいでしょう。
import lightgbm as lgb params = { 'n_estimators': 10000 # 大きめにとっておく # 他のパラメータは省略 } model = lgb.LGBMClassifier(**params) model.fit(X_train, y_train, eval_set=[(X_valid, y_valid)], early_stopping_rounds=100)
scikit-learn interfaceを使っていないなら、lgb.train
関数にvalid_sets
、early_stopping_rounds
を設定すれば動きます。num_boost_round
はn_estimators
と同様、大きめに取っておきましょう。
import lightgbm as lgb train_set = lgb.Dataset(X_train, y_train) valid_set = lgb.Dataset(X_valid, y_valid) model = lgb.train(params, train_set, num_boost_round=10000, valid_sets=[valid_set], early_stopping_rounds=100)
KerasでEarlyStoppingを行うならkeras.callbacks.EarlyStopping
を使います。使う場合はkeras.callbacks.ModelCheckpoint
と組み合わせ、最も良いモデルを保存するようにしておくと良いでしょう。
from keras import callbacks # モデルの定義とcompileは予めしておく callbacks = [ callbacks.ModelCheckpoint('best_model.h5', save_best_only=True) callbacks.EarlyStopping(patience=5) # 5epoch改善が無ければ止める ] model.fit(X_train, y_train, epochs=1000, callbacks=callbacks, validation_data=[(X_valid, y_valid)]
PyTorchなど、他のフレームワークの場合もEarlyStoppingの仕組みは実装されています。
まとめ
学習の途中経過が全て確認可能な手法を使うとき、n_estimatorsやepochsのような「学習回数」のパラメータを調整するのは時間がもったいないです。
他にもやりがちな例で各種ライブラリのverbose
をチューニングしてしまう、というものがあります。verboseは学習中にどれくらいメッセージを吐き出すかというパラメータで、モデルの精度には一切影響を与えません。精度が変わらないので、もちろんチューニングする必要はありません。
機械学習モデルをチューニングする時はそれぞれのパラメータの意味を理解した上で行うようにしましょう。使っているアルゴリズムに対しての理解を深めることも助けになるでしょう。
Dockerでデータ分析環境を手軽に作る方法
何かデータ分析を行わなければいけないとき、手軽に分析環境を用意したいというニーズがあります。
Jupyter Notebook上でnumpy、pandas、matplotlib、scikit-learnあたりが使えれば十分でしょうか。XGBoostやLightGBMといったライブラリも使えるようにしておきたいという人もいるかと思います。
一方、ローカルにいろいろなライブラリをインストールしていくと、次第に環境が汚れていってライブラリの衝突などが起こりやすくなってしまいます。
KaggleにはKernelという計算環境があり、そこには主要な機械学習ライブラリが予めインストールされています。データ分析をやっていく上で不自由はありません。今回はDockerとdocker-composeを使ってKaggle Kernelを手元に再現し、ポータブルな分析環境として使う方法を紹介します。
データ分析界隈には、なんとなくDockerを敬遠しているような人もいるかと思いますので、この機会に入門してしまいましょう。
解説はMacOSで行いますが、Dockerとdocker-composeがインストールできれば同じ方法が使えるかと思います。
登場する用語の説明
Dockerは、まっさらな環境を簡単に作るツールです。
Dockerfileというファイルに環境の設定を書けば、そのとおりの環境を作ることができます。他人が作ったDockerfileに自分なりのカスタマイズを加えることも可能です。
今回は、Kaggle公式が公開しているpython環境をもとにカスタマイズを行っていきます。
加えてコンテナ(container)とイメージ(image)という用語も覚えましょう。
イメージはDockerfileをビルドして作ります。イメージはDockerfileという設計図を元に作られた「型」だと考えて良いと思います。
イメージを元にコンテナを起動し、そのコンテナ上でJupyterを立ち上げます。利用する際はコンテナにアクセスします。イメージができていればコンテナは数秒で起動できますし、どれだけ環境を汚してもイメージから起動しなおせばまた使うことができます。同じイメージから何個もコンテナを起動することも可能です。
Google Container Registryは便利なイメージが多く登録されているサイトです。今回はKaggleが提供しているPythonイメージを利用します。
docker-composeはDockerfileのビルドと起動を管理するツールです。
Dockerコンテナを起動する際、いろいろなオプションをつけることができます。しかし、それをいちいちコマンドラインで打つのは煩雑です。docker-composeを使えば、様々なオプションをdocker-compose.ymlというYAMLファイルで設定することができます。
docker-composeは本来は複数のコンテナを連携して使うために作られたツールですが、今回はコンテナを簡単に立ち上げるためだけに使いたいと思います。
Dockerとdocker-composeのインストール
それでは、Dockerとdocker-composeをインストールしていきましょう。
既にインストール済みの方はこの節は読み飛ばしてください。
https://docs.docker.com/docker-for-mac/install/ に従ってDocker Desktop for Macをインストールします。
インストールが終わったら、アプリケーションからDocker.app
を起動します。(起動にはしばらく時間がかかるかもしれません)
Dockerが起動されたら以下のコマンドを打ち、dockerとdocker-composeのバージョンが表示されるか確認してください。
$ docker --version
$ docker-compose --version
Kaggle Kernelを元に分析環境を作る
それでは、Kaggleが提供しているDockerfileを元に、データ分析の環境を作っていきます。
今回は以下のようなフォルダ構成で解析を進めていくとします。
.
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── input/ # データ
├── notebook/ # Jupyter Notebook
└── script/ # スクリプト
このうち、以下の3つのファイルは新しく作成します。
- Dockerfile
- docker-compose.yml
- .dockerignore
Dockerfile
Dockerfileは、イメージの定義です。どういうOSを使って、どういうライブラリをインストールするのか、という内容を書きます。
# kaggleのpython環境をベースにする
FROM gcr.io/kaggle-images/python:v56
# ライブラリの追加インストール
RUN pip install -U pip && \
pip install fastprogress japanize-matplotlib
内容はこれだけです。
FROM gcr.io/kaggle-images/python:v56
は、Google Container Registry(gcr.io)上にあるKaggle公式Dockerイメージを出発点にしてイメージを定義する、という意味です。
gcr.io/kaggle-images/python
には、既にnumpyやpandasを始めとして、データ分析に必要な多くのライブラリが含まれています。
:v56
はタグの指定です。KaggleのDockerイメージは日々改善されているので、今の時点での最新版を書いておくと良いでしょう。タグの一覧はgcr.io/kaggle-images/pythonにアクセスすると見ることができます。
:latest
と指定して最新版を入れることもできますが、イメージが更新されるたびにダウンロードが走ってしまいますし、環境がころころ変わるのは望ましいことではないので、バージョンを特定しておくことをおすすめします。
もし足りないライブラリがあるならば、 RUN
コマンドを使うことで pip install
をすることができます。
docker-compose.yml
docker-compose.yml
にはコンテナを起動する際の設定を書きます。
version: "3"
services:
jupyter:
build: .
volumes:
- $PWD:/tmp/working
working_dir: /tmp/working
ports:
- 8888:8888
command: jupyter notebook --ip=0.0.0.0 --allow-root --no-browser
version
はdocker-compose.ymlの書き方のバージョンです。3を指定しておけばよいかと思います。
services
以下に起動するコンテナの設定を書いていきます。jupyter
はコンテナを管理するための名前です。
build: .
は同じフォルダにあるDockerfileをビルドする、という意味です。先程作成したDockerfileがビルドされます。
volumes
はマウントするフォルダの設定です。$PWD:/tmp/working
とすることにより、現在のディレクトリを、Dockerコンテナ上の/tmp/working
に紐付けることができます。これにより、Dockerコンテナの中からでも現在のディレクトリ(とその中のデータとか)にアクセスすることができます。
working_dir
はコンテナ内の初期ディレクトリの設定です。今回は/tmp/working
に設定しています。
ports
はポートフォワードの設定です。手元のポート:コンテナのポート
の形式で書きます。
ふだんJupyter Notebookを起動するとき、localhost:8888
からアクセスすることになるかと思います。今回はDockerコンテナ上でJupyterを起動するため、Dockerコンテナ内のlocalhost:8888
にアクセスしないとJupyterを見ることができません。しかし、portsの設定をしておくことで、Dockerコンテナ内の localhost:8888
を手元のlocalhost:8888
につなぐことができます。
command
はDockerコンテナを起動したときに走るコマンドの内容です。今回はJupyter Notebookを起動しています。
いくつか見慣れないオプションがついているかと思います。
--ip=0.0.0.0
は起動したJupyter Notebookにコンテナ外からアクセスできるようにするオプションです。
--allow-root
はルートでの実行を許可するオプションです。Dockerコンテナはデフォルトでルート権限で起動するので、このオプションが必要です。
--no-browser
はJupyter Notebook起動時にブラウザを立ち上げないオプションです。
.dockerignore
.dockerignoreは、Dockerfileをビルドする際に無視するファイルを列挙するものです。
今回の場合、データやノートブックはDockerfileのビルドの瞬間は必要ありませんから、.dockerignore
に書いておきましょう。(コンテナとのデータの共有は、ビルドを行った後にマウントで対応します)
input
notebook
script
input/
などに大容量のデータが入っていて、かつ.dockerignore
に記述もしなかった場合、Dockerのビルドの際に時間がかかるようになってしまいます。ビルドに必要の無いファイルやフォルダは、.dockerignore
に書くように心がけましょう。
起動してみる
それでは、実際に分析環境を起動してみましょう。
ここで注意ですが、この操作は初回時に限りめちゃめちゃ重いデータをダウンロードする必要があります。実は、Kaggleの公式イメージは便利なライブラリがたくさん入っている反面、とても重いDockerイメージになっています。その容量、なんと18GB!
学校や職場など、WiFi環境が整っている場所で行ってください。間違ってもテザリング中なんかにやらないようにしてくださいね。
$ docker-compose up --build
まず、最初にDockerfileのビルドが走り、その後Dockerコンテナの中でJupyter Notebookが起動されます。最後のJupyter Notebookが起動するところはだいたいこんな感じのログになるかと思います。
jupyter_1 | [I 07:11:32.796 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
jupyter_1 | [I 07:11:33.065 NotebookApp] JupyterLab extension loaded from /opt/conda/lib/python3.6/site-packages/jupyterlab
jupyter_1 | [I 07:11:33.065 NotebookApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
jupyter_1 | [I 07:11:33.068 NotebookApp] Serving notebooks from local directory: /tmp/working
jupyter_1 | [I 07:11:33.069 NotebookApp] 0 active kernels
jupyter_1 | [I 07:11:33.069 NotebookApp] The Jupyter Notebook is running at:
jupyter_1 | [I 07:11:33.069 NotebookApp] http://33c518cf1679:8888/?token=hogefugatoken
jupyter_1 | [I 07:11:33.069 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
jupyter_1 | [C 07:11:33.069 NotebookApp]
jupyter_1 |
jupyter_1 | Copy/paste this URL into your browser when you connect for the first time,
jupyter_1 | to login with a token:
jupyter_1 | http://33c518cf1679:8888/?token=hogefugatoken
最後のlogin with a tokenの下のURLをコピーし、33c518cf1679
の部分をlocalhost
に変えてブラウザのURLバーに入れればJupyter Notebookにアクセスできます。
ローカルにあるファイルにもアクセスできることを確認してください。
まとめ
今回はDockerとdocker-composeを使い、Kaggleの環境をベースにした分析環境の立ち上げを行いました。
自分なりにカスタマイズしたDockerfileを持っておけば、新しいデータが来たときも迅速に解析に取り掛かることができます。
みなさんもぜひ試してみてください。
- 作者: 山田明憲
- 出版社/メーカー: 技術評論社
- 発売日: 2018/08/25
- メディア: Kindle版
- この商品を含むブログを見る
追記 (2019/05/13)
Twitterでmhiro2さんにいただいた指摘を元に記事を修正しました。ありがとうございました!
Docker Hub のイメージは古いので、https://t.co/mGMtjYLeFK を使った方がより現行カーネルの環境に近くライブラリも新しいと思います。fastprogress も カーネルでは既に使えますし、gcr の方の latest だと入ってるみたいです。GPU 前提ならビルドするしかないので、NGC 使うのがベターですね。
— mhiro2 (@mhiro2_1127) 2019年5月13日
追記2 (2019/07/12)
Dockerのメモリ上限を大きくする方法についてu++さんが書いてくださいました。メニューバーのDockerマークから設定に飛び、メモリ上限を拡張してください。
Kaggle Eloコンペの振り返り・上位解法まとめ
KaggleのElo Merchant Category Recommendationコンペに参加しました。
僕は@kasuminkoさん、@hirokasさんとチームを組んで、ラスト2週間だけ参加しました。結果から書くと、Public 221位からのPrivate 2220位という乱高下で儚く散りました。
手元に銀メダル相当のスコアを持っていたにもかかわらず、間違ったサブミットを選んでしまっており、とても悔しいです。
- コンペの概要
- 上位解法
- 1st place solution (30CrMnSiA)
- 特徴量
- モデル
- 5th place solution (Evgeny Patekha)
- 特徴量
- モデル
- 7th place solution (senkin)
- 特徴量
- モデル
- その他
- 11th place solution (Zakaria EL Mesaoudi)
- 特徴量
- モデル
- その他
- 16th place solution (nlgn)
- 特徴量
- モデル
- 18th place solution (pocket)
- 特徴量
- モデル
- 上位解法のうち、今後も使えそうなアイディアのメモ
- 1st place solution (30CrMnSiA)
- 僕たちの解法
コンペの概要
Eloというブラジルのカード会社が主催となり、各ユーザーの購買行動に対応したRoyalty Scoreという値を予測するコンペです。
データとしては各カードの属性を書いたtrain.csv
/test.csv
と、それぞれのカードに紐付いた購買履歴(transaction)を表すhistorical_transactions.csv
/new_merchant_transactions.csv
の4ファイルが与えられていました。
タスクの種類は値を当てる回帰で、targetの分布は0を中心とした分布に加え、-33付近に外れ値が存在するというものでした。
評価指標はRMSEで、外れ値の影響を受けやすい指標です。特に今回は-33.22付近とかなり外れた位置にサンプルが存在するため、外れ値をどう捌くかが大きな論点のひとつでした。
続きを読むQuoraコンペの振り返りと上位解法まとめ
KaggleのQuora Insincere Questions Classificationコンペに参加しました。
結果は121位で、銀メダルでした。これで銀メダルが3枚目です。わーい。
Public Leaderboardで692位と振るわず、コンペのdeadlineが修論発表の当日だったので直前ほとんど何も出来ず、「Quora何もわからなかった」という絶望感でいっぱいでした。
Quora、最終日が修論発表当日なので時期が悪すぎる……最初は意気揚々とやってたけど今は普通に沼っているので終わったらちゃんと復習する枠かなぁ
— えじ|Amane Suzuki (@SakuEji) 2019年2月2日
蓋を開けてみたら多くのTeamが消え、あれよあれよという間に銀メダルになりました。
- タスクの概要
- 上位陣の手法
- 1st place solution (Psi)
- モデル
- Embedding
- 閾値
- バリデーション
- 2nd place solution (takapt)
- 3rd place solution (Guanshuo Xu)
- 7th place solution (yufuin)
- 10th place solution (tks)
- 13th place solution (Canming Wang)
- 15th place solution (Bai)
- 1st place solution (Psi)
- 覚えておきたいテクニック
- 【おまけ】151th place solution (僕)
- 終わりに
タスクの概要
QuoraというQ&Aサイト(日本で言うYahoo!知恵袋)に投稿された質問の中で、有害なもの(攻撃的、差別的、卑猥など)を分類するコンペです。
タスクの種類は0 or 1の2値分類で、正例がかなり少ない不均衡なデータセットになっています。
評価指標はF1 scoreです。確率として出力した予測値をどこから1として、どこから0とするかの閾値決めが大きく最終スコアに影響を与えました。
コンペの形式としてはKernel経由の提出のみを受け入れるKernel Onlyコンペかつ、締切後にテストデータを差し替えて順位を決定する2-stage制コンペでした。 1st stageではテストデータが56,000件程度であるのに対し、2nd stageでは376,000件と6倍程度に増えることが予告されていました。
制限時間内に実行出来なかった場合は順位がつかないという厳しいペナルティがあるため、素早く前処理をしたり、上手くモデルを訓練する必要のあるコンペだったと言えるでしょう。
続きを読む決定木は本当に変換に依存しないのか?
決定木をベースとしたモデル(RandomForestやXGBoost、LightGBMなど)は正規化などの前処理が必要ないと言われています。
理由として挙げられるのは「決定木は特徴量の大小関係のみに着目しており、値自体には意味がないから」というものです。
先日もkaggler-jaというSlackチャンネルでこの話が出ました(細かく読まなくて大丈夫です)。
自分で質問に答えておいて、「本当か?」という疑問が湧いてきたので実験してみることにしました。
結論から言うと、StandardScalerやMinMaxScalerなどの各特徴量について線形な変換に対しては結果は変化しないけど、logをかけたりするとちょっと変わるということが分かりました。
決定木のアルゴリズム(ざっくり)
決定木を作成する際のおおまかな流れは以下のとおりです。
- 目的変数(target)をうまく分離できる特徴量と、分割境界を決める
- サンプルを基準よりも小さい群と大きい群に分ける
- それぞれの群を繰り返し分割していく