Kaggle PetFinder.my : 愛らしさスコアを予測する

Kaggle PetFinder.my : 愛らしさスコアを予測する

Google Gemini DeepResearchでの戦略立案から、EDA・特徴量空間の可視化・SVR/DL構築・Kaggle提出の顛末、そして次に狙うべき改善点まで — 一つのストーリーとして辿る全記録。

Private ScorePublic ScoreLocal CV1位解法
17.0952417.9427017.130616.8225

戦略の出所: Google Gemini DeepResearch

今回の全工程は、事前にGoogle Gemini DeepResearchで作成した戦略レポート「Kaggle PetFinder.my – Pawpularity Contest 最高得点解法の網羅的解析とRTX 3060環境下における最適ローカルCV構築戦略」を設計図として進めた。1位解法(Giba氏、Private LB 16.8225)を中心に上位解法を分析し、次の柱を提示していた。

  • RAPIDS SVR Magic — 数百の事前学習済みモデルから特徴量を抽出し、cuML SVRで高速に学習・Forward Feature Selection(山登り法)でモデル組み合わせを探索する表形式アプローチ。1位解法の核心。
  • BCE Loss(Quasi-MLE) — Pawpularityの0/100付近のファットテール分布に対し、RMSEではなくBCE Lossで学習することで平均への回帰バイアスを回避する。
  • 厳密なCV構築 — pHashによる重複除去とStratifiedGroupKFoldで、CVとLBを高精度に一致させる。「Trust Your CV」の原則。
  • VRAM 12GB向け省メモリ技術 — Gradient Checkpointing・Gradient Accumulation・AMPを組み合わせ、RTX 3060でも大型モデルを学習可能にする。

上位解法に共通するその他のパラダイム
2〜6位クラスの解法では、メタデータを直接使わず補助ターゲット(マルチタスク学習)として使う手法や、過去コンペ(PetFinder Adoption Prediction)の画像とハッシュ照合してAdoptionSpeed等を特徴量に注入する手法も見られた。この2つは今回のパイプラインでは未実装のまま残っている(後述)。

Part 1 / 4: データとCV基盤

まず生データそのものを見る — ターゲットの分布、実際の画像、メタデータとの関係、そして重複除去とCV設計。ここで確認した事実が、後段の全ての意思決定の土台になった。

1.0 データセット概要

配布されているのはtrain.csv(学習用、Pawpularityラベル付き)とtest.csv(コードコンペのため実際の隠しテストセットの代わりに8行のダミーが公開されている)。画像はJPEG形式で別ディレクトリに格納。

学習画像数9,912
列数14
欠損値0
重複Id0
公開test行数8
Pawpularity 平均 ± 標準偏差38.04 ± 20.59
Pawpularity 中央値(25%–75%)33(25–46)
Pawpularity 範囲1–100
画像サイズ(幅)257–1280px(平均823px)
画像サイズ(高さ)295–1280px(平均917px)
画像モード / 破損チェック全てRGB・破損なし(300枚サンプル検証)

train.csvの先頭5行:

IdSubject FocusEyesFaceNearActionAccessoryGroupCollageHumanOcclusionInfoBlurPawpularity
0007de1884…01110010000063
0009c66b94…01100000000042
0013fd999c…01110000110028
0018df346a…01110000000015
001dc955e1…00010010000072

メタデータ列(Subject Focus〜Blur)は全て0/1の2値フラグ。1.3で見る通り、これらとPawpularityの相関はほぼゼロだった。

1.1 Pawpularityは正規分布ではない

平均38.04・標準偏差20.59だが、0付近と100付近に不自然なピーク(ファットテール)がある。

高スコアのポイント
この分布特性を確認していたからこそ、DL学習ではBCE Loss(Quasi-MLE)を採用する判断ができた。ターゲットPawpularity=100の288件が実際にBCEの発散バグを引き起こし、対処が必要だった(Part 3で詳述)。

