TensorFlow・Keras を使って回帰問題を学習する

前回まで学習してきた、回帰問題と分類問題を TensorFlow・Keras を用いて学習していきたいと思います。 まずは、回帰問題から学習します。設定や学習する問題は、前回の回帰問題とできるだけ同じになるようにします。

学習する問題

前回の回帰問題と同じく y = sin(x) という、いわゆるサインカーブを学習します。入力 x に対して、出力 y の値が sin(x) に近いほど良いということになります。

設定

入力層・中間層・出力層の3層からなるネットワークにします。各種設定は下記の通りです。

  • 活性化関数(中間層):シグモイド関数
  • 活性化関数(出力層):恒等関数
  • 誤差:二乗和誤差
  • 学習:オンライン学習

環境

TensorFlow の Docker イメージ tensorflow/tensorflow:latest-jupyter でコンテナを立ち上げ、jupyter notebook 上で動作させています。そのため、下記に紹介するソースの中で、matplotlib によるグラフ表示は、jupyter notebook でないと適切に表示されないと思われるので、ご注意ください。

プログラムソース

まずは、ソースの全てを貼ります。

import copy
import os
import shutil

import matplotlib.pyplot as plt
import numpy as np
from keras.callbacks import ModelCheckpoint
from keras.layers import Dense
from keras.models import Sequential, load_model
%matplotlib inline

def prot_result(ordered_x, ordered_y, predicts):
    """予測結果を描画する

    :param ordered_x: 順番に並んだx座標のデータ
    :param ordered_y: 順番に並んだy座標のデータ
    :param predicts: ニューラルネットワークで計算されたy座標のデータ
    """
    plt.plot(ordered_x, ordered_y, color="green", label="sin(x)")
    plt.plot(ordered_x, predicts, color="blue", linestyle="--", label="predicts")
    plt.legend()


# 設定
train_num = 1000    # 訓練データ数
batch_size = 1      # バッチサイズ
epochs = 20         # エポック数
neuron_num = 15     # 中間層のニューロン数
model_dir = "/root/practice/model/"

# ディレクトリを空にする
shutil.rmtree(model_dir)
os.makedirs(model_dir, exist_ok=True)

# 訓練・教師データ作成
ordered_x = np.linspace(-np.pi, np.pi, train_num)
ordered_y = np.sin(ordered_x)

trainings = copy.deepcopy(ordered_x)
np.random.shuffle(trainings)
teachings = np.sin(trainings)

# モデル作成
model = Sequential()
model.add(Dense(neuron_num, activation="sigmoid", input_shape=(1,)))
model.add(Dense(1))
model.compile(optimizer="SGD", loss="mse")

model_file = os.path.join(model_dir, "{epoch:03d}.hdf5")
checkpoint = ModelCheckpoint(model_file, verbose=0)

# 学習
history = model.fit(
    trainings,
    teachings,
    batch_size=batch_size,
    epochs=epochs,
    verbose=1,
    callbacks=[checkpoint],
)

# 結果表示
plt.plot(history.history["loss"])

predicts = model.predict(ordered_x, 1, verbose=1)
prot_result(ordered_x, ordered_y, predicts)

一つ一つ説明します。

import copy
import os
import shutil

import matplotlib.pyplot as plt
import numpy as np
from keras.callbacks import ModelCheckpoint
from keras.layers import Dense
from keras.models import Sequential, load_model
%matplotlib inline

必要なライブラリのインポートと、matplotlib のグラフをインラインで表示させるための記述です。

def prot_result(ordered_x, ordered_y, predicts):
    """予測結果を描画する

    :param ordered_x: 順番に並んだx座標のデータ
    :param ordered_y: 順番に並んだy座標のデータ
    :param predicts: ニューラルネットワークで計算されたy座標のデータ
    """
    plt.plot(ordered_x, ordered_y, color="green", label="sin(x)")
    plt.plot(ordered_x, predicts, color="blue", linestyle="--", label="predicts")
    plt.legend()

予測結果をグラフで描画する関数を先に定義しておきます。引数 predicts は構築したニューラルネットワークで予測されたy座標のデータになります。正解のサインカーブと合わせて描画します。

# 設定
train_num = 1000    # 訓練データ数
batch_size = 1      # バッチサイズ
epochs = 20         # エポック数
neuron_num = 15     # 中間層のニューロン数
model_dir = "/root/practice/model/" # 保存したモデルファイルを格納するディレクトリ

# ディレクトリを空にする
shutil.rmtree(model_dir)
os.makedirs(model_dir, exist_ok=True)

各種設定です。前回の回帰問題と、なるべく同じになるよう設定します。訓練データ数を1000、オンライン学習なのでバッチサイズを1、中間層のニューロン数を15とします。総学習回数を同じにするためにエポック数は20にします。(学習回数は訓練データ数×エポック数で20000回となる)モデルファイルを保存するディレクトリを設定し、中身を空にしておきます。

# 訓練・教師データ作成
ordered_x = np.linspace(-np.pi, np.pi, train_num)
ordered_y = np.sin(ordered_x)

