🐶

Pet Finder 2 振り返り🐶🐱

2022/01/15に公開

はじめに🐶🐱

犬猫ちゃんの人気度を予測する眼福コンペに参加しました。
Kaggle歴8ヶ月の初心者の学びと解法を徒然なるままに纏めました。

これまでの成績
🥉SETI : 初めてのKaggle。ほぼフリーライド銅
🥉G2Net: ガチ参加。だけどほぼ貢献できないチーム銅
🥈圧力コンペ: Masterと組んでチーム銀

https://www.kaggle.com/c/petfinder-pawpularity-score/overview

目標:ソロ銀🥈

  • 圧力コンペでも途中までソロ銀で行けてましたが、最後にMasterの方と組む機会をいただいたので結果チーム銀。今回はソロ銀を取ることを目標にしました。

結果:ソロ銀🥈

  • 無事、目標を達成することができました!やったね。

やったこと

ベースライン

  • まずはいつも通り、Nakamaさんのコードをベースラインにしました。(いつもお世話になっております。)

https://www.kaggle.com/yasufuminakama/petfinder-efficientnet-b0-starter-training

Swin Transformer

  • コンペ序盤から「SwinTransformerが効く」とDiscussionが盛り上がっていたので、今回初めてSwinTransformerを使いました。最後まで主軸はSwinでしたね。

LR調整

  • まずはLRの調整をやりました。が、ここはあまり拘らなくてもよかった気がします。
  • 本コンペではSwinを使うと早ければ2エポック目くらいにベストスコアが出るのですが、「ちゃんと緩やかな学習曲線になってないとモデルの善し悪しは判断できないのでは?」と勝手に思い、2e-4, 3e-4, 4e-4 と試したりしてました。
  • たしかに精度は僅かに改善するのですが、層を増やすとまた微妙にズレるので序盤でやることではない気がします。今後はCNNだと1e-4, Swinだと1e-5とかで決め打ちでいくと思います。
  • もしくはこの辺りは後述するFastAIに任せるのがいいのかもしれません。

途中で Nomi さんのツイートに出会いました。これが真理な気がします。

FastAI①

  • 本コンペでは終始、FastAIが公開Notebookで無双していました。
  • ただやっていることはひねりのない素のSwinTransformerだったことと、公開Notebookに負けたくないという安いプライドが邪魔をし、自分のコードでFastAIに勝ってやろうとがんばりました。

Seedの固定

  • コンペとは直接関係のない初心者らしい気づきですが、Seed固定についても理解が深まりました。
  • 10Foldsのうち、CVが最高・最悪の3、7Fold目のCVで評価していたのですが、1~10Foldsをすべて実行したときと、3,7 Fold目だけを実行したときで結果が変わってしまいました。
  • onoderaさんに教えていただき、Fold内で固定すると固定できることを学びました。

https://zenn.dev/kohecchi/articles/121bae7e352d65

CVとLBの相関

  • 今回のコンペは、CVとLBの間にギャップがあり、相関がないことが特徴的でした。
  • 手元でいいスコアが出て、嬉々としてSubすると全然スコアが伸びないと、一気にモチベーション下がりますよね。なかなか悩みました。

「Public LBの評価で使われるデータ量は5FoldのEvaluationデータより少ないからTrust CVだ」というコメントを読み、強い気持ちでCVを信じました。

Epochの途中でのEvaluation

I noticed that the evaluation process after every 32 step gives better results than evaluation at the end of each epoch.
https://www.kaggle.com/c/petfinder-pawpularity-score/discussion/289889

  • Discussionでの投稿をヒントにエポック途中での評価を実装しました。
  • 今回ベストが2エポック目とかなので、エポックの途中で32Step単位に評価を挟むことで、本当にベストなタイミングでのモデルを保存できるのだと思います。
  • CVが0.05~0.1くらい改善しました。

重複データの削除

  • コサイン類似度を比較したところ完全一致するデータがいくつかあることが判明。
  • 完全一致するデータのうち、Pawpularityが10以上離れるデータは削除しました。
  • 重複を削除しない方がCV・LBがいいときもあり、最後までどう扱うべきかは分かりませんでした。

