※このページではアフィリエイト広告を利用しています

Pythonでマルコフ連鎖を用いた文章生成に挑戦してみた。

Python
スポンサーリンク
スポンサーリンク

そもそも、マルコフ連鎖とは?

マルコフ連鎖とは、確率過程の一種で、過去の状態に依存して次の状態を決定するような過程のことを指します。具体的には、ある状態にあるシステムが次の状態に遷移する確率が、現在の状態にのみ依存し、それ以前の状態の履歴には依存しないという性質を持ちます。

テキスト生成においては、マルコフ連鎖を用いて、過去の単語の履歴に基づいて、次の単語を予測することができます。たとえば、「私は」という単語が与えられたときに、その次に来る単語が「学生です」や「ご飯が好きです」などのように、ある程度自然な文章を生成することができます。このように、マルコフ連鎖を用いることで、自然言語処理において文章生成や文章修正などのタスクが行われます。

プログラム

import random
import re

#1---テキストファイルを読み込む
try:
    with open('test.txt', encoding='utf-8') as f:
        text = f.read()
except FileNotFoundError:
    print('テキストファイルが存在しません')
    exit()

#2---テキストを単語に分割する
words = re.findall(r'\w+', text)

#3---2次元辞書を作成する
chain = {}
prev_word = ''
for word in words:
    if prev_word not in chain:
        chain[prev_word] = {}
    if word not in chain[prev_word]:
        chain[prev_word][word] = 0
    chain[prev_word][word] += 1
    prev_word = word

#4---初めの単語を選択する
word1 = ''
while not word1:
    word1 = random.choice(words)

#5---前の単語、その後の出現単語と出現回数の出力
word2 = ''
while not word2:
    try:
        word2 = random.choice(list(chain[word1].keys()))
    except KeyError:
        word1 = ''
        while not word1:
            word1 = random.choice(words)
#6---出力先ファイルの指定
open_file = open("test.txt",mode = "w",encoding = "utf-8")
endflag =0
#7---マルコフ連鎖で文章を生成する
while True:
    try:
        if isinstance(chain[word1][word2], dict):
            weight_sum = sum(chain[word1][word2].values())
            word3 = random.choices(list(chain[word1][word2].keys()), weights=[count/weight_sum for count in chain[word1][word2].values()])[0]
        else:
            word3 = word2
#8---KeyError時の処理
   except KeyError:
        word1 = ''
        while not word1:
            word1 = random.choice(words)
        word2 = ''
        while not word2:
            try:
                word2 = random.choice(list(chain[word1].keys()))
            except KeyError:
                word1 = ''
                while not word1:
                    word1 = random.choice(words)
        continue
    word1, word2 = word2, word3
    endflag = endflag + 1
#9---書込み
    open_file.write(word3)
#10---単語の区切り指定
    if word3.endswith('。') or word3.endswith('?') or word3.endswith('!') or word3.endswith('」'):
        break
#11---終了Flag
    if endflag == 500:
        break
open_file.close()

上記のプログラムは、任意のテキストファイルを読込みんで、マルコフ連鎖で出力した結果をテキストファイルに保存するものです。

以下は解説になります。

#1---テキストファイルを読み込む
try:
    with open('test.txt', encoding='utf-8') as f:
        text = f.read()
except FileNotFoundError:
    print('テキストファイルが存在しません')
    exit()

1の部分では読込むファイルを指定しています。

#2---テキストを単語に分割する
words = re.findall(r'\w+', text)

2の部分ではテキストを単語に分割しています。

#3---2次元辞書を作成する
chain = {}
prev_word = ''
for word in words:
    if prev_word not in chain:
        chain[prev_word] = {}
    if word not in chain[prev_word]:
        chain[prev_word][word] = 0
    chain[prev_word][word] += 1
    prev_word = word

3の部分では2次元辞書を作成しています。分割した単語をそれぞれ格納しています。

#4---初めの単語を選択する
word1 = ''
while not word1:
    word1 = random.choice(words)

4の部分では初めの単語をchoice関数で取り出しています。

#5---前の単語、その後の出現単語と出現回数の出力
word2 = ''
while not word2:
    try:
        word2 = random.choice(list(chain[word1].keys()))
    except KeyError:
        word1 = ''
        while not word1:
            word1 = random.choice(words)
#6---出力先ファイルの指定
open_file = open("test.txt",mode = "w",encoding = "utf-8")
endflag =0

5の部分では辞書のキーは前の単語、値はその後に出現した単語とその出現回数を出力しています。

続けて6の部分では出力先ファイルの指定。「endflag」は後ほど文章生成をしたときに任意の値になったら処理を終了させるFlagです。

#7---マルコフ連鎖で文章を生成する
while True:
    try:
        if isinstance(chain[word1][word2], dict):
            weight_sum = sum(chain[word1][word2].values())
            word3 = random.choices(list(chain[word1][word2].keys()), weights=[count/weight_sum for count in chain[word1][word2].values()])[0]
        else:
            word3 = word2

7の部分ではマルコフ連鎖で文章を生成しています。While構文で条件が満たすまで実行しています。try構文の「isinstance」で辞書型であるかどうかを確認しています。辞書である場合にのみ、次の単語をランダムに選択しています。辞書でない場合は、前の単語 word2を次の単語word3として使用しています。

#8---KeyError時の処理
   except KeyError:
        word1 = ''
        while not word1:
            word1 = random.choice(words)
        word2 = ''
        while not word2:
            try:
                word2 = random.choice(list(chain[word1].keys()))
            except KeyError:
                word1 = ''
                while not word1:
                    word1 = random.choice(words)
        continue
    word1, word2 = word2, word3

8の部分では、文章の生成中に、2つ前の単語が存在しない場合には、初めの2つの単語を再度選択しています。

#9---書込み
    open_file.write(word3)

9の部分ではテキストをファイルに書き込んでいます。

#10---単語の区切り指定
    if word3.endswith('。') or word3.endswith('?') or word3.endswith('!') or word3.endswith('」'):
        break

10の部分では、単語の区切りに記号を指定しています。

#11---終了Flag
    if endflag == 500:
        break
open_file.close()

11の部分ではフラグで条件を満たしたらBreakして抜けて、ファイルを閉じて終了させています。

結果

の内にの内にあきらめの朝まわが歌しばらく東京で無為徒食してわびしさの堪えか日に虫食われゆき何かとでもいったようなものを何か声を失い自信に似たものを得て忍従の夜わかさとなむ何か声を失い陋巷わびしさの堪えかまえから腹案していた長い小説に取りかかったそのおのれの作品に依って知らされ何かとでもいったようなものを歌でなくあきらめの努めか見つけし歌

結果はとある青空文庫の文章を読み込んだものになります。

タイトルとURLをコピーしました