1.2 スコア別サンプル画像

低スコア・中間・高スコアの実画像を並べてみる。構図やクロップは一見バラバラで、メタデータのフラグだけでは説明がつかない。

LOW(Pawpularity 1–8)

MID(Pawpularity 35–42)

HIGH(Pawpularity 93–100)

高スコアのポイント
人間の目でも明確なルールを言語化しづらいからこそ、事前学習済み画像埋め込みを特徴量として使うアプローチが有効だった — その埋め込み空間の中身はPart 2で覗く。

1.3 メタデータ12項目はほぼ無相関

Subject Focus・Eyes・Face・Near・Blur等、全項目で|r|<0.03。

高スコアのポイント
メタデータを漫然と特徴量に混ぜてノイズを増やす誘惑を避け、画像そのものから学習する方針に計算資源を集中できた。ただしこれは「線形相関がない」ことの確認に過ぎず、補助ターゲットとしての価値までは否定していない(後述)。

1.4 重複画像はCVリークの罠

pHash + EfficientNet-B0コサイン類似度の両方で「重複」と判定された実例。

高スコアのポイント
9912枚中38組がこの種の近似重複。StratifiedGroupKFoldでグループ単位に隔離したことが、後述の「ローカルCVとLBの一致(差0.035)」に直結した。

1.5 10-fold全てで分布が安定

Sturges則(14ビン)によるビニング × StratifiedGroupKFold。

高スコアのポイント
グループ間リークはゼロ。この「信頼できるCV基盤」が、以降の全モデル選択・ブレンド比率決定の判断基準になった。

Part 2 / 4: 特徴量空間を覗く

Part 1で「メタデータではPawpularityを説明できない」ことが分かった。では、SVRやDLが実際に見ている埋め込み空間には何が写っているのか — UMAP・t-SNEで2次元に落として確認する。

2.1 読み方: UMAP と t-SNE の違い

どちらも高次元データを2次元に落として眺めるための次元削減手法だが、仕組みと得意分野が異なる。

t-SNE(t-distributed Stochastic Neighbor Embedding) — 近傍点同士の類似度を確率分布として表現し、高次元・低次元での分布の差(KLダイバージェンス)を最小化する。局所的なまとまりの再現には強いが、クラスタ間の距離には意味を持たせにくい。

UMAP(Uniform Manifold Approximation and Projection) — 多様体学習に基づき、局所構造を保ちながら大域的な配置もある程度保持する。t-SNEより高速。n_neighborsmin_distで局所⇔大域のバランスを調整する。

今回の見え方
犬/猫の分離のような強い信号は両手法で共通して現れる一方、クラスタの形・間隔はUMAPとt-SNEで異なって見える。2つを見比べて共通して現れる構造ほど信頼できる。

2.2 読み方: 採用した特徴量埋め込み

性質の異なる2系統の事前学習済みモデルを比較に用いた。

CLIP(Contrastive Language-Image Pre-training, OpenAI) — 画像とテキストを同じベクトル空間に埋め込むよう約4億組のペアで対照学習されたモデル。言語による意味的な指導を受けているため、被写体の種類のような高レベルな意味概念を強く捉える。

timm系モデル(tf_efficientnet_l2_ns_475 ほか) — ImageNetの1000クラス分類で事前学習。言語情報なし・画像のみで学習されているため、質感・形状・構図といった低レベルな視覚的特徴を反映しやすい。

特徴量セット内訳
clipCLIP ViT-B/16
l2ns475tf_effnet_l2_ns_475
svr9comboSVR 9モデル結合
metadataメタデータのみ
meta_clipメタデータ + CLIP

2.3 特徴量セット別 UMAP / t-SNE

同じ9912枚を5通りの特徴量表現で投影。Pawpularityスコアを七色グラデーション(紫→青→緑→黄→橙→赤)で色分けしている。

CLIP ViT-B/16 — 512次元・意味的/視覚的な類似性を捉える汎用埋め込み

