【H&Mコンペ】Kaggle初参加で銀メダル取れたので振り返る


## はじめに
Kaggle初挑戦でH&Mコンペに参加したので、振り返ってみたいと思う。
この記事の目的は
- コンペの記憶がなくならないうちに知見を残しておく
- Kaggle初心者がどのようにKaggleに臨み、何を得て何に苦労したのかをまとめておく

できるだけ有益な記事になるようにしたいと思う反面、記事の性質上ポエムっぽくなってしまう点はご容赦いただきたい。

## 僕について
- Kaggleは昔にタイタニックチュートリアルをやっただけの初心者
- 業務でレコメンドを扱っているので、機械学習、ランク学習についてある程度の知見はある

端的に言えば「**業務で機械学習は使うけどKaggleのことはよくわからないよ**」状態。
今回はソロで「銅メダル以上」「Kaggleを通して勉強すること」を目標に参加した。

## どんなコンペ？

https://www.kaggle.com/competitions/h-and-m-personalized-fashion-recommendations

![スクリーンショット 2022-05-12 20.22.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547532/f6432065-ab9c-da36-ef63-8573617daf20.png)


H&Mの過去のデータから、将来ユーザーがどの商品を購入するかを当てる。
評価指標としてはMAP@12を用いる。
つまり、ユーザーごとに購入しそうな商品を12個ずつ並べて、上位の商品が正解するほどスコアは高くなる。
俗に言う時系列データであり、過去のデータから将来のデータを予測することになる。
単純な分類タスクと異なり、どのような順番で並べるかも重要になる。

コンペ期間: 2022/2月 - 5月

最終的に参加者は3000組となり、

- 上位10% (300位以内): 銅メダル
- 上位5% (150位以内): 銀メダル
- 上位0.5% (16位以内): 金メダル

上位そして上位6人にはそれぞれ順位に応じた賞金が出る。
一位になると15,000ドル = 190万円くらい (現在は円安なので) もらえる。



## どんなデータ？
約二年分の「**どのユーザーがどの商品を買ったか**」という過去の購買情報が与えられている。
商品、ユーザーについてはそれぞれマスターテーブルが与えられていて、商品ID、ユーザーIDを使って購買データにJOINできるようになっている。
商品情報は「色」「カテゴリ」などの商品に関する情報。
ユーザー情報は「年齢」「暗号化された郵便番号」など。
購買データには「オンライン/オフライン」「購入価格」など。

「購入価格は商品情報では？」という疑問は当然沸くが、クーポンを使った場合などは同じ商品でも価格が変わるっぽいので購買データに含まれるみたいだ。



## コンペ中の（僕の）大まかな流れ
3ヶ月間に僕が何をしていたかをおおまかにまとめてみる。

### 最初の1ヶ月
右も左もわからない状態だったので、まずは公開されているEDAのnotebookを手元で動かしてみたりした。
Pandas力が全然足りていなかったのでPandasの使い方を勉強しながら手元でデータをいじくってみたり。
Kaggleも昔にタイタニックのチュートリアルを一度やったきりだったのでSubmissionの仕方やコンペの仕組みなどを調べたりした。
メトリクスについての理解もここで深めた。

こんな感じで「**Kaggleを知る**」「**Kaggleに慣れる**」ために最初の1ヶ月を丸々使ってしまった。
当然精度の良いsubmissionなどできていなかったのでLBの順位は常に下から数えたほうが早かった。

だが、この期間にEDAを丁寧にやったおかげてデータについて知ることができたのでよかったと思う。
多分次に別のコンペに参加することになった場合はこのあたりの工程を大幅に短縮できると思う。

## 1ヶ月 -　1ヶ月半
この辺りでいいスコアのsubmissionをしてLBの順位を上げることを考えるようになる。
公開されているnotebookでスコアの高いものを弄ってどのようなロジックだとMAPが高くなるのかを考える。
公開notebookに手を加えたものをRecall (=Retrieval/Matching) Phaseの候補商品群とし、それをinputとしてLightGBMを通して学習し始める。


## 1ヶ月半 - 2ヶ月
どんな素性が効くのかを考える。
「素性を加える → 学習　→ 提出 → 考察 → 新たな素性を考える」 のサイクルを安定して回せるようになる。
このあたりのパイプラインをうまく構築することが大事だと理解する。
LBは１日5回までしか提出できない上、LBばかり気にしてもPublic LBのデータに過学習してしまうため、提出する代わりに手元のcvスコアを参考にするようにした。
この辺りで上位（銀圏内）に潜り込めるようになった。

