RNNのアンサンブルによる音楽の新規創出
近況
Qiitaに投稿しない理由なのですが、自分はQiitaを技術ナレッジのレポジトリのような使い方を想定してしていて、実際、多くの方が素晴らしい技術的なプラクティスを提供なさっています。
古い言葉でチラシの裏という表現がありますが、今回の私のは、ブログが良い選択肢であるの思うのです。十全な責任を持てない発信やSNSでの補足が必要になるレベルになるものに関してはブログを利用しています。
モチベーション
RNNという技術がなんともオワコンと言われているので、手向けに興味があった音楽の生成を行うことにしました。
機械学習が流行りだしてから、まるで機械学習といえば絵を描いたり、音楽を奏でたりということができるという認識がニュースで流行りだしたりしていて、実際にやってみた系の記事が、音楽であったでしょうか。Qiitaではありますが、あれは音楽をRNNに記録させて取り出したというもののような気がします。[0]
RNNといえども、基本はニューラルネットワークに情報の符号をモデルに刻み込んでいくスタイルなので、どうしてもある程度特徴量の抽出はするものの、そのまま覚え、覚えた内容をそのまま出力するという流れになりがちです。[1][2]
自然語処理の分野でも、他の方が自分の著書を書いて、その内容を記録させるということをしていますが、その著書の範囲のコンテンツしか創造能力が獲得できません。[11]
特段新しい手法ではない(と思うの)ですが、複数のモデルの結果を合成(この文脈ではアンサンブルと呼ぶ)することで創造能力の獲得を目指します。
CNNなどの識別タスクやGANなどの生成タスクでの応用などが考えられます。
前提
この世に完全なるオリジナルは存在しない
なんとも哲学的な題材なので、正直これについては述べたくなかったけど、これを最初に言っておかないと後が続かない気がするので言いますと、多くの芸術家が言っていることとして、オリジナルは存在しなく、創作物は何らかの参照と引用で構築されているという意見です。[3][4]
これは今の私には否定出来ないことなので、正しいと思うのです。
ピカソとポーラ・シェールの本質であるコンビナトリアル・クリエイティビティなど、ネットワーク化された知識です。私たちが真に創造し世界に貢献するためには、無数の点を結びつけ、豊かな分野のアイデアを相互に結びつけ、これらの要素を結合して再結合させ、新しい城を建設することができなければなりません。[4]
提案
複数音楽を学習したモデルを合成することで新規性を獲得する
人間がいくつかの経験をし、新たに直面する問題に対処するときは、おそらくそれまでの経験の幾つか思い出して手法を組み合わせて対処するかもしれません。それを真似しようという発想です。
RNNとは、過去の出力状態に依存した確率場を表現する出力をするものというものです。mutableなニューラルネットワークです。
もう何度も図示しましたが、中二チックに単語を選んだ図を再掲します。
引用になりますが、Conditional Random Fieldとも見れます。
ベイズ系の記事を読んだ時、ヒートマップで表現している方法が分かりやすかったです。
数式です。
アンサンブルは機械学習では特段珍しい手法ではないのですが、創造系のタスクでアンサンブルは珍しいと思います。
提案したいことは単純で、それぞれの時間系列 t+1に対して次の音が来る確率の中庸点(特定のモデルにバイアスをかけることもある)を求めます。
結果が確定したら、RNNの状態をアップデート(ステップを進める)して次に来る確率をまた予想します。
図では、2つのモデルの合成のみが示されていますが、実際には多くの音楽を学び、たくさんのモデルがあればその組み合わせはかなりの数になります。これは到底人間が一生かかって創造活動しても網羅できないレベルです。
実験環境
音の符号化
一番苦労した点はこの符号化の点であると感じています。音を記号で示すにはどうしたら良いでしょうか。WaveNet[5]や他の研究では音をそのまま波形として扱うこともあるのですが、精度は高いものの非常に生成するのに時間がかかるということで、よりかんたんなMML(Music Macro Language)を使用しました。
MMLはかなりの方言があり、一般的と呼ばれる記述を調べるのに非常に苦労しました。a~gのcharactorが音階になっていて、それを装飾するl, p, gなどの符号と量を示す数値で構成されているようだとわかりました。符号の粒度が分かれば、RNNで学習することができます。
いーあるふぁんくらぶの例 liarufanclub$ cat input.txt.org v100q25o5l1a a a a a a a l2a q100l8d c l4d l8.d d l16d d l8c d l2.d l8d c l4d l8d l16d l8d l8.d l8d f f f g f q66l4.d q100l16d c q50l4d q100l8.d d l16d d l8c d l2.d l16a a a a a a a a l8g g g g f l2^4.d l8d c l4d l8d l16d l8.d l8d c d l2.d l8d c l4d l8d l16d l8.d l8d d l16f f l8f f g f l4.d l16d c q50l4d q100l8.d l16d l8d ...
また、MIDIとMMLは一部互換性があるらしく、運がよいとMIDIをMMLに変化することができます。変換に使用したソフトは、テキスト音楽「サクラ」になります。[6]
これを自作のスクリプトでかなりアドホックですが、一つの素性の単位に分解します。コードは別途転載するので、必要な方は参照してください。[7]
RNNの学習
ざっくりとですがパラメータサーチを行った結果
epoch:200 batch:50 rnn_unit: 386 layer: 3 学習率: 2e-3
ぐらいが曲を丸ごと覚えられる感じだったので、採用しました。
loss errorは10^-3以下になります。
これを一曲一つのモデルで学習させていきます。
学習させた曲は、以下の通りです。
いーあるふぁんくらぶ(最初の冗長なイントロまで再現されています)
バッドアップル
ロストワンの号哭
千本桜
RNNのアンサンブルのコード
自然言語処理でどうしようもなく、精度が出ないときがあったのでアンサンブルの発想に至ったのですが、まぁ、普通に考えたらno free lunchの定理より、多くのモデルを混ぜると精度は改悪するので、idea賞というレベルのものを今回再利用しました。結構強引な改修を行ったのでコードが汚いです。[8]
結果
いーあるふぁんくらぶ、バッドアップル、ロストワンの号哭、千本桜を合成するのに、それぞれのウェイト(重要度のようなもの)を指定して、任意の割合で合成できるようにしました。
いーあるふぁんくらぶ50% + 千本桜50% ○ 新規性ありそう
いーあるふぁんくらぶ50% + Bad Apple50% △ 後半、いーあるふぁんくらぶ強くなってく
Bad Apple50% + 千本桜50% △ Bad Appleから入っていく感じ、似てるけど別物
ロストワンの号哭50% + いーあるふぁんくらぶ50% ○ そういう曲だと言われれば?
いーあるふぁんくらぶ90% + Bad Apple 90% + ロストワンの号哭 100% + 千本桜 100% ○なんか新しい曲調
色々良さそうな音楽を探索したいのもやまやまなんですが、疲れてきたのでこの辺にしときます。
コードを公開するので、自由にしてください。
結論
任意の組み合わせで、再生に耐えられる音楽を作り出すことはできました。しかし、これが歌詞や音楽を知っているから、違和感なく感じるのか、音楽のプロが見たらどう判断するのか正直なところわかっていないです。
自分に音楽の才能が著しくないことを理解しているので、どんどんやっていこうというモチベーションは出ないですが、人並みには興味があったのでやってみたというところが事実です。
途中に介入したり、Conditionalに指定の曲調を作り出すことも、まぁ、可能なのですがデータがないのと手がまわらないのでどうしようもない感じです。
音楽は画像生成タスクに比べれば楽という印象ですが、前処理、学習、評価、Webのアップロードだけでプライベートな時間の殆どを消費してしまいました。悲しい。
謝辞
これは前からあったアイディアで、プログラムを作るの結構めんどくさかったので、半ば放置していたのでした。そんな折、My name is HAPPY HARDCORE.という音楽に出会いまして、最後までプログラムを書き上げるために強制的にテンションを上げていただき、ありがとうございます。この音楽がなかったら、この発想は私の中で永久に検証する機会がなかったと思います。
参考文献
[0] http://qiita.com/komakomako/items/9ba38fc38f098c0e8b9b
[1] http://repository.dl.itc.u-tokyo.ac.jp/dspace/bitstream/2261/59449/1/48146440.pdf
[2] http://qiita.com/komakomako/items/9ba38fc38f098c0e8b9b
[3] http://www.lifehacker.jp/2016/07/160713misconceptions_about_creativity.html
[4] https://www.brainpickings.org/2011/08/01/networked-knowledge-combinatorial-creativity/
[5] https://deepmind.com/blog/wavenet-generative-model-raw-audio/
[6] http://oto.chu.jp/top/
[7] https://github.com/GINK03/KindleReferencedIndexScore/blob/master/query-expansion/chainer-chiptune-rnn/splitter.py
[8] https://github.com/GINK03/KindleReferencedIndexScore/blob/master/query-expansion/chainer-chiptune-rnn/EnsembleConditionalSampler.py
[9] https://www.youtube.com/watch?v=OxE-FFNB74M
[11] http://d.hatena.ne.jp/shi3z/20150831/1441003474
GANによるノイズ・モザイク等の除去
GANによるノイズ・モザイク等の除去
近況
眠い。
実は今月、三回も停電が起きてそのたびに機械学習のモデルが飛んでいます。原因は電子レンジとケトルを両方利用したとか、そういった当たり前のことなのですが...(ブレーカを飛ばしているの私でないです)。
モチベーション
pix2pixなどのニューラルネットでの画像変換は、通常のフィルタと違って、情報量を増加させることができるような振る舞いをすることがある。
画像は大学の学部で習ったレベルの知識しかないが、平滑化フィルタなどの画像を変換する系のやつは大抵が行列で表現されるようなやつで、平均値だの分散などを取るので、情報量が下がるものがほとんどだった(というかそれしか知らない)。
GANでモザイク除去や、なんやらができるとは先行研究でわかっていたし、理論は単純だし、この前のpix2pix with textに比べて簡単だと思った。
先行研究
- GANによる去年の実装 http://www.yukisako.xyz/entry/mosaic
- 2017/01/21 arxiv submiited 数日差で負けた感じ.仕方がない https://128.84.21.199/pdf/1701.05957v1.pdf
- 有名なWaifu2x,学習するドメインを限定すればより高機能に使えるという発想のもとになった https://github.com/nagadomi/waifu2x
理論
- GANなので、生成DNNと判別DNNがお互いを騙そうと、競争し合う。
- 十分に学習した生成DNNが生成タスクとして利用可能
- 一番良い理解はソースコードを見ることだと思う
実験環境
- この前のpix2pix with textを更に変更したモデルを利用
- textの領域にランダムなデータを入れることで、もとのDCGANのような、ランダムに近いZシードの位置づけを作成した(直感的な理解であるが、いらなくてもなんとかなるのでは)
- GTX 1060, GTX 1070で4日ほど回した
- 途中、chainerのバグを踏む、serializerがメモリバカ食いという点はあったものの、色々細工することで乗り切った. (バグは今は直っている。大容量なswapをNVMeに作成するなど)
- 学習データとは別のデータセットを用意して、各モデルの学習の進み具合を確認してepochを進めていった. leakは防げていると考えている。
- epoch: 350
- 画像ドメイン: 艦これの7500枚の一人だけ表示されている絵、ただし縦横比が2:1, 1:2までのカラー画像のみ
- その他パラメータは原則、Facadeのものに従っている
モザイク生成
- opencvを利用
- 一度1/10まで縮小して単純に10倍に拡大することで、モザイクを発生させる[1]
- モザイク状態のデータと、正解データを対応させて学習する
ランダム情報欠落
- opencvを利用
- 全体の画像の面積の半分を白いブロック領域でランダムにデータを欠落させる[1]
- 情報が欠落した状態のデータと、正解データを対応させて学習する
評価
- 評価には、別途用意した500枚の画像を利用する
結果
- 概ね良好な結果が得られた。
- inputとはgeneratorの入力値である
- predictとはgeneratorの出力値である
- ground truthとは未加工のもとデータである
考察
- ランダム情報欠落は、復元しやすいのかかなり強力に動作しているように見える
- GANの特徴として、判別機を騙せるようにGeneratorは学習していくので、ニューラルネットという人間に近い判別の仕方、判別能を持っているので、人間をうまい感じに騙せような補完をしてくれる(別にground truthに近くなるわけでない)
- 停電でpix2pix with textのデータが飛んでしまったので、しかたなくやった感があったが、思ったより簡単に結果が出たので、学部の論文とかにはいいかもしれない.ただし、通用するのは今年までな気がする
- 比較こそしていないが、少しの汚れや情報欠落ぐらいでは、うまくドメインが合致していれば、もとの絵とイメージは違うけど、十分見れるものを出力できている.色々応用は広そうである
Future Work
ベイズ最適化と、しりとり
モチベーション
機械学習ばかりやらずに、久々にプログラミングをしたくなったというか、下手の横好きというか、プログラムを書いてみたくなりました。自分で思った通りにロジックを組めるのは楽しいものです。
YouTubeを見ることを趣味の一つにしているのですが、たまに見かけるトリビアの泉の番組が結構面白い。知識欲とは人間が本来持つ本能的な欲求なんですかね。
トリビアの泉で、広辞苑の辞書の単語をつなげていったら、どれくらいつながるかという問題設定で、東大協力のもと、15万語ぐらいを可能な限りつなげていったら、75775語繋がったという話。[1]
動画中では何の疑問点もなく、最長のしりとりネットワークを作成したといっていたが、数万を超える単語の問題をソルバー等で解くのは容易ではないのではないかという疑問が結構強く残った。
自分でも再現してみたくなったけど、広辞苑の日本語をワープロソフトで手入力していくのはホント闇というかやりたくないなぁと思ってコメント書いたら突っ込まれた。これもネットの宿命。
単語を集める
有償でも、広辞苑の辞書から単語が引っ張れるのなら電子版を買おうかと思ったが、パースできるかどうか保証がないし、一般的な語にしか対応しておらず、最新の単語データとくらべてしまうとどうも見劣りする。
NEologdという形態素解析エンジンがある。頻繁にアップデートされており、この前Fate/Grand Orderという単語も追加された。
@HOPPOU:~/mecab-ipadic-neologd$ echo "Fate/Grand Order" | mecab Fate/Grand Order 名詞,固有名詞,一般,*,*,*,Fate/Grand Order フェイトグランドオーダー,フェイトグランドオーダー
NEologdのgithubでcloneしてインストールするために様々なファイルをダウンロードすると、ディレクトリの中に、名詞が含まれるcsvが含まれている。
$ ls | egrep "(n|N)oun" Noun.adjv.csv Noun.adverbal.csv Noun.csv Noun.demonst.csv Noun.nai.csv Noun.name.csv Noun.number.csv Noun.org.csv Noun.others.csv Noun.place.csv Noun.proper.csv Noun.verbal.csv neologd-common-noun-ortho-variant-dict-seed.20161017.csv neologd-noun-sahen-conn-ortho-variant-dict-seed.20160323.csv neologd-proper-noun-ortho-variant-dict-seed.20161110.csv $ ls | egrep "(n|N)oun" | xargs wc -l 546969 合計
語尾と語頭を普通につなげていってもうまくいかない
最初は広辞苑より収録数が多いし、適当につなげていってもうまくいくだろうという思惑があったが、うまくいかない。問題点として、語尾で終わる単語の分布と、語頭が特定の単語で始まる分布の違いが明らかになった。
68326個 都ジマ ミヤコジマ 68327個 まわりはな マワリハナ 68328個 鍋もの ナベモノ 68329個 野面 ノツラ 最後の文字は、 野面 ノツラ <- ラで始まる単語を発見できなかった☆
提案
うまくいかない原因として特定の語尾で終わる単語を頻出させすぎると、語頭でその語で始まる語が消費され尽くしてしまい、上手くいかないのではという仮定がある。
そこで、確率的に採択するように、語尾をキーにして採択確率を変えるという手法を提案する。
さて、単語の採択率を調整するはいいが、どのように決めようかとなる。何らかの最適化アルゴリズムを使うのが妥当だと思われるが、以前なら探査空間を限定したlbfgs[3]を使うことが多かったのだが、iteration何回か指定できない、数値に対して厳密に動作するので確率的に決定する系では難しいなどの理由で、少々使いづらいと考えていた。
最近良く聞く、ベイズ最適化を適応できる問題(機械学習を考えたくなかったけど、近いアルゴリズムを採用せざる得なかっためう)ということに気づいて実装を行った。
ベイズ最適化とは
詳しく説明すると、より詳しい人に刺されるので、表面的な知識だけ。
手法にもよるかも知れないが、基本的には背景の分布を説明するのに最も適したガウス過程の事後分布を作っていくものである。観測する回数が大きくなれば、その分、真の事後分布に近づいて行くものと思われる。
あまり賢くない最適化手法では、グリッドサーチで探査空間を量で押し切ったり、極小解に陥ってしまいそうであるが、gifアニメを見てみると、赤の分布が3つほどあるが一番大きな赤を集中して探索しているため、賢いという印象。
実装
実装の詳細はgithubに転載するので、確認する必要があれば参考にしてほしい[4]
基本的には、最初に単語のプールを作って、ランダムにそのプールから取り出していくのであるが、この取り出しのランダム性に細工をして、特定の語尾の単語を引き当てにくくするというアプローチである。
泥臭い実装になってしまったが、まあ私はこんなものです。
nが単語のデータオブジェクトで、n.tailが語尾です。randomで採択関数をスキップしています。
if 'ラ' == n.tail and random.random() < ps[7]: continue if 'ウ' == n.tail and random.random() < ps[6]: continue if 'ゲ' == n.tail and random.random() < ps[5]: continue if 'ズ' == n.tail and random.random() < ps[4]: continue if 'マ' == n.tail and random.random() < ps[3]: continue if 'リ' == n.tail and random.random() < ps[2]: continue if 'ケ' == n.tail and random.random() < ps[1]: continue if 'エ' == n.tail and random.random() < ps[0]: continue
psがベイズ最適化で決定されるパラメータ郡で、最適な不採択率を決定する。
また、今回のベイズ最適化では、Gaussian Processを利用した。
# 目的の分布がこの関数で発生させる。 # 目的関数って言っちゃうとだめというのは知ってる。 # os.systemしてるの、ゴミと言うツッコミは勘弁して def target(x1, x2, x3, x4, x5, x6, x7, x8): xs = map(str, [x1, x2, x3, x4, x5, x6, x7, x8]) command = ["pypy3", "siritori.py"] command.extend(xs) os.system(' '.join(command) + " | tee buff " ) # 結果を取得 fret = float( open('buff').read().split('\n')[-3].split(' ')[0] ) return fret # boオブジェクトと探索範囲の決定 bo = BayesianOptimization(target, {'x1': (0, 1), 'x2': (0, 1), 'x3': (0, 1), 'x4':(0, 1), 'x5':(0, 1), 'x6':(0,1), 'x7':(0,1), 'x8':(0,1)}) bo.explore({'x1': [0, 1], 'x2': [0, 1], 'x3':[0, 1], 'x4':[0, 1], 'x5':[0, 1], 'x6':[0,1], 'x7':[0,1], 'x8':(0,1) }) # maximizeの実行, iter 30 bo.maximize(init_points=5, n_iter=30, kappa=2) # 最大化するときのx1~x8のパラメータの表示 print(bo.res['max'])
結果
139,813語 + 1(ンで終わる単語)で、しりとりチェーンを構築することができたので、まぁ、こんなものなのかなという感じ。詰めればもっと行きそうだけど、しりとりチェーンコンテストとかあっても良いかもしれない。もっとiteration回せば、もっと行くようにも思える。
考察
1. ベイズ最適化はパラメータの探索に非常に効果があると思われる。従来の探査手法よりも、探査が少なく、なおかつ、確率的な系(予測する分布)に対しても有効そうである。(lbfgsbはすぐおかしくなる)
2. 機械学習では結構重い学習も度々行う必要があり、コンペティションなどになると、探査を効率よくできたものが時間的にも精度的にも有利なので積極的に採用していきたい技術である。
3. よくわかんないけど、アドホックな手法を使わずに演繹的にしりとりをこれだけの量解けなくない?Youtubeにサブグループ化してソルバーで解くとの案があったけど、どうなのかな。
結論
早速手持ちの案件に導入してみました。
パラメータサーチテクとして、優秀です。積極的に使っていきましょう。
Pix2pix with Text
Pix2Pixとは
01/06/2017. この記事の生成物に関して、修正点があるのであとで修正します
- 自動生成系の深層学習の一つ
- 2つの画像の差を学習して、その差を補う形で画像などを出力する
先行研究
初心者がchainerで線画着色してみた。わりとできた。[1]
- ヒント情報として、色情報を書き足すことで、色を指定している
- Pic Source: qiita.com
モチベーション
- 色指定しなくても、テキスト情報のみでキャラクタの着色を行う
- 例えば、艦これの響なら髪の色は白で、暁ならば紺色に着色する。モノクロ画像では両キャラクタは非常に似ており、人間が見てもどちらに塗るのが正解か不明瞭
- テキスト情報でこれは「響」と記されていれば、自動で髪の毛を白く塗ってくれるのでは。
提案手法
- Pix2Pixの入力に用いられるテンソルは、非常に高次元であり、RGB程度の粒度であれば、たかだか256*256*3程度に過ぎない
- テキスト情報を格納できる領域を増やし以下のように、テンソルを拡張する
(256, 256, 3) -> (256, 256, 4)
- 空いた次元に、画像のタグ情報をベクトル化して、組み込む
実験環境
- ChainerのPix2Pixをもとに変更、拡張
- Epoch: 300
- BatchSize: 1
- 学習率: 0.00001
使用したマイパソコンのキャッシュに残っている画2万枚
使用したキャラクタードメイン: 艦これ、東方、FGO、グラブル
使用したテキスト情報: タグ情報
Pic Source: pixv.net
大体 GTX 1080で一週間ぐらいかかった
実験結果
Optional: カラー化とカラー化抑制
- 明示的に「ノート」や「モノクロ」を意味するベクトルが入ると、カラー処理は行われないようである
Optional: 謎の言語を生成する
考察
- ヒント情報が画像と同じように処理したためかだいぶ拡大して用いたがたまに、潰れている可能性も否定できない。CNNで処理しても生き残るような素性に加工するする必要がありそう
- 改良点が多く、テキストのベクトル化方法や、更に詳細な画像を出力するようにStack GANの発想で、全体的な方向性を決定するモデル・より詳細に描き上げるモデルなど組み合わせても良いかもしれない。
- ここから、もとの線画を欠落させると、text2imageと近しいモデルになるかと思われる
Advanced Work
- Text2Imageを再現してみる
- 高解像度化する
- Web経由で任意の単語を切り替えることで、任意の画像の作成を可能にする
- Skip Thought Vectorなどのより高性能なEmbeddingなどを用いる
- 省メモリ設計にする:今回の実験だけで、32GByte中、30GByteを学習で消費したので、何らかの変更を行う