判定特化AI「Jev」に音楽のジャンルを判定させてみた ─ librosa で測って Jev で決める

ロリポップ!AIゲートウェイが対応した判定特化モデル「Jev」を使って、音楽ファイルのジャンルやムードを判定する CLI を作りました。librosa で測った特徴を Jev に渡す 2 段構成で、精度を上げていく過程で見えてきた「Jev は渡したデータをどう読んでいるのか」を、実験結果と合わせて紹介します。

はじめに

GMOペパボの黒瀧です。ロリポップ!AIゲートウェイが TypeSafe AI の判定特化モデル Jev に対応しました(お知らせ)。

Jev は「生成」ではなく「判定」に特化したモデルで、選択肢から選ぶ・段階で評価する・Yes/No を判定する、といったことを 0.1〜0.5 秒程度で返してくれます。せっかくなので触ってみようと思い、音楽ファイルからジャンルやムードを判定する CLI を作ってみました。

作る過程で「Jev は渡したデータをどう読んでいるのか」が少し見えてきたので、その実験結果も合わせて紹介します。作ったものは GitHub で公開しています。

kurotaky/jev-music

Jev の呼び出し方

ロリポップ!AIゲートウェイの API キーがあれば、すぐに使えます。ほかのモデルで使う /v1/chat/completions ではなく、Jev 専用の /v1/systemone を使います。

リクエストには、判定の材料となる state と、聞きたいことを並べた questions を渡します。

AI_GATEWAY_BASE_URL=https://ai-gateway.lolipop.jp
curl -sS "$AI_GATEWAY_BASE_URL/v1/systemone" \
  -H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "typesafe/jev-latest",
  "state": {
    "inquiry": "先週注文したマグカップがまだ届きません。注文番号は12345です。急ぎで使いたいので早く対応してほしいです。"
  },
  "questions": {
    "category": {
      "type": "choice",
      "instructions": "問い合わせの種類はどれか?",
      "criteria": {
        "shipping": "配送・到着に関する問い合わせ",
        "refund": "返品・返金に関する問い合わせ",
        "product": "商品仕様に関する質問"
      }
    },
    "urgency": {
      "type": "score",
      "instructions": "問い合わせの緊急度は?",
      "criteria": ["低い", "やや低い", "普通", "やや高い", "高い"]
    },
    "angry": {
      "type": "noul",
      "instructions": "問い合わせ者は怒っているか?"
    }
  }
}'

questions の type は 3 種類あります。

type用途criteria返ってくるもの
choice選択肢から 1 つ選ぶ{キー: 説明文} のオブジェクトchoice・confidence・全選択肢の probabilities
score段階で評価する低い順に並べた配列確率で重み付けした score(連続値)・probabilities
noulYes/No を判定する不要0〜1 の noul

上のリクエストの結果は、category が shipping(確率 1.0)、urgency が 3.16(「やや高い」78%・「高い」19%)、angry が 0.49 でした。1 回のリクエストで複数の判定をまとめて聞けて、応答は 0.42 秒でした。

作ったもの: jev-music

Jev の state はテキスト(JSON)なので、音声ファイルをそのまま渡すことはできません。そこで、次の 2 段構成にしました。

mp3/wav ──librosa──▶ 特徴量(BPM, キー, 音圧, 音色…)──テキスト化──▶ Jev ──▶ ジャンル・ムード
          「測定」                                              「判定」
  • 測定(テンポ・キー・音圧・音色など)は librosa に任せる。信号処理で正確な数値が出るものを、わざわざ AI に推測させる必要はありません
  • 判定(ジャンル・ムード・踊れるか など)は Jev に任せる。正解が 1 つに決まらず、確率で答えるのが自然なものです
uv run jev-music song.mp3
🎵 guitar_obsession-80s-radio-tune-113798.mp3
[librosa] 測定
  テンポ     117.5 BPM(ビート 239 個 / 拍の明確さ 0.576 / 2・4拍子系)
  キー       F major(確からしさ 0.138)
  長さ       120.0 秒 / 音圧 -16.3 dBFS / 抑揚 20.2 dB
  音色       重心 2711 Hz / 低音比 0.444 / 高音比 0.0428 / ノイズ感 0.0342
  リズム     打楽器比 0.221 / 毎秒 2.71 音
[Jev] 判定
  ジャンル   rock(rock 80% / pop 19% / house_techno 1%)
  ムード     energetic(energetic 85% / happy 13% / calm 1%)
  明るさ     2.62 / 4
  激しさ     2.58 / 4
  踊れる     65%
  作業BGM    54%
  電子音中心 58%
  ⏱ 解析 4.58s / Jev 0.49s

librosa で測っているもの

特徴中身
テンポlibrosa.beat.beat_track
拍の明確さオンセット強度の自己相関のピーク(0〜1)
拍子自己相関の 3 拍目と 4 拍目の強さを比べて 3 拍子系 / 2・4 拍子系を推定
キークロマ特徴と Krumhansl のキープロファイルの相関
音圧・抑揚RMS の平均と、上位 5% と下位 5% の差
音色スペクトル重心、150Hz 未満・4kHz 超のエネルギー比、スペクトル平坦度
打楽器比HPSS(調波音と打楽器音の分離)でのエネルギー比

