マルコフ連鎖で文豪っぽい文章を自動生成してみる【Python】

Pythonを使ってマルコフ連鎖で文章を自動生成

せっかくPythonを勉強したから、それっぽいことをやってみよう!

こんな思いつきで、マルコフ連鎖を使ったPythonの文章自動生成に取り組んでみました。

今回のテーマは、青空文庫から適当に名作を引っ張ってきて、そのテキスト情報を元にマルコフ連鎖で文章を自動生成することです。

この記事でわかること
  • マルコフ連鎖を使って文章を自動生成する仕組み
  • Python初学者がどこまで文章を自動生成できるのか
この記事を書いている人
プログラミングをかじり始めてからもうすぐ1年たちます。最初はWeb系の言語中心に勉強していたのですが、ここ数ヶ月はPythonに力を入れ始めました。数ヶ月の勉強でも、この記事程度のレベルであれば到達できると思うので、1つの道しるべとしてご覧ください。

マルコフ連鎖とはそもそも何か?

本題に入る前に、マルコフ連鎖とはそもそも何なのか。

マルコフ連鎖は、未来の挙動が現在の値だけで決定され、過去の挙動と無関係である(マルコフ性)。各時刻において起こる状態変化(遷移または推移)に関して、マルコフ連鎖は遷移確率が過去の状態によらず、現在の状態のみによる系列である。

Wikipediaより引用

天気を例にとると、「現在の状態(値)」が今日の天気。「未来の挙動」が明日の天気。「過去の挙動」は昨日の天気に例えられます。

天気は大気の動きに影響されるので、明日の天気は昨日の天気に大きく関係します。しかし、マルコフ連鎖では今日の天気だけをみて明日の天気を決定します。(確率的な意味で)

マルコフ連鎖については私自身もしっかりと理解している訳ではないので、正確な説明は他に預けます。

以下を読めば、なんとなくイメージがつくようになるはずです。

Pythonを使ってマルコフ連鎖で文章を自動生成するには?

ある単語が出てきた際に、次の単語が出てくる確率を調べて、ランダムに単語を並べ続けることで文章が自動生成されます。

私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。

例えばこの文章。これは夏目漱石の「こころ」の書き出しです。これを単語に区切るとこうなります。

私 / は / その / 人 / を / 常に / 先生 / / 呼ん / で / い / た / 。 / だから / ここ / でも / ただ / 先生 / と / 書く / だけ / で / 本名 / は / 打ち明け / ない / 。/

太字になっている「と」に着目すると、その次に続く単語は「呼ん」と「書く」の2通り。

この例だけで言えば、「と」が出てきたら、50%の確率で「呼ん」が次にきて、50%の確率で「書く」が続くルールができました。

「先生」「と」の次は「呼ん」が必ず出てくるから、ルールの範囲を1つ前の単語(『過去の挙動』)まで広げればもっと正確なルールを作ることができるのでは?

そのとおりです。しかし、先ほどの章で説明した通り、純粋なマルコフ連鎖は『現在の値(「と」)』によってのみ次の単語を予測するので、ここでは考慮しません。(これについては別記事で触れます)

マルコフ連鎖で文章を自動生成する仕組みはこんな感じです。上の例ではたった2文でしたが、文章が増えれば増えるほど、ルールが固まり、作者特有の雰囲気が出せるようになってきます。

本題に入る前にもう1つ確認したいのが、どうやって文章を単語に区切るかという点。

英語であれば単語はスペースで区切られているので一目瞭然。しかし日本語の場合、自力でプログラムを組んで単語に区切るのは、とても難しいでしょう。

そこで使えるのがPythonの”Janome”というライブラリ。これを使えば、ほぼ正確に文章を単語に区切ることができます。

実際にPythonを使ってマルコフ連鎖で文章を自動生成してみる

先ほど紹介した通り、Pythonを使ってマルコフ連鎖で文章を自動生成するためには、ルールの元となる文章が必要です。

今回は、青空文庫から夏目漱石の「こころ」を拝借し、文章を自動生成していきます。

少し長いですが、関数ごとに分けてみると、そう複雑ではありません。

まずはじめの関数では、インポートしたテキストから無駄な表現を削り、単語ごとに区切り、辞書をつくります。

