トップ «前の日記(09/13/2014) 最新 次の日記(12/01/2015)» 編集

本 日 の h o g e

hogeとはワイルドカードのようなものです。日々起こった、さまざまなこと −すなわちワイルドカード− を取り上げて日記を書く、という意味で名付けたのかというとそうでもありません。適当に決めたらこんな理由が浮かんできました。

更新情報の取得には rdflirs を使ってもらえると嬉しいです.


09/14/2014 うむ [長年日記]

tDiary 4343日目

[Py][小ネタ] 人工無脳 amazonas 実践編

さて昨日 この人工無脳の構造やアルゴリズム的なところを書いた わけだけれど,実際に,どんな文章を学習するとどんな文章を吐き出すのか,それを簡単に書いてみようと思う.

昨日,最後に「試したいことがあった」と書いたけれど,それは「歌詞の自動生成」. これは「歌詞とか詩的なものって文章の繋がりとか多少変でも気にならないというかむしろ多少変な方がそれっぽいんじゃね?」という変な思いつきから生まれたもの. よく Web 上では「J-POP の歌詞はワンパターンだ」と揶揄されているようだけれど,ワンパターンなら,文章の自動生成にはうってつけの題材でもあるはずだ.

ただし何か特定の用途向けにコードをチューニングするのはつまらないので,パラメタはチューニングするとしても,アルゴリズム自体を歌詞向けに特化させるようなことはしない. あくまでただのマルコフ連鎖型文章生成器で,どのような歌詞を吐き出せるのかを見てみたい.

なおテーマ自体は「歌詞 自動生成」とかでぐぐれば大量に出てくることなので,さして新しいことというわけではない.

動作環境を整える

インストールとかその辺は pip とか ebuild 書くとか setup.py 叩くとかで適当に. インストールすると amzweb (人工無脳 API サーバ),amzcons (簡易コンソール),amzirc (IRC Bot) という実行ファイルが作られるので,設定ファイルと共に起動してやる. サーバの設定は下記としてみた.

[web]
instances = second
host      = 127.0.0.1
port      = 8349
daemon    = false
debug     = true

[module]
parsers   = juman
databases = dictdb

[textgen:second]
score_threshold = 0.0
nr_retry        = 50
nr_history      = 0
nr_wordclass    = 100
nr_entrypoint   = 0

[markov:second]
level    = 2
maxchain = 350

[parser:morph:second]
type   = Juman
path   = /usr/bin/juman
encode = utf-8

[db:markov:second]
type = Dict
path = markov.json

[db:entrypoint:second]
type = Dict
path = entrypoint.json

形態素解析器に juman を,データストアに dict/json を使い,2階マルコフ連鎖で最大 350 回まで連鎖させる. ただし連鎖数は初期値.連鎖数は学習する文章の単語数から勝手に調整されていく. スコアの初期値も 0 としておいて,閾値は学習から適当に調整させる. 歌詞生成において「最近の話題を」とか「繰り返しは禁止」というようなことは不要なので,ヒストリ等は 0 にしてある.

$ amzweb amzweb.ini
 * Running on http://127.0.0.1:8349/

設定した "second" という名前の人工無脳インスタンスを起動したので, コンソールから "second" インスタンスを操作する.

$ amzcons second
amzcons>

コンソールから learn というコマンドを使って,ファイルから文章を学習することができる.

一行文生成

手始めに西野カナの歌詞 109 個を一行ずつ学習させた. その結果,下記のような状態となった.

amzcons> stat
score threshold: 0.164676
markov maxchain: 6
markov keys:     8942
entrypoints:     1340

実際に文章を生成してみる.

amzcons> print
近すぎると怖くなって [0.167785]

ふむ何やらそれっぽい.最後の数値はこの文章のスコアを示す.

一行文の連結による歌詞の生成

一行ずつ学習させた結果だから一行ずつしか出てこないので,print コマンドを何度か叩いて結果だけを集めた結果がこちら. なお空行は適当に手で入れた.

近すぎると怖くなって
リピートできないくらい泣いて
嫌いだった鏡に問いかける

