hitode909の日記

趣味はマリンスポーツですの日記です

マルコフ連鎖してみた

だいぶ前にはじめてのAIプログラミングという本を読んで、N-Gramを作ってみた。

今日少し時間があったからマルコフ連鎖もやってみた。

はじめてのAIプログラミング―C言語で作る人工知能と人工無能

はじめてのAIプログラミング―C言語で作る人工知能と人工無能

マルコフ連鎖を使った文の生成

  1. ある文章を解析して、ある単語が出現した次にどの単語が出現することがあるかを調べる
  2. 文の開始となる単語を1つ選ぶ
  3. その単語に続く単語を確率的に選択していく
  4. 3をしばらく繰り返す

こうすると、文っぽいものができるらしい。
あまり覚えていないけど、マルコフ連鎖というのは、次の要素が直前の要素のみによって決まる、という性質がある言語で、その性質を使って、文を作ることができる、という話だった気がする。

やったこと

  1. 入力された文章をN-gramして、どの単語の次にどの単語がでるかの表を作る(重複あり)
  2. 文頭の単語を1つ選ぶ
  3. 次の単語を表の中からランダムに1つ選ぶ
  4. 文が指定された数生成されるまで選んだ単語をつなげていく

ふつうは、「この単語の次にこの単語が来る確率」を求めていくのだけど、今回は、重複させて表に追加していくことで、確率を計算せずに済むようにした。
簡単に書くとこんなかんじ。

# ふつう
index = { '食べ'=>{ 'ます'=>0.25, 'ません'=>0.75 } }
# 今回
index = { '食べ' => ['ます', 'ません', 'ません', 'ません'] }

生成結果

青空文庫から夏目漱石の「こころ」を取ってきて、3文字ずつ分割して10文生成させてみた。

自分の居間まで重そうな母の言葉は母も聞きます。
彼はどこへか行って、いつも古本屋に売るさ」。
先生の宅とこの木の下を歩いていた。
ところが実際は、あなたがた顫え出したつもりでいたのですが、一年と立ち二年と過ぎ、もう一杯上げます。
その時分から、Kの墓へ毎月お参りをしようとしたのです。
私は迂闊な青年であった。
けれども無理はないそのたびに先生のこの病は慢性であったのです。
しかし決して使われた。
だから、私までも引き留めたでしょうけれども事情にうといったでしょうか」「綺麗に刈り込んだ時、私はまた腕組みをしてよそを向いて、もし坊さんは二、三日前晩食に呼ばれそうに見えました。
日本の女らしく思う気は少しも起ります。

ランダムに生成したので文脈的な内容はないが、文法はおおむね合っていて、完全に文になっているところもある。
また、原文の文体が再現されている。


ついでに1文字で分割してやってみた。

漁師かならしょうと思って、それるといた」「私は寝て、漸とこのう兄が出やくにす。
不愉快の付けたのたの中に行くか考えたけ答えて、私がですの歌留めました。
私は、新着いな呑みんでしたの知れた。
「覚悟が、三日ばかいていものを見えませんの心だ笑談した。
すが常に心のがどっした機会を与えは何もなさなおいに左へ突然とい意味がちょう。
といましかもあるのたの中を引きるだろ質的なる反対としく解り私宛て日にしまで、読み出まったし君だからり過去を懸ければなく知ら。
昔の枕元のです。
あったのご病気の着けなくこの人違の電報を拵えるとす。
こうして見えてまして、これかはなっとしゃん。
その妻に自然していのく聞い盛んでそうに困らな路だろうに立ちに着けて、女の心持に子は察して私は医者を眺めかった。

3文字の場合と比べて、文法が乱れていて、意味不明。
3文字の場合、ある程度単語としての固まりは残るが、1文字の場合、文字の出現回数によって文が生成されるので、見た目の雰囲気程度しか再現されないのだと思う。


ついでに10文字でやってみた。

わが家に取り残すのもまた甚だしい不安であった。
それだのに、東京で好い地位を求めろといって、私を強いたがる父の頭には矛盾があった。
私はその矛盾をおかしく思ったと同時に、そのお蔭でまた東京へ出られるのを喜んだ。

私は父や母の手前、この地位をできるだけの努力で求めつつあるごとくに装おわなくてはならなかった。
私は先生に手紙を書いて、家の事情を精しく述べた。
もし自分の力でできる事があったら何でもするから周旋してくれと頼んだ。
私は先生が私の依頼に取り合うまいと思いながらこの手紙を書いた。
しかし私は先生からこの手紙に対する返事がきっと来るだろうと思って書いた。

完全に原文と一致している。
日本語で連続する10文字が完全に一致すること自体あまりないので、こうなることははじめから予想されていた。

考察

記憶を頼りに作った割にはちゃんと文っぽいものができた。
今回は文章の解析にN-Gramを使ってみたのだが、本来は単語単位で切っていくべきである。
文を生成するときに、単純につないでいくと、つなぎめのn-1文字は一致しているので、単語の最後の1文字だけを追加していく必要があった。
日本語的な知識を持ったプログラムで文章を文節ごとに切ることができれば、よりよい連鎖ができるはずなので、時間があれば、mecabを使ってみるとよいかもしれない。

ソース


青空文庫のテキストファイルで遊ぶには、事前に適切に加工(文字コードUTF-8に変更、ルビを除去)する必要があるのだけど、nkfRubyワンライナーでできた。

cat input.txt nkf -w -Lu | ruby -e 'puts ARGF.read.gsub(/|/, "").gsub(/《.+?》/,"")' > output.txt

実行方法

入力のテキストを引数で渡す.

cat input.txt | ruby marcov.rb

気をつけるべきこと

下のほうで,

  texp = /。| /

しているのだけど,ここに書いた正規表現にマッチした文字だけが文の区切りになるので,入力に「。」や「 」が含まれていないと,うまく動かない.