【Keras入門(6)】単純なRNNモデル定義(最終出力のみ使用)

前回記事[「【Keras入門(5)】単純なRNNモデル定義」](https://qiita.com/FukuharaYohei/items/25de4a0faf634ad34efc)では、RNNを使って1つの入力値に対して次の値を予測しました。今回は、10個の入力値に対して1つの出力をするモデルにします。
これは、文章に対するネガポジ予測(ネガティブ/ポジティブ)や、文書の分類などに使えます。

以下のシリーズにしています。

-[ 【Keras入門(1)】単純なディープラーニングモデル定義](https://qiita.com/FukuharaYohei/items/579cc0184885b4ba75df)
- [【Keras入門(2)】訓練モデル保存(KerasモデルとSavedModel)](https://qiita.com/FukuharaYohei/items/ac6333391b8abda94bdc)
- [【Keras入門(3)】TensorBoardで見える化](https://qiita.com/FukuharaYohei/items/fe73cdb63ee82a7ea318)
- [【Keras入門(4)】Kerasの評価関数(Metrics)](https://qiita.com/FukuharaYohei/items/f7df70b984a4c7a53d58)
- [【Keras入門(5)】単純なRNNモデル定義](https://qiita.com/FukuharaYohei/items/25de4a0faf634ad34efc)
- 【Keras入門(6)】単純なRNNモデル定義(最終出力のみ使用) <- 本記事
- [【Keras入門(7)】単純なSeq2Seqモデル定義](https://qiita.com/FukuharaYohei/items/27cd247342a0f7006511)

## 使ったPythonパッケージ
[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)でインストール済の以下のパッケージとバージョンを使っています。KerasはTensorFlowに統合されているものを使っているので、ピュアなKerasは使っていません。Pythonは3.6です。

- tensorflow: 1.14.0
- Numpy: 1.16.4
- matplotlib: 3.0.3


# 処理概要
等差数列が増加か減少かを判断します。

|1st|2nd|3rd|4th|5th|6th|7th|8th|9th|10th|等差数列|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|0|1|2|3|4|5|6|7|8|9|増加(0)|
|0|-1|-2|-3|-4|-5|-6|-7|-8|-9|減少(0)|

# 処理プログラム
プログラム全体は[GitHub](https://github.com/YoheiFukuhara/keras-for-beginner/blob/master/RNN02_RNN_no_seq.ipynb)を参照ください。

## 1. ライブラリインポート
前回から追加して乱数を発生するrandomも読み込んでいます。

```python
from random import randint

import numpy as np
import matplotlib.pyplot as plt

# TensorFlowに統合されたKerasを使用
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, SimpleRNN
```

##2. 前処理 
等差数列の配列を増加・減少パターンを交互に作っています。

```python
NUM_RNN = 10
NUM_DATA = 200

# 空の器を作成
x_train = np.empty((0, NUM_RNN))
y_train = np.empty((0, 1))

for i in range(NUM_DATA):
    num_random = randint(-20, 20)
    if i % 2 == 1:  # 奇数の場合
        x_train = np.append(x_train, np.linspace(num_random, num_random+NUM_RNN-1, num=NUM_RNN).reshape(1, NUM_RNN), axis=0)
        y_train = np.append(y_train, np.zeros(1).reshape(1, 1), axis=0)
    else: # 偶数の場合
        x_train = np.append(x_train, np.linspace(num_random, num_random-NUM_RNN+1, num=NUM_RNN).reshape(1, NUM_RNN), axis=0)
        y_train = np.append(y_train, np.ones(1).reshape(1, 1), axis=0)

x_train = x_train.reshape(NUM_DATA, NUM_RNN, 1)
y_train = y_train.reshape(NUM_DATA, 1)
```

## 3. モデル定義
今回のRNNモデルは前回と異なり、最終出力のみを使います。そのために下図のようなモデルです。
![60.Keras_RNN_Overview01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/c1ed2c46-cd56-2743-1fb9-46c3b16fb326.jpeg)

参考として、前回のモデル(最終出力以外も使用)はこんなでした。
<img wieth="401" alt="51.Keras_RNN_Overview01.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/dbe9a031-e90a-d7ba-c395-cbac6da51c22.jpeg">

[SimpleRNN関数](https://keras.io/ja/layers/recurrent/#simplernn)の`return_sequences`の値をFalseにして使わないようにします。また、最後の全結合層は1次元にして二値分類です。

```python
NUM_DIM = 16  # 中間層の次元数

model = Sequential()

# return_sequenceがFalseなので最後のRNN層のみが出力を返す
model.add(SimpleRNN(NUM_DIM, batch_input_shape=(None, NUM_RNN, 1), return_sequences=False))
model.add(Dense(1, activation='sigmoid'))  #全結合層
model.compile(loss='binary_crossentropy', optimizer='adam')

model.summary()
```

`summary`関数で以下のようなモデルサマリを出してくれます。

```bash
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
simple_rnn (SimpleRNN)       (None, 8)                 80        
_________________________________________________________________
dense (Dense)                (None, 1)                 9         
=================================================================
Total params: 89
Trainable params: 89
Non-trainable params: 0
_________________________________________________________________
```

##4. 訓練実行
[fit関数](https://keras.io/ja/models/model/#fit)を使って訓練実行です。30epoch程度でそこそこいい精度が出ます。

```python
history = model.fit(x_train, y_train, epochs=30, batch_size=8)
loss = history.history['loss']

plt.plot(np.arange(len(loss)), loss) # np.arangeはlossの連番数列を生成(今回はepoch数の0から29)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/33765070-329b-0399-c822-4bd1c49e9486.png)

## 5. テスト
最後にテストです。

## 5.1. テスト実行
訓練データの最初の10件をテストデータとします。
[predict関数](https://keras.io/ja/models/model/#predict)を使ってテストデータから予測値を出力します。

```python
# データ数(10回)ループ
for i in range(10):
    y_pred = model.predict(x_train[i].reshape(1, NUM_RNN, 1))
    print(y_pred[0], ':', x_train[i].reshape(NUM_RNN))
```

見た限り全問正解です(4件だけ抜粋)。

```
[0.9673256] : [ -6.  -7.  -8.  -9. -10. -11. -12. -13. -14. -15.]
[0.01308651] : [-6. -5. -4. -3. -2. -1.  0.  1.  2.  3.]
[0.9779032] : [12. 11. 10.  9.  8.  7.  6.  5.  4.  3.]
[0.01788531] : [-2. -1.  0.  1.  2.  3.  4.  5.  6.  7.]
```