trainings = copy.deepcopy(ordered_x)
np.random.shuffle(trainings)
teachings = np.sin(trainings)

訓練・教師データを作成します。作り方は前回の回帰問題と同じです。今回は numpy を使っているので、前回よりもかなり簡素に書けています。

# モデル作成
model = Sequential()
model.add(Dense(neuron_num, activation="sigmoid", input_shape=(1,)))    // (1)
model.add(Dense(1))     // (2)
model.compile(optimizer="SGD", loss="mse")  // (3)

Sequential を利用してモデルを生成し、add メソッドで層を追加していきます。 まず(1)は中間層と入力層に当たる部分です。Dense は全結合の層で、第一引数はニューロン数、引数 activation は活性化関数、input_shape は入力の形状を表します。今回はx座標の1つしか入力がないので、(1,) となります。(2) は出力層に当たる部分です。出力はy座標の一つしか無いので、第一引数のニューロン数は1、活性化関数は恒等関数なので特に指定していません。(3) の compile メソッドでモデルの設定をします。引数 optimizer は最適化アルゴリズムで、確率的勾配降下法(Stochastic Gradient Descent)を設定します。loss は損失関数で、平均二乗誤差(Mean Squared Error)を設定します。

model_file = os.path.join(model_dir, "{epoch:03d}.hdf5")
checkpoint = ModelCheckpoint(model_file, verbose=0)

# 学習
history = model.fit(
    trainings,
    teachings,
    batch_size=batch_size,
    epochs=epochs,
    verbose=1,    
    callbacks=[checkpoint]
)

学習の前に callback の設定をします。ModelCheckpoint はある頻度で、モデルを保存するためのコールバックです。デフォルトで1エポック終了毎に保存されます。コンストラクタの第一引数は保存するファイルパスで、{epoch:03d} は3桁(0埋め)エポック番号になります。verbose は0に設定すると、コールバックが動作してもメッセージを表示しません。

fit メソッドで学習を実行します。第一引数は訓練データ、第二引数は教師データになります。引数 batch_size はバッチサイズ、epochs はエポック数、callbacks はコールバックをそれぞれ指定します。verbose を1に設定すると、学習の進捗状況を表示します。

# 結果表示
plt.plot(history.history["loss"])

predicts = model.predict(ordered_x, 1, verbose=1)
prot_result(ordered_x, ordered_y, predicts)

結果を表示します。history の中に結果が入っているので、それを利用します。history.history["loss"] はエポック毎の損失の推移になります。 predict メソッドは、学習後のニューラルネットワークを用いて出力を予測します。第一引数は入力、第二引数はバッチサイズ、verbose は進捗状況を表示するかどうかです。得られた予測結果 predictsprot_result に渡して、予測結果がどれだけ正解に近いのかを描画します。

結果

結果は下記の通りになりました。まずは誤差の推移です。 縦軸が誤差、横軸がエポックです。前回よりも誤差の下がり方が緩やかです。

続いて、学習後のニューラルネットワークで予測した結果です。 緑の実線が正解のサインカーブ、青の点線がニューラルネットワークで予測した結果です。こちらは20000回学習後のニューラルネットワークで予測した結果ですが、残念ながら芳しくないようです。

では、設定を少し変更して、学習し直してみます。

# 設定
train_num = 10000    # 訓練データ数
batch_size = 10      # バッチサイズ
epochs = 30          # エポック数
neuron_num = 15      # 中間層のニューロン数
model_dir = "/root/practice/model/"

訓練データ数を10000、バッチサイズを10、エポック数を30(1エポックの学習回数は、訓練データ数10000 ÷ バッチサイズ10 = 1000回、総学習回数は、1エポックの学習回数1000 × エポック数30 = 30000回)にします。

# モデル作成
model = Sequential()
model.add(Dense(neuron_num, activation='sigmoid', input_shape=(1,)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

最適化アルゴリズム optimizer を adam に変更します。

この設定で再度学習した結果が下記になります。まずは誤差の推移です。 誤差の減少度合いがやや早くなってますね。

続いて、学習後のニューラルネットワークで予測した結果です。   ニューラルネットワークで予測した結果が、正解のサインカーブとほぼほぼ重なっていることがわかります。

学習途中のニューラルネットワークで予測した結果も表示します。結果を出力するためのソースコードは下記です。

# 途中結果表示用
model_file = os.path.join(model_dir, "001.hdf5")
target_model = load_model(model_file)
target_predicts = target_model.predict(ordered_x, 1, verbose=1)
prot_result(ordered_x, ordered_y, target_predicts)

上から順に、1エポック終了後、10エポック終了後、20エポック終了後のニューラルネットワークが予測した結果になります。   1エポック終了後は、まだまだ学習が足りず正解のサインカーブからかなり離れていますが、学習が進むにつれて正解のサインカーブに近づいているのがわかります。20エポック終了後だと、正解のサインカーブに大分近くなっていることがわかります。

次回は前回学習した分類問題について、TensorFlow・Keras を使って学習していきます。