赤飯にかかったアレ

雑多なメモ帳

AIに文章を書かせたい! [Keras-LSTMサンプルコードで太宰治に文章を書かせたい]

機械の文章力の成長過程が見たい!!

テキストマイニングがしたい! part3. ディープラーニング幼稚園児の文章生成編 [Keras-LSTM文字生成サンプルコード] - 赤飯にかかったアレ

の続きです。

前は

  • 使ったテキストが少なかった(短編1作品のみ)
  • 形態素解析してない文章を使っていた
  • 理解度0でサンプルコード実行しただけ(今回もだけど...)

太宰治の文章をMeCab分かち書きし、KerasのLSTMサンプルコードにちょっと手を加えて文章を生成します。

ついでにWord2Vecでモデルも作ってみます。

 

機械の文章力の成長過程が見たい!!

ジュババァって機械動くの気持ちいいよね

 

あっ!ついでだけど芥川龍之介でこれやったら芥川賞クラスの文になるのかな? 試せたら試そう。

# 念のため_______________

AIという言葉は、色々な解釈ができると思います。

僕のAIの定義は学習し、判断できるものです。

今回は、

  • 事前に文章から辞書を作る。
  • テキストをベクトル化する、モデルを作る。
  • 学習して文章を生成する。

ということを行います。

学習と判断を行うため、タイトルにAIを入れました。

これをみっちゃった人のAIの定義と違ってたらごめんね

 _________________

 

テキストから辞書を作る

 太宰治のテキストを形態素解析して

 MeCab分かち書き(Word2Vecに読み込ませてn次元(nはどうしよ、とりあえず100?)のベクトルデータに)したものをモデルとして保存します。

手順

  1. 太宰治の作品一覧を読み込む
  2. テキストを読み込む
  3. 形態素解析して語句ごとにスペースで区切りテキストファイルに保存。(一旦出力する)
  4. 保存したファイルをWord2Vecで辞書を作成し、モデルファイルとして保存する

1. 太宰治の作品一覧をもらう

青空文庫に公開中の作品を作家別に一括ダウンロードできるサイトがありました。ありがたく使わせていただきます

青空文庫作家別一括ダウンロード

 

 ZipDownload.py

# 夏目漱石の作品一覧のダウンロード
local = 'natume.zip'

if not os.path.exists(local):
print('開始')
req.urlretrieve(url, local)
print('終了')

 

最初夏目漱石でやろうと思ってた名残がコードに出てますがurl変えればダウンロードできます。

 取得したzipをzip.extractall(path=os.path.dirname(保存場所のpath))

で展開して..と思ったのですがなんか文字化けする。

文字コードshift_jisであることが原因みたいだけど・・・ちょっとよくわからない

 

仕方がないのでzipファイルは普通にダブルクリックで展開しました。

2. テキストを読み込む

太宰治のzipファイルを展開すると、全部で250ファイルあります。(夏目漱石の時は99ファイルしかなかったので変更しました。)昔、機械学習にはいっぱい学習データが必要で〜。みたいな記事を読んだ気がしますが、気のせいです。

 

展開したテキストファイルは青空文庫形式なのでshift_jis 

これに注意して読み込もうとするとこんな感じかなと

# 青空文庫Shift_JISファイルを読み込む
bindata = open(sakuhin_file, 'rb').read()
text = bindata.decode('shift_jis')

3. 形態素解析する

展開したファイルを一つずつ開き、青空文庫特有のヘッダーとフッダー、ルビや脚注を削除。

MeCab形態素解析する。

大した手間ではないの形態素解析した文章の中から名詞、動詞、形容詞だけを抽出

(学習直前に助詞と記号も追加で抽出しました)

def Wkati(text):
m = MeCab.Tagger('-Ochasen')

# テキストの先頭にあるヘッダとフッタを削除
text = re.split(r'\-{5,}',text)[2]
text = re.split(r'底本:', text)[0]
text = text.strip()
# ルビを削除
text = text.replace('', '')
text = re.sub(r'.+?', '', text)
# テキスト内の脚注を削除
text = re.sub(r'[#.+?', '', text)

# 形態素解析した文章の中から名詞、動詞、形容詞だけを抽出
words = m.parseToNode(text)
keyword = []
while words:
if words.feature.split(',')[0]==u'名詞' or words.feature.split(',')[0]==u'動詞'or words.feature.split(',')[0]==u'形容詞':
keyword.append(words.surface)
words = words.next
# print(keyword)
return keyword

 

