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)


参考

 

rgl の plot3d で3Dグラフを描く

困った

眠れん。
仕方ないのでブログ書く←

rgl とは

rgl

rgl: 3D visualization device system (OpenGL)

R上で、OpenGLを使って高度な3Dグラフを描くためのパッケージ。インタラクティブな操作も可能で、3D表示させたものをマウスでぐりぐり回転できる。なかなか良い。

rgl のインストール

インストールは若干やっかいかもしれない。X11のライブラリが見つからないと怒られることがよくあるようだ。オイラもそこで少しハマった。Fedora では以下のコマンドを実行してX11関連のライブラリをインストール。

yum groupinstall "X Software Development"


その後通常通り rgl パッケージをインストールすればOK。

> install.packages('rgl')


rgl を使うには以下のようにライブラリをロードする。

library(rgl)

plot3d

plot3d というコマンドを使って3Dグラフを表示させることができる。


サイズの同じ vector を3つ作ってデータを入れる。

x <- 1:10
y <- seq(1, 19, 2)
z <- c(5, 3, 8, 9, 3, 7, 3, 2, 9, 5)


対応が分かりやすいようにデータフレームに入れてみる。

d <- data.frame(x, y, z)
d
x y z
1 1 1 5
2 2 3 3
3 3 5 8
4 4 7 9
5 5 9 3
6 6 11 7
7 7 13 3
8 8 15 2
9 9 17 9
10 10 19 5


これをプロットする。

plot3d(d$x, d$y, d$z)


デフォルトでは点で描かれる。ちょっと見づらければ球体にすることもできる。色もつけてみよう。

plot3d(d$x, d$y, d$z, type="s", col="green")



type で形状を、col で色を指定可能。またグラフが表示されているウィンドウ上でマウスを操作すると、いろいろな角度から見ることができる。

音声の解析に使ってみる

せっかくなのでもうちょっと実用的なものを表示してみたいと思う。CMajのコードを演奏した音源を用意し、その周波数成分が時間経過でどのように変化するかを見てみる。CMajといっても、ド・ミ・ソ のサイン波を単に合成したものではなく、QY70というシーケンサーで CMajのパターンを数秒間演奏させたものを使う。ドラムやベースなど様々な楽器で構成されているので、いわゆる「雑音」もそれなりに入っている。


プログラムとサンプル音源はここに置いてある。

R --vanilla --slave --args Cmajor.wav < fft3D.R




(画像クリックで拡大)


3Dだとわかりづらいが、result$times(0, 20, 40, 60) が時間(1 = 約0.01秒)、result$freqlist(0, 100, 200, 300) が周波数(Hz)、そして縦軸のresult$amp(0, 5e+06, ...) が音量を表す。4/4拍子なので、時間軸に沿って強い山の間に弱い山が3つほどあるのが何となく分かる。


プログラム内では、result というデータフレームに解析結果を格納し、それを以下で描画している。

plot3d(result$times, result$freqlist, result$amp, col=rainbow(numPreview * 3), type="l", lwd=0.5)

col=rainbow を指定すると色が虹色に変化しながら表示される。すぐ隣のフレームとの変化を見やすくするためにパラメータを微調整した(ので、numPreview * 3 という数値に深い意味はない)。


ところでプログラムの実行が終了するとグラフのウィンドウが消えてしまうので、最後に Sys.sleep(1000)とか入れてみたんだけど、もっといい方法ないですかね。w

Coursera の Machine Learning

Coursera というオンライン学習サイトに Machine Learning という講座がある。6月から受講開始して約3ヶ月。ようやくすべて終えることができた。受けて本当に良かったと思っているので、紹介したいと思う。


この講座を知ったのは、たしか5月くらいだったと思う。「機械学習」と聞いてはじめに思ったのは、オイラに理解できるわけないだろう、と。しかしちょうど音楽解析に興味を持ち始めた時期ということもあって、とりあえずやれるところまでやってみて、ダメだったらそのときやめればいい、って思ってた。


そして6月に講座が始まる。


…やっぱりかなり難しかったw


大体週に5-7時間の時間を、講座と課題の作成に取られた。仕事しながらこのくらいの時間を確保するのは、なかなかに大変だ。特にここ数カ月は仕事のほうがかなり佳境に入ってきてて、平日はほぼ家に帰って何もできない状態だった。仕方なく、休日にまとめて課題を進めることで、なんとか毎週の課題提出期限を守っていた。

