📊

外部データ追加で Acc 0.97 出たけどリークでした|SIGNATE 音楽ジャンル分類で 13 位(2026/02/26 時点)

に公開

概要

業務で音声特徴を取り扱うようになったので、ドメイン理解を深めるため SIGNATE の音楽ラベリングのチュートリアル に取り組みました。
チュートリアルの問題ですが、上位のスコアにするためにはそこそこチューニングしないといけない内容だったので取り組みがいがあったと思います。

また、途中で外部データを追加したところ、Public LB が 0.97 になり一時的に 1 位になってしまいました。
しかし調査すると、データリークが発生していました。

進め方など不慣れな部分がありましたが、最終的にそこそこスコアを上げることが出来たので実施内容をこちらで記載します。

ベースラインの作成

音声波形をパワースペクトルに変換し、時系列的に並べると画像化できるので、CNN で分類できるのでは?と考え EfficientNet を使ってベースラインを作成しました。
下記を実施した上で 9:1 で train-val を分割し、hold-out で学習させたところ、Public LB でのスコアが 0.616 になりました。

  • メルスペクトログラム画像を入力特徴とし、CNN(EfficientNet B7)
  • 欠損値をゼロパディング
  • 画像は 600x600 へリサイズ

交差検証もしたかったのですが、初期はあまり時間をかけたくなかったので hold-out で学習検証を実施しています。

データオーギュメンテーション

