テキストのLanguage Modelで「音楽アドリブ生成AIコンテスト」に参戦

yatszhash氏:「歌え!GPT!text LLMによるアドリブ生成AIコンテスト参戦!」というタイトルで発表させていただきます。

と言っても、すみません、私、口下手なので、まずは1曲演奏をさせてください。言語モデルにさせます。

ということで、こちら、オルガンのメロディー部分が生成部分になります。伴奏は、人間が作ったものです。

今回の話は、GPTとは別の文脈のコンテストがあるんですが、(コンテストに)テキストのLanguage Modelで殴り込んで、なんだかんだMLシステムあるあるの苦労をした話です。

なので、結局話すトピックはいつも聞くような話が多いです。GPT-3のファインチューニングだったり、前処理の工夫であったり、ローカルのLanguage Modelへの移行であったり、後処理や推論の工夫であったり、評価の話という、わりとオールドな機械学習の話題が多いです。

私は、機械学習エンジニアの端くれということで、Language Modelとの関わりとして、医療会話の要約などをやっています。一応、「ChatGPT以前から」というところは強調しておきます。

AIミュージックバトル!『弁財天』とは?

今回参加した音楽アドリブ生成AIコンテストは、「弁財天」という名前なんですが、どういうものかというと、こういうものになります。

アドリブ生成AIのコンテストです。2月に行われたもので、珍しく1対1のトーナメント形式になっています。ふだんのコンペ……「Kaggle」などをイメージしてもらうとわかると思いますが、サブミットしてどうのこうのと順位を競うことがわりと多いですが、(今回のコンテストは)1対1のトーナメント形式という珍しい形式になっています。

特徴的なのは、音楽生成のAIのコンテストということで、観客の投票で勝敗を決めるというところです。

じゃあ、どういう感じでやるかというと、人間が作ったお題の伴奏のMIDIと、コード進行が運営から与えられます。

各々の選手は、MIDIのメロディーを生成するAIを作ってきます。これをその場で推論して、メロディーを生成するという流れになっています。

なぜこれに参加しようと思ったか。わかる人いるかな……2022年末、(結束バンドに)音楽的なところを熱されて、何も考えずに参加を申し込みました。

音楽AIのコンテストなのにGPT-3でのアプローチに縛った

何を思ったのか、音楽AIのコンテストなのにGPT-3でのアプローチに縛るという、わりとアレなことをします。

「これって音楽の生成にも使えるんだっけ?」という話ですが、あくまで自然言語でトレーニングされた事前学習モデルなので、これはとても相性が悪いです。

やはり、四則演算などの数値の扱いが苦手だったり、ある一定の(形式の)出力を常にすることを生成系の言語モデルで保証することはできません。

どういうことが起こるかというと、例えば4分の4拍子のものの1小節にすごくたくさん音符がついちゃったり、逆にぜんぜん音符が入っていなかったり、そういったことが起こっちゃいます。

あと、そもそも、やはりGPTの学習に使われている事前学習のコーパスは、基本的に一般ドメインのものなので、歌詞以外の音楽データはあまり入ってなさそうという感じで、そもそも相性が悪そうだよねという感じです。

もっと根本的なことを話すと、結果を求めるならアプローチを縛るのは絶対に良くないです。なんでそれでも縛るかというと、単に遊びだから……(スライドに)本心が透けていますけど(笑)……ですと。

今回はシンプルなabc記譜法を採用

どうやってテキストのLanguage Modelで実現したのかという話に入っていきます。「そもそも音楽ってテキストで表現できるんだっけ?」という話です。

プログラムで音楽を扱う時は、例えば波形であったり、ピアノロールであったり、MIDIであったり、楽譜であったり、そういうかたちがあると思います。今回、データセットの都合もあって楽譜を利用しています。

楽譜は、実はわりとすでに確立された構造化テキストの形式があって、一番普及しているのはMusicXML、電子楽譜のデファクトのデータ構造です。

今回はそれを使うのではなくて、abc記譜法というかなりシンプルなものを使っています。なんでかというと、言語モデルは結局、入出力長の制約がすごく大きいため、MusicXMLみたいなでかいXMLを吐いたり(出力したり)入力したりするのが難しかったからです。そのため、abc記譜法という、もうちょっとシンプルなものを採用しています。

abc記譜法はどういうものか。あまり詳しくは話せませんが、(スライドを示して)こんなものです。これについて詳しくは話しません。

GPTをどう活用したか

今回、結局どういうシステムを作ったか。今回、伴奏とコード進行を渡されているのですが、コード進行だけを利用します。前処理を通してコード進行をテキストに変換して、GPT-3などの言語モデルに入れます。

abcからMIDIに変換して、GPT-3もabcというものを吐いて、abcから最後にMIDIに変換して、メロディーのMIDIと伴奏のMIDIをマージするということを行っています。