実行中

草枕.txt
[解析失敗] 夏目漱石/草枕.txt 'utf-8' codec can't decode byte 0xe3 in position 0:
unexpected end of data
虞美人草.txt
[解析成功] 夏目漱石/虞美人草.txt
『吾輩は猫である』上篇自序.txt
[解析成功] 夏目漱石/『吾輩は猫である』上篇自序.txt

 

途中経過がわかるのは必須実装事項ですよね!

なんか文字がいっぱい出ると楽しい

 

解析した文章をnatume.wakatiとして保存

# ファイルの作成
file = 'natume.wakati'
wt = ' '.join(results)
with open(file, 'w', encoding='utf-8') as fp:
fp.write(' '.join(results))

Word2vec用のモデルを作る

なんとなく100次元で作った

# word2vec用のモデル作成
word2 = word2vec.LineSentence(file)
model = word2vec.Word2Vec(word2, size=100, window=3, hs=1, min_count=1, sg=1)
model.save('natume.model')

print('モデルできた')

 

文字の生成

 ここで夏目漱石で行なっていたことを太宰治に変更しました

natume.zipはdazai.zipに

natume.wakatiはdazai.wakatiに

natume.modelはdazai.modelとして生成してましゅ。

 

  早速、作ったモデルで文字を生成だぁ。

といきたいところなんですが、このまま進むとこうなります。

the kernel appears to have died. It will restart automatically.

 

はい、カーネルが死にます。(やっちゃったぜ)

原因は分かち書きした文章の保存部分

 

# ファイルの作成
file = 'natume.wakati'
wt = ' '.join(results)
with open(file, 'w', encoding='utf-8') as fp:
fp.write(' '.join(results))

 これを

# ファイルの作成
file = 'dazai.wakati'
wt = ''.join(results) # 単語おきに空白は入れないようにする
with open(file, 'w', encoding='utf-8') as fp:
fp.write(''.join(results)) # 抽出単語おきにスペースを入れていたがカーネルが死ぬのでやめる

こうするといけました。

スペースを入れたのがダメだったぽい

 

  •  LSTM

前の記事同様以下のサイトのリファクタリングコードを使って文字を生成します。

qiita.com

 ソースコードに自分なりの補足も追加してますが、このコードを使用して文字を生成し、ファイルとして保存してみました。

LSRMMakeTxt.py

# utf-8
'''
分かち書きした太宰治の作品一覧を使い
以下のサンプルコード文章に手を加えつつ文章をつくる
https://github.com/keras-team/keras/blob/master/examples/lstm_text_generation.py

参考にした解説
KerasのSingle-LSTM文字生成サンプルコードを解説
https://qiita.com/YankeeDeltaBravo225/items/487dbfa1bef02bcfb84c

https://github.com/YankeeDeltaBravo225/lstm_text_generation_comment/blob/master/lstm_text_generation_refactored.py
'''

from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM
from keras.optimizers import RMSprop # RMSprop手法名
from keras.utils.data_utils import get_file
import numpy as np
import random, sys # 乱数の発生とファイルのパス取得

# 使うファイル名をバイナリデータとして開く
path = "./dazai.wakati" # ./が必要な理由がいまいちわからない、macだから?
with open(path, 'rb') as t:
text = t.read().decode('utf-8') # .decode('utf-8') エラーが出た場合は t.read.().decode('utf-8'とすれば解決するかも

print('コーパスの長さ:', len(text)) # テキストのサイズの取得 文字数の取得

# 1文字を分解して番号をつけ、辞書にする
chars = sorted(list(set(text))) # 1文字ずつ切り出す
print('使われている文字数', len(chars)) # 重複文字は入らない
char_indices = dict((c, i) for i, c in enumerate(chars)) # 文字から番号 enumerate番号付きのデータを出力する
indices_char = dict((i, c) for i, c in enumerate(chars)) # 番号から文字を引く

# print(char_indices)
# print(indices_char)

