nnetを使ってコード解析作ってみた

以前このブログにも書いた通り、今年の夏に Coursera の Machine Learning の講座を受けた。これがきっかけで機械学習にすごく興味を持ち、何か自分でも面白いものを作ってやるぜって意気込んでた。オイラ腰が重くてなかなか手をつけられないでいたが、今回、特に自分の興味のあるところから攻めてみようってことで、音楽のコード解析をやってみた。


今回やること

とはいえいきなり大掛かりなものから始めるのは大変なので、まずはCMaj, DbMajの2種類のコードで演奏した音源を用意。これらをニューラルネットワークを使って正しく分類できるようにする。


プログラム一式とサンプルデータはgithubに置いてあります。


音源の準備

さて、一番始めに悩んだのがこれ。学習に十分な量の演奏データって10個?100個?いやもうちょっと欲しい気がする。しかしそれだけ大量の演奏データを準備するのは案外大変だ。


どうしようかと暫く悩んでいたのだが、ふと昔趣味で作曲をやっていたのを思い出した。そういや昔 YAMAHA の QY70 ってシーケンサ使ってなかったっけ?
これを使えば、内蔵されている128種類 x バリエーション数種類の演奏パターンを使って、任意のコード進行を演奏させることができる。これ使えばいいじゃん!

 

f:id:taegawa:20141103180311j:plain


というわけでQY70を押し入れから引っ張りだし、早速これを使ってCMaj, DbMajでの演奏パターン、各128種類をものの30分ほどで用意することができた。
音源を録音して切り出し、128パターン x 2コード = 256個の wav ファイルを生成した。


FFT -> データの整理

コード解析を行うには、どの音程(周波数)の音が鳴っているかを知る必要があるため、フーリエ解析を行う。音源の開始位置から0.2秒ほど後の位置を解析対象とした。開始直後の位置ではドラム系の音が強くノイジーになり、コード解析という観点からは音程という特徴をとらえにくいと考えたからだ。これで1演奏データあたり32768個のデータが得られる。
これらのうち意味のあるのは最初の16384個で、さらに高周波成分のデータについては約1Hzという細かい粒度で取得するのはあまり意味がないと考えた。データ量を少なくしたいという意図もあり、A0(27.5Hz)からA8(7040Hz)の間で、1オクターブあたり24の区間に分割し、それぞれの区間での音量の平均値を求めた。この結果、1パターンあたり191個の周波数成分データを含む解析結果ファイルを得た。

 

# すべての wav ファイルに対して実行
R --vanilla --slave --args do_patterns001.wav do_result_001.txt < createFftData.R

 

結果サンプル


トレーニングデータ、テストデータの生成

演奏データは1コードあたり128パターン用意しているが、これらをトレーニング用とテスト用に7:3の比率で振り分け、上で得られた解析結果ファイルをそれぞれ1つのファイルにまとめる。

perl merge_fft_data.pl

結果データは、トレーニング用、テスト用それぞれ /tmp/fft_result_train.txt, /tmp/fft_result_test.txt に出力した。

結果を出力する際、R の data frame として取り込みやすいようにヘッダ行をつけた。周波数成分データには x1 〜 x191 という名前をつける。さらに、これらがCMaj と DbMaj のどちらのコードを表すかを result という名前で追加する。CMaj, DbMaj に対し、それぞれ do, reb という値をつけた。


トレーニング実施

R の nnet というパッケージを使うと、簡単にニューラルネットワークを利用することができる。
まずトレーニングデータを読み込む。

 

dtrain <- read.table("/tmp/fft_result_train.txt", header=T)

 

で、トレーニング実行。パラメータはいろいろ変えて試してみるとよさそう。

 

> d.nnet <- nnet(result~., dtrain, size = 5, decay = 1, maxit=10000)
# weights: 966
initial value 299.267352
iter 10 value 117.659656
iter 20 value 111.959352
iter 30 value 88.714983
iter 40 value 64.720808
iter 50 value 62.994615
iter 60 value 62.094382
iter 70 value 54.606176
iter 80 value 48.565453
iter 90 value 44.998767
iter 100 value 44.173409
iter 110 value 42.210244
iter 120 value 37.521031
iter 130 value 36.635122
iter 140 value 35.544440
iter 150 value 34.254634
final value 34.178917
converged

 

 

検証

トレーニング結果を、テストデータ(82個)を使って検証する。

 

dtest <- read.table("/tmp/fft_result_test.txt", header=T)
table(dtest$result, round(predict(d.nnet, dtest[,-192]), 0))

0 1
do 38 3
reb 3 38

 

 

do-0, reb-1 が、正しくコードを判定できた数。正解率 92.68%ということはなかなか良い結果なのだろうか。まぁ7%の確率で判別ミスってのも痛いがw


まとめ

はじめてニューラルネットワークを学んだときは、仕組みが複雑すぎてなかなか手を出しづらいなぁという印象があった。しかし nnet のように、ニューラルネットワークを簡単に扱うためのパッケージも用意されており、中の仕組みを意識しなくても簡単に使える。こういうのを積極的に使って、音楽以外にも面白いこといろいろ実現できるんじゃないかなぁ、と思った。


今後の目標

とりあえず今回は2コードのみの分類を取り扱ったが、これを拡張して、最終的には3和音のレベルですべてのコードを判別できるようなものを作りたい。(ってか本当に欲しいのはそっちだw)


参考