梶井基次郎の著作データといっても、ここでは全文を使わずに以下のようなデータを使ってみます。これは前回の最後に紹介したコード(にもう少し手を加えたもの)を使って作成したkajii.txtファイルに含まれている『檸檬』の本文を抜粋したものです。このファイルを作成するコードは今回のノートブックの末尾に掲載しておきます。ノートブック実行時にkajii.txtファイルが必要であれば、最後の(2つの)コードを実行してください。
kajii_data = '''えたいの知れない不吉な塊が私の心を始終圧えつけていた。
焦躁と言おうか、嫌悪と言おうか――酒を飲んだあとに宿酔があるように、酒を毎日飲んでいると宿酔に相当した時期がやって来る。
それが来たのだ。
これはちょっといけなかった。
結果した肺尖カタルや神経衰弱がいけないのではない。
また背を焼くような借金などがいけないのではない。
いけないのはその不吉な塊だ。
以前私を喜ばせたどんな美しい音楽も、どんな美しい詩の一節も辛抱がならなくなった。
蓄音器を聴かせてもらいにわざわざ出かけて行っても、最初の二三小節で不意に立ち上がってしまいたくなる。
何かが私を居堪らずさせるのだ。
それで始終私は街から街を浮浪し続けていた。
何故だかその頃私は見すぼらしくて美しいものに強くひきつけられたのを覚えている。
風景にしても壊れかかった街だとか、その街にしてもよそよそしい表通りよりもどこか親しみのある、汚い洗濯物が干してあったりがらくたが転がしてあったりむさくるしい部屋が覗いていたりする裏通りが好きであった。
雨や風が蝕んでやがて土に帰ってしまう、と言ったような趣きのある街で、土塀が崩れていたり家並が傾きかかっていたり――勢いのいいのは植物だけで、時とするとびっくりさせるような向日葵があったりカンナが咲いていたりする。
時どき私はそんな路を歩きながら、ふと、そこが京都ではなくて京都から何百里も離れた仙台とか長崎とか――そのような市へ今自分が来ているのだ――という錯覚を起こそうと努める。
私は、できることなら京都から逃げ出して誰一人知らないような市へ行ってしまいたかった。'''
1972(昭和47)年12月10日初版発行
1974(昭和49)年第4刷発行
初出:「青空 創刊号」青空社
1925(大正14)年1月
※表題は底本では、「檸檬れもん」となっています。
※編集部による傍注は省略しました。
入力:j.utiyama
校正:野口英司
1998年8月31日公開
2016年7月5日修正
青空文庫作成ファイル:
このファイルは、インターネットの図書館、青空文庫(http://www.aozora.gr.jp/)で作られました。入力、校正、制作にあたったのは、ボランティアの皆さんです。
このデータを使って、モデルを作成する前に筆者が以下の関数を定義しておきました。これはモデル作成の前にいろいろと前処理をしておくとよさそうだったからです。
def preprocess(txt_data):
result = wakati.parse(txt_data)
tbl = str.maketrans({'。': '。\n', '!': '!\n', '?': '?\n', ')': ')\n'})
result = result.translate(tbl).replace('\n ', '\n')
return result
この関数では、分かち書きを行った後に、不要な半角空白文字を削除したり、句点の直後に改行を挿入したりといった処理を行っています。この関数を呼び出した結果をmake_1state_model関数に渡してモデルを作成し、さらにそのモデルをgenerate_sentence関数に渡してみましょう。
tmp = preprocess(kajii_data)
model2 = make_onestate_model(tmp)
for _ in range(5):
print(generate_sentence(model2))
実行結果を以下に示します。
どうでしょう。正直、あまり文として意味があるものになっているとは思えません。そこで、次に2階マルコフ連鎖を行うコードを見てみます。
2階マルコフ連鎖とは、先ほども述べたように、「僕」「は」の次には「カレー」が登場し、「は」「カレー」の次には「が」が登場するといった具合に次の状態を決定していこうという考え方です。今の状態だけではなく、その1つ前の状態も含めることで、次の状態に何が出現するかの選択肢に制限を加え、より自然な文が生成できるようになるのではないかと考えられます。
以下に2階マルコフ連鎖によってモデルを作成するmake_2states_model関数とそれにより作成されたモデルを使って文を生成するgenerate_sentence2関数を示します。
def make_2states_model(txt_data):
model = {}
txt_data = txt_data.split('\n')
for sentence in txt_data:
if not sentence:
break
eos_mark = '。!?'
if sentence[-1] not in eos_mark:
print('not process:', sentence)
continue
words = sentence.split(' ')
word0 = 'BoS0' # begin of sentence
word1 = 'BoS1'
for word in words:
key = (word0, word1)
if key in model:
model[key].append(word)
else:
model[key] = [word]
word0, word1 = word1, word
return model
def generate_sentence2(model):
eos_mark = '。!?'
key0_list = model[('BoS0', 'BoS1')]
key0 = key0_list[randint(0, len(key0_list)-1)]
key1_list = model[('BoS1', key0)]
key1 = key1_list[randint(0, len(key1_list)-1)]
result = key0 + key1
while key1 not in eos_mark:
key_list = model[(key0, key1)]
key = key_list[randint(0, len(key_list)-1)]
result += key
key0, key1 = key1, key
return result
基本の形は単純マルコフ連鎖のものと同様なので、詳細な説明は省略します。文頭を意味する文字列が'BoS0'と'BoS1'の2つに増えているところや、辞書のキーを2つの文字列を要素とするタプルとしているところなどが主な変更点です。
これらを使用して、文を生成してみましょう。
tmp = preprocess(kajii_data)
model3 = make_2states_model(tmp)
for _ in range(5):
print(generate_sentence2(model3))
実行結果を以下に示す。
先ほどよりは文として意味が通りやすくなっていそうな気がしますが、どうでしょう。ただし、筆者が「ここはうまいこと日本語になっているなぁ」と思ったところは、だいたいが元の文をある程度なぞったものになっていました。これもデータが少ないからかもしれません。そこで、全文のデータを保存しているkajii.txtファイルからモデルを作成して、文を生成してみましょう。
tmp = preprocess(kajii_data)
model3 = make_2states_model(tmp)
for _ in range(5):
print(generate_sentence2(model3))
このコードを実行するとモデル作成処理をスキップした文がダラダラッと表示されますが、そこは気にしません。取りあえず、実行結果を見てください。
これも上と似たようなものです。よさげに見える部分は原文をなぞっているようです。興味のある方は上のコードを基に3階マルコフ連鎖を行うコードに挑戦して、その結果を表示してみてください。筆者が試したところでは、梶井基次郎の著作全てからモデルを作成した場合でも、原文のフレーズがそのまま登場することがかなり多かったことを付記しておきます。これは3階マルコフ連鎖では原文における構成要素の連なりが強くなってしまうからでしょう。
似たような、しかし変化がある文を多数集めてくることで、日本語として安定したものとなり、バリエーションも豊富になるかもしれません。ある著者のデータだからといって、それらを基に文を生成したのは失敗かもしれません(なお、3階マルコフ連鎖のコードは今回のノートブックにも含まれているので、興味のある方はそちらも参照してください)。
最後に今までに見てきたようなマルコフ連鎖による文生成に使えるmarkovifyモジュールを簡単に紹介しておきましょう。これはGoogle Colab環境には含まれていないので、以下のコードで事前にインストールしておく必要があります。
!pip install markovify
このモジュールにはTextクラスやNewlineTextクラスなどが含まれています。ここでは改行を含んだ文字列を読み込んで、モデルを作成してくれるmarkovify.NewlineTextクラスを使った例を紹介します。
モデルを作成するには以下のようなコードを実行するだけです。
import markovify
tmp = preprocess(kajii_data)
txt_model = markovify.NewlineText(tmp)
最初に呼び出しているpreprocess関数は上で作成したものです。この関数で分かち書きを行って、不要な空白文字などを削除して、それを基にmarkovify.NewlineTextクラスのインスタンスを生成するだけです。
文を生成するには次のように、make_sentenceメソッドを使用します。
for _ in range(5):
print(txt_model.make_sentence().replace(' ', ''))
実行結果を以下に示します。
今回これまでに書いてきたコードは「マルコフ連鎖ってだいたいこんなものか」ということを理解するためのものであって、実際にはこのようなモジュールを使うのがよいでしょう。
とはいえ、ここまではまだディープラーニングの「デ」の字も出てきていません。次回からはディープラーニング的な手法を試してみるつもりです。
Copyright© Digital Advantage Corp. All Rights Reserved.
Deep Insider 記事ランキング