時系列データを予測するAIプログラムを前回紹介しました。
https://nine-num-98.blogspot.com/2020/08/ai-rnn-01.html
公開したプログラムについて解説します。
GitHub公開プログラム
https://github.com/kotetsu99/rnn
GitHub上で公開しているPythonプログラムは以下の4つです。
(1)01-simple_rnn_train.py: SimpleRNN学習プログラム
・SimpleRNN(1層)を用いた学習モデルを生成。
(2)02-lstm_train.py: LSTM学習プログラム
・LSTM(1層)を用いた学習モデルを生成。
(3)03-gru_train.py: GRU学習プログラム
・GRU(1層)を用いた学習モデルを生成。
(4)04-rnn_predict: 予測プログラム
・使用する学習モデルを指定して、予測結果を出力。
(1)(2)(3)がAIの学習モデルを生成するプログラムです。いずれも学習モデルを生成するという点では同じプログラムですが、生成するモデルがシンプルなRNN、LSTM、GRUとそれぞれ異なります。(4)は(1)(2)(3)で生成した学習モデルを指定して、データ予測を行います。
SimpleRNN学習プログラム
01-simple_rnn_train.py が該当プログラムです。入力層、中間層(フィードバックループ付)、出力層のシンプルなRNNモデルを学習させるプログラムです。プログラムの主な箇所、関数について説明していきます。
# 時系列長 n_rnn = 7 # バッチサイズ設定 n_bs = 4 # 中間層ニューロン数設定 n_units = 20 # 出力層ニューロン数設定 n_out = 1 # ドロップアウト率 r_dropout = 0.0 # エポック数 nb_epochs = 1000 # 学習データセットcsvパス設定 csvfile = 'dataset/temp/train.csv'
プログラムの最初のあたりの箇所ですが、ここでは生成する学習モデルのパラメータ設定を行っています。RNNではある程度の長さを持つ、時系列データを入力として、後続するデータの予測を行います。そのため、時系列長の定義が必要となります。
ここでは時系列長(n_rnn)7としています。その他、バッチサイズや中間層ニューロン数などは、通常のニューラルネットワークと同様に定義します。学習用のデータセット(CSV)のファイルパスの設定もここのパートで定義しています。
この後に、メイン関数の定義が続きます。
def main():
# 環境設定(ディスプレイの出力先をlocalhostにする)
os.environ['DISPLAY'] = ':0'
# 時間計測開始
start = time.time()
# コマンド引数確認
if len(sys.argv) != 2:
print('使用法: python3 本ファイル名.py モデルファイル名.h5')
sys.exit()
# 学習モデルファイルパス取得
savefile = sys.argv[1]
# データセットファイル読み込み
data = load_csv(csvfile)
# サンプル数
n_samples = len(data) - n_rnn
# 入力データ定義
x = np.zeros((n_samples, n_rnn))
# 正解データ定義
t = np.zeros((n_samples,))
for i in range(0, n_samples):
x[i] = data[i:i + n_rnn]
# 正解データは1単位時刻後の値
t[i] = data[i + n_rnn]
#print(x)
#print(t)
# Keras向けにxとtを整形
x = x.reshape(n_samples, n_rnn, 1)
t = t.reshape(n_samples, 1)
#print(x.shape)
#print(t.shape)
# データシャッフル
p = np.random.permutation(n_samples)
x = x[p]
t = t[p]
main関数の途中ですが、ここで時系列データを扱うRNN特有の定義が登場しています。
まずはサンプル数(n_samples)の定義内容です。サンプル数は、AIに与える入力データの数にあたります。ここで1つの入力データが、n_rnn長の時系列データ(ベクトル)となります。この入力データは、元の学習データセットの一部を切り出して作られます。
最初のn_rnn個までが一つの入力データ、これを1時刻スライドさせたn_rnn個が次の入力データ…という要領です。これを学習データセットの最後尾の一つ前の時刻までずらしていった数だけ、入力データが存在します。このため、サンプル数の定義が
n_samples = len(data) - n_rnn
とされているわけです。
続いて、入力データ行列xと正解データベクトルtの定義です。xはn_rnn長の行ベクトルをn_sample分まとめた行列となります。tはxのそれぞれの行ベクトルに対応する正解データをまとめたベクトルです。tはxの時系列データに後続する、次の時点の値が入っています。
これらを、機械学習ライブラリKerasで扱うための、テンソルに整形し、データシャッフルを行った後、学習モデルの生成処理に入ります。
# モデル作成(既存モデルがある場合は読み込んで再学習。なければ新規作成)
if os.path.exists(savefile):
print('モデル再学習')
rnn_model = keras.models.load_model(savefile)
else:
print('モデル新規作成')
rnn_model = rnn_model_maker(n_samples, n_out)
# モデル構造の確認
rnn_model.summary()
# モデルの学習
history = rnn_model.fit(x, t, epochs=nb_epochs, validation_split=0.1, batch_size=n_bs, verbose=2)
# 学習結果を保存
rnn_model.save(savefile)
main関数のモデル学習処理に関するパートです。プログラム実行時に引数として渡した、モデルファイルがすでにある場合、そのモデルを読み込んで、再度学習処理を行います。モデルファイルがなければ、新規にモデルを定義して学習処理を行います。モデルファイルを新規に生成する場合、以下のrnn_model_maker関数が呼び出されます。
def rnn_model_maker(n_samples, n_out):
# 3層RNN(リカレントネットワーク)を定義
model = Sequential()
# 中間層(RNN)を定義
model.add(SimpleRNN(units=n_units, input_shape=(n_rnn, 1), dropout=r_dropout, return_sequences=False))
# 出力層を定義(ニューロン数は1個)
model.add(Dense(units=n_out, activation='linear'))
# 回帰学習モデル作成
model.compile(loss='mean_squared_error', optimizer='rmsprop')
# モデルを返す
return model
3層RNNのモデルを定義して返す関数です。中間層にフィードバックループを持たせるだけのシンプルなRNNは、KerasでSimpleRNN というメソッドを使用すれば、簡単に定義できます。
SimpleRNN に渡している各引数ですが、ここで、return_sequences=False としています。これは、n_rnn長の時系列データに対する、各時点での出力を出すか(True)、最後の時点の出力のみを出すか(False)の違いです。
欲しいのは、最後の時点のデータまで入った状態での、出力結果なので、Falseを選択しています。また、compileで定義する最適化アルゴリズムですが、一般的にRNNにおいては、rmsprop というアルゴリズムを選択するのがよいとされています。
LSTM学習プログラム
LSTM学習プログラム(02-lstm_train.py)についてですが、SimpleRNNがLSTMに変わっただけで、ほぼ01-simple_rnn.py と同じ内容です。モデルを定義する関数が、以下に差し替えられた形になります。
def rnn_model_maker(n_samples, n_out):
# 3層RNN(リカレントネットワーク)を定義
model = Sequential()
# 中間層(RNN)を定義
model.add(LSTM(units=n_units, input_shape=(n_rnn, 1), dropout=r_dropout, return_sequences=False))
# 出力層を定義(ニューロン数は1個)
model.add(Dense(units=n_out, activation='linear'))
# 回帰学習モデル作成
model.compile(loss='mean_squared_error', optimizer='rmsprop')
# モデルを返す
return model
KerasのSimpleRNNメソッドが、LSTM層を作るLSTMメソッドに変わっただけです。単純に定義できますが、LSTMの中身はかなり複雑です。以下の書籍等でその仕組みやKerasを用いないPython実装方法が解説されています。
はじめてのディープラーニング2 Pythonで実装する再帰型ニューラルネットワーク,VAE,GAN Kindle版
GRU学習プログラム
GRU学習プログラム(03-gru_train.py)についてもLSTMと同様、以下の関数の中間層がGRUに変わっただけです。
def rnn_model_maker(n_samples, n_out):
# 3層RNN(リカレントネットワーク)を定義
model = Sequential()
# 中間層(RNN)を定義
model.add(GRU(units=n_units, input_shape=(n_rnn, 1), dropout=r_dropout, return_sequences=False))
# 出力層を定義(ニューロン数は1個)
model.add(Dense(units=n_out, activation='linear'))
# 回帰学習モデル作成
model.compile(loss='mean_squared_error', optimizer='rmsprop')
# モデルを返す
return model
KerasのGRUメソッドを使用すれば簡単に定義できます。LSTMほどではないですが、GRUの仕組みも少し複雑な動きをしています。GRUの仕組みについては先に紹介した書籍などで詳細が解説されています。
予測プログラム
SimpeRNN、LSTM、GRUの各モデルと、入力データを読み込んで予測データを生成するプログラム 04-rnn_predict.py について説明します。上から見ていきます。
# 学習データセットcsvパス設定 csvfile = 'dataset/temp/train.csv'
まずここですが、学習データセットが必要となるため、ファイルパスを定義しています。学習データセットは、入力データの標準化と出力データの標準化逆変換に必要です。つづいてmain関数です。
def main():
# 環境設定(ディスプレイの出力先をlocalhostにする)
os.environ['DISPLAY'] = ':0'
# コマンド引数確認
if len(sys.argv) != 3:
print('使用法: python3 本ファイル名.py モデルファイル名.h5 CSVファイル名
.csv')
sys.exit()
# 学習データセットファイル取得
train_data = load_csv(csvfile)
# 学習モデルファイルパス取得
modelfile = sys.argv[1]
# 学習済ファイルを読み込んでmodelを作成
rnn_model = keras.models.load_model(modelfile)
第1引数で渡されている、学習モデルを読み込む処理を記述しています。
# 入力データファイルパス取得
plotfile = sys.argv[2]
# 入力データをロードし変数に格納
df = pd.read_csv(plotfile)
dfv = df.values.astype(np.float64)
n_dfv = dfv.shape[1]
# 特徴量のセットを変数Xに格納
X = dfv[:, np.array(range(0, n_dfv))]
# 入力データ標準化
X = (X - train_data.mean()) / train_data.std()
第2引数で渡された、入力データを読み込んで、学習データセットを用いて、入力データ値を標準化する処理を行っています。
# 時系列長定義
n_rnn = len(X)
# サンプル数定義
n_samples = len(train_data) - n_rnn
# 予測データの格納ベクトルを定義。最初のn_rnn分はXをコピー
yp = X
# 予測結果の取得
for i in range(0, n_samples):
# 入力データに後続するデータを予測
y = rnn_model.predict(yp[-n_rnn:].reshape(1, n_rnn, 1))
#print(y)
# 出力の最後尾の結果を予測データに格納
yp = np.append(yp, y[0][0])
# 予測結果に対し標準化の逆変換
yp = yp * train_data.std() + train_data.mean()
RNN学習済モデルに入力データ(系列長=n_rnn)を渡して、後続データyを予測していきます。後続データyは、forループ内で1個ずつ出力され、その都度予測結果ベクトルypの最後尾に追加されていきます。
ある時点の予測結果は、次のループの入力データの最後尾に回り、新しい入力データ(yp[-n_rnn:])として、次時点の予測に使用されます。こうして、最終的に出来上がったypに対し、標準化の逆変換を行うことで、予測データが完成します。
# データプロット
plot_data(train_data, yp)
# データをCSVに出力
np.savetxt('result.csv', np.c_[train_data, yp], fmt="%.1f", delimiter=',')
最後にデータをグラフにプロットし、予測結果を学習データと合わせて、CSVファイル出力してプログラムは終了です。
以上、RNNプログラムの解説でした。