久々のオフ 天気も最高
ふたりで過ごしたこと
顔くっつけ合って

恋が凍えてる
先週もまた怒られて
ここに戻ってる
側にいたいよ

最後の恋に恋して
顔くっつけ合って

アイライナー濃いめで
苦いコーヒー流しこみ
この世を去った

何だかよく分からないうちに何かが死んでしまった...

「混ぜる」

意味は分からないが何かちょっと面白いので,悪ノリして Luna Sea の歌詞を 106 個ほど追加学習させてみる. 交わりそうにない西野カナ分と Luna Sea 分を混ぜてみるのだ. 結果は下記の通り.

amzcons> stat
score threshold: 0.150078
markov maxchain: 7
markov keys:     14704
entrypoints:     1875

では文章の生成を行ってみる.

amzcons> print
冷たく透き通る瞳の 恋の色☆ [0.155039]

テンションが低いのか高いのかよく分からない. まあ面白いのでこのままいくつか文章を生成させていく.

冷たく透き通る瞳の 恋の色☆
ナビに怒ってる君を愛してる

伝説の夜に 僕が消えて行く

リミットに気持ちが揺れてる
夢から覚めてすべての事があった

先週もまた怒られて
ローマ風の中でずっと叫んでる
せつなくて ずっと昔から知ってるみたいだね
次の日の未来が違っていたんだ

楽園に刻まれた こんな自分を好きに
伝説の夜 抱きしめた…

淋しげな歌を聴いて
雨音を夜まで数え
夢の中 鍵を探している
窓に映った 自分見つめて

思い通りにはいかないかもしれないけど叫ぶ
限り無く 今はほどいて 頼むからどいて

コノ胸ノ アリフレタ 夢に見る

最初と最後,このテンションの差である.

複数行文生成

一行一行生成して,それっぽいところで手動で空行を挿入するというのもまあ良いのだけれど,そうすると人間の脳と手が介在することになってめんどくさい. この人工無脳エンジンは複数行から成る文章の学習と生成にも対応させてあるので,一発で一気に歌詞を生成できるように,一行一行ではなく歌詞全体を一発で学習させてみる.

AKB48 の歌詞 164 個を行毎ではなく作品毎に学習させてみた結果がこちら.

amzcons> stat
score threshold: 0.267828
markov maxchain: 306
markov keys:     18086
entrypoints:     129

複数行から成る文章の場合,文章中に含まれる単語の品詞の羅列をそのままスコアリングに使ってしまうと,恐ろしく低い値となってしまう (長文同士の比較となり全く似ない). そのため複数行の場合でもスコアリングは行毎に行って,その平均値を使う形にしてある.

さてこの状態で print コマンドを打ってみた結果が以下.

amzcons> print
目の前にそびえる
悩みながら
あの日の栞
輝いた青春の熱は
ヘビーローテーション

時は静かに
広がってく波紋
その瞬間
自分のMINDで動けよ
熱く 燃え尽きるまで
別の力
生まれた場所

やさしさを心の道で
見つけた
どこへ行けばいいのか?

喧嘩して 泣いたこと あきらめかけても
木っ端なんか
ここで死ぬのだろう [0.273942]

...また死んだ.なぜだ.

複数行文も「混ぜる」

では AKB48 分と THE BLUE HEARTS 分を混ぜてみよう. THE BLUE HEARTS の歌詞 93 個を追加学習させた結果がこちら.

amzcons> stat
score threshold: 0.169714
markov maxchain: 160
markov keys:     23498
entrypoints:     199

どうやら歌詞の構成の傾向が違うようで,スコア閾値や最大連鎖数がだいぶ下がっているのが見て取れる.

では歌詞を生成してみよう.

amzcons> print
レストラン レストラン
レストランに行きたい
遠くに見えても
何か別の答えを探す
愛し合おうぜ
裸になっていたんだ

M・O・N・K・E・Y 燃えている

校庭は一面
鈍い銀の世界を
忘れられない

楽しい事をたくさんしたい 喚きたい
ミサイルほどのペンを片手に
僕たちの隠した牙

