🎹

機械学習なんもしらないけどピアノ採譜AIを作りたかった

2024/11/10に公開
5

どうもこんにちは、だだっこぱんだです。
「今年の最も大きなチャレンジ」 ということで、今回は夏休みにちょくちょくやっていたピアノ採譜AIを作るという挑戦について書いていきたいと思います。

ちなみに結論から言うと、一部うまくいきませんでした。けれどもまだまだ諦め切れずにはいます。もし機械学習に強い方で何か気がついたことや、アドバイス等ありましたら、ぜひ教えていただけると嬉しいです。

👇やろうと思ったきっかけになったツイート
https://x.com/ddPn08/status/1821703482996564278

既存の技術

まずは既存の技術を調べていきます。
これについては主にFくんに調べてもらいました。ありがとう😊
https://zenn.dev/fkunn1326/scraps/06499bf82032b7
このスクラップと同じような内容になってしまいますが、再度まとめていきます。

Onsets and Frames

https://magenta.tensorflow.org/onsets-frames
Googleから出ているピアノ採譜AI。Onsets(各音符の開始位置)とFrames(音符が存在するすべてのフレーム)を別タスクに分けて検出することで精度が大幅に上がったとのこと。

hFT-Transformer

https://github.com/sony/hFT-Transformer
Sonyの方々が提案したもの。
音を周波数(音の高さ)軸と時間軸にtransformerを重ねることで正確に識別できるようになったとのこと。

MT3

https://magenta.tensorflow.org/transcription-with-transformers
これもtransformerでなんかやってる。
T5の転移学習でmidi-like tokenを予測させているとのこと。

Transkun

https://github.com/Yujia-Yan/Transkun
セミマルコフCRFモデルを使っているらしいとのこと。よくわからんかったけど名前好き。

bytedance/piano_transcription

https://github.com/bytedance/piano_transcription
Bytedanceというところが出しているピアノ採譜AI
ペダル推論もできる。
なんか三角関数を使ってるらしい。

ConcertCreator

https://www.concertcreator.ai/
音声やmidiファイルを入力すると、3Dモデルがまるで人間が弾いているように演奏してくれるサービス。
wav入力ができて、おそらくではあるが左右分離されたペダル推論つきのmidiをAIで生成している。

比較

mt3, hFT-Transformer, ConcertCreatorを比較しました。
https://x.com/ddPn08/status/1822130882532298885
この中で自分が求めているものに一番近いものはやはりConcertCreatorでした。
ここで比較していないものもいくつかありますが基本的にhFT-Transformerや、mt3と同じ感じでした。

問題点

主に以下の点が自分の感じた問題点です。

  • ペダルが推論されていない
  • ノートが必要以上に伸びている

ペダルが推論されていない点についてはそのまんまです。
「ノートが必要以上に伸びている」というのは、人間が演奏する際にそんなに押し続けないやろ!ってくらいにノートが伸びていることです。

ノートが伸びている例
https://x.com/ddPn08/status/1824645082265592053

ノートが伸びていない例
https://x.com/ddPn08/status/1825016362475114895

いざ演奏しよう!と思う際にみてわかりやすいのは下だと思います。

今回はこの2点の問題を解決するのをゴールにしていきます。

既存の技術を見ていく

とりあえずまずは、既存のものが一体何をしているのかを、初心者なりに理解しようと思いました。
まず見てみたのがhFT-Transformerです。

hFT-Transformer

自分は、AI関連でコードの理解をしたい時は、コードを読んでリライト(0から書き直す)する、ということをしています。ということで、hFT-Transformerを見ながら自分なりの書き方でリライトしてみました。

実際にリライトしたものは以下になります
https://github.com/ddPn08/hft-transformers-rewrite

このhFT-Transformerがやっていることを自分が理解できた範囲でまとめると以下のようになります。

  1. wavをlog-melspectrogramに変換
  2. それをモデルに入力 (たぶんtransformerってやつをやってるんだと思ってる、Attentionとかあったし。しらんけど。)
  3. モデルから、onsets, offsets, mpe, velocityの4つの情報が出力される
  4. それらをmidiに変換

といった流れです。
onsets, offsets, mpe, velocityはそれぞれ、ノートの開始、終了、存在している時間、強さを表しています。全て[T, 88]の形で出力されます。

データセットの処理

まず、ピアノ採譜AIでのデータセットは基本的にmaestro-v3.0.0を使っています。
約200時間のMIDIとその音源がペアになったデータセットです。
MIDIはおそらくwav収録に使用した電子ピアノのMIDI出力をそのまま使ったものでした。