GPTをどう使うかというと、まずはちょっとChatGPTでなにかできないかをやってみるんですね。(スライドを示して)こういうプロンプトを入れてみます。

こういうふうに、ちょっと比較がわかりにくいですが、ほぼほぼコピーをされて、何も意味のない文字列、意味のない和音をいっぱい流している感じになっています。

ここから、プロンプトエンジニアリングでがんばるというのはちょっと現実的ではないので、GPT-3をファインチューニングするという方向に切り替えます。

GPT-3をファインチューニングするためにどう学習データを用意したかというと、もともと研究用の電子楽譜のデータセットというものがあるので、それをコード進行とメロディーに分割して、コードをプロンプト、インプットとして用意して、メロディーをcompletionとして用意するというデータの作り方をしています。

なので、正解(ラベル)がメロディーで、インプットがコード進行という感じになります。

実際に使ったAPIの入力のJSONL(JSON Lines)は(スライドを示して)こんな感じになっています。入力というとアレですが、使ったデータのJSONLですね。

前処理でタスクの難易度を下げる

ただ、これはファインチューニングしようとしても、やはりタスク的に遠いんですよね。GPT-3がやっている言語モデリングというところから、やはりすごく遠いですし、そもそも元のコーパスにデータが入っていないよねという話があります。

こういう時に、1つの手としてあるのが、前処理でともかくタスクの難易度を下げること。

例えば、データのありとあらゆるパターンを前提とするのではなく、ルール上出ないものはもう全部切り捨てたり、変換したりする。この場合だと、ハ長調とイ短調にすべて学習データの曲のキーをそろえます。

あとは、ルール上4分の4拍子の曲しか(お題が)出ないので、4分の3拍子の曲など4分の4拍子以外の曲は削除します。

ほかには、学習データのタイトルなど余計な情報は削除したり、最後にプロンプトでコードネームではなくコードの構成音を明示的に与えます。

最後がわかりにくかったので補足しておくと、コードネームの件に限らず、明示的にルールでわかるものをわざわざGPT-3なり機械学習モデルなりに推論をさせるのは、あまり賢い方法ではないので、こういうコードだというのを、あらかじめ明示的に人間が機械的に変換して与えます。

こういうふうに学習データを前処理をかけて作っていきました。

個人がイテレーションを回して改善していくのは無理だと気づいた

いざ、チューニングをしようと。GPT-3のファインチューニングの設定に関して、モデルは「Curie」を使って、5エポックだけ学習させています。GPT-3は基本的にエポック数を増やしてやることを前提としていないので、だいたい4とか5エポックぐらいで回しています。全部学習で使うのではなく、いったん試験的に3,000件ぐらいでやっています。

やってみました。ファインチューニングされたGPT-3が生成したメロディーがこれです。ちょっと単調ですが、まともに生成できるんだということがわりと驚きで、これはベースラインとしてはわりと良さげでした。

これならいけるということでイテレーションを回そうとしたんですが、学習1回あたりの試算で、1万4,000円以上かかるということがわかってですね……。

(会場笑)

個人がイテレーションを回して改善していくのは無理やんという話です。

GPT-3の代わりに「BART」を活用してみた

この時に諦めるのもありだったし、もっと小さい(GPT-3の)モデルでやるのも手段なんですが、GPT-3以外の言語モデルでどうにかならないかと考えました。

幸い、自分はふだん業務でもわりと使っていて、例えばBARTという、テキスト生成の言語モデルをけっこう知っているので、今回はこの言語モデルを使っています。

これを選んだ理由は単に、小さいGPUでもファインチューニングできるからです。T5とかでもできるので別になんでもよかったんですが、いったんこれを選んでいます。

これは半日以上、学習に時間がかかりますが、BARTでも一応生成ができるようになっていました。ただ、やはり傾向はGPT-3とはけっこう違う感じにはなっていました。

モデルの出力そのままだと、まだ厳しいです。どういうことが起こるかというと、不正なabcが出力されたり、不正なフォーマットのものが出力されたり、あと単純に長音とか長い休符が多くてつまらないということがわりとありました。

これはどうするかというと、もう後処理と推論をがんばるしかないです。

問題その1 不正なフォーマットが出力されてしまう

今回は、上の2つだけに(問題を)絞ります。不正なフォーマットが出力されてしまう問題。

これは、コンテストではすごく致命的です。5分以内にその場でAIを使って生成するというルールがあるので、有効なabcがその場で生成されないと当然詰んじゃいます。どういうものが生成されるかというと、1小節に音符が多すぎるとか、ヘッダー部分の形式が崩れるということが起こっています。

対策の1つ目として、当たり前ですが、バリデーションと補正はしっかりしましょう。エラーの握り潰しをしてくれる(abcからmidihへの変換)ライブラリに差し替えたり、間違いが多いところをルールベースで修正します。これは誰でも思いつくんですけど。