層の凍結

  • 最初の数エポックの間、最終層以外はfreezeすることで、前半の層のpretraindedの重みが壊されなくていいようです。
  • 最初の3エポックだけ凍結してみました。誤差の範囲ですが、CVが0.03程度改善しました。

Data Augumentation(追記)

  • Data Augumentationは以下を入れました。
  • Vertical FlipとRandomBrightnessContrast、ColorJitterはCVを悪化させたので抜きました。
  • ShiftScaleRotateは効くときと効かないときが不安定だったので途中で抜きました。
def get_transforms(*, data):
    if data == 'train':
        return A.Compose([
            A.Resize(CFG.size, CFG.size),
            A.HorizontalFlip(p=0.5),
            A.HueSaturationValue(
                hue_shift_limit=0.2,
                sat_shift_limit=0.2,
                val_shift_limit=0.2,
                p=0.5,
            ),
            A.Normalize(
                mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225],
            ),
            ToTensorV2(),
        ])

TTA(追記)

  • 今回初めてTTAをやりましたが、LBが0.05~0.1くらい改善しました。
  • 最初は全くやり方も分かっていませんでしたが、Yamaneさんが親切に教えていただいたおかげで理解が深まりました。ありがとうございます。
  • HongnanさんのDiscussionを見てトライしてみたのですが、Public LBで検証するだけでいいのか、CVもTTA有と無で比較すべきだったのか、このあたりどうやって評価すればいいのかあまり理解しきれませんでした。
  • 特に今回はPublic LBではTTAが効く一方で、Privateでは悪化したようなので、この辺りもTrust CVを貫くともっとよかったのかもしれませんね。

https://www.kaggle.com/c/petfinder-pawpularity-score/discussion/294108

  • 私の実装したTTAは以下の通りです。4パターンで平均しましたが、正攻法じゃないかも?
  • 上位解法のように、OneOfを効率的に使えるようになりたいです。
def get_transforms(*, data, size, mode):
    elif data == 'valid':
        if mode==0:
            return A.Compose([
                A.Resize(height=size, width=size, p=1.0),
                A.Normalize(
                    mean=[0.485, 0.456, 0.406],
                    std=[0.229, 0.224, 0.225],
                ),
                ToTensorV2(),
            ])
        elif mode==1:
            return A.Compose([
                A.RandomResizedCrop(size, size, scale=(0.85, 1.0)),
                A.HorizontalFlip(p=1),
                A.ShiftScaleRotate(p=1, rotate_limit=15, shift_limit=0.0625, scale_limit=0.1),
                A.HueSaturationValue(
                    hue_shift_limit=0.2,
                    sat_shift_limit=0.2,
                    val_shift_limit=0.2,
                    p=1,
                ),
                A.Normalize(
                    mean=[0.485, 0.456, 0.406],
                    std=[0.229, 0.224, 0.225],
                ),
                ToTensorV2(),
            ])
        elif mode==2:
            return A.Compose([
                A.Resize(size, size),
                A.ShiftScaleRotate(p=1, rotate_limit=15, shift_limit=0.0625, scale_limit=0.1),
                A.HueSaturationValue(
                    hue_shift_limit=0.2,
                    sat_shift_limit=0.2,
                    val_shift_limit=0.2,
                    p=1,
                ),
                A.Normalize(
                    mean=[0.485, 0.456, 0.406],
                    std=[0.229, 0.224, 0.225],
                ),
                ToTensorV2(),
            ])
        elif mode==3:
            return A.Compose([
                A.Resize(size, size),
                A.HueSaturationValue(
                    hue_shift_limit=0.2,
                    sat_shift_limit=0.2,
                    val_shift_limit=0.2,
                    p=1,
                ),
                A.Normalize(
                    mean=[0.485, 0.456, 0.406],
                    std=[0.229, 0.224, 0.225],
                ),
                ToTensorV2(),
            ])

犬猫分類🐶🐱

  • YOLOv5を使って犬猫を分類し、犬モデル、猫モデルを作りました。
  • Discussionにも書かれてましたが、猫の方がCVがよく(15くらい)、犬の方が悪かった(20くらい)です。
  • 通常モデル、犬モデル、猫モデルをアンサンブルすることでCVもLBも0.05~0.1くらい改善しました。(アンサンブルの詳細は後述します)