'''
多分の理解(間違ってる可能性高い)
この生成ではcharベースで行なっているけど単語ベースで行うことも可能なはず

読み込んだ全テキスト(text) len(text)で取得する。
maxlen 一定の文字数で文字を切り取り、その次に来る文字の関係を(文字がなんなのかを)学習させる。
step 切り取る幅のスライド数

sentences 区切った文字数(maxlen)の格納先
next_chars 次に来る文字格納する
sentencesとnext_charsをセットで学習させて推定
その際一旦それぞれをベクトル(配列)として格納し、関係性を学習する
'''

# 文と次に来る文字を配列に入れる
maxlen = 20 # テキストをmaaxlen文字で区切る
step = 3 # スライドするステップ数

sentences =
next_chars =

# 文章全体についてスライドしながらsentencesとnext_charsを格納する
for i in range(0, len(text) - maxlen, step):
sentences.append(text[i: i + maxlen])
next_chars.append(text[i + maxlen])

print('学習する文の数', len(sentences))
# print(len(next_chars)) # lem(sentences)と同じになるよね

# テキストのベクトル化
# numpyのarray
print('テキストをIDベクトルにします...')

X = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool) # サイズは文章の数, 一つ一つの要素の中にmaxlenのデータを持つ, 各文字に対して辞書のサイズを持つ,データタイプは二つの値しか持たないbool型
y = np.zeros((len(sentences), len(chars)), dtype=np.bool) # サイズは文章の数, 文章の数分の文字のサイズの指定

# 全ての文章についてarrayの値を更新する
for i, sentence in enumerate(sentences): # 文章全体についてループする
for t, char in enumerate(sentence): # 各文章の中、各文字について処理を行う
X[i, t, char_indices[char]] = 1 # 文字から番号を引いたところで文字が含む時に1を立てる
y[i, char_indices[next_chars[i]]] = 1 # 文字が含む時に1を立てる [ベクトル化処理]

# モデルを定義する(LSTM)
print('モデルを構築します...')
model = Sequential() # 連続的なデータを扱うという意味
model.add(LSTM(128, input_shape=(maxlen, len(chars)))) # add : NNのモデルの追加 LSTMのサイズ : 128, shape : 入力するデータの形状は1回の入力のデータと辞書の長さ
model.add(Dense(len(chars))) # 全結合する(全てのセルを使ったNNを作る。サイズは辞書のサイズ)
model.add(Activation('softmax')) # 文字の推定をする softmax:出力した値を0〜1に変換する

optimizer = RMSprop(lr=0.01) # RMSpropアルゴリズムを使用し、学習率は0.01とする
model.compile(loss='categorical_crossentropy', optimizer=optimizer) # トレーニングの宣言? 損失関数 categorical_crossentropy:どれくらい離れているか

# トレーニング後、(最後の層を通過した後、)候補の中から値を取り出す
# 選択候補となる配列から値を取り出す
def sample(preds, tempretire=1.0): # preds : 予想値,
preds = np.asarray(preds).astype('float64') # 初期化
preds = np.log(preds) / tempretire
exp_preds = np.exp(preds)
preds = exp_preds / np.sum(exp_preds)
probas = np.random.multinomial(1, preds, 1) # 確率
return np.argmax(probas) # もっとも確率の高い(大きな値の)位置の番号を返す

# 学習させて、テキストを生成する・・・を繰り返す
for iteration in range(1, 10): # 1〜30繰り返す
print() # 改行の出力
print('_'*50)
print('繰り返し回数;',iteration)
model.fit(X, y, batch_size=128, epochs=1) # batch_size:1回に挿入するデータの長さ, epoch:全体のデータを何回繰り返すか
 
# ランダムにテキストのシードを選ぶ
start_index = random.randint(0, len(text)) # 最初に開始するテキストを決めるための変数の定義 0〜(len(text) - maxlen - 1)の間で決定する

# 多様性のパラメータごとに文を生成する
# サンプルデータを出力するためのパラメータを次の四つの中から取り出す
for diversity in [0.2, 0.5, 1.0, 1.2]:
print()
print('-----多様性', diversity)

# 生成する文章を入れる。最初は空で初期化
generated = ''
 
