マルコフ連鎖とは
マルコフ連鎖を簡単に説明すると、前の時点の状態によって次の時点の状態が決まるというものです。
文章での具体例を見てみると、「おなかが」という言葉を見たら次に「空いた」が来そうな感じがしますよね。でもこれは「空いた」だけが正解ではなく、他にも「いっぱい」が来てもいいわけです。なので、これを確率で表すことを考えようと思います。
「おなかが」の次に続く言葉は、60%の確率で「空いた」、40%の確率で「いっぱい」になると仮定しましょう。この確率が遷移確率と呼ばれる次のそれぞれの状態の確率になります。
ここまでで簡単にですが、マルコフ連鎖について説明しました。ここら辺の話を詳しく知りたい方は マルコフ連鎖の基本とコルモゴロフ方程式(高校数学の美しい物語) 読んでみてください
ただし、文章がマルコフ連鎖ですべて説明できるかといわれたらそうではありません。例えば「僕は おなかが」ときたら「空いた」になる確率が高そうですが、「もう おなかが」ときたら「いっぱい」になる確率が高そうです。
これは文章は一つ前だけでなくもっと前の言葉にも依存しているということです。もっと言えば文脈なんてのにも依存します。しかし、この記事ではマルコフ連鎖を扱うのでここら辺は別の記事で紹介できればと思います。
プログラム
今回作成したプログラムは自分で作成した報告書のデータを用いて新たなレポートを自動生成することを目的としました。ということでまずファイルを読み込んでいきます。
import random
from janome.tokenizer import Tokenizer
with open("data.csv", "rt", encoding="utf-8_sig") as f:
text_raws = f.read()
text_raws = text_raws.replace("\n", "@\n").split("\n")
data.csvを読み込みました。ここでは筆者の報告書のデータですがこれを外部に公開するのは多少まずい気がするので、githubに載せる際は適当な文章にしておきます。
読み込んでから置換しているのは文末のマークとして@
を挿入したかったからです。
text_lists = []
t = Tokenizer()
for text_raw in text_raws:
text_list = []
tokens = t.tokenize(text_raw, wakati=True)
for token in tokens:
text_list.append(token)
text_lists.append(text_list)
Tokenizerを用いて形態素解析を行っていきます。形態素解析とは文章を単語に分かち書きするもので、例えば以下のようになります。
["僕はqiitaに記事を投稿する。"]
↓
['僕', 'は', 'qiita', 'に', '記事', 'を', '投稿', 'する', '。']
また、デフォルトでは品詞などの余分な情報も付記されてしまうのでパラメーターをwakati=True
とすることで言葉だけを取り出しています。
dic = {}
for text_list in text_lists:
for i in range(len(text_list) - 1):
if text_list[i] in dic:
lists = dic[text_list[i]]
else:
lists = []
lists.append(text_list[i + 1])
dic[text_list[i]] = lists
ここでは、{"おなか":["すいた","いっぱい"]}のような辞書形式で前の言葉と次の言葉の対応関係を生成しています。
word = input("最初の言葉を入力してください")
generate = word
word = list(t.tokenize(word, wakati=True))[-1]
limit = 10000
cnt = 0
while cnt < limit:
try:
word = random.choice(dic[word])
if word == "@":
break
except:
break
cnt += 1
generate += word
print(generate)
最初の言葉は入力してもらう形式にしました。そして入力した言葉を形態素解析して、その最後の単語からマルコフ連鎖をスタートさせます。遷移確率に関しては辞書の中からランダムに取り出すことで、出現回数に応じて比例させることにしています。
最後に文末のしるしとして導入した、@
に到達するか無限ループしないように上限を決めて終了です。
以上で、プログラムは完成しました。実際に試してみましょう。
入力「今日は」
生成「今日はあまり満足の手順を集約して不自然な技術を踏まえることを利用することが遅れができた。」
入力「人」
生成「人に着けようともっと多くている将来に具体的大雨のデータの手順を技術支援という流れを見ることが必要と思った。」
ちょっと何言ってるかわかんないですね。冒頭でも述べましたが、言語は直前の言葉だけで決まるものではないため、「多く→て→いる」のように不自然なつながり方をしてしまいました。次回はLSTMなどを使ってより多くの過去の状態から最適な語を判断できるように改良したいと思います。
ソースコードはこちら
参考文献
Pythonでツイートを生成したい! -マルコフ連鎖編-
マルコフ連鎖の基本とコルモゴロフ方程式(高校数学の美しい物語)