こんにちは、皆さん。
人は、鳴き声を聞けば動物を、タイピング音を聞けばコンピューターのある部屋を思い浮かべられます。音を数値の vector に変換したとき、その近さにも同じような意味が残るのでしょうか。
さて、今日は CLAP の audio encoder を ONNX へ export し、環境音から生成した embedding を ESC-50 の 50 クラスで比較します。
今回検証する内容
CLAP(Contrastive Language-Audio Pretraining) は、音声とテキストを共通の embedding space で扱うモデルです。今回は laion/clap-htsat-unfused の audio encoder だけを ONNX Runtime で実行し、次の点を確認します。
- 5 秒の環境音から 512 次元の正規化 embedding を生成できるか
- 別録音の同一クラスが cosine similarity の最近傍になるか
- 50 の細分類を外した場合も、音が 5 つの大分類の近くにまとまるか
- Apple Silicon の CPU でどの程度の時間がかかるか
完全なコードと再現可能な環境は、kiarina/labs の clap-onnx-esc50 lab で公開しています。
検証環境を準備する
実行には、次のツールと約 2 GB の空き容量が必要です。
次のコマンドで対象の lab だけを取得して実行できます。
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .gitignore .mise/tasks Makefile mise.toml \
2026/07/06/clap-onnx-esc50
mise -C 2026/07/06/clap-onnx-esc50 run
初回は Hugging Face から checkpoint を取得し、lab 内に model.onnx を生成してから評価します。2 回目以降は ONNX の入出力と SHA-256 を検証して再利用します。ESC-50 の metadata と選択した WAV 100 本も、固定した revision から自動的に取得します。
モデルとデータセット
モデルは laion/clap-htsat-unfused の commit 84bcbbd1d619e407a8216371ddef36e458d95d93 に固定しました。PyTorch 2.8.0、Transformers 4.57.3、opset 18 で audio encoder、projection layer、L2 normalization を含む ONNX を生成します。
SHA-256: b23099962830b1afa5398efbb6f5321ef8f63f8fcf93f5019837c47118a8a1c5
inputs: input_features [batch, 1, 1001, 64], is_longer [batch, 1]
output: embeddings [batch, 512]
この ONNX には text encoder がありません。そのため、テキストでクラス名を与える CLAP の zero-shot classification ではなく、音同士の近さを使った評価です。
入力には ESC-50 を使います。ESC-50 は 5 秒の環境音 2,000 本を 50 クラスに分類し、各クラスを次の 5 大分類にまとめたデータセットです。
- animals
- natural soundscapes and water
- human non-speech
- interior and domestic
- exterior and urban
dataset revision は 33c8ce9eb2cf0b1c2f8bcf322eb349b6be34dbb6 に固定しました。ESC-50 全体は CC BY-NC で提供されているため、全 50 クラスを使う今回の検証も非商用条件に従う必要があります。
学習なしの 1-NN で比較する
評価には全 2,000 本ではなく、各クラス 2 本、合計 100 本を使います。
- fold 1 から各クラスのファイル名順で最初の 1 本を選び、50 本の参照集合にする
- fold 5 から同様に 50 本を選び、問い合わせ集合にする
- 問い合わせごとに、cosine similarity が高い参照音を並べる
- 最近傍のクラス、上位 5 件、大分類が正解と一致するか調べる
つまり、分類器の学習も 5-fold cross-validation も行いません。各クラスを 1 本だけで代表させる、固定された 50-way 1-nearest-neighbor 評価です。
前処理は checkpoint の preprocessor_config.json に合わせ、NumPy で実装しました。
| 項目 | 値 |
|---|---|
| sample rate | 48 kHz mono |
| input length | 10 秒 |
| 5 秒音声の調整 | repeatpad |
| FFT size / hop length | 1,024 / 480 |
| frequency range | 50 Hz–14 kHz |
| mel filter bank | 64-bin HTK |
| embedding dimension | 512 |
| normalization | L2 |
| similarity | cosine similarity |
| execution provider | CPUExecutionProvider |
ESC-50 の 5 秒音声は繰り返してから末尾を pad し、10 秒に揃えます。log-mel spectrogram を ONNX へ入力し、出力を再度 L2 normalize します。正規化済み embedding 同士では、dot product が cosine similarity になります。
実行結果
Mac Studio でウォームアップなしに 1 回実行した結果は、次のとおりでした。
fine accuracy@1: 45/50 = 90%
fine accuracy@5: 50/50 = 100%
coarse accuracy@1: 47/50 = 94%
100 clips elapsed: 5.458 s
seconds per clip: 0.055 s
問い合わせ 50 本のうち 45 本は、同じ細分類の別録音が最近傍になりました。残りの 5 本も、正解クラスが上位 5 件には入っています。5 大分類では 47 本が一致しました。
fine accuracy@1 で誤った 5 件を確認すると、次の組み合わせでした。
| actual | nearest | cosine similarity | coarse category |
|---|---|---|---|
| crickets | frog | 0.690 | 不一致 |
| footsteps | door_wood_knock | 0.603 | 不一致 |
| drinking_sipping | brushing_teeth | 0.492 | 一致 |
| mouse_click | keyboard_typing | 0.616 | 一致 |
| airplane | wind | 0.717 | 不一致 |
drinking_sipping → brushing_teeth と mouse_click → keyboard_typing は細分類こそ異なりますが、どちらも同じ大分類内です。音の発生源や動作が近い組み合わせでもあります。一方、残る 3 件は大分類も異なりました。
検証環境は次のとおりです。
- machine: Mac Studio(Mac16,9)
- chip: Apple M4 Max
- OS: macOS 26.5.1(25F80)、arm64
- Python: 3.12.11
- NumPy: 2.5.1
- ONNX Runtime: 1.27.0
- execution provider: CPUExecutionProvider
処理時間には 100 ファイルの FFmpeg decode、NumPy 前処理、ONNX 推論、L2 normalization が含まれます。モデル初期化と download は含みません。ウォームアップなしの単発測定なので、厳密な benchmark ではなく参考値です。
結果を考える
今回選んだ 100 本では、CLAP の audio embedding が環境音の意味カテゴリをかなりよく保持していました。参照音が各クラス 1 本しかないにもかかわらず、fine accuracy@1 は 90%、top-5 は 100% です。完全に同じ音色でなくても、「何の音か」に関係する特徴が embedding の距離へ反映されていると考えられます。
誤りにも興味深い傾向があります。マウスクリックとキーボード入力、飲む音と歯磨きのように、同じ状況で発生しやすい音が近くなりました。CLAP embedding を音の検索へ使う場合、厳密なラベル一致だけでなく、意味的に関連する音を候補として返せる可能性があります。
ただし、この 90% を ESC-50 の一般的な分類精度とは比較できません。評価は固定された fold 1 と fold 5 の一部だけで、各クラス 1 本の参照音に強く依存します。全 2,000 本を用いた cross-validation でも、未知データへの汎化性能の評価でもありません。また、CLAP の学習データと ESC-50 の重複は調査していません。
ESC-50 の公式リポジトリには、元音源へクラス依存の前処理が施されたことによる情報漏洩の可能性も記載されています。さらに、今回 NumPy で再実装した前処理が Hugging Face の ClapFeatureExtractor と数値的に完全一致するかは未確認です。これらは結果を読むうえでの制約です。
速度は 1 clip あたり約 55 ms でした。各入力は前処理上 10 秒なので、リアルタイムの環境音検索にも十分検討できる速さです。ただし 100 本を順次処理した値であり、batch 化や provider の比較はしていません。
試してみた感想
話者 embedding の次に環境音 embedding を試してみると、同じ「音を vector にする」処理でも、残る意味がまったく違うのが面白いです。512 個の数値を dot product するだけで、別録音のカエルやタイピング音が近くへ戻ってくる結果には、音の検索基盤としての手応えがありました。
一方で、airplane → wind の similarity が 0.717 と高いように、近さの理由をラベルだけで断定することはできません。飛行機の録音に含まれる風切り音を拾ったのか、別の特徴なのかは、この評価だけではわかりません。
次は audio tagging model を試し、1 つの音に含まれる複数の音響イベントをラベルとして検出できるか確かめたいです。embedding による類似音検索とは異なる方法で、環境の中で何が起きているのかをどこまで捉えられるのか検証します。