sentence = text[start_index: start_index + maxlen]
generated += sentence
print('-----シードを生成しました"' + 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. # 文字が張っているところのBITを立てる

# 次に来る文字を予測
preds = model.predict(x, verbose=0)[0] # 0番目の予測値を出す
next_index = sample(preds, diversity)
next_char = indices_char[next_index] # 番号に相当する文字
 
# 既存の文に予測した一文字を足す
generated += next_char
sentence = sentence[1:] + next_char # sentenceの更新
sys.stdout.write(next_char)
sys.stdout.flush()
# open('./dazai'+ iteration +'.lstm', 'w', encoding='utf-8').write(sentence) # 生成結果の出力、いっぱいできるので注意
# open('./test/dazai.lstm', 'a', encoding='utf-8').write(sentence) # 事前にこのプログラムファイルと同じ場所にtestディレクトリを作っておけばそこに生成された文章が保存される

# 事前にこのプログラムファイルと同じ場所にtestディレクトリを作っておけばそこに生成された文章が保存される
with open('./test/dazaisentence.lstm', "a", encoding="utf-8") as f:
f.write("".join(sentence)) 
 
# 事前にこのプログラムファイルと同じ場所にtestディレクトリを作っておけばそこに生成された文章が保存される
with open('./test/dazaigenerated.lstm', "a", encoding="utf-8") as f:
f.write("".join(generated)) 

print() # 改行

結果

  • 繰り返し1回目の結果 

 

繰り返し回数; 1

Epoch 1/1

Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA

472054/472054 [==============================] - 3127s 7ms/step - loss: 4.3106

 

-----多様性 0.2

-----シードを生成しました"色は他の膏薬とかはつてもゐよ。ものが。」"

「、、、、いまは、私は、いまは、私は、私は、私は、私は、私は、私は、私は、あなたは、私は、私は、私は、私は、私は、私は、それを思い出して、私は、私は、私は、私は、私は、それを、私は、私は、私は、私は、それを言う事になっているの。、私は、私は、私の家には、私は、私は、私は、私は、私は、私は、私は、お前には、私は、私は、私は、、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私の家には、私は、私のほうに、、私は、私は、私は、私は、私は、私の家には、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、それは、私は、私は、私は、私は、私は、私は、私は、私の家には、私は、私は、それを言うと、、私は、私は、私は、私は、、私は、私は、私は、私は、私は、私の家に、それは、私は、私は、私は、私は、それを思い出して、私は、私は、私は、私は、私は、私は、私は、

 

-----多様性 0.5

-----シードを生成しました"色は他の膏薬とかはつてもゐよ。ものが。」"

 と言い。、、私は、私の事は、私は、それを思い自分は、私は、私は、おまえには、、高く、一つの話をしていのが、、私は、手紙をつけ、それから、みんな、のものというと お事は、それは、私の家には、、お前には、私の中で、私は、私の時、、それは、小説を八つならながら、私は、それを、おいやに行って行って、それをもあれのと思う。、私は、私は、名前の口をかけて言うのから、、は、ころいころがある。いまは、あたし、、                                                                                                      

 

-----多様性 1.0

-----シードを生成しました"色は他の膏薬とかはつてもゐよ。ものが。」"

色は他の膏薬とかはつてもゐよ。ものが。」と心本く宿の学か、言えされる事は思いこと「、ようからね。立たしてリのね。」と方から物かれ、青いたまっの目へよろ。此の、と思っ。悪若小に、教えていと思をりも、東京そう足へ行ざ情られて来ん。、いい家無い事は、水の思生になって来て、読者をメして、庭赤(茶田 顔なつ私、役人酒だけ一つラく、よいざつ男。自分のびこる妹夜も見えられている文学者からはげさまらせて、色の抜いお方。なら。「生きてるかたさら子、私は、神ンを事はうがいして屋は、うちろ男かうと「風からそれに高い胸がない、女よりは許からが、何かて、音ばはそれを自分を方の私に「のスるサ屈本」、よりは、おれでは、負けこと送りねえ。よいある初の土理に、ちは僕もあつまでは、自分は無い復五ながら、店の見取見してゐ。あた長に派い感大を僕まの向りて、自分コほんかも知れて気もするので、とゃたちろいいいというより

 

-----多様性 1.2

-----シードを生成しました"色は他の膏薬とかはつてもゐよ。ものが。」"

「愉が食悼だしいひはちかい合あんのほうじゃ色し。礼か王上(妙たびに緒を美女とメもで食べ。、と素聞を書く酔りパきくないしとっ焼、下手は痛!のに私は思われるの者手ろではなか。んなら用事は、二だ田の最快で雪れのものだが、実平の原稿を直って呼んでいざま対してはねぎに考えばならと、ひとを水チく語らす々なむくむしり行たと降上ば事しい秋飛側はして情まりかそ、いやに廻そ然、日本の地とへ帰るふりま多切拝説たし帰って せ情合名飛局娘て答えのも陰け。毎は嫌とだって一、は。と風上度を酔い風天ば腹きでたでき。それに演やに君が多く感想らも方紙に思も。「し、ばかなは私たち以後心にも口出のところど頭のやる。立ち上りて、聞えもののの小とさだマのまま場愛、。時から、派こ問い、いま思る事色ながらわ、風書い保無かっ事、「たんは、のかやり、そか、

 

 

  • 繰り返し9回目の結果

繰り返し回数; 9

Epoch 1/1

472054/472054 [==============================] - 3205s 7ms/step - loss: 4.1498

 

-----多様性 0.2

-----シードを生成しました"、いちまい、いちまい、私は私の手紙のレタ"

、いちまい、いちまい、私は私の手紙のレタイは、、私は、私は、私のところに、、私は、私は、私の家には、、私は、私には、、私は、私の家の中には、私は、私は、私は、私が、私は、私は、私は、私の家には、私は、私は、私は、私は、私は、私の家には、私は、私は、私には、私は、私の家の中には、一言は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私は、私のところに「、」というものは、私は、私は、私は、私には、私は、私の、私は、それには、私は、私は、私は、私は、私は、私には、私は、私は、私には、私は、私は、私には、私は、私は、私は、私は、私は、私の家の中には、私の、私は、私のところに、私は、私は、私の、私は、私は、私は、私は、私は、私は、私は、私は、私が、私は、私は、私には、私は、私は、私は、私のところに、それには、私は、私は、私に

 

-----多様性 0.5

-----シードを生成しました"、いちまい、いちまい、私は私の手紙のレタ"

 、それは、、私の手紙を、私は、お、おりの、何か言って、一つの、あなたの私のに、と言いされている。それも、私は、うちに、あたし、、、、、小さいから、私は、私は、 私は、、私には、、いま、、私が、私は、全部、と何も、私は、、しよと思っている。 私は、私には、私は、私には、気がきい、ひとりで、、いまでもない。明私のから、「 私は、このごろは、私にも、これには、それに書かれて、私は、私は、こ

 

-----多様性 1.0

-----シードを生成しました"、いちまい、いちまい、私は私の手紙のレタ"

、いちまい、いちまい、私は私の手紙のレタル。シャ地に容貌の白いい誰に入につからくて、しまっの。ね。」家分は、私には、私はせやかってゐるふたみにという出さと、私などのとしては、三弟には、信じそびよう。所謂「というので、五十十人さわしのことの。 、章上げの春。いまの立ちどうものは、アめになれよ、とついて見て、私は生き。美しく私のさちにが飛んで、私には、ひどいに二、よ。し事。キヌ子は、一人話の上むと、事「帰り、着見と所三太笑いはとするのが、よう奥服ばさぐすとる先生できだけはこのは無 君など? 親さん、なき。、小説的大子ふっといるものを持っているが、シと思つてゐが、(面白私は、あたしたちの有落大を思い出すきをしら。言えば、酒を飲みかけて、こ「、あいつはのじゃか。にいや、し女の光上にあらわれ。

 

-----多様性 1.2

-----シードを生成しました"、いちまい、いちまい、私は私の手紙のレタ"

、いちまい、いちまい、私は私の手紙のレタスに勝弟生って紙幣宿で、みんなで、やって来酒がもいや、大きさい無、さぐっなり四十を六め悪いもの、と近くいま、し、度な庭を 不格郎自ス聞本私との美案では間抜け、十島の六ここ渡風の何ね、ふぶ紙章きをにゐ事はと思っさ家直が、しずかも散次発言できは、ぶ。文学は発部苦をして行かば、出台と何のわがままさまあろ上りをぐって、女紙残つてとら買くせに夜にらもされ、笑っ。いや。合、、間、一ャ紙が、私が大。はなし三ら焼下抜き、ね一意品の外では 客ちかけ十時、 六下の奥さん、百頭にはいるようつきも。君のダれよ、屋ひど一人の。気在大思い確」 服装置かみ。君にはいって気持を、見様と知って流れ酒がら。気決さ入病花のずるに引こつけよう眼をそり

 

きちんとした文章はむずかし!

 

今回のソースコードはここ

github.com