tf_effnet_l2_ns_475 — 5504次元・単体SVRで最良だったモデル

SVR 9モデル結合 — 13952次元・実際に本番SVRへ入力した特徴量そのもの

メタデータのみ — 12項目の2値フラグのみ(埋め込みなし)

メタデータ + CLIP — 12項目のフラグとCLIP埋め込みを結合

Finding
メタデータのみの投影ではスコアがランダムに散らばり構造が見えない — 1.3の「相関ほぼゼロ」と整合する。埋め込み系ではスコアの高い点がゆるやかに偏る領域が見え、視覚特徴とPawpularityの構造的な関係を裏付けている。

2.4 「二つの島」の正体 — 犬 vs 猫

CLIPのゼロショット分類(画像埋め込みと「a photo of a dog」/「a photo of a cat」テキスト埋め込みのコサイン類似度)で各画像を犬・猫に振り分け、実際の島の分離と重ねてみた。

CLIP ViT-B/16

SVR 9モデル結合

Finding — 99.7%一致
UMAP上の島をKMeansで機械的に2分割し、CLIPのゼロショット予測(犬 4539枚 / 猫 5373枚)と突き合わせたところ、9885/9912枚(99.7%)が一致。svr9combo(本番SVR特徴量)でも同じ分離が保たれており、CLIP由来の「種の違い」という粗い意味的軸が9モデル結合後も支配的に残っていた。Pawpularityのスコア差は、この軸に直交するより繊細な構造として重なっている。

2.5 クラスタ解釈: SVR 9モデル結合特徴量

本番SVR特徴量(13952次元)をKMeans(k=8)でクラスタリングし、クラスタ毎の平均Pawpularityとサンプル画像を確認する。

クラスタ2 — 平均 60.9 / n=771 / std 29.0

クラスタ3 — 平均 42.5 / n=955 / std 20.8

クラスタ0 — 平均 41.8 / n=875 / std 20.9

クラスタ5 — 平均 40.6 / n=616 / std 21.4

クラスタ4 — 平均 40.5 / n=936 / std 19.9

クラスタ6 — 平均 33.6 / n=2429 / std 16.5

クラスタ1 — 平均 32.7 / n=1999 / std 16.2

クラスタ7 — 平均 32.2 / n=1331 / std 15.9

高スコアのポイント
8クラスタ中、最も平均スコアが高いクラスタ(mean≈61、他は32–43)が明確に分離した。サンプル画像を見ると単体被写体・中央構図・シンプルな背景に偏る傾向 — メタデータのNear/Face/Eyesフラグ単体では相関ゼロだったが、埋め込み空間ではこうした構図パターンが暗黙的に捉えられていた。

Part 3 / 4: モデル構築

Part 2で見た「埋め込み空間には構造がある」という事実を土台に、実際にどうモデルを組み立てたか。環境構築からSVR・DL・ブレンドまでを辿る。

3.1 環境構築

環境内容
Pet(Windows/Anaconda)Python 3.10, PyTorch 2.5.1(CUDA 12.1), timm 1.0.28, scikit-learn, albumentations, open_clip_torch
rapids / rapids2602(WSL2)RAPIDS 25.06 → 26.02(Kaggle側cuMLとバージョンを合わせるため後日追加)— SVR学習用

WSL2は未導入だったため導入から着手。RAPIDSはrapidsaiチャンネルからインストールし、scikit-learnのバージョン競合を都度調整した。

3.2 事前学習済みモデルを「特徴量」として使う

1位解法の核心 — timm/CLIPの事前学習済みモデルからembeddingを抽出し、SVRで回帰する表形式アプローチ。11モデル比較では単体でも17.5–18.0台と極めて競争力がある。

高スコアのポイント
End-to-Endで巨大モデルを学習するより遥かに軽い計算コストで強力なベースラインが得られる。RTX 3060 1台・WSL2上のcuML SVRで11モデル分のCVを合計48秒で評価できた。