もう1つの対策は、候補をたくさん出すこと。候補をとにかくたくさん出して、そのどれかに有効なabcが含まれていることを祈るという、最後はやはり祈るんだということが起こるんですけど。

どういうことをやっているかというと、1つ目は単純で、複数のバージョンのモデルを用意して、パラレルに推論するということを行います。この場合はGPT-3も含めてですね。BARTも、両方ですね。

あと、もう1つ、インプットのデータセットのサブセット名のprefixを添えて、出力のバリエーションを出す。自分で読んでいてもよくわからないんですけど(笑)。

これはどういうことをやっているかというと、データセットの中にサブセットがあって、charlie parkerというコーパスから来ているものと、wiki(wikifonia)というコーパスから来ている音源と、実は2、3種類ぐらい混ぜているんですね。

それをコーパスによって出し分けられるように、こういうふうに接頭辞をつけます。これは別になんでもいいんですが、とりあえず「?char」みたいな感じでつけています。そうすると、出力が同じもの(コード)を入れたとしても変わってくる。こうやってバリエーションをつけていきます。当然これは、学習時にも同様のものをつけています。

あと、候補が多いという問題はもう、あとは最後祈るということで……もう、それしかなかったんですけど。

問題その2 長音や長い休符が多くてつまらない

じゃあ、問題2。そもそもつまらんという話。やはりアドリブなので、音が多くないと、とにかくつまらないんですよね。これは補正前のものだったので、ちょっと拍子がおかしいです。

この対策ですが、推論時にコードを1拍ごとに細かくして与える。

これもちょっと言葉だけだとイメージしにくいと思います。元の入力って、なんか1小節ごとの頭にコードが書かれている感じだったんですが、これを同じコードなんだけど1拍ごとに入れる、わざわざ入れています。

そうすると、コードの変更がすごくたくさん起こっているという錯覚をモデルがして、こういうふうにすごい音が……5連符がめっちゃありますけど(笑)、そういう錯覚を起こして、音の動きを出すということを行っています。

後処理、推論の工夫で、そこそこのメロディーが生成できるようになってきました。

勝てるものを作るために評価システムも設計

今まで話していないのですが、ここですごく大事なことをお話しします。結局、自分で作りながら、俺が聞いて、俺の好みだけで評価しちゃっています。

これはよろしくないので、評価システムを用意しましょう。アドリブの評価はやはり主観的なものなのですごく難しいんですが、使えるものを作るためにはやはり評価方法をきちんと設計しましょうという、教科書どおりなんですが、そういうものがあります。

具体的には、例えばバリデーションセットがないなら自分で用意しましょう。代表的な文献、コード進行のパターンを調べて網羅するパターンを作って追加するということは、別にできますよね。

2番目として、俺の好み以外の評価指標を用意する。例えばですが、音楽ならどれぐらい調性に合っているか、どれぐらい不協和音がないかということは、ある程度実はルールで自動評価できるので、それを使う。

あとは、音楽をやっている複数人に評価をお願いするということをやれば、俺の評価以外をきちんと用意できますよねということです。

ただし、すごく重要なこととして、人が評価する場合に「AIが作ったわりにすごい」みたいなバイアスが入らないように注意は必要になってきます。

これは、実務でもけっこうあるあるです。AIが作ったらすごいんだけど、実際に実務で使うとぜんぜん使えんというのは、わりとある話なので、「AIが作ったわりに」というのをいかになくすかが、評価設計上けっこう重要なのかなと思っています。これ、本来は(プロジェクトの)最初のほうにやることです。

予選は無事通過、果たして本戦は?

じゃあ、いざ本番です。いざ本番、システムも、評価システムもできました。実は予選があったんですが、これもテキスト言語モデルでやったのに無事通過しました。

本選はこんな感じでした。きちんとトーナメントになっていて自分の名前も入っています。

最後の評価設計をさぼって、1回戦で負けたやつがいるらしいんですよ。まぁ、私なんですけど(笑)。

全部は流さないんですけど、こういう感じでした。

それで、対戦者がすごく象徴的で……。

なんでしょう。わりと対戦者はメカニカルなフレーズで、なるべく音程を外さないということをやっていた一方、自分はわりとダイナミックに動かすフレーズを好んで出しちゃう傾向があったんですよね。それは評価に反映されていなかったので、対戦者の曲のほうがやはり観客の好みに合って対戦者が勝ったという感じ。

やはりそのあたりを評価にきちんと折り込んでおくと、勝てていた……ぜんぜん違う結果になったのではないかと思います。

言っておきますが、実務ではわりと評価を気にしているので。

(会場笑)

そこは言っておきます(笑)。

まとめ

まとめです。テキストの言語モデルでも、そこそこのアドリブ生成AIができました。前処理も後処理、推論もなんだかんだ工夫は必要でしたができました。

ただ、僕が感じる最強のAIでは勝てない、使えないということで、評価設計をきちんとしましょうという、いつものオチになりました。そういう感じで以上になります。

(会場拍手)