Pitch Shifting, Time Stretching, Adding Noise, Time Shifting を実施したところ、そこそこ数値が増えました。(Public LB Acc:0.734

実施内容

パターン 内容 パラメータ範囲
pitch_shift ピッチをランダムに変更(50%の確率で適用) ±2 半音
time_stretch テンポをランダムに変更(ピッチ維持・50%の確率で適用) 0.8 ~ 1.2 倍
noise ガウシアンノイズ追加(50%の確率で適用) 係数 0.001 ~ 0.01
time_shift 前後にランダムシフト(ゼロパディング・50%の確率で適用) 最大 ±0.2 秒

3ch(original + vocal + acc)に分離して入力

ジャンル識別では「ボーカルの有無」や「楽器構成」が強い手がかりになると考え、原音・ボーカル・伴奏をそれぞれ独立チャネルとして入力しました。
RGB画像のようにチャネルごとに異なる情報を持たせることで、特徴抽出の幅が広がることを期待して変更を加えたところ、Public LB の値が上昇しました(Public LB Acc:0.77

また、ここで学習時間の短縮のため EfficientNet を B7 → B3 にしています。
B7 → B3 によって 0.778 -> 0.77 になったので最終モデルでは B7 に戻しています。

ボーカル除去オーギュメンテーション

音声ファイルを EDA していると、イントロなどボーカルがない音声データがいくつかあったので、分離したボーカル音声の音圧値を時間方向に積分し全データ別にその分布を見てみることにしました。(下記は分布の一部)

この分布を見つつ、実際の音声を聴いてみると、ボーカルがほとんど入っていない音声は積分した値が 2000~3000 程度でした。

そのため 2500 以下のデータは伴奏のみと判断し、ジャンル別に割合を見たところ下記のようになりました。

=== Values <= 2500.0 ===
Train: 116 / 500 = 23.2% (2.32割)
Test: 107 / 500 = 21.4% (2.14割)
Total: 223 / 1000 = 22.3% (2.23割)

Train by label (values <= 2500.0):
blues: 9 / 50 = 18.0% (1.80割)
classical: 46 / 50 = 92.0% (9.20割)
country: 1 / 50 = 2.0% (0.20割)
disco: 4 / 50 = 8.0% (0.80割)
country: 1 / 50 = 2.0% (0.20割)
disco: 4 / 50 = 8.0% (0.80割)
hiphop: 0 / 50 = 0.0% (0.00割)
jazz: 36 / 50 = 72.0% (7.20割)
metal: 4 / 50 = 8.0% (0.80割)
pop: 0 / 50 = 0.0% (0.00割)
reggae: 12 / 50 = 24.0% (2.40割)
rock: 4 / 50 = 8.0% (0.80割)

伴奏のみの割合が少ないジャンルについては、ボーカルありの音声データから抜き出して伴奏のみの音声データをかさ増しさせてみることにしました。

すると、Public LB でのスコアが 0.794 となり改善しました。

※ classical のように「低ボーカル」がすでに多いジャンル(例: 46/50 = 92%)では、ボーカル抜きオーギュメンテーションは適用させてません。

しかし、最終的なモデルではボーカル除去のオーギュメンテーションが敵対的に働いたのかスコアが下がったためこの施策を取り除いています。

公開データセットの追加

ここで、公開データセットで良いものがあればそれでさらにかさ増しできるのではと思いました。
調べてみると、The MagnaTagATune Dataset というデータセットが 30 秒程度の音声に曲ジャンルをラベルの一部として使っていたので、こちらを追加することにしました。(今回のコンペではルール的には問題ないようです)
今回の課題ではジャンルが10種類あり、blues,classical,country,disco,hiphop,jazz,metal,pop,reggae,rock となります。
これらと同じラベルを持つ音声だけを抜き出して、データ不均衡がないように収集し下記のようにジャンル別に50個サンプリングすることにしました。

=== MagnaTagATune 外部データ読み込み (50 件/クラス) ===
  MagnaTagATune ジャンル別利用可能数:
    blues           180 件 → 50 件サンプリング
    classical      4232 件 → 50 件サンプリング
    country         434 件 → 50 件サンプリング
    disco            57 件 → 50 件サンプリング
    hiphop           62 件 → 50 件サンプリング
    jazz            394 件 → 50 件サンプリング
    metal           505 件 → 50 件サンプリング
    pop             821 件 → 50 件サンプリング
    reggae           52 件 → 50 件サンプリング
    rock           1433 件 → 50 件サンプリング
  追加: 500 件 → 学習ファイル合計: 1000 件

これを追加したところ、Public LB が 0.816 になりました。
また今更ですが、hold-out でのスコアの偏りがありそうな感じがあったのでここで交差検証を入れてます。

オーギュメンテーションの調整

最初のオーギュメンテーションを強めにし、さらに交差検証時のベストエポックの平均値で全データで学習させました。
オーギュメンテーションはこちらの記事をそのまま参考にしています。

元音声 + 6種類の拡張 = 7パターンから毎回ランダムに1つ選択

パターン 内容 パラメータ範囲
original 元音声そのまま -
pitch_up ピッチを上げる +2 ~ +5 半音
pitch_down ピッチを下げる -5 ~ -1 半音
stretch_slow テンポを遅くする 0.7 ~ 0.9 倍
stretch_fast テンポを速くする 1.1 ~ 1.4 倍
noise ガウシアンノイズ追加 std 0.005 ~ 0.015
time_shift 循環シフト(np.roll) 音声長の 10% ~ 50%

Public LB が 0.82 になりました。

公開データセットの追加2

次に GTZAN Dataset を加えたところ、Public LB: 0.97 となりました😲
これは流石に増えすぎなのでほんまかいな!?と思いました。
0.86~ とかならわかるのですが流石にリークしてそうなので、GTZAN の音声データとコンペのテストデータのログメル画像を使ってコサイン類似度を計算し一致度を調べました。

--- GTZAN → Test 最近傍ペア (Top 30) ---
  # 1  sim=1.000001  GTZAN: jazz.00079.wav                  ←→  Test: test_171.au
  # 2  sim=1.000001  GTZAN: jazz.00020.wav                  ←→  Test: test_28.au
  # 3  sim=1.000000  GTZAN: blues.00010.wav                 ←→  Test: test_254.au
  ...
  #28  sim=1.000000  GTZAN: country.00072.wav               ←→  Test: test_176.au
  #29  sim=1.000000  GTZAN: country.00099.wav               ←→  Test: test_112.au
  #30  sim=1.000000  GTZAN: disco.00035.wav                 ←→  Test: test_81.au

上記を見ると明らかで、GTZAN と Test の間で完全一致した音声が検出されてます
そのため、GTZAN をそのまま学習データに使用するとリークしてしまうようです🙃

本件については不本意なので、問い合わせフォームに投稿し修正希望を公式へ出しましたところ、一週間くらいで対応して頂き1位を取り下げてもらいました。

記念にスクショは撮りました。

公開データセットの追加3

GTZAN を利用するのを止め、FMA を追加しました(各クラスで 74 ずつ)
また、MagnaTagATune の方でクラスごとのサンプリング数を 50 -> 52 にしています。

=== MagnaTagATune 外部データ読み込み (52 件/クラス) ===
  MagnaTagATune ジャンル別利用可能数:
    blues           180 件 → 52 件サンプリング
    classical      4232 件 → 52 件サンプリング
    country         434 件 → 52 件サンプリング
    disco            57 件 → 52 件サンプリング
    hiphop           62 件 → 52 件サンプリング
    jazz            394 件 → 52 件サンプリング
    metal           505 件 → 52 件サンプリング
    pop             821 件 → 52 件サンプリング
    reggae           52 件 → 52 件サンプリング
    rock           1433 件 → 52 件サンプリング
  追加: 520 件 → 学習ファイル合計: 1020 件
=== FMA 外部データ読み込み (74 件/クラス) ===
  FMA フィルタ後: 25000 件 (subset=['small', 'medium'], duration>=30s)
  FMA ジャンル別利用可能数:
    blues            74 件 → 74 件サンプリング
    classical       619 件 → 74 件サンプリング
    country         178 件 → 74 件サンプリング
    disco            86 件 → 74 件サンプリング
    hiphop         2201 件 → 74 件サンプリング
    jazz            384 件 → 74 件サンプリング
    metal           577 件 → 74 件サンプリング
    pop            1186 件 → 74 件サンプリング
    reggae          233 件 → 74 件サンプリング
    rock           6526 件 → 74 件サンプリング
  追加: 740 件 → 学習ファイル合計: 1760 件

怖いので念のためこちらもリークがないか確認したところ、安全そうだったので活用することにしました。
この辺でジャンル別のスコアをログに出すようにしてます。

また、この時は検証データ内にも外部データが混入していたのですが、よくよく考えると本番分布で評価しないと妥当でなくないか?と思い、CV の切り方を下記のようにしました。

  • valid: 元データ 125件/fold (500 / 4) → 本番分布と同一(fold 5件だと val が少なくスコアのばらつきがありそうなので4にした)
  • train: 元データ 375件 + 外部全量 → 外部データは学習補助のみ
  • CV スコア: 本番と同じ分布で測定されるはず

重みのフリーズの範囲

これまで EfficientNet に対して features[:6] で凍結していましたが、後半の方まで凍結しすぎている可能性があるので features[:4] にしました。
事前学習重みは ImageNet のものですが、入力のメルスペクトログラム画像は低次元の画像特徴(エッジ、コーナーなど)は同じものの、車や犬などの高次特徴の理解は不要なはずなので、低次層は活かしつつ、より高次の層は音声データに適応させる方が良いと考え凍結範囲を縮小しました。

アンサンブル

最後に EfficientNet を B3 -> B7 にし、さらにダメ押しでシード値を変えてアンサンブルさせました。
そして最終的なスコアは、、、 0.846 になりました。
※ 2026/02/26 時点で 13位

最終モデル

Input: (B, 3, H, W)   ← 3ch メルスペクトログラム(original / accompaniment / vocals)          ↓
EfficientNet B7(features[:4] 凍結)

Classifier:
  Dropout(0.5) → Linear(1536, 512) → ReLU → Dropout(0.3) → Linear(512, 10)

Output logits

その他実施したが効かなかったもの

  • オーバーサンプリング
    • 追加データセットにおいて、データの少ないクラスに合わせてダウンサンプリングしていましたので、ジャンルによっては余剰分がありまだ活用できそうでした。
    • そこで、CV で fold ごとのベストエポックでのジャンル別の val の割合を平均し、over sampling の割合を決めるのはどうか?と考えたのでそれを試しましたが、こちらは今回伸びませんでした。
  • BPM の偏りの慣らす
    • ドラムのデータから BPM を算出(librosa.beat.beat_trac を使用)
    • ヒストグラムを出力したところ、下記のようになったのでこの分布慣らしたら改善しないかと浅く考えましたが変わらずむしろ悪化しました。

反省点・感想

Public LB の値ではなく初めから交差検証をして CV の結果ベースでモデルのチューニングをすべきだったと思いました。
途中で wav2vec2 の存在を知ったのですが、こちらは前段に CNN エンコーダを持ち、その後 Transformer で時系列モデリングを行う構造のため、スペクトログラムベースの CNN とは異なるアプローチで特徴抽出が可能だったかもしれません。
GTZAN とコンペデータの間に重複が存在している気付かなかったので、公開データを利用する際は必ず重複確認を行うべきだと学びましたが Accuracy 0.97 という急激なスコア上昇には違和感を持てたのは幸いでした。

ジャンル別のスコア出力で分類できなかった val データを記載していたのですが、ここで毎回取れてなさそうな特徴を EDA して対策するともっと伸びたかもしれません。
音声特徴初心者で、参考書を読みながら試行錯誤したので学びは多かったかなと思います。

株式会社ログビー(Logbii, Inc.)

Discussion