読者です 読者をやめる 読者になる 読者になる

今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

TensorBoardで学習の過程を可視化する ~株価予測(3)~

今回やること

前回までに用意したデータ・ニューラルネットでは、日経平均の終値が前日より上がるか下がるかを60%後半台の精度で予測できていました。
今回は、ネットワークが最適化されていく過程をTensorBoardを使って可視化して確認してみたいと思います。

TensorBoardとは?

TensorBoardとは、TensorFlowで構築したグラフやグラフの様々な値を可視化するために用意されたツールです。
日本語でもどのようなことができるか解説している記事がたくさんあります。
TensorFlowとTensorBoardでニューラルネットワークを可視化 - Qiita
TensorFlow : How To : TensorBoard: 学習を視覚化する – TensorFlow

Bash on Windows を使ってWindows 10環境でも気軽に試せるようになったので、前回までのグラフの様々な値を可視化して観察してみたいと思います。

損失関数を可視化してみる

まずは手始めに、最適化の対象である損失関数の値が、学習のイテレーションを回すごとにどう変化しているかを可視化してみたいと思います。

ある値を可視化したいときには、サマリーと呼ばれる、その値についてまとめたデータをログファイルに書き出します。次に、tensorboardというコマンドを使用してログファイルを開くと、その値が自動的に可視化されるようです。

可視化したい値がスカラー値の場合は、tf.scalar_summaryという関数でスカラー値をサマリーに追加します。

def training(cost, learning_rate=0.0001):
    tf.scalar_summary("Cross entropy", cost)

    training_op = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
    return training_op

サマリーをマージして、ファイルに書き出します。tf.train.SummaryWriterの第1引数では、ログを出力するディレクトリを指定できます。
今回は, subdirにはタイムスタンプを入れて、毎回異なるフォルダに保存されるようにしています。

summary_op = tf.merge_all_summaries()
summary_writer = tf.train.SummaryWriter("/tmp/pred225_log/" + subdir, sess.graph)

ログファイルは、一つのフォルダに出力するよりは、サブディレクトリを作成してそのサブディレクトリに作成したほうがいいです。
参考:How to "reset" tensorboard data after killing tensorflow instance - Stack Overflow
私も実際少しはまったのですが、同じフォルダにログを出力してしまうと、ログファイルを削除して画面をリフレッシュしても消したはずの可視化データがTensorBoardのプロセス内に残ってしまい、思った通りの可視化が得られなくなります。

実際に観察したい値を書き出す処理は、イテレーションを回している部分で次のように書きます。

if i % 100 == 0:
    summary_str = sess.run(summary_op, feed_dict=dict_data)
    summary_writer.add_summary(summary_str, i)
    summary_writer.flush()

損失関数の可視化を見てみる

上記の変更を加えて再度学習を走らせたあと、下記のコマンドを実行してブラウザでhttp://localhost:6006/を開きます。

tensorboard --logdir=/tmp/pred225_log

(なぜかlocalhost:6006にアクセスしたときに空白のページが表示されることがあるのですが、その場合は何度か更新をしたら正しく表示されるようになりました。。)
すると、Eventタブに下記のようなグラフが表示されました。
f:id:kanohk:20160813232035p:plain

これが欲しかった、損失関数(今回の実装ではクロスエントロピー)のイテレーションごとの変化のグラフになります。
イテレーションを回すごとに、しっかりと値が小さくなっていることが確認できるかと思います。
(そして、イテレーションを増やせばまだまだ値が小さくなりそう?)

グラフ全体の可視化

Graphタブに切り替えてみると、下の図のような、ニューラルネットを可視化したものも簡単に見ることができます。
f:id:kanohk:20160813231418p:plain

隠れ層ありのグラフも可視化してみる

同様に、隠れ層ありのグラフも可視化してみると、次のようなグラフが得られました。(緑色の線が隠れ層ありです)
f:id:kanohk:20160814001121p:plain

損失関数の値が、最初の5000ループほどは急速に小さくなっていく様子が確認できます。その後、値はあまり減少しなくなるのですが、27000回以降でまた少し損失関数が小さくなるスピードが上がっているようにも見えます。
また、隠れ層がない場合と比べると、圧倒的にクロスエントロピーが小さくなっています。

精度も可視化してみる

精度の計算方法

この調子で、イテレーションを追うごとに精度がどのように変化しているかも可視化してみます。
まずは訓練データに対する精度を評価する計算がどう定義されていたかを詳しく見てみます。