犬種分類(失敗)

  • 犬の改善がコンペのカギだろうと思い、犬モデルの改善に取り組みました。
  • 「犬種は人気の重要な要素だろう」と思い、犬種分類器の作成を試みました。
  • Pet1のデータを使おうとしたら、既に試行済の人がノートを公開していました。
  • このコードを参考にBreed(犬種)データの予測モデルを作ってみましたが、残念ながら精度が出ず、断念…。
    https://www.kaggle.com/alexvishnevskiy/breed-identification

※今見たらTakamiさんやあまえびんさんがPet1の方にアンサンブルコードをPublic公開してました笑 これを覗いていたら、もう少しヒントが得られたかも? (Public公開されたのはコンペ終了後でした)

画像のCrop

  • YOLOで得られたバウンディングボックスの座標データを用いて画像をクロップしました。
  • これ単体ではあまり効果がありませんでしたが、後述するアンサンブルでは仕事をしてくれました。
  • 推論時にCropした画像をローカル保存したらCSV Not Found Errorになるらしく、それに苦しんでいる方が多かったですね。
  • 僕の場合はDataLoaderから画像を取得するタイミングで都度Cropしていたので、非効率でしたが、その罠にはかからずに済みました。

メタデータ

  • コンペ序盤から「提供されたメタデータはPawpularityとの相関が低く使い物にならない」と議論されていました。
  • YOLOから得られたメタデータなら効くかと思い、画像の縦・横・面積・縦横比、ペットの縦・横・面積・縦横比、画像におけるペットの面積の割合など色々な特徴量を作り、NNのインプットに組み込んでみました。
  • 色々と試してみましたが、あまり効きませんでした。
  • 上位の方はPet1のデータと混ぜて、うまく活用されていたようです。さすがです。

Triplet Attention

  • SETIで3位のkenjiさんが使っていたTriplet Attentionを試しました。
  • CNNにしか適用できない(はず)なので、Swin以外のモデルに使いました。(詳細はアンサンブルの章にて後述)
  • CVが0.05~0.1くらい改善しました。

https://www.kaggle.com/c/seti-breakthrough-listen/discussion/266403#1480432

FastAI②

  • ここまで、あの手この手で、コンペ終了3週間前までFastAIに頼らずにあがいてみました。
  • 結果、単体モデルでは勝てませんでした…😢
  • アンサンブルのためにFastAIを試しましたが、本当に簡単にCV、LBが伸びる伸びる。
  • どうしてこんなにいいスコアが出るんですかね。LRの自動調整のおかげ?
  • コンペ終了後、Private LBを比較しましたが、モデル単体でのベストは、FastAIなしが「17.20」、FastAI(公開Notebook)が「17.11」だったので、やはりFastAIは強かったです。悔しい…。
  • FastAIの公開Notebookはどれもoof_dfを出力していなかったので、取得しようとしたのですが、なかなか苦戦しました。何よりFold数やSeedをちょっと弄ったらLBが大幅に上下するのであまりコードを触りたくなかったです。

Ensemble

  • 学習時に出力したoof_dfをもとにCVがベストになるよう加重平均をしました。
  • 全部入り、猫、犬でそれぞれベストCVを作り、最後に3つをアンサンブルしました。
  • 最終的にアンサンブルしたモデルは以下の通り。
Model CV 備考
swin_large_patch4_window7_224 17.412 カスタムHead、SAM、Mixup
swin_large_patch4_window7_224 17.546 FastAI
eca_nfnet_l1 17.684 カスタムHead、SAM、Mixup、Triplet Attention、Crop
swin_large_patch4_window12_384 17.690 カスタムHead、SAM
swin_large_patch4_window7_224 15.154 猫モデル、カスタムHead、SAM、Mixup
eca_nfnet_l1 15.17 猫モデル、カスタムHead、SAM、Mixup、Triplet Attention、Crop
swin_large_patch4_window12_384 15.352 猫モデル、FastAI
eca_nfnet_l1 15.358 猫モデル、カスタムHead、SAM、Mixup、Crop
eca_nfnet_l1 20.07 犬モデル、カスタムHead、SAM、Mixup
  • Ensembleの結果スタメン落ちしたけど試したもの
    ig_resnext_38d

