YOASOBIっぽい歌詞を文豪の作品から作れる??マルコフ連鎖で文章生成【自然言語処理】

どうも、Champignon(きのこ)です。

今回は、自然言語処理、文章生成のお話です。

この記事を読んでいただきたい方
●Pythonで自然言語処理・文章生成をしたい方
●AIがどんな文章を作れるか興味がある方(AIが小説を出版する日は来るのか?)

過去何回かやってきていますが、今回は時間のかかるディープラーニング(深層学習)ではなく、
番外編的に「マルコフ連鎖」を用いた文章生成を行っていきたいと思います。

この記事のゴール
今話題のアーティスト、「YOASOBI」さんのような歌詞を、偉大な文豪の小説から生成することです。
(彼らは『夜に駆ける』や『群青』など、小説を音楽家するアーティストとのことなので、青空文庫に掲載されているような偉大な文豪の小説からセンテンスを作ればすごい歌詞ができるんじゃないかと。)

便利なライブラリ(https://github.com/jsvine/markovify)が存在するので、こちらに沿って挑戦していきたいと思います!!

機械学習にあまり興味がない方は、
目次から最後の方の「YOASOBIっぽい曲になったのか。。。」に飛んでください!

ちなみに、過去の文章生成の記事はこちらからお願いします。

【文章生成】太宰治『人間失格』をLSTMで学習してみた【自然言語処理】

【文章生成】太宰治『人間失格』を双方向LSTMで学習してみた【自然言語処理】

双方向LSTMで谷崎・太宰を学習させて文章生成してみた。【自然言語処理】【文章生成】

なぜマルコフ連鎖から歌詞なの?

なぜ、マルコフ連鎖から歌詞を生成しようと思ったかというと、後述するように“make_short_sentence(x)”というファンクションを用いると、簡単に、指定した文字数内で短いセンテンスを生成できるからです。

あまりに長文が生成されてしまうと、歌詞としてはあまり適さないことから、短いセンテンスを生成できるファンクションが備わっているのは非常に便利です。
(このライブラリのコードを参考にすれば、機械学習の文章生成の方でも文字数コントロールできるんじゃない!?と思いました。のちのちやってみたいです!)

マルコフ連鎖とは

マルコフ連鎖による文章生成
(著)山たー 最近はDNN(RNNやLSTMなど)を用いた文章生成が盛んですが、時代を逆行して、マルコフ連鎖による文章生成(botなどのいわゆる『人工無能』)をPythonで行ってみました。マルコフ連鎖については以前書いた記事のマルコフ連鎖 (Markov chain)を参照してください。

文系の私が解説するよりも上記リンクをご参照いただくと非常に分かりやすいです!

この記事での使い方を簡単にまとめると、
特定の文字からその次の文字が出現する確率を学習させて、それに基づいて文章を吐き出す
、ということを行います。(図は上記リンクから引用)

マルコフ連鎖のREADME(基本的な使い方)

インストール

インストールは


pip install markovify

で可能です。

なお、動作確認は、
Python 3.6, 3.7, 3.8, and 3.9
までで実施されているようです。

とりあえずマルコフ連鎖で文章生成

以下がREADMEに記載されている基本的な使用方法です。


import markovify

# Get raw text as string.
with open("/path/to/my/corpus.txt") as f:
    text = f.read()

# Build the model.
text_model = markovify.Text(text)

# Print five randomly-generated sentences
for i in range(5):
    print(text_model.make_sentence())

英文をマルコフ連鎖で生成する場合、冒頭の”with open”のパスを英文が記載されたファイルに記載しなおせば、そのまま文章が生成できると思います。

一方で、後述する理由からこれでは日本語の文章生成はできません。

そのため少し補足していきます。以下にて、問題となる部分とその対策について言及していきます。

単語ごとにスペースで区切られていない!

モデル学習のときに、“text_model = markovify.Text(text)”は「半角スペース」を1文字と認識するように設定されています。しかし、日本語はそのような構成にはなっておらず、このままこのファンクションを利用することはできません。

そのため、文章を区切って半角スペースで区切ってやる必要があります。
これまで同様MeCabを使って分かち書きしていこうと思います。(このマルコフモデルには適さないかもですが、一文字ずつ区切っていっても、生成される文章としては面白くなると思います!)

文章の終わりがピリオド(.)ではない!

“text_model = markovify.Text(text)”は半角スペースで区切られているとお話しましたが、さらにこのファンクションは、文章の終わりを” . “(ピリオド)で認識するように記述されております。

これも英語向けで、ピリオドではなく” 。 “(読点)を文章の終わりとしている日本語には適していません(小説の最初から最後までを1文と認識してしまいます。そのため、学習をさせても、連鎖が分岐せず、文章生成しようとすると小説そのままが生成されてしまいます)。

しかし、このライブラリでは、ピリオドで文章が終了しない言語のために、便利なファンクションを設けてくれており、
“text_model =markovify.NewlineText(text)”
を用います。これは「ピリオド」ではなく、「改行」を1文として認識させて学習させるファンクションになります。
そのため、日本語の場合、「読点(。)」を「改行」に置き換えて、“NewlineText”に読み込ませることで、学習ができます!

マルコフモデルで使った作家と生成文

それでは、上記処理を施したうえで、何人かの作家の文章を学習をさせていきます。
機械学習モデルと違って、前処理で文字を数値化する必要もないので、メモリ消費も少なく、すべての作品を学習に取り込むことができました。

YOASOBIっぽい歌詞を生成する前に、まずは、マルコフモデルを利用して学習させると、どんなふうな文章が生成されるか見ていただくために、各作家ごとに5つ文章を生成してみます。

作家ごとの違いを感じていただくために、文章の初めを「私」という文字に統一して生成します。

設定方法はこちらです。


text_model.make_sentence_with_start(beginning="私")

合わせて、機械学習モデルとの学習時間の差も見ていただきたいと思います。

谷崎潤一郎の全小説

小説のリンク:https://www.aozora.gr.jp/index_pages/person1383.html
学習時間:12.7 秒

私はあなたに話しましたが、その歌の調子も舞ひ込んで行きたくないと思ったのを、不思議とも巧く交際していたのである。 

私は最後の談判をすればそなたのを待つ積りで、たつた一遍僕の妻との距離が遠くから思いをなさいました。 

私は椅子に腰かけて一度、近松や西鶴の作品を読むとか、編物をする者はそのつもりである。

私は彼女の口から手が山に傾きかけた時分で、それからみんなで蓄音機をかけられたら、まろの姿がしていたが、こんな悲しい眼をぼんやり開けて遠くに殿堂の廻廊を望む丘陵に出て行くように頼んで居たんや。

私の作品を読むとか、編物をするか。

太宰治の全小説

小説のリンク:https://www.aozora.gr.jp/index_pages/person35.html
学習時間:23.6秒

私は、ひとり笑いや秋の海水浴場に足をもくどくどと繰り返して言いたいのか。

私より上手になっているであろうかと、結託しているわけはない。

私たちと一緒になった。 

私はひとりで口早に囁いた。 

私は、さらに無かった筈です。 

夢野久作の全小説

小説のリンク:https://www.aozora.gr.jp/index_pages/person96.html
学習時間:21.7秒

私は一層威儀を正した。 

私は短刀で、それと同時にハッと眼をソロソロと椅子の方で大勢人が親切に、まじめにきいたわけでも、ピーヴォでも西洋人があります。 

私は、何やら狂気のように頭の中には念を押し立てていた。 

私はイヨイヨ勢を夢のように何て奇麗な袖で顔を仰ぎつつ、廻転椅子の中を旅行してやろう。 

私は、そこいら中がモウ一度に私を誰にもお話して、一心同体となって現われた手附きでネクタイを直した小娘はそうした軽率さを感じた。

一旦ここまでの所感

  • 何よりもまず、学習が早い!!この分量で合計1分もかかってない。
    機械学習をやってきたところからすると非常に楽です。
  • (学習方法から想像はされたものの)文章も普通に読める程度のものが生成される!(文法の獲得が容易)
  • コードも少なくて楽!

一方で、

  • 文法は獲得しやすいものの、単語の意味の獲得はモデルの性質上できないし、文章を見てもしている様子はない。
  • 1文ごとに生成が断絶されてしまうため、1つ前の生成された文章をもとに、次の文章を生成することはできない。そのため、私の最終目的である小説を生成することは難しい。

これらを踏まえて、作った3つのモデル(谷崎モデル、太宰モデル、夢野モデル)を合体させて、YOASOBIっぽい歌詞の生成に進みましょう!!

マルコフモデルの合体

作家ごとに作ったモデルをまずは合体させます。(以下はgithubの例にコメントで私が補足したものです。)


#モデル作成(日本語の場合はtext_model =markovify.NewlineText(text)にしてくださいね)
model_a = markovify.Text(text_a)
model_b = markovify.Text(text_b)

#モデルを合体させます。第2引数は重さを調整できます。下記の場合、50%多くmodel_aが重視されるように合体させています。
model_combo = markovify.combine([ model_a, model_b ], [ 1.5, 1 ])

今回、私は、
【谷崎モデル, 太宰モデル, 夢野モデル】=【2, 2, 1】
という割合で学習させました。

特に深い意味はないですが、
谷崎と太宰は大好きなので、要素として取り込みたかったことに加えて、夢野久作は『ドグラ・マグラ』という、「読むと気が狂う」小説で有名な作家です。
要素として取り込むと、いいアクセントになるんじゃないか、と期待しての選択です。
(YOASOBIの歌詞にも、ある種の陰の要素みたいなものがあるので、よいのではと。)


あとは、歌詞としてあまりに長文が生成されるのは違和感があるので、下記のように、生成される文字数に上限を設定してみましょう!


# Xに任意の数字を入れます。
sentence = text_model.make_short_sentence(X)


それでは、
Let’s 歌詞生成!!

YOASOBIっぽい曲になったのか。。。

不思議不思議……。

そして右の耳に口が悪くって、みんなに憎まれるんだ。

そうなって、小春を使って、彼女がその真っ白な肌の白さだ。 

気を許したことを云って。 

但、これは一体、何が仕合わせにしてやることがありません。 

見やがれ……。 

最後の成績を取入れねばならなかった。 

発病以来三箇月で……ビックリしなければならないのね。 

あの娘を一席弁じた。 

人間の過去を正直に発表された。 

無事なことばかり云うものの、思うにそれは………。 

私はさう云ふのは熊谷の所に泊った。 

出て来て、その膝に前脚をかけますと。 

彼が聞いていたのか。 

毎年夏から始まっておりました。 

彼女が恋しさの何物も取り敢えず走って行った時分だった。 

奇抜な余興になると、夫と喧嘩しておきました。 

眩暈ハ脳動脈硬化ノ結果診断ガツイテオリマス。 

悪サニモ限度ガアリ、可ナリ面倒デアル。 

そして悦子が綴方に書いても一向不思議はないんだ。 

それから漸く、ゆる/\庭の明りの下に直ぐ本牧の海。

どうでしょう??

いくつか、「おっ結構いいやん!」ってところがあると思うのですが、いかがでしょうか??(YOASOBIっぽいかといわれると微妙ですが、、、)

例えば、
「不思議不思議……。」
「見やがれ……。」
「毎年夏から始まっておりました。」
とかは普通に使えそうですね!!

また、
「そして右の耳に口が悪くって、みんなに憎まれるんだ。」
「気を許したことを云って。」
「人間の過去を正直に発表された。」
「彼女がその真っ白な肌の白さだ。」
あたりは、含蓄があっていい感じです!!

いい曲をつけると、結構聴ける感じの楽曲になりそうな雰囲気あります。

さいごに

いかがでしょうか??
結構いい感じに生成されたものの、
例えば、
名詞が小説固有のものを使ってしまっているため、他の部分の詩と調和していない、など改善の余地はありそうです。

また、YOASOBIっぽいかと言われると微妙でした。
学習データとしてYOASOBIの歌詞を使うと結果はまた変わるかも
と思ったりします。

あくまで機械学習による小説生成の番外編的にやってみたのですが、結構おもしろいので、また気が向いたときに改善方法を考えて再チャレンジしてみたいと思います。

ほな、さいなら。