BirdCLEF 2024に挑戦| Kaggleチャレンジ記録
Kaggleのコンテスト「BirdCLEF 2024」へチャレンジした記録です。
今回はLBでは24位でしたが、最終は486/991位とかなりシェークダウンする結果となりました。どうも、LBに過剰適合していたようです。ここのところ、メダル圏外が続いていますが、めげずにチャレンジしていきたいと思います。
BirdCLEF 2024の概要
BirdCLEFコンペは、2021年/2022年/2023年/2024年と継続して実施されている、鳥の音声データを用いて鳥の種を特定することを目的とした機械学習コンペティションです。
毎年、少しづつ異なるテーマになっており、今回は、4分間のでデータを5秒間隔で区切り、各区間で鳴いているのがどの鳥なのかを当てるコンテストです。出力がクラスラベルではなく、全部で182種類の鳥それぞれについて数値を返す形となっています。
BirdCLEFに参加するのは、今回で二回目です。前回参加したのはBirdCLEF2021で、結果は59/816位の銅メダルでした。ということで、今回は「前回よりも良い結果」を目指して参加しました(結果は圏外でしたが)。
コンペの内容としては、鳥の鳴き声から種類を識別し結果を返すという点では同じですが、細かな内容が異なります。が、基本的な考え方は同じと考えて良さそうでした。
このコンペの難しい部分は、自然環境での録音には風や他の動物の声、川のせせらぎなどのノイズが含まれており、雑音が含まれるデータから鳥の種別を判定する必要がある点です。
前回と大きく違う点は、「CPUで2時間以内に推論」という制約で、前回参加した時と比べて極端に短くなっています。このため処理量が大きなモデルを使うことができない点もポイントです(ちなみに、昨年も同様だったようです)。
この記事では、BirdCLEF2024の私の取り組みについて話したいと思います。
取り組み内容
今回も参加が遅くて、ゴールデンウィークの途中から取り掛かりました。開始が4月3日なので、開始から1ヶ月遅れての参加になります。直前に参加していたコンペが4月中旬に終了し、その後少し休んでいたため参加が遅れました。
とりあえず、公開コードとディスカッションを眺めてみる
とりあえず、いつものように、公開コードとディスカッションを眺めてみます。鳥コンペは2021年に参加していましたが、ルールは若干違いがあります。今回の提出は、5秒毎にどの鳥が鳴いているかを182種類の鳥全ての確らしさ(Probability)を出力する形式でした。
とりあえず、公開コードを物色するとPytorch Lightningを用いて書かれていた推論と訓練コードがありましたので、それをベースとすることにしました。
参加した時の公開コードの一番高いスコアはLB0.64でした。参考にしたコードはLB0.61と決して高いスコアではありませんでしたが、Lightningが使われていたことと、変更しやすそうなことからこれを参考にすることにしました。今回は、最後まで、このコードを改変したコードを利用して学習・推論をしています。
学習・推論コードを作成
参考にした公開コードを書き換えて、オリジナルの学習・推論コードを作成しました。
まずは、公開コードで気になった部分を変更していきました。変更点は以下になります。
- スペクトログラムからメルスペクトログラムへ変更
公開コードはスペクトログラムを利用していましたが、2021年に参加した時にメルスペクトログラムの方が良かった記憶があるので、torchaudioのメルスペクトログラムを使う様に変更しました。 - モデルの修正
ベースにしたモデルは、timmのfeatureだけ利用してhead部を自作していました。また、入力が3chでした。これを、入力を1chにし、出力もtimmのパラメータ設定で182クラス出力にするように変更しました。timmの機能を使って、なるべく追加コードを書かない形に修正したイメージです。 - ロス関数
CrossEntropyLossをFocalLossBCEへ変更しました。こちらは、他のコードを参考にしました。これも2021年のコードで使っていたので合わせた形です。 - スケジューラの変更
CosineAnnealingWamRestartsから、GradualWarmupSchedulerにスケジューラーを変更しました。こちらも、GradualWarmupSchedulerを修正したバージョンが他のコードにあったのでそちらを参考に修正しました。 - ログ記録
Weight&Bias(wandb)へ結果とモデルを保存する様にコードを追加しました。 - 学習コードからデータセット作成コードを分離
データセット作成が重いので、ここを切り離して「データセット作成用のノートブック」を作成しました。データセット作成部分はGPUを使わずにCPUだけで処理することで、GPU時間を節約するのが狙いです。
とりあえず、上記の変更を行なったコードを作成して、以降の検討を行いました。
初サブ(LB0.61)
ベースコードは、efficientnet_b0を使ったコードでしたが、少しオリジナリティを出したいと思ってefficinetvit_v0を使うように変更して学習・推論を行ない、動作確認を含めてサブしてみました。
初サブのLBは0.61と、参考にしたベースラインと同程度の結果でした。
まずは、きちんと動作したことが確認でき、スコアも可もなく不可もなくといったところでした。
ここから、改善検討を行いました。
検討内容
以下、今回検討したことを簡単に紹介していきます。
検討したこと
スケジューラ調整
学習データセットを生成するコードを修正
学習データセットを生成するこコードを修正して、オーバーサンプリング、メルスペクトログラムのパラメータ調整、nocall(鳴いていない)データ追加など行いました。
label smooting
学習時にはラベルスムージングを行いました。
mixup/cutmix
mixup/cutmixを入れて学習させました
データ拡張
ホワイトノイズ追加、ピンクノイズ追加などの音声データに対するデータ拡張を行いました。
色々なモデルをチェック
efficientvit_b1, efficientnet_b0/b1, resnet18d, eca_nfnet_l0などのいくつかのモデルを試しました。timmを使っていたのでここは簡単でした
アンサンブル
複数の学習したモデルのアンサンブルを行いました。efficientvit_b1が思いのほか高速だったので結構な数のアンサンブルが可能でした。
OpenVINOによる高速化
実行時間が2時間+CPUでの推論という制約があるのでOpenVINOを使って高速化しました。体感2倍くらい高速になります。
バックグラウンドデータ作成
BirdCLEF 2021のtrainデータからnocallを切り出してサウンドデータを作成し、バックグランドデータ(データ拡張)に利用しました。
n_melsを 128から256に変更
スペクトログラムの解像度が256×256にしていたので、メルスペクトログラムのフィルタバンクサイズを 256に変更。
予測結果の時間平滑化
予測結果を最終的に、±10秒の範囲で平滑化する処理を後処理として加えました。コンペ後に公開モデルに対しても適用してみましたが、これは効果があった様です。
検討しなかったこと
プレトレーニング
プレトレーニングは今回検討しませんでした。
最終的なモデル
最終的なモデルは
- efficientvit_b0を3モデル
- efficientvit_b1を3モデル
- mobilenetv3_large_100を3モデル
- resnet18dを1モデル
のアンサンブルです。OpenVINOを使うと、このモデルで2時間の制限にギリギリ間に合う形でした。
結果
目標にしていたLB0.70に、締切1日前になんとか到達できました。若干オーバフィッティングが不安ですが、とりあえず目標を達成できたことは大きいです。
最終的な結果は、PB0.59で486位と、LB24位から-462位と大幅シェークダウンしました。かなりオーバーフィッティングしてしまっていた様です。
感想など
前回BirdCLEF2021に参加した時は、「音声処理ってなに?」という状態からのスタートでしたが、今回は音声データの処理についてある程度理解した状態からスタートできました。また、3年前は「PyTorchって??」な初心者でしたが、PyTorchでモデルを設計できる程度には知識をつけた状態です。前回の参加に比べて、音声処理、PyTorchによるモデル作成・学習等のスキルは大幅にアップしています。
このおかげで、いろいろなことを考えることができ、また取り組むこともできました。今回は、3年前からのスキルアップを実感できたコンテストだと思います。
ただ、結果に関しては、オーバーフィッティングしてしまいパッとしなかったのが残念です。
CVが全然あてにならなかったので、「オーバフィッティングしてしまったのはしょうがないかな」と少し思っています(LBでメダル圏内だった人のたくさんの人がオーバフィッティングして順位を落としていました。メダル圏内順位を維持できたのは20組くらいで、後は圏外に転落しています。)
After Contest
After contestとして、34位にエントリーしていた公開ノートブックに、検討していた後処理(時間方向の平滑化)を加えただけのやつがPB0.662と20位以内になりました。後処理については間違っていなかったみたいです。
また、上位解法に音声信号へのデータ拡張は行わずに、スペクトログラム変換後の画像に対してのみmixupなどのデータ拡張を行うものがありました。これを参考に、オーバサンプリングその他の工夫は全部やめて、mixupと画像に対するデータ拡張で行うだけのものに変更してサブしてみました。efficientvit_b1のモデルを使って実験してみましたが、スコアはLB0.625, PB0.571と特に変化はありませんでした。ということで、私のコードの場合、オーバサンプリング、オーディオ信号へのデータ拡張がスコア悪化に繋がったわけではなさそうです。
以上、After Contestで実験してみた結果です。
結局、モデルの作りがシンプルすぎたのもの問題かもしれません。Head部分も工夫すればよかったかもしれません。
まとめ
ICRに引き続き大幅なシェークダウンを経験しました。LBに公開しているデータが全体のデータと特性が異なる場合にオーバフィッティングしやすいと思うのですが、今回はそこまでオーバーフィッティングしていないと思っていたのに予想外でした。数人の方はそこまで落ちていないのを考えると、うまく学習させる方法があった様です。