コンペ終了1週間前(公開Notebookの新規公開禁止)

  • 今回のコンペから1週間前からのNotebookの公開が禁止されました。
  • コンペ終了直前にハイスコアカーネルが公開されることによって、これまでの2ヶ月の努力がコンペ終了前日に水泡に帰すという事態を避けるための対応です。
  • が、既に公開されたNotebookの更新はできてしまうようで、結果的にコンペ数日前に更新されたハイスコアカーネルによって銅メダル圏が荒れてしまいましたね。
  • 更新も禁止すべき!というDiscussionもあがってたので、Upvoteしておきました。今後に注目です。
  • ただ、後述するConvnextのような形でDiscussion上にDatasetsと共に公開する人も出てくるかもしれませんね。

https://www.kaggle.com/general/291540

Convnext

  • コンペ終了直前に「Convnextが効きそうだ」という一言と共に、モデルとコードがDiscussionに公開されました。
  • 最後のあがきで試してみたところ、アンサンブルに貢献してくれそうなレベルのCVをたたき出してくれました(Convnext_1k_384を利用)
  • が、推論ノートにて「models」というモジュール名がYOLOと重複してしまい、学習したモデルをうまくimportできない事態に。もうあと1日あればモジュール名を修正して、モデルを再学習して解決できましたが、残念ながらタイムアップ。
  • これが使えたら、もう少しだけ上位に食い込めたかも?(と言っても金メダルは厳しかっただろうけど)

結果

  • 116位の銀メダルでした。目標のソロ銀達成です🥈
  • Public LB 259 からの143人抜きの大幅シェイクアップ。
  • 単体モデルでは銅圏外だったので、戦略的にモデルのバリエーションを増やし、アンサンブルによる最後の一押しで銀メダルを掴んだなという気がします。
Private LB
単体モデルベスト 17.11
銀のボーダー 17.065
銅のボーダー 17.09
アンサンブル後 17.04

上位者の解法

  • 詳細はこれから勉強しますが、Pet Finder 1の画像データとコサイン類似度が一致するデータを取得することで、一部データについては提供されていないBreed情報やAdoption Speed情報を取得できたようです。
  • Pet Finder1のデータを使うところ、犬種を活用するアイディアまでは辿り着けていたので、もう一息だった気がします…。

https://www.kaggle.com/kaerunantoka/offense-final

  • 1位のGibaさんはSVR(CV: 16.92)とbeit_large_patch16_224(CV:約17.38)で、アンサンブルしてCV: 17.02とのことでした。Pet1のデータは使ってないんですかね?だとしたらすごい。

https://www.kaggle.com/c/petfinder-pawpularity-score/discussion/300938

感想

  • SETI、G2Netと画像コンペで銅メダルを2つ取り、テクニックの引き出しも増えてきたので、今回はソロ銀いけるだろと自信を持って臨みましたが、FastAIに手を出すまでは銅メダル圏内に入ることもできず、最後まで苦戦しました。
  • ただ、色々な種類のモデルを構築できたこと(と言ってもtimmを使っているだけですが…)、Triplet Attention、32ステップ毎の評価などのテクニックでCVを改善でき、学びの多いコンペだったなと思いました。
  • 本コンペとは直接関係ないですが、Seedの固定の仕方、LR調整へのこだわりなどの基本的な部分についても身に付けることができたのも収穫でした。
  • また、最期は強い気持ちでTrust CVを貫くことの大切さも実感しました。Public LBを信じていたら銅メダルでした…。
  • FastAIやpyTorch Lighteningは食わず嫌いをしていたのですが、FastAIは今回無双していましたね。アンサンブルのためにFastAIにも手を出しましたが、めちゃくちゃ簡単。結果的にはFastAIの知見も得られてよかったです。自分の持っている武器とアイデアで戦うことも大事ですが、新たな武器を身に付けることも大事ですね。今後も小さなプライドは捨てて、バランスよくやっていこうと思います。

最後に

今回コンペに参加された皆様、お疲れ様でした。
また別のコンペでよろしくお願いします。
ありがとうございました。

Discussion