文章生成で失敗した話

## はじめに
Kerasを使ってseq2seqを構築し、文章生成しようとしたのだが、
いろいろやっても失敗したので、一旦まとめ。
勘違いしてるところやアドバイスがあれば教えて頂きたいです。

## 手法
文章生成には、**seq2seq**を使います。
seq2seqとは、翻訳やチャットボットに使われるニューラルネットワークで、
例えば、「I have a pen.」という文章を入力に与えると、「私はペンを持っています。」のような文章が出力されます。

seq2seqの構造としては、エンコーダーとデコーダーと呼ばれる2つのネットワークを持ち、それぞれRNNやLSTMを用いて構築されます。文章をエンコーダーに与え、ベクトル化し、そのベクトルをデコーダーに与えることで文章生成します。

論文 : http://papers.nips.cc/paper/5346-sequence-to-sequence-learning-with-neural
## 実装
Keras(2.0.8)を使います。
Pythonのバージョンは、```Python 3.5.4 :: Anaconda 2.5.0 (x86_64)```です。

### モデル
モデルは、**LSTM**を使ってseq2seqを構築します。

```
Ecoder長、Decoder長はそれぞれ : 40次元
エンコードされたベクトル : 500次元
損失関数 : 平均自乗誤差
最適化アルゴリズム : Adam
```

```
latent_dim = 1000
inputs = Input(shape=(40, 500)
encoded = LSTM(latent_dim)(inputs)
decoded = RepeatVector(40)(encoded)
decoded = LSTM(500, return_sequences=True)(decoded)
self.sequence_autoencoder = Model(inputs, decoded)
self.sequence_autoencoder.compile(loss='mean_squared_error', optimizer='Adam')
```

### 学習データ
学習データには、青空文庫にある江戸川乱歩の小説、44個を用いました。
http://www.aozora.gr.jp/index_pages/person1779.html

全ての文章の中からランダムに1つ文章を選択します。選択された文章を入力に、選択された文章に続く文章が出力になるようにニューラルネットワークに与えます。ただし、長すぎる文章では、学習精度が下がる可能性があるため、40単語以下の文章のみを選択するようにしました。また、seq2seqの入力に与える文章は、反転させた方が良いらしく、反転させたものを用いています。


### 文章長の調節
選択した文章と選択した文章に続く文章は、文章長が異なることが多いです。ニューラルネットに与える場合は、次元数(文章長)を揃える必要があります。mecabを用いて文章を単語ごとに分割し、文章長が40単語となるように、40単語に満たない文章は、今回の学習でEOSを表す[。]で埋める処理を行なっています。
(この処理があってるのかは不明です。良い方法があれば教えて頂きたいです。)


### 単語のベクトル化
文章をニューラルネットワークに与えるために、ベクトル化する必要があります。そのため、単語をベクトル化し、その単語ベクトルを繋げ、文章をベクトルとして表現します。