## 2ヶ月　- 3ヶ月
思いつく素性を一通りためしたので、ハイパーパラメータのチューニングとアンサンブルに力を入れる。

ハイパーパラメータチューニングにはOptunaを用いた。
アンサンブルはLightGBMに加えてXGBoost, CatBoostを試した。
また、線形モデル (ロジスティック回帰)やニューラルネット（MLP）も試したがこちらは精度が微妙だった。

あとは公開notebookを適当にアンサンブルして最終提出とした。
最後は力尽きて他の人たちがラストスパートをかけていく中、部屋の隅っこで祈ってた。

## コンペの結果

嬉しいことに **81/3000 位で銀メダル**を獲得することができた。
これはTop 3%にあたる。
なぜ銀メダルを取れることができたのか、逆になぜこれより上位にいけなかったのかは後に述べる。


## Recall Phaseの詳細
商品のレコメンドにおいては、ユーザーごとにレコメンド候補となる商品群を集める **Recall/Retrieval Phase**と、
集めた商品を機械学習を用いて並び替える**Ranking**という2つの工程から成る。
今回はユーザーごとに最終的に12個の商品を選ぶ（+並べ替える）ことになるため、Recall Phaseの段階では12個よりもう少し多めの
商品を確保しておくのが一般的だ。
まずはRecall Phaseについて考える。

### 1. Item base Collaborative Filterling
最初は自己流でitem base Collaborative Filterling (商品ベースの協調フィルタリング)を試した。
以下item CFと呼ぶ。

これは単純に「**ある商品を購入した人は他にどの商品を買うのか**」かをカウントすることで、2つの商品の共起を数えることができる。
共起の多い順に数えることで、ある商品に対して類似度の高い商品を選ぶことができる。

今回はユーザーごとに商品を推薦するため、item2item(商品と商品の関連度)ではなくuser2item（ユーザーと商品の関連度）を求める必要がある。

そこで、ユーザーの過去の購入履歴をseedとして、類似度の高い商品を推薦することでそのユーザーに関連度の高い商品をおすすめすることにした。
これらレコメンドでは比較的昔から使われている方法だ。

この方法には欠点としては、人気のない商品の場合は十分な数の商品を確保できないことがある。

### 2. Popular item
1の方法ではユーザーによっては商品数が12件に満たないことがある。
その場合は直近一週間の人気商品のランキングで埋めた。
これをPopular itemと呼ぶことにする。
手元のcvだと単純なランキングよりも年齢ごとのランキングで埋めたほうがスコアが高かったため、最終的には
年齢ごと/オンラインorオフライン別のランキングを使った。

### 3. Repurchase
自分で試したのが1と2だったが、それだけだとスコアがイマイチ伸びなかった。
公開notebookやdiscussionを見て気づいたのが、このコンペにおいては**再購入商品がめちゃくちゃ強い**ということだ。
普通に考えれば以前購入した商品を同じユーザーが再購入することはなさそうだが、予想に反して多くのユーザーが再購入していた。
特にオンラインユーザーの再購入率が高く、おそらくオンラインの方が過去の購入履歴からサイズ違いとかをポチポチ購入しやすいのではいかとdiscussionでは議論されていた。

開始2ヶ月の時点でこれら3つを組み合わせた公開notebookが高いスコアを出していたため、このnotebookをベースに
recall phaseの商品群とすることにした。
具体的には

1. customerの過去の購入商品のうち、最後の購入から1週間以内に購入された商品を最初に持ってくる
2. それらの商品と同時に買われる商品で一番共起の多い物を持ってくる
3. 12個に満たなかった場合は直近1週間のrankingで埋める

という方針だ。
公開notebookでは12件をそのままsubmitしていたが、後のrerankの工程に合わせて40件の候補を出力した。
また、公開notebookでの単純なランキングを年齢別のランキングにしたり、いくつかのパラメータを弄ったりした。