Jev に渡しているもの

数値だけだと多いのか少ないのかが分かりにくいので、目安の言葉を添えて state にしています。

"state": {
  "audio_features": {
    "tempo": "117.5 BPM(中くらい。ビート検出は倍・半分に誤ることがある)",
    "pulse_clarity": "0.576(拍の感じやすさ。はっきりしている)",
    "key": "F major(長調、推定の確からしさ 0.138)",
    "loudness": "平均 -16.3 dBFS(音圧高い)",
    "bass": "150Hz未満のエネルギー比 0.444(ベース・キックなど低音が中くらい)",
    "treble": "4kHz超のエネルギー比 0.0428(シンバル・ハイハット・歪みなど高音成分が多い)",
    "noisiness": "スペクトル平坦度 0.0342(ノイズ・歪み・シンセ成分が多い)",
    "percussive_ratio": "打楽器成分の割合 0.221(打楽器成分中くらい)"
  }
}

questions には、ジャンル(17 種の choice)、ムード(7 種の choice)、明るさ・激しさ(score)、踊れるか・作業 BGM 向きか・電子音中心か(noul)の 7 つを並べています。

精度を上げる: 2/7 → 6/7

librosa には、ジャンルがはっきりしたサンプル曲(ブラームス、ドラムンベース、ワルツ、ラグタイムなど)が付属しています。この 7 曲を正解データにして、改善していきました。

最初の版は 2/7 正解。7 曲中 5 曲が house_techno(ハウス/テクノ)と判定されていました。

原因 1: どの曲でも「ビートが非常に一定」になっていた

最初はビート間隔のばらつきを「規則性」として渡していたのですが、これが全曲 0.93〜0.96 になっていました。ビート検出器は等間隔にビートを置こうとするので、ビート間隔からテンポの揺れを測っても差が出ないのです。

その結果、全曲に「非常に一定(機械的)」という言葉が付き、4 つ打ちのハウス/テクノに引っ張られていました。

オンセット強度の自己相関から「拍の明確さ」を取るように変えると、テンポを揺らして演奏するブラームスは 0.17、機械的なワルツは 0.9 と、ちゃんと差が出るようになりました。

原因 2: ジャンルの説明と特徴量の言葉がかみ合っていなかった

低音比・高音比・平坦度を追加しても 3/7 どまり。効いたのは criteria(ジャンルの説明文)の書き直し でした。

- "classical": "クラシック。オーケストラ・ピアノ、打楽器が少なくダイナミクスが大きい",
+ "classical": "クラシック。オーケストラ・ピアノ・弦楽・室内楽。ドラムもベースラインも無いので低音と打楽器成分が少ない、澄んだ楽音、録音の音圧は控えめで抑揚が大きい、3拍子のワルツも多い",
- "jazz": "ジャズ。スウィング、即興、ビートの揺らぎ",
+ "jazz": "ジャズ。ウッドベースが鳴り続けるため低音が多い一方、ドラムはブラシやシンバル中心で打楽器成分は少ない。ピアノ・管楽器、スウィング",

state で使っている言葉(低音・打楽器成分・音圧・抑揚)と同じ言葉でジャンルを説明するようにしたところ、6/7 正解 になりました。

曲改善前改善後
brahms(クラシック)classical ✅classical 84% ✅
choice(ドラムンベース)house_techno ❌drum_and_bass 95% ✅
nutcracker(クラシック)lofi ❌classical 82% ✅
pistachio(ラグタイム)house_techno ❌classical 81% ✅
sweetwaltz(ワルツ)house_techno ❌classical 99% ✅
vibeace(ジャズ寄りラウンジ)lofi ✅jazz 60% ✅
fishin(カントリーポップ)house_techno ❌pop 79% ❌

残った fishin は歌もののカントリーポップで、特徴量の上ではポップスそのものです。バンジョーの音色は今の特徴量では見分けられないので、この 1 曲に合わせて調整するのはやめました。

実験: Jev は数値を読んでいるのか、言葉を見ているのか

ここまでの経緯から、「Jev は state と criteria の言葉を照らし合わせているのでは?」という疑問が湧きました。jev-music は入力を完全に制御できるので、これを確かめる実験台にしてみました。

実験 1: 数値+言葉 / 数値だけ / 言葉だけ

同じ 7 曲で、state の渡し方だけを変えました。

state の渡し方例正解数正解ジャンルへの確率(平均)
数値+言葉0.794(低音が非常に多い)6/774%
数値だけ0.7946/757%
言葉だけ低音が非常に多い6/772%

数値だけでも正解数は変わりません。 Jev は数値の大小もちゃんと判定に使えています。ただし確信度は下がり、それを押し上げているのは主に言葉のほうでした。

実験 2: 特徴を 1 つずつ動かす

