Kaggle振り返り PetFinder.my - Pawpularity Contest

# 初めに
　この記事は2021年から2022年にかけて開催された「PetFinder.my - Pawpularity Contest」の内容を振り返るために書いています。開始１か月後から参加し、私の結果は、114 / 3537 (Solo silver medal, [Leaderboard](https://www.kaggle.com/c/petfinder-pawpularity-score/leaderboard)) でした。

Kaggle に [コード](https://www.kaggle.com/c/petfinder-pawpularity-score/discussion/301029) を公開していますので、ご興味のある方は是非。

![top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930460/78b36d0a-e52f-28eb-3f85-2f72e87f99b6.png)

# PetFinderコンペについて
## 概要
　2020年にも開催されたPetFinderコンペです。今年のお題は [PetFinder.my](https://www.petfinder.my/) にて掲載されている犬 or 猫の写真のページビュー（統計処理あり）を当てることでした。コンペではページビューのことを「Pawpularity」と呼んでいました。
　Petfiner.my には捨てられた犬や猫の写真が掲載されています。Pawpularity を正確に予測するモデルを構築することによって、どのような写真を撮影すればクリック数が多くなるのか/少なくなるのかが分かるようになります。これにより、犬や猫の里親が早く見つかることに貢献できます。ちなみに、前回コンペは、里親が見つかるまでの早さを直接的に予測するコンペでした。

## データと評価方法
　与えられたデータは、画像データ（9,912枚）と、CSVデータです。今回も Code competition であり、テストデータは数枚だけ提供されている状況でした。しかし、今回のテストデータは完全なノイズ画像/ランダムな数値の CSV となっていたため、テストデータに関する情報は一切与えられていませんでした。Metrics は RMSE でした。

以下にデータの詳細を記載します。

- 画像データ  
犬と猫の画像データです。１枚の画像の中に複数の犬/猫が写っているケースや、文字情報（性別、年齢等）が記載されているケース等がありました。全てのデータを見たわけではないですが、犬か猫のどちらかが写っていると思われます。
- CSVデータ  
各画像データに対するメタデータです。メタデータは12列あり、目が写っているか、写真がぼやけているか等の項目に対して 0 or 1 の値が入力されていました。1 が該当を意味します。最後の13列目が Pawpularity です。これは、0~100 の値を取ります。

# 提出したアプローチ
　最終的には、Pseudo Label による Semi-Supervised Learning を実施したモデル ＋ 通常の手法で学習を実施したモデル、計７モデルのアンサンブルを提出しました。Pipeline は以下の通りです。

![pipeline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930460/975daad0-129b-39a7-386b-38036950b4ae.png)

　図中に出てくるモデルの正式名称及び [timm](https://github.com/rwightman/pytorch-image-models) での設定名称（利用したPre-trained model）は以下の通りです。上図の中央上に出てくる「Pre-trained weight」を入力している３モデル以外は、timm の Pre-trained weight を利用しています。これらのモデルは timm のサイトに掲載されている [精度表](https://github.com/rwightman/pytorch-image-models/blob/master/results/results-imagenet.csv) を基に、Kaggle の GPU 環境に乗るアルゴリズムを選定しました。アルゴリズムの詳細は今回の記事では割愛します。

- Swin: Swin Transformer, swin_large_patch4_window7_224_in22k / swin_large_patch4_window7_224 , [paper](https://arxiv.org/abs/2103.14030)
- EffNet: EfficientNet, tf_efficientnet_b5_ns, [paper](https://arxiv.org/abs/1905.11946)
- ViT: Vision Transformer, vit_large_patch16_224, [paper](https://arxiv.org/abs/2010.11929)
- BEiT: BERT Pre-Training of Image Transformers, beit_base_patch16_384 , [paper](https://arxiv.org/abs/2106.08254)
- CaiT: Class-Attention in Image Transformers, cait_s24_384, [paper](https://arxiv.org/abs/2103.17239)

　以下で詳細を説明する Semi-Supervised Learning 以外の学習は、KFold = 4 で実施しています。また、全ての推論は TTA[^1] = 2 で実施しています。そのため、実際の推論は各モデル 4 * 2 = 8 推論している計算です。
　Transformer 系のアルゴリズムは、画像の入力サイズが固定されているため、timm の Weight の名称に付いている画像サイズを利用しています（例：swin_large_patch4_window7_224 は、画像サイズ 224）。EfficientNet は画像サイズ 512 で学習しています。
　次の章からパイプラインの詳細及び Tips を書いていきます。

## 詳細

### Semi-Supervised Learning
　2020年に開催された PetFinder コンペがあったため、過去データを使って Pseudo Label を作成しました。作成手順は色々あるようですが、基本的には、まずは現コンペのデータで１つ以上のモデルを学習させることから始めます。次に、正解ラベルを保有していない別データに対して推論を行い、疑似ラベル（Pseudo Label）を取得します。
　クラス分類系のタスクであれば、出力が確率になっているため、例えば、別データに対して推論した結果、確率 0.9 以上のデータを採用する、といったように、比較的推論結果が合っていると思われるデータを利用することが出来ます。しかし、今回の出力は、0~100 の値をとる連続値データであったため、この方法が使えません[^2]。そのため、[こちら](https://www.kaggle.com/c/tensorflow-speech-recognition-challenge/discussion/47722) の TensorFlow Speech Recognition Challenge 3rd place solution を参考に、Semi-Supervised Learning を実施しました。Discussion の中で２つの方法が紹介されています。１つ目は、学習データとして「100% of training data + 20%-35% of test data selected per epoch」を利用する方法、２つ目は「100% of test data」を利用する方法です。どちらも試した結果、精度向上が出来た２つ目の方法を採用しました。
　Pipeline の図を見てわかるよう、Semi-Supervised Learning を実施したのは、Swin と EffNet だけです。この２つのモデルは精度向上が出来たのですが、例えば、BEiT を同じ手順で学習させると、通常の学習手法（現コンペのデータで普通に学習）より、精度が悪化しました。ViT、CaiT で試す時間はなかったのですが、こちらも同じ傾向になる可能性があったため、最終的には Swin と EffNet でのみ Semi-Supervised Learning を実施しました。

### Swin Transformer + Image + Meta data
　Semi-Supervised Learning で学習した Pre-Trained weight をベースに、Meta data を加えて Fine-tuning を実施しました。アーキテクチャは下図の通り大きく２パターンを考えました。ただ、パターン１だと画像自体に改良を行うため、学習した表現が壊れてしまう恐れがありました。そのため、パターン２を採用し、低めの学習率で数エポックだけ学習を実施しました。Meta data との結合部分は、結合だけでなく足し算や掛け算等でもOKです。色々試した結果、精度が良かった足し算を採用しました。

![archtecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930460/838eb40e-e354-cc09-5436-57210d214d82.png)

　ちなみに、パターン１と２の両方実施するのもありです。Semi-Supervised Learning ではなく、通常の Pre-trained weight を使って学習する場合は、両方実施したケースが一番精度が高かったです。

### アンサンブル
　今回のコンペで精度向上に繋がった一番の要素は、色々な種類のモデルをアンサンブルすることだったと思います。多くの人が、多種多様なモデルをアンサンブルしていました。私もコンペ序盤にアンサンブルで精度が上がることに気付き、Semi-Supervised Learning の実装を一通り終えた後は、アンサンブル用のモデルを作成していました。
　今回のコンペでは、アンサンブル手法に単純な平均を利用しました。他には 重み付き平均、Stacking 等、アンサンブル手法にも色々種類があります。私も Stacking は実装して、最終提出の２枠に入れた（CV Best）のですが、過学習していたようでスコアが非常に低かったです。銀メダルが取れたのは、もう一つの Public Leaderboard Best でした。

## Tips

### Pytorch vs FastAI
　今回のコンペでは、[FastAI](https://docs.fast.ai/) を利用している人が多数いました。私は、コンペ序盤は Pytorch で実装した学習スクリプトを利用して、実験を繰り返していました。その後（約１カ月後）、Public Notebook/Discussion を一通り確認したところ、FastAI の部分以外は同じような実装をしているにも関わらず、精度が高い、という Notebook が散見されました。そこで、Pytorch の実装を全て FastAI に変えて学習したところ、精度改善に繋がりました。
　FastAI には「lr_find」「fit_one_cycle」という強力な手法が提供されています。ir_find は、学習率と Loss の関係性を Plot してくれるため、良い精度に到達する学習率の目星をつけることが出来ます。また、fit_one_cycle は、学習時に学習率を上下させることによって、鞍点（Saddle point）に落ちにくくすることが可能な手法です。今回のデータでは、これらの手法が効いたのだと思います。実装も簡単なので、是非試してみてください。

### BCE Loss
　今回のコンペの目的変数は 0~100 の値を取る変数でした。通常ですと、Loss を RMSE 等の連続値を扱うものにすると思います。しかし、今回は 0~100 の値を 100 で割り、0~1 に変換し、BCE Loss（Binary Cross Entropy Loss）を計算するという手法が主流になっていました。Loss を変更しただけで精度向上が確認できたので、これは応用範囲の広い手法だと思いました。

### Cross Validation (bin)
　KFold の手法として、今回は Stratified KFold を利用しました。これは、設定した列の値の分布を維持するようにデータを分割してくれる手法です。最初は、Pawpularity を設定し、これの分布を維持するように４つのデータセットに分割をしていました。コンペ終盤、改めて Public Notebook/Discussion を読んでいたところ Pawpularity を [Rice Rule](https://www.statology.org/sturges-rule/) で分割した bin で、Stratified KFold しているものがありました。またしても、コードの内容はほぼ同じで、 bin のところだけが異なり精度が高いという現象が起きていたため、私も bin に変更しました。
　Rice Rule は、正規分布のデータに対して bin 数を算出する式であり、他にも Sturges’ Rule や、Square-root Rule 等があります。どれを選ぶかは分布の形と、試行錯誤というところでしょうか。

### 重複データの削除
　今回のコンペには全く同じ、または、ほぼ同じ画像が数十枚含まれていました。これも Public Notebook/Discussion を見ていて気付いたものです。[除外の方法](https://www.kaggle.com/c/petfinder-pawpularity-score/discussion/278497) が公開されており、手法としては、画像の Hash 値を計算して、画像の類似度を計算するものでした。今回私が利用したデータは全て、この処理を実施した後のデータです。

# 終わりに
　前回参加したNFLコンペ（記事は[こちら](https://qiita.com/tt20210824/items/7386e06285ec2b2e7a01)）から連続しての Kaggle 参加で、銀メダルを獲得することが出来ました。いつも Kaggle の環境しか使わないのですが、銀圏はいけたものの金圏はまだまだ遠い印象です。画像・言語系コンペの上位入賞者の多くは、Cloud 契約して GPU 使ってたりする印象なので、勉強も兼ねて Cloud 使ってみるのもありかなと思い始めています。今年１回ぐらいはやってみようかな。もちろんもっと独自の手法を考えるのも非常に重要ですが。今回も学びの多いコンペでした。参加された皆様、お疲れさまでした。

[^1]: TTAは、Test Time Augmentation の略です。参考 [cifar10 で Test Time Augmentation (TTA) の実験](https://qiita.com/cfiken/items/7cbf63357c7374f43372)
[^2]: 連続値を 10 刻みでクラス化し、分類問題として解くことを試しましたが、精度が非常に低く、向上できる気がしなかったので諦めました。