correct_prediction = tf.equal(tf.argmax(model, 1), tf.argmax(actual_classes, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

訓練データの個数をN個としたとき、modelやactual_classesはサイズNx2の行列で、ひとつの行が一つの入力データに対応しているようなものでした。例えば、1行目は1つ目の訓練データの日経平均が上がるなら0番目の要素が1、下がるなら1番目の要素が1となる2次元のベクトルとなっています。

tf.argmax(model, 1)とすると、0スタートで1番目の次元(=2次元ベクトルのほう)に沿って最大となる添え字を返してくれます。つまり、tf.argmax(model, 1)が返してくれる値はN次元ベクトルで、そのi番目の要素にはmodelのi行目に入っている2次元ベクトルの要素のうち最大のものの添え字を返してくれます。
要するに、tf.argmax(model, 1)はN個の訓練データに対する今回のモデルの予測結果、actual_classesはN個の訓練データの実際の株価の上下の結果が入っていることになります。(0なら上がる、1なら下がる)

tf.equal(x, y)は単純に2つのベクトルの各要素が等しいかどうかをbool値で返してくれるので、tf.equal(tf.argmax(model, 1), tf.argmax(actual_classes, 1))のtrueの値の割合を計算してあげると、精度が計算できます。2行目でその計算を行っていて、bool値はTrue -> 1, False -> 0とマッピングされることを利用して計算しています。

訓練データに対する精度の可視化

では、早速訓練データに対する精度を可視化してみます。コードの変更は非常に単純で、精度計算部分に1行追加するだけです。

correct_prediction = tf.equal(tf.argmax(model, 1), tf.argmax(actual_classes, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
tf.scalar_summary("Accuracy on Train", accuracy)

すると、このような結果が得られます。
f:id:kanohk:20160815154021p:plain

テストデータに対する精度は?

テストデータに対する精度も、同じ計算式で計算できます。ただし、feature_dataに与えるデータセットを、訓練データからテストデータに変えてあげないといけません。
現在のコードでは、ログを書き出す際のデータセットは次のように指定しています。

train_dict = {
    feature_data: training_predictors_tf.values,
    labels: training_classes_tf.values.reshape(len(training_classes_tf.values), 2)}

summary_str = sess.run(summary_op, feed_dict=train_dict)

この最後の行のfeed_dictにテストデータを与えてあげればよさそうです。つまり、今までsummary_opと一つのオペレーションだったところを、訓練データ用とテストデータ用の2つのオペレーションを用意して走らせてあげる必要がありそうです。

TensorBoardでTrainとTest, Validationを分けて学習状況を把握する - Qiita というページを参考に、書き直したコードが次に通りです。

correct_prediction = tf.equal(tf.argmax(model, 1), tf.argmax(labels, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
accuracy_op_train = tf.scalar_summary("Accuracy on Train", accuracy)
accuracy_op_test = tf.scalar_summary("Accuracy on Test", accuracy)

# summary_op = tf.merge_all_summaries() の代わりに、訓練データとテストデータ用のサマリーを別々にマージする
summary_op_train = tf.merge_summary([cost_summary_op, accuracy_op_train])
summary_op_test = tf.merge_summary([accuracy_op_test])
summary_writer = tf.train.SummaryWriter("/tmp/pred225_log/" + subdir, sess.graph)

# 中略

# サマリーを書き出すところでは、訓練データとテストデータ別々に書き出す
if i % 100 == 0:
    # summary_str = sess.run(summary_op, feed_dict=train_dict)
    summary_str = sess.run(summary_op_train, feed_dict=train_dict)
    summary_writer.add_summary(summary_str, i)
    summary_str = sess.run(summary_op_test, feed_dict=test_dict)
    summary_writer.add_summary(summary_str, i)
    summary_writer.flush()

コード全体はこちらにおいてあります。
pred225/eval.py at 10b19627441a5197d421a2ac2ce43cfa6bebce91 · kanoh-k/pred225 · GitHub


すると、このように欲しかったグラフが得られました。(損失関数のグラフは省略してあります)
f:id:kanohk:20160815164341p:plain

これで、学習の過程が観察できる環境が整いました。
いよいよ次回からは、パラメータやモデルを変化させると学習の過程・結果がどのように変化するかを観察していきたいと思います。

(2016/08/31追記:プログラムのデータを作成する部分にバグがあり、この数値は正しくないものであることが判明しました。手法や記事で直接言及した部分の実装は問題ないはずです。)
(2016/9/20追記:正しい数値での記事を投稿しました(クリックで記事へ移動)

次回記事:
隠れ層の数を調整してみる ~株価予測(4)~ - 今日も窓辺でプログラム

関連記事

TensorBoardを使ったEmbedding(単語ベクトル)の可視化の方法について解説しています。
www.madopro.net