Edited at

【Keras入門(5)】単純なRNNモデル定義

入門者に向けてKerasを使ったRNN(Recurrentニューラルネットワーク)の初歩を解説します。RNNは時系列データの予測やNLP(自然言語処理)などに強く、使いどころが多い便利な手法です。

Google Colaboratoryを使っているのでローカルでの環境準備すらしていません。Google Colaboratoryについては「Google Colaboratory概要と使用手順(TensorFlowもGPUも使える)」の記事を参照ください。

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

- 【Keras入門(1)】単純なディープラーニングモデル定義

- 【Keras入門(2)】訓練モデル保存(KerasモデルとSavedModel)

- 【Keras入門(3)】TensorBoardで見える化

- 【Keras入門(4)】Kerasの評価関数(Metrics)

- 【Keras入門(5)】単純なRNNモデル定義 <- 本記事

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

- 【Keras入門(7)】単純なSeq2Seqモデル定義


使ったPythonパッケージ

Google Colaboratoryでインストール済の以下のパッケージとバージョンを使っています。KerasはTensorFlowに統合されているものを使っているので、ピュアなKerasは使っていません。Pythonは3.6です。


  • tensorflow: 1.14.0

  • Numpy: 1.16.4

  • matplotlib: 3.0.3


処理概要

時系列データ予測として、正弦関数(sine)の値を予測します。

正弦関数は、こんなウネウネした値が変わっていくやつです。

image.png

以下の2種類の配列を作って、Kerasを使ったディープラーニングで予測モデルを作ります。

種類
Shape
データ

説明変数(配列x_train)
10 X 40
xが-4.9から4.9までの0.2間隔の正弦関数の値

目的変数(配列y_train)
10 X 40
説明変数の1ずれた値

言葉だとわかりにくいですが、表とグラフにするとこんな感じ。目的変数の次の値を予測しています(つまり時系列データ予測)。そして、2つ目のデータセットでは、1つ目のデータセットから1つずつずらしています。!

50.Keras_RNN_list01.JPG

そして、訓練時のRNNへのデータの渡し方は下記のような形です。

51.Keras_RNN_Overview01.JPG

でもって、予測時は下記のように予測結果を次の予測データ元へ渡します。モデルが予測した値を再度使うあたりが再帰(Recurrent)たる所以ですね。

image.png


処理プログラム

プログラム全体はGitHubを参照ください。


1. ライブラリインポート

今回はmatplotlibとnumpyとtensorflowに統合されているkerasを使います。ピュアなkerasでも問題なく、インポート元を変えるだけです。

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. 前処理


2.1. 正弦関数配列作成

-4.9から4.9までの50要素の等差数列(0.2間隔)の配列(x_sin)を作成し、対応する正弦関数の値の配列(y_sin)も

作成します。

# -4.9から4.9までの50要素の等差数列(0.2間隔)

x_sin = np.linspace(-4.9, 4.9)
y_sin = np.sin(x_sin)
plt.plot(x_sin, y_sin)
plt.show()

matplotlibでグラフ出力するとこんなです。

image.png


2.2. 説明変数(x_train)と目的変数(y_train)作成

RNNに流すための形に整形します。

NUM_RNN = 10  # 1時系列のデータ数

NUM_DATA = len(x_sin) - NUM_RNN # 今回は40(=50-10)
x = []
y = []

for i in range(NUM_DATA):
x.append(y_sin[i:i+NUM_RNN]) # 説明変数
y.append(y_sin[i+1:i+NUM_RNN+1]) # 正解データなので1ずらした値

else:
x_train = np.array(x).reshape(NUM_DATA, NUM_RNN, 1) # 入力を(サンプル数、時系列の数、入力層のニューロン数)にする
y_train = np.array(y).reshape(NUM_DATA, NUM_RNN, 1) # 説明変数(x_train)と同様のshape


3. モデル定義

記事「【Keras入門(1)】単純なディープラーニングモデル定義」のときと違い、simpleRNNを使っています。実際にはLSTMやGRUなどを使うことが多いかと思いますが、今回はsimpleRNNで十分な精度が出ます。また、LSTMやGRUを使う場合も呼び出し方はほとんど変わりません。

NUM_DIM = 8  # 中間層の次元数


model = Sequential()

# return_sequenceがTrueなので全RNN層が出力を返す(Falseだと最後のRNN層のみが出力を返す)
model.add(SimpleRNN(NUM_DIM, input_shape=(NUM_RNN, 1), return_sequences=True))
model.add(Dense(1, activation="linear")) #全結合層
model.compile(loss="mean_squared_error", optimizer="sgd")
model.summary()

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

_________________________________________________________________

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


4. 訓練実行

fit関数を使って訓練実行です。20epoch程度でそこそこいい精度が出ます。

history = model.fit(x_train, y_train, epochs=20, batch_size=8)

# Lossをグラフ表示
loss = history.history['loss']
plt.plot(np.arange(len(loss)), loss) # np.arangeはlossの連番数列を生成(今回はepoch数の0から19)
plt.show()

image.png


5. テスト

最後にテストです。


5.1. テストデータ作成

訓練データの最初の10件をテストデータとします。

# x[0]は最初の入力(時系列10個の数)。reshape(-1)で一次元のベクトルにする。

x_test = x_train[0].reshape(-1)


4.2. 訓練済モデルを使ったテストデータの答え合わせ

predict関数を使ってテストデータから予測値を出力し、さらに次の予測の元データとします。

# データ数(40回)ループ

for i in range(NUM_DATA):
y_pred = model.predict(x_test[-NUM_RNN:].reshape(1, NUM_RNN, 1)) # 直近データ(最後から10要素)を使って予測
x_test = np.append(x_test, y_pred[0][NUM_RNN-1][0]) # 出力結果をx_testに追加(n_rnn-1が10番目を意味している)

# 最初の10要素は完全に同じ
plt.plot(x_sin, y_sin, label="Training data")
plt.plot(x_sin, x_test, label="Predicted")
plt.legend()
plt.show()

予測値と訓練データを比較でグラフ表示してみます。-4.9から-2.9までの値は完全に同じです。それ以降は、微妙にずれてはいますが、それっぽい値が出ています。

image.png


簡単な変更でモデルを複雑に変更

simpleRNNではなく、実務上はLSTMを使うことが多いはずです。

その場合、モデルの層をこんな感じにするだけです。

GPU向けのCuDNNLSTMもあります(確かモデル間の互換性がなかったような気がします(未確認))。

from tensorflow.keras.models import Sequential

from tensorflow.keras.layers import Dense, LSTM

model.add(LSTM(NUM_DIM, input_shape=(NUM_RNN, 1), return_sequences=True))

GRUだとこう。GPU向けのCuDNNGRUもあります(確かモデル間の互換性がなかったような気がします(未確認))。

from tensorflow.keras.models import Sequential

from tensorflow.keras.layers import Dense, GRU

model.add(GRU(NUM_DIM, input_shape=(NUM_RNN, 1), return_sequences=True))

双方向にしたい場合はBidirectionalを使います。

from tensorflow.keras.models import Sequential

from tensorflow.keras.layers import Dense, simpleRNN, Bidirectional

model.add(Bidirectional(SimpleRNN(NUM_DIM, return_sequences=True), input_shape=(NUM_RNN, 1)))

複数層に重ねることもできます。

from tensorflow.keras.models import Sequential

from tensorflow.keras.layers import Dense, simpleRNN

model.add(SimpleRNN(NUM_DIM, input_shape=(NUM_RNN, 1), return_sequences=True))
model.add(SimpleRNN(NUM_DIM, input_shape=(NUM_RNN, 1), return_sequences=True))