基準曲(くるみ割り人形。低音比 0.087)の低音比だけを書き換えて、ジャンルの確率がどう動くかを見ました。

低音比数値だけ: classicaljazz数値+言葉: classicaljazz
0.172%2%84%·
0.263%4%16%3%
0.349%6%12%4%
0.431%10%12%5%
0.520%13%12%5%
0.611%32%2%54%

(数値+言葉の 0.2〜0.5 は folk_acoustic が 1 位)

  • 数値だけ: classical が 72 → 63 → 49 → 31% と、なめらかに下がっていく
  • 数値+言葉: 言葉が「少ない → 中くらい」に切り替わる 0.2 で、84% → 16% に急落。0.2〜0.5 の間はほぼ横ばい

言葉があると、Jev は数値よりも言葉に従います。つまり、こちらが決めた言葉のしきい値が、そのまま判定の境界になります。

BPM でも面白い結果が出ました。

BPM数値だけ: classicaldrum_and_bass数値+言葉: classicaldrum_and_bass
13551%12%83%3%
15066%10%88%1%
16543%49%82%7%
18026%67%83%8%

数値だけのときは、drum_and_bass の説明文に書いた「BPM 160-180 前後」の範囲に入ったところで逆転しました。state の数値と criteria に書いた数値の範囲を照らし合わせている ようです。一方、言葉を添えると 135 以上はすべて「速い」になるため、180 でもほとんど反応しませんでした。言葉が数値の情報を隠してしまったわけです。

state 設計の指針

やりたいこと渡し方
判定をはっきりさせたい(確信度を上げたい)言葉を添える。ただし境界は自分で決めることになる
境界付近の値をきめ細かく扱いたい数値だけを渡し、criteria 側にも数値の目安を書く
特定の範囲で判定を切り替えたいcriteria に数値の範囲を明記する(例: BPM 160-180)

7 曲・基準曲 1 曲・各条件 1 回の小さな実験なので傾向として見てほしいのですが、「state と criteria を同じ言葉で書く」「言葉のしきい値は判定の境界になる」は、Jev を使うときに意識しておくとよさそうです。実験の詳細は docs/jev-probe.md にまとめています。

実際の曲でも試してみる

サンプル以外の曲でも試してみました。ファイル名に「edm」「rock」などが入っていますが、Jev に渡しているのは librosa の特徴量だけで、ファイル名は渡していません。

EDM×ロック系のビート80s ラジオ風の曲
ジャンルhiphop 25% / drum_and_bass 23% / rock 17% / edm 16% / house_techno 14%rock 81% / pop 18%
genre の confidence0.190.79
ムードdark 74%energetic 86%
電子音中心75%58%

80s ラジオ風の曲は、「高音成分が多い」「ノイズ・歪み成分が多い」「音圧高い」という state が、rock の説明文とそのまま重なって rock 81% になりました。

EDM×ロックの曲は、1 位の hiphop でも 25% しかなく、電子系とロックに確率が割れました。電子音とロックが混ざった曲なので、この迷い方はむしろ正直です。こういう曲では 1 位だけでなく、確率の上位をまとめて「ジャンル候補」として見せるほうが、Jev の出力に合っていると感じました。

速度とコスト

  • 応答時間: 7 つの質問をまとめて聞いても、毎回 0.4〜0.5 秒
  • トークン: 1 回あたり入力約 2,270 トークン。その大半は 17 ジャンルの説明文で、state は 1 割程度
  • 料金: 入力 $0.042 / 100 万トークン、出力は無料。1 回あたり約 $0.0001(2026年9月18日〜24日は無料キャンペーン)

同じ入力で 2 回呼び出しても、確率の差は 1〜2% でした。判定が安定していて、速く、安いので、大量のデータを捌く処理や、LLM を呼ぶ前の振り分けに向いていそうです。

まとめ

  • Jev は音声を直接は受け取れないので、「測定」は librosa、「判定」は Jev と役割を分けるとうまくはまった
  • 精度に一番効いたのは、特徴量を増やすことより、criteria を state と同じ言葉で書くこと だった
  • Jev は数値も読んでいるが、言葉を添えると言葉に従う。言葉のしきい値がそのまま判定の境界になる

今後は、4 ビート・8 ビート・16 ビートといったリズムの刻みやテンポの揺れの判定、mp3 のタグ(曲名・アーティスト)をヒントとして渡す機能を試してみたいと思っています。

参考

ブログの著者欄

黒瀧 悠太

GMOペパボ株式会社

オリジナルグッズを手軽に作成・販売できる「SUZURI byGMOペパボ」、国内最大級のハンドメイドマーケット「minne byGMOペパボ」 の技術戦略とプロダクト開発を統括。テクノロジーとクリエイター支援の両軸から、事業部全体の価値最大化に取り組んでいる。GMOインターネットグループ エキスパート(次世代IoTシステム)も兼任。趣味は音楽鑑賞、ドラム演奏。

採用情報

関連記事

KEYWORD

TAG

もっとタグを見る

採用情報

SNS FOLLOW

GMOインターネットグループのSNSをフォローして最新情報をチェック