青空文庫では、本文中にふりがなが振られていたり、縦線「|」が使われていたりするので、ルールづくりに邪魔な表現はとってしまいます。

単なる改行や記号は消すだけでOKですが、ふりがな等は正規表現を使って消しています。

青空文庫において、ふりがなは《》で囲まれているので、この記号で囲まれた文字列を、正規表現”《./》”で表現して削除しています。

次に単語に区切ります。Janomeをインポートしていれば、たったこれだけで「こころ」の文章全てを単語に区切ることができます。

最後に、単語の辞書を作ります。この部分が本記事で一番大事な部分です

ここで作りたいのはこんな辞書です。こころの本文中に出てくる単語を一覧化し、それぞれの単語の次に出てきた単語をリスト化します。

作りたい辞書

{{“私” :[“は”, “も”, “も”, “が”,……]}

{“は” :[“先生”, “晴れ”, ……]

………}

setdefaultを使いつつ、キーがない場合はキーを設定。あとはそのキーに対して続く単語をリスト化する作業を、テキストに含まれる単語の数−1回「len(words)-1」回繰り返します。

あとはこのリストに中からランダムに単語を選ぶコードを書いていけばOKですね。
次の関数がこちら。前の関数で作られた辞書から、指定されたキーの次に続く単語をランダムに選択する関数です。

次の関数は、テキストデータを読み込む関数です。青空文庫を読み込むには文字コードを”shift-jis”に指定する必要があります。

最後のカタマリは、今まで定義してきた関数を呼び出して、文章を自動生成させるためのコードです。

“length”で何回続けるか定義したあと、変数”chain”に単語を格納していきます。
自動生成する文章のはじめの単語は「私」をしました。
これを上記で指定した”length”回繰り返し、最後にコマンドライン上にプリントします。

Pythonで記述したコードを実行してみる

それでは、実際に先ほどPythonで書いたコードを実行してみましょう。


私はこれからはこういう事も持病の奥さんからそこをした。「もう好いが私は来た。「…私に張りあげてそこに掛けちゃなるとすれば、従妹も始終静かに思われるより辛いんだ大変怒りました。「奥さんもそんなにと先生としては、先生がとかく理屈っぽくなったの中に何事もいわせるために感ずる場合に口になって聞かずにはさらによかった。「今度は何

「先生」とか「奥さん」とかが出てくると、なんとなく「こころ」っぽさが出てきます。もう一度実行してみましょう。

私はこれらのです。そんな忙しいところも邪魔な会話がどんな人のです。むしろ抽象的な言葉を待つ間際に大事にしていた波瀾るだけであったまま腰を一転し、何百人しかなかった。しかしこういった。妹のです。私は彼と答えて死にましたせいでしょう」「何だかKに置いて、Kのが取り残された時私の暴力を話しました書物ならば手

今度はKが出てきました。これで完全に「こころ」であることがわかりますね。とはいえ、文章が支離滅裂なので、全く意味をなしていません。

「先生」「と」の次は「呼ん」が必ず出てくるから、ルールの範囲を1つ前の単語(『過去の挙動』)まで広げればもっと正確なルールを作ることができるのでは?

先ほど出てきたこの少年の発言がヒントです。この記事で文章を自動生成する大枠の仕組みがわかったと思うので、次回の記事ではより文章を精緻に自動生成できる「N階層のマルコフ連鎖」に挑戦していきます。

Pythonを使ってマルコフ連鎖で文章を自動生成してみた感想と課題

自分で書いたコードで、文章が自動生成されるとワクワク感が止まりません。

しかし、上の出力結果の通り、文章の意味がまったくわからない状態。文章の意味自体は「N階層のマルコフ連鎖」を活用すれば改善できるのですが、それ以外にも下記のような課題があります。

  • 文末が句点で終わっていない
  • セリフが出てしまうと”「”だけになったり”」”だけになったりする
  • 文の始まりが”私”だけしか指定できない

などなど、そのほかにも改善点は多くありそうです。次回の記事では、これらの課題も踏まえて、Pythonを使ってN階層のマルコフ連鎖でより精緻な文章を自動生成していきたいと思います。

以上です。ここまで読んでいただき、ありがとうございました!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です