小説からRNNを用いて文章自動生成(勉強メモ)

#小説からRNNを用いて文章自動生成
文豪の小説を学習させ、文章の自動生成に取り組んでみた勉強メモ。
(先に、勉強メモと断っておきます。)

夏目漱石の"坊ちゃん"をリカレントニューラルネットワークで学習させ、
坊ちゃんのような軽快な文章の自動生成に挑戦する。


##参考URL
https://sleepless-se.net/2018/08/20/python-machine-learning/

##準備

ニューラルネットワークはkerasでモデル作成する。
文章の解析にはjanomeを利用した。

```python
from keras.layers import Dense, Activation, SimpleRNN
from keras.models import Sequential
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import janome
import numpy as np
```

試しに、簡単な文章を分かち書きする。

```python
#テスト
from janome.tokenizer import Tokenizer
t = Tokenizer(wakati=True)
s = 'スピードワゴンはクールに去るぜ'

t.tokenize(s)
```

下記のように、表示される。
['スピード', 'ワゴン', 'は', 'クール', 'に', '去る', 'ぜ']

...スピードワゴンが分かれてしまいましたがスルー。。

ちなみに、janomeはインストールが非常に簡単です。
下記コマンドでインストール完了です。
有名なMeCabですが、Windowsでインストールするのはやっかいで、
janomeにしました。

```python
>pip install janome
```

続いて、坊ちゃんを分かち書きします。
ローカルにダウンロードしていた坊ちゃんの情報を読み込みます。

```python
FILE_PATH = "./bocchan.txt"
text=""
with open(FILE_PATH, 'r',encoding="Shift-JIS") as f:
    for line in f:
        t = Tokenizer(wakati=True)
        lines = t.tokenize(line)
        text += " ".join(lines)
text[0:500]
```

'坊っちゃん夏目 漱石+ 目次一親 譲 お やゆ ずり の 無鉄砲 むてっぽうで 小 供 の 時 から 損 ばかり し て いる 。 小学校 に 居る 時分 学校 の 二 階 から 飛び降り て 一 週間 ほど 腰 こし を 抜 ぬかし た 事 が ある 。 なぜ そんな 無闇 むやみ を し た と 聞く 人 が ある かも 知れ ぬ 。 別段 深い 理由 で も ない 。 新築 の 二 階 から 首 を 出し て い たら 、 同級生 の 一 人 が 冗談 じ ょうだんに 、 いくら 威張 いばっ て も 、 そこ から 飛び降りる 事 は 出来 まい 。 弱虫 や ー い 。 と 囃 は やし た から で ある 。 小使 こづかい に 負ぶさっ て 帰っ て 来 た 時 、 おやじ が 大きな 眼 め を し て 二 階 ぐらい から 飛び降り て 腰 を 抜かす 奴 やつ が ある か と 云い っ た から 、 この 次 は 抜かさ ず に 飛ん で 見せ ます と 答え た 。親類 の もの から 西洋 製 の ナイフ を 貰 もらっ て 奇麗 きれい な 刃 は を 日 '

strをリスト型に変換します。
区切り文字は半角の空白です。

```python
#リスト = str.split(‘区切り文字’)
text_list = text.split(" ")
#リスト型になっているか確認
text_list[8]#'無鉄砲'
```

分かち書きした単語数を算出します。
坊ちゃんは6832単語使われている様。

```python
#分かち書きした単語数を算出
text_uniqlist= list(set(text_list))
len(text_uniqlist) #6832 
```

続いて、単語をIDに変換し、学習データを作成します。

```python
#坊ちゃんに含まれる単語をID化する
ID2word = dict((i,c) for i, c in enumerate(text_uniqlist))
word2ID = dict((c,i) for i, c in enumerate(text_uniqlist))


#学習データの作成
SEQLEN = 5

xtrain_chars = []
ytrain_chars = []

for i in range( 0,len(text_list) - SEQLEN):
    xtrain_chars.append(text_list[i:i+SEQLEN])  #学習対象のデータ一覧。
    ytrain_chars.append(text_list[i+SEQLEN])  #予測対象のこと。

#ONE-HOT化
num_word = len(text_uniqlist)#6832単語
Xtrain = np.zeros((len(xtrain_chars),SEQLEN,num_word),dtype=np.bool) # len(xtrain_chars):60906 SEQLEN:5, num_word:6832
Ytrain = np.zeros((len(xtrain_chars),num_word),dtype=np.bool) # len(xtrain_chars):60906,  num_word:6832

for i , train_char in enumerate(xtrain_chars):
    for j , ch in enumerate(train_char):
        Xtrain[i,j,word2ID[ch]] = 1
        Ytrain[i,word2ID[ytrain_chars[i]]] = 1

```

len(xtrain_chars)(=60906
) * SEQLEN(=5) * len(text_uniqlist) (=6832)の大きさでかつ非常に疎な行列となるが、
このままRNNで学習させてみる。


```python
EPISODE = 10
HIDDEN_SIZE = 128

#学習モデルの構築
model = Sequential()
model.add (SimpleRNN( HIDDEN_SIZE , return_sequences = False , input_shape = ( SEQLEN , num_word ),unroll = True ))
model.add ( Dense(num_word) )
model.add ( Activation("softmax"))
model.compile( loss = "categorical_crossentropy" , optimizer = "rmsprop" )

for epoch in range(EPISODE):
    model.fit(Xtrain,Ytrain)

```

学習済モデルで予測します。

```python
#予測
PREDS_EPISODE = 60

TESTLEN = 5
x_test = np.zeros((1, TESTLEN, num_word),dtype=np.bool)
#print(x_test)
test_index = np.random.randint(len(xtrain_chars))
test_chars = text_list[test_index:test_index+TESTLEN] 
print(test_chars)


for i in range (PREDS_EPISODE):
    for i ,ch in enumerate(test_chars):        
        x_test[0, i , word2ID[ch]]=1
        #print(x_test)
    pred = model.predict(x_test,verbose=0)[0]
    predword = ID2word[np.argmax(pred)]
    del test_chars[0]
    print(predword,end="")
    test_chars.append(predword)
```   

['う', 'と', '論断', 'し', 'た']
。。がおれはははははははははははははははははははははははははははははははははははははははははははははははははははははははは


すごい文章ができてしまった。
同じ語句が常に選択されている。


まずは、さわりでざっと作ってしまったので、
何が原因なのか究明をしていきます。
後、単語の分散表現やRNN以外の手法を試してみる予定。