3.3 貪欲法で最強の組み合わせを探す

単体最良モデルから開始し、CVが改善する限りモデルを1つずつ追加する貪欲法。

高スコアのポイント
17.52 → 17.14まで、9モデルの組み合わせで-0.375改善。cuMLのGPU高速化があったからこそ、全55回の候補評価を約13分で完了できた。この9モデルの組み合わせが、Part 2で可視化したsvr9combo特徴量そのものである。

3.4 DL学習: 2つのバグ、遡って修正

Bug 1 — BCEの境界値発散
BCE Lossは連続ターゲットの最適ロジットがtarget→1で発散する。Pawpularity=100の行(288件)がこの境界に一致し、学習が崩壊(val RMSE 36超)。ターゲットを[1e-3, 1-1e-3]にクリップして解消。

Bug 2 — ヘッドの初期スケール
新規初期化された単一ロジット出力ヘッドが過大なスケールで初期化され、sigmoidが即座に飽和。ヘッドを小さく再初期化し、バイアスをグローバル平均のlogitに設定して解消。修正後、3エポックのスモークテストでCV RMSE 18.04(ベースライン20.59を上回る)を確認。

使用PC(Intel Core i7-11700K)はCステート電圧・クロック変動により不定期に再起動する既知の問題があるため、train_dl.pyにエポック単位のチェックポイントとfold単位の状態ファイルを実装。学習を意図的に1エポック目の途中でkillし、再実行で正しく再開することを検証した。

3.5 DLはまだ伸びしろが大きく残った

beit_large_patch16_224をBCE Lossで10-fold学習。戦略レポート推奨の16–48epochに対し、計算時間の都合で5epochに絞ったため、fold間のばらつきが大きい(17.35–20.05)。

振り返りポイント
fold9(17.35)は戦略レポートのbeit_large単体値(17.38)にほぼ並んでおり、エポック数を増やせば他foldも同水準に届く可能性が高い。

3.6 SVR × DL ブレンド

SVR単体DL単体最適ブレンド(0.93·SVR + 0.07·DL)
17.141018.679117.1306

DLモデルの精度がSVRより大きく劣るため寄与は小さい(+0.0104のみ)。戦略レポートの1位解法でもSVRアンサンブル(16.92)がDLアンサンブル(17.02)をわずかに上回っており、同じ方向性の結果となった。

Part 4 / 4: Kaggle提出の顛末

学習済み重み一式を計3つのKaggle Dataset(合計約8GB)としてパッケージ化し、オフライン推論Notebookを構築 — スモークテストから本番提出まで、想定外のトラブルが連続した。

4.1 パイプライン全体像: ローカル学習 → オフラインKaggle推論

「ローカルで5時間学習し、その成果物だけをオフラインのKaggle Notebookに渡して推論する」という2フェーズ構成が、このコンペを乗り切るための基本設計だった。ポイントは、ローカルとKaggleで同じ特徴抽出コードを2回走らせること — 学習データや計算そのものではなく、学習済みパラメータ(重み・SVR係数)だけが境界を越える。

ローカル側では、train画像に対して(1)9モデルでの特徴量抽出とcuML SVRの学習(Hill Climbing込みで約1時間)、(2)並行してbeit_large_patch16_224のBCE Loss 10-fold学習(合計5.5時間)を実行。最終的にSVRは全データで再fitし、DLはfold9のチェックポイントのみを採用した(3.5節で見た通り、10fold中もっとも収束が良かったため)。

Kaggle Notebook側は、Dataset読み込み後にローカルと全く同じ特徴抽出コードをtest画像(隠しセット)に対して再実行する。学習コードそのものは一切持ち込まず、frozen(凍結)されたパラメータを読み込んで推論するだけ — この設計により、学習データの同期やコードの複雑な差分管理を一切気にせずに済んだ。

