AIミュージックバトル!『弁財天』のスターターキットをPyTorchに移植してアドリブメロディの自動生成を試してみた

はじめに

最近、下記のイベントが開催されることがアナウンスされた。

benzaiten.studio.site

AIミュージックバトル!『弁財天』は「伴奏」から「アドリブメロディ」をAIで生成し「どれだけイケてるメロディか」を競うAI自動作曲コンテストです。』 とのことである。 本コンテストではTensorFlowベースのスターターキット(Google Colabで実行可能)が提供されており、自動作曲初心者でも無理なく始められるようになっている。

筆者はPyTorchユーザなので、スターターキットのPyTorch版を作成しておきたいと思ったわけである。自動作曲自体に興味があったということもある。

注意:本記事のPyTorch移植はGoogle Colab上での実行を想定していない。すなわち、Colab Notebookを提供するものではない。あくまでGPUを搭載した各自の卓上計算機(具体的にはUbuntu)で実行されることを想定した実装である。 とはいえ、Google Driveを適切にマウントすれば、実装をすぐに使えるようになるだろう。

12月3日追記

以下の記事にて、PyTorch版スターターキットのColab Notebookを提供するようにした。

tam5917.hatenablog.com

スターターキットについて

仕様を記述したドキュメントは主催者により用意されている。ぜひ一読されたい。

スターターキットが提供するモデルの概要

コンテストでは伴奏のMIDIファイルとコード進行を表すデータ(CSV形式)が主催者より与えられ、これが自動作曲システムへの入力となる。求められる出力は伴奏にメロディーを付与したMIDIファイルである。

これを受けてスターターキットでは、サンプルのMIDIファイルおよびコード進行データ(CSV形式)が提供されている。これらを前処理し、以下のようにメロディ生成モデルへの入出力としている。

  • モデルの入力:メロディをone-hot エンコーディング(ノート番号を表現)、コード(chord)をmany-hot エンコーディング、休符の有無を0/1で表したうえで、それらを結合したベクトル(61次元)系列
  • モデルの出力:同じくone-hot エンコーディングされたメロディ + 休符からなるベクトル(49次元)系列
  • モデルの構造:LSTMを用いて入力系列を固定次元の隠れ状態ベクトルへとエンコードし、そして再びLSTMを用いて隠れ状態ベクトルから出力系列をデコードする。それら2つのRNNの間に、変分自己符号化器(Variational AutoEncoder; VAE)が挟まっている形である。

LSTMエンコーダ:(メロディ+コード系列)→LSTM → 隠れ状態ベクトル h

VAEエンコーダ:隠れ状態ベクトル h → 潜在変数zの平均μ, 分散σ

VAEデコーダ: 潜在変数のサンプリング z' 〜 N(μ, σ) → 隠れ状態ベクトル h'

LSTMデコーダ:隠れ状態ベクトル h' →LSTM→(メロディー系列)

のようにデータが流れる。スターターキットの説明・実装ではLSTM部分を含めてVAEのエンコーダ / デコーダとなっているが、上記のようにモジュールごとに分けて解釈するのが個人的には理解がスムーズだった。上記VAEにはサンプリングの手続きが入るため、メロディ生成にランダム性を持たせることが可能となる。つまり生成のたびに毎回異なるメロディが得られる。ちなみに、隠れ状態ベクトル hとは最終フレーム(時刻)におけるLSTMの出力ベクトルを指す。

実装

以下のリポジトリに置いた。Enjoy!

github.com

必要なパッケージ

Ubuntuで動かす場合、FluidSynthに関するサウンドフォントが必要なので入れておく。

apt-get install fluidsynth

MacOSではFluidSynthに関するパッケージがbrewから提供されているので、参考まで。

brew install fluid-synth

ほか、pipでパッケージを入れておく(順番は適当)。

python -m pip install torch
python -m pip install midi2audio
python -m pip install music21
python -m pip install hydra-core
python -m pip install joblib
python -m pip install progressbar2
python -m pip install matplotlib
python -m pip install numpy
python -m pip install scipy

実装の概要

主要なファイルと機能について示しておく。

ファイル名 機能
config.yaml 各種設定を記述したYAML形式のファイル
preprocess.py 前処理を実施するスクリプト
training.py モデルの訓練を実施するスクリプト
synthesis.py 訓練済のモデルを用いてメロディーを合成するスクリプト

コードの動かし方は、

  1. config.yamlの編集
  2. preprocess.py による前処理の実施
  3. training.pyによるモデル訓練の実施
  4. synthesis.pyによるメロディー合成の実施

となる。preprocess.pyは一度だけ動かせばよい。 なお前処理にはモデル訓練に用いるMusicXML群のダウンロードや、それらからの特徴量抽出、またスターターキットが提供する伴奏データ・コード進行データのダウンロードが含まれる。

synthesis.pyには合成結果のMIDIファイルへの書き出し、Wavファイルへのポート、またメロディのピアノロール画像の作成・保存が含まれる。

メロディ生成実験

実装に基づいてスクリプトを動かしてみた。

実験条件

訓練データは下記サイトにて提供されている50個のMusicXMLファイルから準備した。 homepages.loria.fr