単語のベクトル化には、単語をone-hotベクトル[*1](https://qiita.com/iss-f/items/bbd3456b1a82b5d56e7c#one-hot%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB)を作る手法が一般的かと思います。
しかし、今回は**word2vec**を用いて、単語を500次元のベクトルにします。

word2vecを使う理由は2つあります。
1つめの理由は、ニューラルネットワークの構造を複雑にしないためです。one-hotベクトルは、vocabularyが増えるにつれ、ベクトルの次元数が増えてしまいます。つまり、vocabularyが増えると、ニューラルネットワークの構造も複雑になるということです。

2つめの理由は、vocabularyの動的な変更に対しニューラルネットワークの再学習の必要がないからです。vocabularyの変更により、入力次元が変わると、その都度ニューラルネットワークの重みの再学習が必要となります。人の場合そのようなことを行なっていることはせず、動的にvocabularyを増やしていると思います。
(入力層の次元のみ動的に変化させ、そのほかの層の重みは共有することもできそうですが、そういった手法があるのかどうかは不明です。)

これより、vocabularyの数に依存しない次元のベクトルを作るため、word2vecを用いて単語をベクトル化します。

ちなみに、word2vecのもつvocabularyは、19002個となりました。


## 学習
ニューラルネットワークに与えるデータの次元は、文章長が40、単語ベクトルが500次元のため、入力データ(X_train)、教師データ(Y_train)はそれぞれ[batch_size, 40, 500]となります。

batch_sizeは2500としてます。epoch数は5となります。この学習を1学習として、5回の学習を行います。batch_sizeを大きくしすぎるとメモリを食い過ぎるからです。これにより、全ての学習において、ランダムに選択した12500の文章を入力に与えたことになります(重複はあると思いますが)。


```
self.sequence_autoencoder.fit(X_train, Y_train,
	shuffle=True,
	nb_epoch=5,
    batch_size=self.batch_size,
	validation_split=0.1,
	verbose=1)
```

今回は、validation_splitを0.1としているので、学習に与えられたデータは
```Train on 2250 samples, validate on 250 samples```
となります。
最後の学習である5回目は、次のようになります。

```
Epoch 1/5
2250/2250 [==============================] - 164s - loss: 0.0782 - val_loss: 0.0730
Epoch 2/5
2250/2250 [==============================] - 159s - loss: 0.0769 - val_loss: 0.0726
Epoch 3/5
2250/2250 [==============================] - 161s - loss: 0.0765 - val_loss: 0.0719
Epoch 4/5
2250/2250 [==============================] - 156s - loss: 0.0757 - val_loss: 0.0711
Epoch 5/5
2250/2250 [==============================] - 152s - loss: 0.0750 - val_loss: 0.0705
```

過学習はしてなさそうですが、ちゃんと収束してるのかはわかりません。


## 学習データに対するテスト
全文章から1つランダムに文章を選択したし、テストデータとして与えます。

```
>> 今や「黒トカゲ」は悪魔の本性を暴露しい口調で指図を与えた。
斬る,斬る,斬る,斬る,斬る,斬る,斬る,斬る,斬る,考えつく,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,

>> 尾行は失敗におわりました。
斬る,斬る,斬る,斬る,斬る,斬る,斬る,斬る,斬る,考えつく,考えつく,考えつく,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,

>> その道は、いくつもまがりかどがありましたが、やがて、ドアのひらく音がして、一つの部屋にはいりました。
よじのぼる,よじのぼる,よじのぼる,よじのぼる,斬る,斬る,斬る,斬る,斬る,斬る,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,

>> だれかが、とんきょうな声で、さけびました。
斬る,斬る,斬る,斬る,斬る,斬る,斬る,斬る,斬る,考えつく,考えつく,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,がんばら,
```

同じ言葉の繰り返しになってしまいました.....
似たような単語の繰り返しで、単語のバリエーションが少なすぎます。
最後の学習の結果を引きずっているのでしょうか???
また、EOSを表す[。]が一つも出てきていません。


## 未知データに対するテスト
こちらでてきとうに作った文章をテストデータとして入力してみました。

```
>>  探偵が急に現れました。
すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた,すめた


>>  小林少年が静かにあとをつけました。
よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞よえこ聞こつみく

>>  彼は走った。
にうそんねんざにうそんねんざにうそんねんざにうそんねんざにうそんねんざにうそんねんざにうそんねんざくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはくしかずはうがねうがねうがねうがねうがね
```

こちらも、同じ言葉の繰り返しです.....
[こんにちわ]などの言葉を入力すると、word2vecの辞書に登録されてないと怒られたため、
江戸川乱歩の小説に出てきそうな言葉で入力を作るのがむずかしかったです。

江戸川乱歩は、大正から昭和期に活躍された小説家なので、
mecabによる単語の分割がうまくできてなかったのでしょうか...?

<!-- ### word2vecの出力するベクトルと予測ベクトルの比較 -->


## 失敗した原因の調査項目
 - mecabによる単語分割はうまくできていたか
 - ネットワークの複雑さに対して、学習データは十分な量がだったか
 - LSTMの損失関数、最適化アルゴリズムは適切だったか
 - word2vecの出力するベクトルとLSTMの出力するベクトルの比較
 - LSTMの収束確認
 - LSTMの過学習確認
 - seq2seqの最近のアーキテクチャの適応

くらいか?

## まとめ
seq2seqを使って文章生成を行いました。
生成された文章は、同じ単語が連続し、読めたものではありませんでした。
原因解明のため考察を行なっていきたいと思います。

2017/11/4更新
次回：https://qiita.com/iss-f/items/ce423ab83c40e9eebedd



## one-hotベクトル
one-hotベクトルとは、単語をベクトル表現する手法のひとつです。
例えば、
[I have a pen.]
[I have an apple.]
という2の文章があるとします。文章の出現単語のユニークな集合をとります。
[I have a an pen apple]
この集合をvocabularyと呼びます。
集合を用いてそれぞれの単語を、ベクトルにより表現します。
例えば、
I → [1, 0, 0, 0, 0, 0]
pen → [0, 0, 0, 0, 1, 0]
となります。