設計のポイント
「同じ特徴抽出関数をローカル(学習用データ)とKaggle(隠しテストデータ)の両方で再利用する」ことが、このアーキテクチャの要。関数を一つ書けば、学習時の特徴量とKaggle推論時の特徴量が完全に同じ処理経路(同一モデル・同一前処理)を通ることが保証され、学習/推論のスキュー(前処理の食い違い)を構造的に防げる。

4.2 オフライン推論のためのパッケージング

このコンペはCode Competitionで、採点用の再実行はインターネット接続が完全に遮断される。手元で学習・抽出した重みや、Kaggle標準環境に無いパッケージを、事前にKaggle Datasetとしてアップロードしてオフラインで読み込む工夫が必要だった。

Datasetサイズ中身
petfinder-svr-dl-weights≈7.5GB9モデル分のHugging Face Hubキャッシュ(timm/CLIP事前学習重み)+ fine-tuned beit_largeチェックポイント
petfinder-final-svr≈525MBfitted scaler.joblib / svr.joblib / feature_order.json
petfinder-offline-wheels≈2MBopen_clip_torch / ftfy / regex の.whlファイル(Kaggle標準環境に未同梱)

重みのオフライン化 — ローカルの~/.cache/huggingface/hub/models--timm--<repo>/ディレクトリ構造をそのままDatasetとしてアップロード。Notebook側ではhuggingface_hubに触れるimportより前にHF_HOMEHF_HUB_OFFLINE=1を設定するだけで、timm.create_model(..., pretrained=True)open_clip.create_model_and_transforms(...)のコードは一切変更せずローカルキャッシュから読み込ませられる。

未同梱パッケージのオフラインインストール — ローカルでpip download --no-depsして.whlファイルを取得し、小さいDatasetとしてアップロード。Notebookではpip install --no-index --find-links=<dataset path>でネットワークに一切触れずインストールできる。

工夫
Kaggleの/kaggle/input/配下のマウントパス規則は環境によって変わりうるため、決め打ちせず再帰探索するfind_input_dir()ヘルパーを用意し、どちらの規則でも動くようにした。また重み・SVR・wheelsを、あえて3つの別々のDatasetに分けたのは、次の4.3で述べる「大きいDatasetの差分更新が詰まる」問題を避けるため — 頻繁に更新する小さい成果物と、一度作ったら変わらない大きい重み一式を混ぜないという教訓から。

4.3 4つのトラブルと解決

  1. 大きいDatasetの差分更新が詰まる — 7.5GBのDatasetにwheelsフォルダを追加するバージョン更新がKaggle側で81%のまま進まなくなった。別の小さいDataset(petfinder-offline-wheels)として切り出すことで回避。
  2. P100が”no kernel image available”で全滅 — 割り当てられたGPU(Tesla P100, Pascal / Compute Capability 6.0)で、fp16はもちろんfp32でも一部モデルの演算が失敗。各モデルの特徴抽出をGPU→CPUフォールバック方式に変更して回避。
  3. cuMLのSVR.predict()がサイレントにゼロを返す — 特徴量自体は正常(ローカルGPU抽出と分布がほぼ一致)にもかかわらず、SVR予測が全サンプルで厳密に0.0。cuMLはPyTorchと違いGPUカーネル起動失敗を例外化せず、ゼロ初期化バッファをそのまま返していたと判明。fitted modelのsupport_vectors_ / dual_coef_ / intercept_ / gammaを取り出し、RBFカーネルをnumpyで直接手計算する方式に切り替え(cuMLのpredict()と5–6桁一致することを検証済み)。GPU/cuMLの状態に一切依存しない、恒久的に堅牢な予測経路になった。
  4. Session OptionsのT4x2選択がCommit実行に反映されない — Webサイトの「Session options」でGPU T4 x2を選んでも、Save & Run All (Commit)は繰り返しP100に固定された。ライブのインタラクティブセッションでT4x2を実際に起動・確認したうえで、そのセッションから直接Save Versionすることで、ようやくCommit実行にもT4x2が反映された。