hFT-Transformerでは、このデータセットを以下のように処理していきます。
まず、MIDIはonsets, offsets, mpe, velocityに変換されます。
この際に、offsetsをペダルが踏まれている分だけ伸ばす処理がデフォルトで入っています......!

ということでここで1つの問題が解決しました。ノートの伸びを解決するためには、offsetsをそのままのデータでラベル付けすればいいだけです。
これは他のピアノ採譜AIでも同じ処理をされていました。ペダルを推論しない代わりにノートを伸ばして入力音声に近づけていたのかと思われます。(bytedanceのピアノ採譜AIはペダル推論できるのにノートを伸ばす処理をしていました。謎いですね。)

ペダル推論

さて、残すはペダル推論だけです。
まずは、hFT-Transformerにペダル推論機能を実装していく方針で進めました。
というかここについては僕じゃなくてFくんが色々実装考えてくれました。ありがとう😊

👇Fくんが書いてくれたコード
https://github.com/fkunn1326/hFT-Transformer/

これを参考に、自分のリライトしたリポジトリにペダル推論機能を実装していきました。

方針その1

始めに、ノートのラベルを onset, offset, mpe, onpedal, offpedal, mpe_pedal, velocity に変更しました。
1つ1つのノートにそのタイミングでペダルが踏まれていたかどうかの情報を持たせた、という感じです。

これで学習した結果は、以下のとおりです
https://x.com/ddPn08/status/1824598008639746530

ものすごい数のペダルイベントが推論されてしまいました。
推論後の後処理が良くなかったのか、方針自体が良くなかったのかなんとも言えないですが一旦別の方法を考えることにしました。

方針その2

今度は、ペダルとノートを別ものとして捉えて学習するようにしました。
ノートのラベルは onset, offset, mpe, velocity に戻し、ペダルのラベルは onpedal, offpedal, mpe_pedal としました。

しかし結果は、ノートすら推論されないという結果になりました。

方針その3

方針その2では、ノートとペダルに同じnn.Moduleを使用していたので、それらを全て分けるように実装し直しました。

そうすると結果は少しだけ変わりました。

https://x.com/ddPn08/status/1825016362475114895
ノートの推論は完璧な形に

しかし、このまましばらく学習を進めていくと。。。

https://x.com/ddPn08/status/1825085035516715168
なぜかノートが推論されなくなってしまいました。

方針その4

なんならもうペダルとノートの学習自体分けてしまった方が良いのでは!!
ということでモデルごと分けて学習することにしました。

結果はかなり惜しいところまで行きました。
冒頭はすこしおかしいところがあったのですが後半の推論がかなり良かったです。
https://x.com/ddPn08/status/1825508906891903487
しっかり「小節の頭でペダルを踏み直す」、という動作が推論できていたのでかなりテンション上がりました。

しかし、そのまま学習を進めていくと、結果があまり変わらなくなりました。なんなら少し悪化しているくらいになってしまったので、この方針も失敗と判断しました。
https://x.com/ddPn08/status/1825656777389818273

諦め

hFT-Transformerにペダル推論機能を追加するのはなんかきつそうと思ったのでやめました。

ByteDanceのピアノ採譜AI

次に、ByteDanceのピアノ採譜AIを見ていくことにしました。
大まかな流れはhFT-Transformerと同じで

  1. wavをlog-melspectrogramに変換
  2. それをモデルに入力
  3. モデルから、onset, offset, reg_onset, reg_offset, frame, velocityの6つの情報が出力される
  4. それらをmidiに変換

といった流れです。

頭にreg_がついているのはおそらく、三角関数関係?のものかなと思っています。

一応理解するためにコードのリライトはしましたが、なんかわからんところがおおかったです。
https://github.com/ddPn08/bytedance-piano-transcription-rewrite

解決?

これについてはもうペダル推論機能はあります。あとはノートの伸びを解決するだけです。

https://x.com/ddPn08/status/1825493720910954605

hFT-Transformerの時も説明しましたが、ノートの伸びの原因はoffsetsをペダルが踏まれている分だけ伸ばしていることです。
https://github.com/bytedance/piano_transcription/blob/1ade7dcd4348add669a67c6e6282456c8c6633bd/utils/utilities.py#L369-L370

ということはこれをオフにして学習すればいいだけじゃん!!と思い、学習を進めようとしました。

処理速度

