はじめまして、Aidemy研修生のいとぅー(@andmohiko)です。
自然言語処理では文章の自動生成というテーマがあります。
今回は、夏目漱石っぽい文章を自動生成してみようと思います。
手法
LSTM-RNNを使います。
LSTMについては詳しくはこちら
簡単に説明すると、 ある長さの文字列から次の一文字を予測する ということをひたすら繰り返すことで文章が自動生成されていくというものです。マルコフ連鎖は前の2文字しか見ないため、LSTMを使うことで文脈に沿った文章が生成されやすくなるという利点があります。
データセット
青空文庫で公開されている夏目漱石の「坊ちゃん」「吾輩は猫である」「こころ」「夢十夜」を使います。
こちら→作家別作品リスト:夏目 漱石
自然言語処理はデータを集めてくるのが大変なのでとてもありがたいですね。
前処理
ダウンロードしてきた本文にはルビや注釈なのど情報が含まれているため、 これらを削除し地の文のみにします。
import sys
import re
paths = ["bocchan.txt", "kokoro.txt", "wagahaiwa_nekodearu.txt", "yume_juya.txt"]
for path in paths:
bindata = open("./" + path, "rb")
lines = bindata.readlines()
for line in lines:
text = line.decode('Shift_JIS')
text = re.split(r'\r', text)[0]
text = re.split(r'底本', text)[0]
text = text.replace('|', '')
text = re.sub(r'《.+?》', '', text)
text = re.sub(r'[#.+?]', '' , text)
with open('./data_' + path, 'a', encoding='utf-8') as f:
f.write(text + '\n')
これを夏目漱石の他の作品についてもやり、最後に4作品を繋げて一つの長いテキストファイルにする。
TensorFlow + KerasでLSTMを実装
いよいよモデルを実装していきます。 Kerasを使いますが、裏側はTensorFlowのお世話になります。
from keras.models import Sequential,load_model
from keras.layers import Dense, Activation, LSTM
from keras.optimizers import RMSprop
from keras.utils.data_utils import get_file
import numpy as np
import random
import sys
path = "./data_souseki.txt"
bindata = open(path, "rb").read()
text = bindata.decode("utf-8")
print("Size of text: ",len(text))
chars = sorted(list(set(text)))
print("Total chars :",len(chars))
char_indices = dict((c,i) for i,c in enumerate(chars))
indices_char = dict((i,c) for i,c in enumerate(chars))
maxlen = 40
step = 3
sentences = []
next_chars = []
for i in range(0, len(text)-maxlen, step):
sentences.append(text[i:i+maxlen])
next_chars.append(text[i+maxlen])
X = np.zeros((len(sentences),maxlen,len(chars)),dtype=np.bool)
y = np.zeros((len(sentences),len(chars)),dtype=np.bool)
for i, sentence in enumerate(sentences):
for t ,char in enumerate(sentence):
X[i,t,char_indices[char]] = 1
y[i,char_indices[next_chars[i]]] = 1
X = np.zeros((len(sentences),maxlen,len(chars)),dtype=np.bool)
y = np.zeros((len(sentences),len(chars)),dtype=np.bool)
for i, sentence in enumerate(sentences):
for t ,char in enumerate(sentence):
X[i,t,char_indices[char]] = 1
y[i,char_indices[next_chars[i]]] = 1
model = Sequential()
model.add(LSTM(128, input_shape=(maxlen,len(chars))))
model.add(Dense(len(chars)))
model.add(Activation("softmax"))
optimizer = RMSprop(lr = 0.01)
model.compile(loss="categorical_crossentropy",optimizer=optimizer)
def sample(preds, temperature=1.0):
preds = np.asarray(preds).astype("float64")
preds = np.log(preds) / temperature
exp_preds = np.exp(preds)
preds = exp_preds / np.sum(exp_preds)
probs = np.random.multinomial(1, preds, 1)
return np.argmax(probs)
for iteration in range(1,30):
print()
print("-"*50)
print("繰り返し回数: ",iteration)
model.fit(X, y, batch_size=128, epochs=1)
start_index = random.randint(0, len(text)-maxlen-1)
for diversity in [0.2, 0.5, 1.0, 1.2]:
print()
print("-----diversity", diversity)
generated =""
sentence = text[start_index: start_index + maxlen ]
generated += sentence
print("-----Seedを生成しました: " + sentence + '"')
sys.stdout.write(generated)
for i in range(400):
x = np.zeros((1,maxlen,len(chars)))
for t,char in enumerate(sentence):
x[0, t, char_indices[char]] = 1
preds = model.predict(x, verbose =9)[0]
next_index = sample(preds, diversity)
next_char = indices_char[next_index]
generated += next_char
sentence = sentence[1:] + next_char
sys.stdout.write(next_char)
sys.stdout.flush()
print()
model.save('souseki_model.h5')
file = open('sousekigentext.txt','w+',encoding='utf-8').write(generated)
データ量が多いのでGPUを使わない場合は丸一日かかることもあります。しばらく放置しましょう。
学習の様子
今回は時間の都合でepoch数を30にしています。
1回目 loss関数: 4.1994
申しょいを少しも充に落ちる語ったかど勢だ」「いらせんと従ょいに詩に至った文考を逢ってかかり方でいし出である武び少しドャ大椀屋に見て、這入ったが内にステ動りら間」
「人のく迷亭はかあした共安である。あるむを御当坊の起粧にな通ったの云き返す小ッちにも通ろ首の朝な性に学者口を高「え最がつけない」「すん、毫は食うんだ流山主人はレ一の子口学者は奥ずや。あ昔威横あが、しいくオあした 活で、学種爺とにこの作団だからパ気と云いえ面を見た。
読めない、、、
27回目
loss関数: 3.3512
大きな声を第一のはなかったから、それだから、どうかした事がない。その時の方だからしきりになっている事はないが、これからその事だから、自分でも、そのそとでない。吾輩は人間になると、吾輩には文明のごとくのはない。一るのは一際もなくなる。それなら飛び出して来たものがて来たのだが、またはなかったが、それではあいまさに、云わぬが、なるほどのところがある。ただ一人がいい。
結果と今後の発展
夏目漱石っぽい文章を生成することができました。 学習されていく過程を見ると徐々に日本語らしくなっていくところが見ていて楽しかったです。
loss関数が想定よりも小さくならなかったので、次やるときはloss関数の値を小さくするようにしたいです。 27回目の学習結果でも日本語として意味が成り立っているわけではないのがもうひといきな感じですね。loss関数が小さくなるとより日本語として意味が通ったものになりそうです。
今回は1文字ずつ予測していくことで文章を生成していましたが、単語分割をして一単語ずつ予測させるとより日本語っぽくなるかなと思いました。 また、夏目漱石っぽさを評価するために、何種類かの文章を用意してどれが夏目漱石の小説を学習させた結果かをアンケートでとるなどすると客観的に評価ができると思いました。
どちらも要チェック!それではまた次の記事でお会いしましょう。最後までご覧くださりありがとうございました。