Result
T4x2での正式コミット(Version #10)は全モデルがGPUエラーなしで完走し、特徴抽出は61.5秒(CPUフォールバック時の約190秒から約3倍高速化)。予測値もローカル参照値と一致した。

最終結果

petfinder-full-inference · Version 11 · Late Submission(コンペ終了後の提出)

Private ScorePublic Score
17.09524(1位解法: 16.8225)17.94270

ローカルCV(17.1306)とPrivate Score(17.095)の差はわずか0.035。「Trust Your CV」の原則が、このスケールダウンした再現でも成立した。使用モデル数・学習エポック数はいずれも1位解法から大幅に削減(SVR特徴量9モデル vs 数百、DLは1foldのみ・5epoch vs 16–48epochアンサンブル)しているにもかかわらず、1位解法との差は約0.27ポイントに収まった。

さらなる改良の余地

ここまでのEDA・可視化で得た知見のうち、実際にモデルへ反映済みのものと、示唆はあったのに未着手のままのものを切り分ける。

織り込み済み

EDAの発見反映した対策状態
ファットテール(0/100付近)BCE Loss(DL)・ターゲットクリッピング(SVR)✅ 反映済み
メタデータほぼ無相関直接特徴量には使わない判断✅ 反映済み
重複画像の存在pHash除去 + StratifiedGroupKFold✅ 反映済み
埋め込み空間に構造がある(UMAP)SVRが同じ特徴量で直接学習(暗黙的に反映)✅ 反映済み

未着手 — 伸びしろの候補(効きそうな順)

  1. DL推論を10fold中1foldのみで実施 — Kaggle提出ではfold9単体のみ使用。10fold平均(またはOOF重み付け)にすればDLの寄与が安定し、ブレンド比率(現状SVR93%・DL7%)も改善する可能性が高い。最も低コストで試せる。
  2. メタデータを補助ターゲットとして使うマルチタスク学習 — EDAで確認したのは「線形相関がほぼゼロ」という事実のみで、これは入力特徴量として無意味なことしか示していない。戦略レポートの6位解法が使った補助ターゲット方式は未検証のまま。
  3. TTA(Test-Time Augmentation)未実装 — 1位解法は水平反転TTAを使用していたが、今回は時間の都合で省略した。
  4. DLのエポック数不足 — 5epochのみ(推奨16–48)。fold間ばらつきの主因。
  5. SVRのハイパーパラメータ未調整 — C=10.0, epsilon=0.1, gamma=’scale’は固定値のまま。グリッドサーチ等は未実施。
  6. 候補モデル数が少ない — Hill Climbingの候補は11モデルのみ(1位解法は「数百」)。候補を増やせばさらに改善余地がある。
  7. 犬/猫の分離という発見を未活用 — UMAPで判明した「種の違いが最も強い軸」という事実を、種別セグメンテーションや明示的特徴量として使えるか未検証。
  8. 過去コンペとのハッシュ照合が未実施 — 2位・5位解法が使った、PetFinder Adoption Predictionの画像とハッシュ照合してAdoptionSpeed等を特徴量に注入する手法は、今回全く手をつけていない大きな伸びしろ。
  9. クラスタ別のエラー分析が未実施 — 「高スコアクラスタC2」のようなセグメントでOOF予測誤差がどう分布しているか未確認。次に何を改善すべきかをデータドリブンに判断する材料になる。

まとめ
EDAの「発見」自体はモデル構築の前提(損失関数・CV設計・特徴量選択の除外基準)には反映済みだが、EDAが示唆した具体的な改善アイデア(マルチタスク学習・種別セグメンテーション・エラー分析)はまだ実行段階に落とし込めていない。候補1と2が、着手コストの割に効果が見込める最有力候補。

Kaggle: PetFinder.my – Pawpularity Contest

ML/AI/Kaggleカテゴリの最新記事