いざ、学習をさせようとしたら、なんかすごくおそいです。
GPU使用率が一瞬100%になってすぐ0%に落ちるという挙動をしていました。
調べて行ったところ、get_regressionという関数がものすごく時間を使っていることがわかりました。
この関数は冒頭の説明で述べた、三角関数の計算をしている部分なのではと思っています。

先に計算してしまおう

学習にはRunpodのGPUサーバーを借りていたので時間をかけるとその分お金がかかります。そのためGPUの使用率はなるべくあげたいです。
そこで、get_regressionの計算を先にしてしまい結果を保存して、学習時にはそれを読み込むだけに使用と思いました。
事前処理にget_regressionの処理を追加して、いざ家のPCで計算を行おうとしたのですが、終わるのにかかる時間がなんと約70時間...
vultrでベアメタルのつよつよCPUサーバーを借りて、計算しても7時間。。。

諦め

流石に時間かかりすぎるのと、bytedanceのコードは若干よくわからないところが多かったので、一旦諦めました。

Onsets and Frames

最後に、Onsets and Framesを見ていきます。
これのpyrorch実装のコードがすんごく見やすくてありがたすぎました。
https://github.com/jongwook/onsets-and-frames

これもリライトしました。
https://github.com/ddPn08/onsets-and-frames

処理の流れはこれもhFT-Transformerとだいたい同じで

  1. wavをlog-melspectrogramに変換
  2. それをモデルに入力
  3. モデルから、onset, offset, frame, velocityの4つの情報が出力される
  4. それらをmidiに変換
    といった感じでした。

ペダル推論

Onsets and Framesにはペダル推論機能がないので、自分で実装していかないといけません。
これについてはhFT-Transformerで行った方針その4と同じく、ノートとペダルを別々に学習することにしました。

結果、ノートの推論は普通にできたのですが、ペダルの推論はやはりできなかったです。
関係あるのかわからないですが、lossがすごい動きをしていました。
https://x.com/ddPn08/status/1828954861364543542

特にframeのlossがすごい変な動きをしていたので、これが原因かもしれないと思いました。
https://x.com/ddPn08/status/1828977010653491213

冒頭で述べた通り、Onsets and Framesのframeの部分は音符の長さです。ペダルには長さがなく、ただのon/offだけなので、frameはそもそも要らなかったのかも?と思いました。
この考え方が正しいのかわかりませんが。。。
とりあえずframeを削除して学習を進めてみました。

結果、lossの動きは安定しましたが、値がものすごく小さくなりました。
https://x.com/ddPn08/status/1828990253509816514

推論もペダルは全く推論されない状態でした。

諦め

ここまでで約1ヶ月経ちました。そろそろ疲れてきたので一旦やめて、この記事にまとめることにしました。
https://x.com/ddPn08/status/1830555450691191265

まとめ

基礎的な知識がなく、なんとなくでやってきましたが、やっぱり機械学習は難しいなと言うことがわかりました。コードのを見ていても何をやってるのかよくわからない関数だったりクラスがいっぱいなのでまずはこのあたりをしっかり知っていく必要があるなと感じました。
しかし、少しでもペダル推論が綺麗に動いたときはものすごく感動しました。ゴールには達しませんでしたが、まさかここまで作れるとは思っていなかったので始めた時の想定以上の結果が出せたと思います。

たすけて

ピアノ採譜AI(というかピアノのペダル推論AI)についてはまだまだ諦めてないのでゆっくりと勉強しながら進められたらと思っています。
冒頭でも書いた通り、もし機械学習つよつよな方でお手伝いしてくださる方がいたらすごく嬉しいです!!!!!!!!!

Discussion

まっくすまっくす

とても面白い記事で勉強になりました。これだけ手を動かしているの…流石です。
何かしら意見を求めていそうなので読んで感じたことを書いてみます。私は音声分野やこのタスクについては全くの門外漢なのと、コードも読んでいないのでトンチンカンなことも書いてますでしょうし、アイディアの参考程度にしてくれたらと思います。

hFT-Transformerの学習について

方針その3について

最初はノートを検出できていたけどそのあと検出されなかったとのことですが、ノート推論とペダル推論の loss の計算部分でそれぞれ重み付けなどされてるといいかも、と思いました。この場合だとペダル推論のほうの重み計算の割合が大きくて、そっちに引っ張られて後半学習が崩壊してるのかな?など思ったりしました。

方針その4について

