#前書き
前回
Python + Janomeでマルコフ連鎖人工無脳(1)Janome入門
今回はマルコフ連鎖の下準備として、Janomeを使ってまとまった文章をばらばらにしたり、簡単な単純マルコフ連鎖を実装してみます。
#マルコフ連鎖とは?
##導入
マルコフ連鎖(マルコフれんさ、英: Markov chain)とは、確率過程の一種であるマルコフ過程のうち、とりうる状態が離散的(有限または可算)なもの(離散状態マルコフ過程)をいう。
(Wikipediaより)
シンプルなすごろくを想像してください。あるマスでサイコロを振り、出た目の分だけ前進します。
スタート地点がどこだとしても、それまでのサイコロが出してきた値も、「4マス先に進む」という未来の事象には一切関係しません。これがマルコフ性です。
マスとマスの間に挟まるようなことはなく、いずれかのマスにたどりつきます。これが離散的であるということです。
このようなマルコフ過程は単純マルコフ連鎖といいますが、たとえば
「前回の目と今回の目の和だけ進む」
というようなルールを設ければ、これは2階マルコフ連鎖になります。
##文章生成への応用
文章生成では、N階マルコフ連鎖を利用します。
次の文章を例に考えます。
マルコフ過程とは、マルコフ性をもつ確率過程のことをいう。
未来の挙動が現在の値だけで決定され、過去の挙動と無関係であるという性質を持つ確率過程である。
これらをJanomeを使ってばらすと、このようになります。
マルコフ|過程|と|は|、|マルコフ|性|を|もつ|確率|過程|の|こと|を|いう|。
未来|の|挙動|が|現在|の|値|だけ|で|決定|さ|れ|、|過去|の|挙動|と|無関係|で|ある|という|性質|を|持つ|確率|過程|で|ある|。
まず、始点を任意に定めてやります。「マルコフ」にしましょう。
現在の状態「マルコフ」を参考に、次のブロックを決めます。
「マルコフ」とつながっているのは「過程」と「性」ですから、そのいずれかを選びます。「過程」にしましょう。
単純マルコフ連鎖なら次は「過程」に続く「と」「の」「で」から選び、
2階マルコフ連鎖なら「マルコフ」「過程」の2ブロックに続く「と」を選びます。
今回はカンタンに、N=1の単純マルコフ連鎖を実装してみましょう。
#実装(単純マルコフ過程)
from janome.tokenizer import Tokenizer
import random
t = Tokenizer()
s = "マルコフ過程とは、マルコフ性をもつ確率過程のことをいう。\
未来の挙動が現在の値だけで決定され、過去の挙動と無関係であるという性質を持つ確率過程である。"
line = ""
for token in t.tokenize(s):
line += token.surface
line += "|"
word_list = line.split("|")
word_list.pop()
dictionary = {}
queue = ""
for word in word_list:
if queue != "" and queue != "。":
if queue not in dictionary:
dictionary[queue] = []
dictionary[queue].append(word)
else:
dictionary[queue].append(word)
queue = word
def generator(start):
sentence = start
now_word = start
for i in range (1000):
if now_word == "。":
break
else:
next_word = random.choice(dictionary[now_word])
now_word = next_word
sentence += next_word
return sentence
for i in range(5):
print(generator("マルコフ"))
===========以下注釈===========
from janome.tokenizer import Tokenizer
import random
t = Tokenizer()
s = "マルコフ過程とは、マルコフ性をもつ確率過程のことをいう。\
未来の挙動が現在の値だけで決定され、過去の挙動と無関係であるという性質を持つ確率過程である。"
line = ""
for token in t.tokenize(s):
line += token.surface
line += "|"
word_list = line.split("|")
word_list.pop()
前半部分では、上の文章をバラバラにして格納した配列を生成しています。
上に述べたような|区切りの文字列を生成し、|でsplit。
しんがりに空文字が残っているので、popで除きます。
dictionary = {}
queue = ""
for word in word_list:
if queue != "" and queue != "。":
if queue not in dictionary:
dictionary[queue] = []
dictionary[queue].append(word)
else:
dictionary[queue].append(word)
queue = word
中間部分では、前半で作った配列を使って辞書(dict型オブジェクト)を作っています。
queueに現在の単語を入れておき、
(項目がない場合は新しく作って)後続の単語wordを追加しています。
追加した後は、queueを後続の単語wordに差し替えます。
辞書の中身は
{'マルコフ': ['過程', '性'], '過程': ['と', 'の', 'で'], 'と': ['は', '無関係'], 'は': ['、'], '、': ['マルコフ', '過去'], '性': ['を'], 'を': ['もつ', 'いう', '持つ'], 'もつ': ['確
率'], '確率': ['過程', '過程'], 'の': ['こと', '挙動', '値', '挙動'], 'こと': ['を'], 'いう': ['。'], '未来': ['の'], '挙動': ['が', 'と'], 'が': ['現在'], '現在': ['の'], '値': ['だ
け'], 'だけ': ['で'], 'で': ['決定', 'ある', 'ある'], '決定': ['さ'], 'さ': ['れ'], 'れ': ['、'], '過去': ['の'], '無関係': ['で'], 'ある': ['という', '。'], 'という': ['性質'], '性
質': ['を'], '持つ': ['確率']}
こんな感じです。
def generator(start):
sentence = start
now_word = start
for i in range (1000):
if now_word == "。":
break
else:
next_word = random.choice(dictionary[now_word])
now_word = next_word
sentence += next_word
return sentence
for i in range(5):
print(generator("マルコフ"))
後半部分では、いよいよ文章生成です。
現在の単語の後続の単語群から次の単語をランダムに選び取って、sentenceにくっつけていきます。
"。"が来たら終了するように書きましたが、確率的には永遠に"。"が選ばれないこともありえるので繰り返しはせいぜい1000回で終わるように指定しています。
"マルコフ"から始まる文章を5本書き出してもらいましょう。
マルコフ性をもつ確率過程と無関係である。
マルコフ性を持つ確率過程と無関係である。
マルコフ性を持つ確率過程の挙動が現在のことをいう。
マルコフ性をいう。
マルコフ過程のことを持つ確率過程である。
(だぶっちゃった)
しかし、それらしい文章は生めましたね。
#次回への展望
単純マルコフ連鎖は過去との脈絡が一切ないだけ、上のように支離滅裂になりがちです。
また、今回は辞書が貧弱すぎたのもあり、似たり寄ったりな文章になってしまっています。
次回は複数個の連鎖をもとに次の単語を選び出して文章を生成するN階マルコフ連鎖を、
もっと大きな辞書で実装してみようと思います。