計算機環境を示す。

計算機環境 バージョンなど
OS Ubuntu 22.04
CPU Intel i9-9900K
GPU RTX2070
Python 3.10.6
PyTorch 1.12

訓練の基本的な設定は以下の通りである。

項目 設定
ミニバッチサイズ 32
エポック数 3000
学習率 0.0003

スターターキットでは学習率が0.0005だったが、これはやや大きかったので、本記事の実験では学習率をやや下げたうえで、MultistepLRにより学習率をスケジューリングした。エポック数は大きめに取ったが、これでも訓練に要する時間はトータルで1時間を下回った。

その他、ニューラルネットワークの細かい設定(ユニット数など)は上記リポジトリのconfig.yamlを見よ。

訓練が終わったのち、synthesis.pyを用いてメロディを生成する前に、サウンドフォントファイルへのパスもconfig.yamlで指定しておくことは注意を要する。本実験(Ubuntu環境)では以下のパスであった:

sound_font: "/usr/share/sounds/sf2/FluidR3_GM.sf2"

実験結果

「伴奏のみ」と「伴奏+メロディ」音源を併せて示す。

伴奏のみ(主催者から提供されたMIDI 伴奏+メロディ

メロディ生成結果を図1に示す(縦軸と横軸にラベルが書かれていないので、減点なのだが)。ピアノロールもどきであり、横軸は時間を表す認識でよい。縦軸の下に向かうほど音が高くなっている点は注意を要する。一番下は休符要素を表す。

図1: 生成されたメロディ

自動生成メロディもくり返し聞くとジワジワ来るものはある。これはこれでアリではないかと(評価が難しい!コンテスト実施の必要性を実感)。

実装の舞台裏やTIPS、TensorFlow版との違いなど

  • training.pyは結局オレオレ実装になってしまった 。main()に全てをベタ書きでもよいとは思ったが、ひとつひとつの関数をコンパクト(行数少なめ)にしたかった。
  • スターターキットにおいて、VAEの全結合層(隠れ状態ベクトル h, h'とμ, σの間; input-latent)は特に活性化関数を与えていなかった。一方、本記事の実装では、エンコーダとデコーダに全結合層がそれぞれ一つ余分に増える形となった(input-hidden-latent)。そこで、input-hidden間とhidden-latent間に活性化関数としてReLU関数をそれぞれ1つ入れてみた。完全移植でない所以でもある(これぐらいの変更は別に構わないだろう!気になるひとは取り除けばよい)。
  • デコーダの入力は、VAEから生成された隠れ状態ベクトル h'を全フレーム(時刻)に渡って複製することで与えている。この点も改良のしどころであり、例えば、直前時刻のデコーダ出力を当該時刻のデコーダ入力として与えるよう変更するのは容易である(いわゆるseq2seqの定式化)。
  • VAEにバッチ正規化は入れていない(入れると訓練誤差が減らない)。本記事の読者が色々と試行錯誤する際は、バッチ正規化やDropoutをはじめとする各種の正則化テクニックを組み込んでみるのも良いだろう。
  • スターターキットではグローバル変数が定義され、それぞれの関数の中に埋め込まれている。今回のPyTorch実装ではそれらグローバル変数たちをソースコードから全て追い出し、YAML形式の設定ファイル(config.yaml)に書くことにした。
  • 今回は組み込み関数のgetattrとhydraパッケージの力を借りて、YAMLに基づいてオプティマイザや学習率スケジューラを構築できるように実装した。ソースコードには具体的なオプティマイザのクラス名、例えば"Adam"や"RAdam"は現れてこないので、汎用性が増す利点がある。その際、ttslearnの実装を大いに参考にした。
  • PyTorchではクロスエントロピー損失関数(CrossEntropy)への与え方がTensorFlowと異なる。予測データはsoftmaxを取る直前のいわゆるlogitsの形で与える必要があり、さらにターゲットラベルはone-hotエンコードしておく必要はない。この仕様のため、スターターキットが提供する一部の関数に仕様の変更が生じた(calc_xy()など)。
  • 主催者より提供された伴奏MIDIをmidi2audioによりwavへ出力すると、曲後に約1分間の無音が挿入される現象を確認している。実体のfluidsynthコマンド単体でも同様の現象が確認された。別のMIDIファイルで試してみると、そのような無音挿入は生じなかった。よって少なくとも伴奏MIDIに特有の現象であるが、原因は不明である。
  • 補助的に作成したモジュールたちは以下の通りである:
ファイル名 機能
dataset.py データセットのクラスBenzaitenDatasetを定義
factory.py オプティマイザ、スケジューラ、損失関数クラスの各インタフェースを定義
model.py メロディを生成するためのクラスSeq2SeqMelodyComposerを定義
util.py スターターキットで提供される関数群を定義

おわりに

スターターキットをPyTorchに移植してみて、確かに色々と改良を図れる箇所がいくつもあることを確認した。コンテストへのエントリー自体はさておき、自動作曲AIでしばらく遊んでみることにする。

最後に、AIミュージックバトル!『弁財天』の関係者各位に深く感謝したい。スターターキットの提供は英断だったと思う。コンテストの成功を祈る。

Let's ad-lib!