ただ、時間確保が難しかったものの、講座の内容自体はすごく興味深いものだった。機械学習について何も知らなかったオイラが、その面白さにぐいぐい引きこまれていく。夢にまで見てうなされるほどになるw 機械学習なんてオイラには…と思っていたオイラが、気がついたらどっぷり興味を注ぐようになってしまっていた。


機械学習を学ぶのに、10週間という期間は短いのかもしれない。しかし機械学習の世界に興味を持ち、足を踏み入れる第一歩としてはすごく良い内容だったと思う。

コースの内容

コースは全体で10週。毎週1つか2つのトピックを扱う。内容は

  • Video Lectures
  • Review Questions
  • Programming Exercises

の3つからなる。まず Video Lecture で動画を見ながら講義を受ける。英語の講義なのだが、日本語の字幕を出すこともできる。翻訳の質もなかなか良い。"We" が「我ら」とか、「そんなことはやらん」とか、言い回しが多少変なところもあるがw 内容を理解する上では全く支障はない。英語のヒアリングが苦手という人でも、字幕を見れば大丈夫だろう。

講義の内容を理解したら、Review Questions を受ける。これは講義の内容をもとにした選択式の問題。コースを修了するためには、毎週設定された期限内にこのテストで平均8割をとらなくてはならない(期限を過ぎるとペナルティがつくが、一応評価はされる)。

もうひとつ、Programming Exercises というプログラミングの課題もある。これは、講義で学習したことを実際に手を動かしてプログラミングするというもの。これがなかなか面白かった。課題では Octave という言語を使用する。(MatlabライクとのことらしいがオイラはMatlab使ったことないから分からない)。Octave は初めて触ったのだが、まぁ普通のプログラミング言語を扱える人であればそれほどハードル高くないかも。この課題もオンラインで採点されて、一定以上の正解を出す必要がある。

※ Review Questions と Programming Exercises は英語の問題文のみ。ここだけ英語をたくさん読まされる。


機械学習は興味あるけど、自分とはあんまり縁がないし…そんなエンジニアの人にこそ、この講座をオススメしたい。あ、ちなみに無料です。


で、10週すべてクリアしたのはいいのだが、ただ覚えただけでは意味がないので、これを実世界で応用したいなぁなんて考えてる。特に音楽関連で何か活かせないかなぁと目論んでる最中。