## Ranking
Recall phaseについては多くの公開notebookで議論されていたが、rankingについは何故かあまり議論されていなかった。
自分はRecall phaseで得られた40件ずつの商品をとりあえずLightGBMに突っ込んだところいい感じの結果が出たので、その方針で精度を高めていく方針を決めた。
しばらくは効きそうな素性をいろいろと試し、最終的に数十個の素性を加えた。
そのあとは上でも述べたように他のGBDTのモデルを試したりアンサンブルに精を出したりした。

## 銀メダルを取れた理由
まず一つに公開notebookをちゃんと追って理解することができたことだ。
公開notebookの中から有用そうなものを選んで自分なりにアレンジするだけでも十分銅メダル圏内には入ることができたと思う。
あとは公開されている情報にRankingに関する情報があまりなかったため、いい感じの素性を集めてGBDTに突っ込んだりアンサンブルしたりして精度を高めることでTop3%まで滑り込むことができた。

他には3ヶ月間ある程度集中力を切らさずに頑張り続けることができたことがある。
Kaggleのコンペは基本的に数ヶ月単位の長期戦になるため、スコアが上がらずやる気が出ない時期が多々ある。
特に自分のようにチームを組まずソロで参加している場合はよりモチベーションの維持が難しい。
そのあたりの自分の気持ちをうまくコントロールしてうまくKaggleと付き合えたというのが大きいのかもしれない。

## なぜ金メダルを取れなかったのか
コンペ終了後に上位陣のsolutionを一通り眺めて感じだことがある。

- みんなRankingの方法は似通っている
    - 上位勢はほぼ全員LightGBMのみ
- Recall Phaseの方法はすごく練って工夫している
    - いろんな方法で試してそれぞれ上位100件を持ってくるなど

つまり、Rankingについては精度の向上に限界があるがRecallにおける候補の選定については工夫次第で
いくらでも精度が上がるということだ。
そのあたりが自分の方針と大きく異なっていた。

自分はKaggle初参戦ということもあり、多くの知識を書籍 『Kaggleで勝つデータ分析の技術』 に頼っていた。
その中で「コンペ中は良い特徴量を探している時間が最も長い」と書かれていたのを読み、

「まずはRecall Phaseをちゃちゃっとやって、特徴量の選定と学習が勝負どころだ！」

と思い込んでしまっていた。（もちろん本は悪くない）
自分はRecallで40件選んだものをRankingに回していたが、上位の人は100-1000くらいの商品をさまざまな方法で選んでおり、
学習時にはnegative samplingを行なっていた。

他にも特徴量の数や学習期間の設定などあらゆる面で自分より上位勢の方がクオリティが高かった。

「もう少しここを工夫すれば銀の上位に行けたな〜」と思う部分は多々あるが、金メダルや賞金圏内に行くには
付け焼き刃の知識や努力では足りなそうだなーというのを深く感じた。

与えられたデータに対する理解、パイプラインの設定、試すモデルの数、学習の方法、素性の選定、そのすべてにおいてクオリティを上げる必要があると感じた。

## 振り返り
今回のKaggleで得られたものは本当に多い

- Pandas、Cuda、notebookの使いこなし方
- GBDTの学習方法やその他のモデルの構築など
- 学習に使うデータをどのように選ぶかという知識
- 業務で使うデータ以外の他社データに触れる経験
- ハイパーパラメータのチューニング方法
- MAPなどのメトリクスの理解
- 特徴量の選定方法や前処理のやり方

などなど、挙げ始めればキリがない。
(Kaggleが強い) ≠ (機械学習を用いた業務で活躍できる) なのは重々承知しているが、業務だけでは得られない数多くの知見が得られたと思う。

あとは自分の実力を客観的に見ることができたというのも大きい。
例えば「業務でレコメンドやってるのでレコメンド得意です！」と口でいうのは簡単だが、結局これは自己評価だし、社内での実績もあくまで
社内データを用いた社内評価でしかない。

Kaggleのような世界中の人が参加するコンペに参加することで、自分の実力がどれくらいあるのかを測る一つの指標が得られると思う。
自分の場合は 「レコメンドのコンペだとそこそこ戦えるが、本当の上位には遠い」 というのを肌で感じることができた。

次は別のテーブルコンペに参加して、自分の専門分野以外で銅メダル以上が取れるように頑張りたいなと思った。
また、似たようなレコメンドのコンペがあったら今よりも上位に行きたいなと思った。

おわり！