恋は
ずっと手を取り 連れ出したい
妄想だけじゃ
はかれない
目の前のマネキンたち 何かキレる音がした
アイスのときめき
ハートに火を吹くぜ

明日は明日のために
おもしろい事をたくさん見たよ
何よりも しょうがないから
オレの心が騒ぐよ
しまっておけない [0.174378]

何言ってんだこいつ.

3階マルコフ連鎖

ここまで 2階マルコフ連鎖で文章を生成してきたけれど,3階マルコフ連鎖としてみよう. 設定ファイルを下記のように書き換えて学習し直せば,3階マルコフ連鎖で文章生成を行える.

[markov:second]
level    = 3

この状態で Luna Sea の歌詞 106 個を学習させて歌詞を生成.

amzcons> print
この詩 今夜おくろう君に かけがえない君に
変わることない風に
失いかけていたんだ この想いさえ
記憶の扉 鍵を壊して uh 二人の隙間で 育ってゆく物は
消えた 記憶のトビラ 開くカギか

Break your mind,
going back the dream I can't live without you
このまま 君だけは ぬれないで ずっと
さよなら 君だけは 微笑んで ずっと
さよなら 揺れていた せつなくて ずっと
さよなら 揺れていた せつなくて ずっと
このまま 君だけは 大切な事 抱きしめていて

嘘の世界であなたと二人
愛し合ってみたい
キミが欲しい キミの匂いと
あどけない 微笑みが
塞がれた こんな夜には
ROSIER 近づけない
ROSIER 抱き締められない
貴方が与えて呉れた一生をばらばらに壊したい

瞳を閉じて フラッシュの中 散り咲こう 無情な夜
今誓う空虚の中 散り咲こう [0.132440]

やはり学習したフレーズがそのまま出てきてしまう率が上がるように見える. 過去の履歴を加味して次の状態を決める場合,次の状態への分岐が少なくなってしまうため,数値を大きくすると「そのまま感」が出てきてしまう.

ただ学習数を増やせば分岐も増えるはずではあるので,このまま AKB48,THE BLUE HEARTS,西野カナを全て混ぜてみて,文章を生成してみよう.

amzcons> print
夢まで寝静まる街
ベッドから抜け出して
大人を起こさないように

パンパンパン パンパンパン
パーンと弾けて 飛んで行け

前を邪魔する奴は
喧嘩上等 明け暮れて
無意味なことだとわかった
生意気な奴をボコボコにしても
僕はぼんやりと
眺めていた
苦しめたくない でも忘れられない 何故?
涙 拭いて
歩き出そうよ
そこに 岸はあるんだ

やわらかい君の声を聞かせて 涙を止めて
Dancing on me
life is cool.

ありふれた毎日が
Baby I like candy candy oh
そうテレビをつけたって
話題のパンケーキ特集だって
妄想限界 Help me
life is "LOVE IS OVER
I tell you
just wait for beauty…
貴方のため 生まれ変わる [0.167322]

やっぱ混ざらんわ.無理.

まとめ

おそらく,似たテイストを持つアーティストに絞ったりして学習を進めていけば,それっぽいものを吐き出せるようにはなるんじゃないかと思う. ただ,歌詞というものには主題となるものがあって,ある種の一貫性を文章全体として求めてくる. マルコフ連鎖で文章を生成する以上,どこまでも確率によって内容が展開されていくので,主題の違う文章を学習させてしまうと,生成された文章の中で矛盾を生んだりすることになる. 「このインスタンスはこういう文章の生成用」等といったように主題別に学習を進めて行けば,もしかしたらそれっぽいものを吐き出せるようになるかも知れない.

あと複数行から成る文章の学習と生成は昨日ざくっと適当に作ったものなので,まだまだコードとしてのチューニングの余地があるのではと思っている. 特に,スコアリングを一行一行に対して行うようにしたのは悪手だったかも知れないと思っている.全体の構造が無視されてしまうからだ. まあ,この辺はまたおいおいと詰めて行こうと思う.

この世を去った