こちら結構うまくいったとのことですがペダル推論のほうがうまく行かなかったとのこと。
これは完全に所感なんですが、ペダルが押されているか押されていないか、という2値分類のタスクなら波形から割とかんたんに推論できそうなタスク?な気もするので、何かしら設定やモデルの問題なのかなと思いました。(実際にやったことはないのでただの所感です)
この場合だと spectrogram から切り出す窓の長さはペダル推論はノート推論よりも長いのかなと思いました。なんとなくノート推論は比較的短い窓で推論でき、ペダル推論はもう少し長い窓で推論しそうな気がした、というだけです。

ByteDance のモデルについて

既にペダルの推論もできてますし、これがうまく学習できたら一番ハッピーな気がしますね…

Onsets and Frames について

これ学習時のデータがどうなってるかわからないのですが、ペダルの on/off をそのまま学習してるのでしょうか?このモデルが何かしらノートが押されたタイミングと押されているフレームに分けて学習しているっぽいので、ペダルも押されたタイミングと押されているフレームに分けるような前処理をして学習するべきなのかな、と思いました(既にそうしているかも。)

以上読んで思った所感でした。既に色々手を動かされておりタスク難易度の肌感もすごくあると思いますので参考程度に...。
とても面白い取り組みだと思うので、頑張ってください!

だだっこぱんだだだっこぱんだ

コメントありがとうございます🙏

何かしら意見を求めていそうなので読んで感じたことを書いてみます。

すごくありがたいです!!!

hFT-Transformerの学習について

方針その3について

ノート推論とペダル推論の loss の計算部分でそれぞれ重み付けなどされてるといいかも

これは

# loss_pedal_weightとloss_note_weightを調節していい感じにする?
loss = loss_pedal * loss_pedal_weight + loss_note * loss_note_weight

みたいなイメージであっていますでしょうか?

方針その4について

この場合だと spectrogram から切り出す窓の長さはペダル推論はノート推論よりも長いのかなと思いました。なんとなくノート推論は比較的短い窓で推論でき、ペダル推論はもう少し長い窓で推論しそうな気がした

すいません!この辺り自分のspectrogramへの理解が0に等しいのであんまり理解できなかったです。
まずはそこから勉強するべきだと感じたので勉強します!

ByteDance のモデルについて

既にペダルの推論もできてますし、これがうまく学習できたら一番ハッピーな気がしますね…

ある程度計算資源にお金かければなんとかなるのですが、それだとなんか面白くないのであえてスルーしたところもあります😓

Onsets and Frames について

これ学習時のデータがどうなってるかわからないのですが、ペダルの on/off をそのまま学習してるのでしょうか?このモデルが何かしらノートが押されたタイミングと押されているフレームに分けて学習しているっぽいので、ペダルも押されたタイミングと押されているフレームに分けるような前処理をして学習するべきなのかな、と思いました(既にそうしているかも。)

frameを使わない処理をする前はそのような処理でした。pedalのon/offをnoteのon/offと捉えて、onset, offset, frameのラベル付けをしました。
いま記事見直すと、onsets and frameの説明だけやたら雑ですね、、、
後ほど追記します。

そして今いただいたコメントを読みつつ返信を書いていたら、記事内の

ペダルには長さがなく、ただのon/offだけなので、frameはそもそも要らなかったのかも?と思いました。

というのはやっぱり間違っているような気がしてきました。
この辺りもう一度見直して再度実験していきたいですね。

まっくすまっくす


方針その3について

loss = loss_pedal * loss_pedal_weight + loss_note * loss_note_weight

そうです!

方針その4について
私もそこらへんあまり詳しくないですが、spectrogram は音声を各フレームで周波数分解して(ここらへんはフーリエ変換とかででてきます!)それが時間方向に並んだものが入力になってそうで、その時間幅をどれくらいとるか?みたいなパラメータですね。(多分この添付画像だとM+1+Mに相当してそう)
https://qiita.com/xiao_ming/items/1f96976553e090d24d88#automaticpiano-transcription-with-hierarchical-frequency-time-transformer
例えばどのノートがなってるかどうかは割と1秒とか切り出しても推定できそうですが、リバーブがかかってるかどうかは1秒とかの短い時間より5秒とかのほうが判定しやすいと思ったりしました。
なんか間違ってたらすみません。

だだっこぱんだだだっこぱんだ

リバーブがかかってるかどうかは1秒とかの短い時間より5秒とかのほうが判定しやすいと思ったりしました。

めちゃくちゃ理解しました!!!わかりやすく説明いただきありがとうございます...!!
この辺りのパラメータの調整は全くやってなかったので試したいですね。