あと、次はやっぱPRMLなんだろうか…(汗

tuneR を使ったMFCCの計算

ほぼ自分用の備忘録←

tuneR で MFCC を求める手順。バージョンは 1.2.1。

library(tuneR)

# wavファイル読み込み
# mp3ファイルの場合は readMP3()を使えばよい
wobj <- readWave(filename)

# melfcc() は現行では mono 音声しか扱えないので必要に応じて変換
wobjm <- mono(wobj)

mfcc <- melfcc(wobjm, 
           numcep = 20, 
           nbands = 36,  
           wintime = 0.023,
           hoptime = 0.023)

nbands はメルフィルタバンクの数。numcepは、離散コサイン変換した結果、低周波成分から数えていくつ分の係数を取得するかを指定。wintimeは1フレームの長さ(ms), hoptimeは、フレーム間の時間間隔(ms)。

結果は (フレーム数)x(numcep) の行列で返ってくる。1列目は各フレームの平均エネルギーを表し、特徴を表すのに有用ではないので切り捨てて19次元にする。

Zero-crossing rate (ZCR) を使った音楽解析

音声認識や音楽解析によく使われる指標として Zero-crossing rate (ZCR) というのがあるそうだ。これは、音声の波形を描いたとき、波が中央より上(正)から中央より下(負)に、またはその逆に変化する頻度を数えて、その頻度により音声の特徴を表すというもの。ZCRが大きいほどより noisy な音声と捉えられるらしい。


この ZCR を使って様々なジャンルの音楽で分析を行った実例が、Elias Pampalk 氏の Computational Models of Music Similarity and their
Application in Music Information Retrieval という論文で紹介されている。面白そうな上、案外簡単に実装できそうな気がしたので、オイラも早速 perl で作ってみた。


(ソースは GitHub に置いておきました)


今回は分析方法として、楽曲の中央から前後30秒を抜き出し、その間に波が正から負、またはその逆に変化した点の数をカウント。1ms あたりの発生数を最終的な結果とした。

使用した楽曲は、オイラの手持ちの曲から適当に10曲ほど。事前に wav(44.1kHz, 16bit, mono)形式に変換しておいた。


結果は以下の通り。ZCRの小さい順に並べてある。

曲名 アーティスト ZCR
日々 吉田山田 1.21
Bossa Nova at 2:00 AM TOMOYOSHI NAKAMURA QUARTET 1.51
Around Forty Blues TOMOYOSHI NAKAMURA QUARTET 2.21
ごはんはおかず 放課後ティータイム 3.02
divine intervention fhana 3.39
Big City Bright Lights ArtOfficial 3.50
すぱそにっ すーぱーそに子 3.63
S・M・L☆ アフィリア・サーガ 4.16
Believe -天真爛漫 Ver.- すずみ 4.46
Skywalker Collioure 4.47
Brave your truth Daisy×Daisy 5.53


曲名についているリンクの飛び先で、楽曲の一部のみだが試聴することができる。実際に聞きながらZCR値を比較してみると効果が分かりやすいかも。(*1)


ZCR が noisy さを表す指標と考えると、概ね合っているかなぁ、という印象(気のせい?)。吉田山田さんの「日々」とか純朴な曲だし、ZCR が低いのは納得。先に紹介した Elias Pampalk 氏の論文だと、ジャズはおおむね低くなる傾向があるとのことだったけど、上記の実験でも TOMOYOSHI NAKAMURA QUARTET さんの2曲(ジャズ)はともに低い ZCR となっていた。


意外だったのが、Collioure さんの Skywalker。結構爽やかなサウンドで、ZCRも低くなるんじゃないかと予想してたのだが、結果は10曲の中で上から2番目。パーカッションが強いからかなぁ。


あと今回は試せなかったのだが、クラシック音楽はあまり良い結果を得られないらしい。そもそも ZCR の限界として、音の高低にも左右されてしまう(高音=周波数が高い=ZCRが高くなる)という点がある。なので ZCR 単体ではなく、他の手法とも併用するのが良いようだ。


とはいえ、実装が案外簡単な割に良い結果が得られた気がする。時間があったら今回試せなかったジャンルの曲も試してみたい。



(*1)ステマ

CPAN Author になった話

先日、ようやく CPAN Author になった。


Data::HandyGen::mysql という、テストデータを効率よく生成するためのモジュールを作って、今年の2月に公開した。はじめて CPAN モジュールを公開するという体験は実にドキドキものである。少年の心が一瞬、蘇った気がする。←
それはそうと、実際に公開するにあたって躓いた点が多々あったので、今回はそのあたりを振り返ってみたいと思う。これから CPAN Author になろうという人たちに少しでも参考になれば。


なお、CPAN へのモジュール登録手順については、ささたつさんのこの記事を参考にさせていただいた。日頃、技術的なことで困ってググるとささたつさんの記事にたどり着くことが実に多い(笑) いつもありがとうございます。

PAUSE ID はすぐには発行されない

CPAN にモジュールを登録するには、まず PAUSE に開発者としてアカウントを登録する必要がある。ただ、ここからの登録申請を承認する作業は人手で行われているらしい。PAUSE のサイトには、

  • 最大で3週間は待って欲しい
  • だいたい1週間以内には登録されると思う


と書かれている。今回オイラは日本時間の 2月15日 夜2:23 に登録申請して、その日の4:15に登録完了のメールが届いた。ほぼ2時間ということでかなり早く処理していただけた。ただ上記のただし書きもあるので、近日中にモジュールを公開する予定であるなら早めに申請しておくと良いかも。

Gravatar に登録する「前に」 PAUSE のメール転送設定を

これ、ささたつさんのサイトにもちゃんと書いてあったのにオイラもハマった。要注意(笑)


PAUSE の登録が完了すると、cpan.org ドメインの新たなメールアドレスが自分用に発行される(egawata@cpan.org みたいなの。以下 xxx@cpan.org)。ただしこのアドレス宛のメールは直接受信することはできず、受け取るためには事前に転送先を登録しておく必要がある。この設定は PAUSE 登録完了後、PAUSE サイトの "Edit Account Info" で設定できる。

  1. 個人のメアドを Secret emaili address... (もしくは公開してよいなら Publicly visible email address) の欄に設定
  2. "The email address xxx@cpan.org should be configured to forward mail to ..." で、xxx@cpan.org 宛のメールの転送先を指定。以下の3つが指定可能。
    • my public email address
    • my secret email address
    • neither nor


一番下の "neither nor" が初期設定。そしてこの設定のときは、どこにも転送されず、おそらく捨てられてしまう。


さて問題はここだ。PAUSE のアカウント申請から登録完了まで時間が空くので「そうだ、今のうちに Gravatar の登録をやってしまおう」なんて、時間効率最適化図ってる俺カッケー的な発想が沸き起こってしまいがちだ。が、これは絶対にやってはいけない


CPAN サイトで表示される自分のアイコンは、先ほど触れた xxx@cpan.org のメアドで Gravatar に登録する必要があるのだ。そしてそのメアド宛に届く確認メールを受け取れないと、アカウント登録をいつまでも完了させられない。
しかもメールが届かないからと再度同じメアドで登録しようとすると、アカウントが重複している旨のエラーが出て登録できない。もしかしてもう一回別の名前で PAUSE へのアカウント登録からやらないとダメ!? なんて焦ってしまう。が、そんな時は慌てず、パスワードリセットのリクエストをするとよい。


いずれにしても、Gravatar の登録は、メール転送設定をしたあとにゆっくりやるのが一番だろう。

PrePAN は投稿後少し待ったほうがよい(?)

PAUSE のサイトでは、特に初めてモジュールをアップロードする開発者に対し、PrePAN というサイトでモジュールのフィードバックを貰うことを強く推奨している。ここでモジュール名と、モジュールの機能について投稿すると、先人から意見が貰えるというものだ。特にモジュールの命名の是非について意見を貰えることが多いようだ。


一応、推奨されているので素直に従っておいた。3日待ったところ、特に何もコメントがなかったので、だったら大丈夫かなぁと思い、公開に踏み切った。
ちなみに過去の投稿を見る限り、モジュールの命名についてコメントが付くのはだいたい2つに一つくらいの割合で、最初にコメントが付くのは遅くとも投稿後3日以内が多いようだ。ここでコメントがつかないようなら、問題ないと考えてしまっていいだろう(多分)。

テストはできるだけ多くの環境で!

これが今回の一番の反省点。オイラが使っている環境は Fedora がメインで、あとは仮想環境で Ubuntu を使うくらい。一応 Windows マシンも持ってはいるのだが、特別の用途(*1)がない限り、ほとんど立ち上げることはない。


ということで、今回うっかり Windows 環境でのテストを忘れてしまったのだ。


その結果…ごらんの有様だよorz


このレポートを受け取って慌てて Windows マシンを立ち上げテストを走らせてみる…
いやぁ、こけるこけるwwwww


とりあえず速攻で対応して Windows でもちゃんとテスト通るように修正したのだが、テストスクリプトだけ更新してバージョン上げるのもどうかなぁと思い、そのままにしてある。次に機能追加するときについでにリリースしよう。


(2014/03/30 更新) v0.0.2公開しました。Windowsでもテスト通るようになりました。


ともあれ、テストは可能な限り多くの環境で行うべし。特に Windows でのテストは忘れがちなので気をつける(*2)


(*1)「特別の用途」が何であるかを突っ込むのは無粋だと思う。
(*2)オイラだけか。



というわけで、反省点をつらつらと。
まぁでも、公開してみて分かったのは「モジュールをCPANに公開するのは案外怖くない」ということ。そして一度公開すると、次は何を作ろうかとか色々考えて楽しくなる。どんなモジュールでもいいから、少しでも他の人の役に立ちそうだと思ったら思い切って公開してしまうと良いんじゃないかな。

機能追加

機能「追加」は決して機能「改良」ではない。

新しい機能が使えるようになるという意味で「改良」だが、システムの複雑度が増すという意味で「改悪」。開発運用のコストも上がるし、ユーザからしても、機能全体を理解するのが難しくなる。

逆に、初期に導入したけど実際に運用しはじめたら実は要らなかったって機能があるなら、どんどん言ってほしい、って個人的には思う。要らない機能を削除することで、開発・運用がやりやすくなり、それは機能「改良」になる。