view_headline機械学習、ディープラーニング

TensorBoardの使い方

TensorBoardはTensorFlowでコーディングした計算グラフを可視化するツールです。 TensorFlowインストール時に一緒にインストールされます。
BASHシェル上から

tensorboard --logdir=<ログディレクトリのパス※後述>

と実行することにより、http://localhost:6006でTensorBoardのWeb画面にアクセスできるようになります。

--logdirのオプションを見て分かる通り、TensorBoardで機械学習モデルを可視化するためには そのモデルのグラフ等のデータが必要になります。

このTensorBoardで利用するデータは下記のTensorFlow APIで生成されるものを利用します。
tf.summary.FileWriter('ログ出力先ディレクトリ', グラフ)

このAPIがTensorFlowのpythonスクリプト内で実行されると、 それまでにグラフに登録されたOperation(変数の演算など)やTensorの計算グラフ全体がTensorBoard上に表示されるようになります。
計算グラフの可視化をするだけであれば上記のtf.summary.FileWriterをTensowFlowのpythonコード中に一行書いてあげればそれでOKです。

computationnal graph image

学習過程のパラメータの変化を追う場合

実際にTensorBoardを使う場合は計算グラフだけではなくトレーニングの過程での誤差の推移等 を確認したい場合が多いと思います。このような場合は下記のAPIを利用し、scalar値として時系列推移グラフを登録します。
summary_op = tf.summary.scalar('loss', loss)
上記のtf.summary.scalarで監視対象のスカラー(0次元テンソル)をオペレーションとしてグラフに登録し、 打刻したい時点でこのsummary_opをセッション上で実行すると都度その時点でのスカラー値がグラフにプロットされ、 TensorBoardのScalarsタブに下記のような 時系列グラフが表示されるようになります。(正確には横軸は時刻ではなく学習のステップになります。)

Summary graph image

その他TensorBoardではヒストグラムや画像、オーディオ等のデータを扱うことができます。ここでは紹介しませんが、 興味ある人はこのTensorBoardのREADMEを確認してください。

サンプル機械学習モデル

断片的にコード片を見ても実感が湧かないと思いますので、 実例を元に全体像を説明します。

TensorFlowを用いて以下のシンプルな行列計算の係数行列$\boldsymbol A$および$\boldsymbol b$の値を推定する機械学習モデルを作り、 これをTensorBoardで可視化することを考えます。 $$ \boldsymbol{y} = \boldsymbol{A} \boldsymbol{x} + \boldsymbol{b} $$ ここで$\boldsymbol{x}$は入力データ、$\boldsymbol{y}$は出力データとします。

今回はTensorFlowで $$ \left(\begin{array}{c} y_1\\y_2\\y_3 \end{array} \right) = \begin{pmatrix} 2&0&0\\0&2&0\\0&0&2 \end{pmatrix} \left(\begin{array}{c} x_1\\x_2\\x_3 \end{array} \right)$$ となるようにモデルを組んでみます。
つまり、学習の結果 $$ \boldsymbol{A} = 2 \boldsymbol{I}\\\boldsymbol{b} = \left(\begin{array}{c} 0\\0\\0 \end{array} \right) $$ に収束するような学習モデルを作ります。ここで $\boldsymbol{I}$ は $3\times3$の単位行列です。

このモデルを実現するために例えば下記のようなトレーニングデータを用意します。 $$ x = \left(\begin{array}{c} 5\\2\\3 \end{array} \right),\, y = \left(\begin{array}{c} 10\\4\\6 \end{array} \right)\\ x = \left(\begin{array}{c} 1\\8\\2 \end{array} \right),\, y = \left(\begin{array}{c} 2\\16\\4 \end{array} \right)\\ \vdots\\ x = \left(\begin{array}{c} x_1\\x_2\\x_3 \end{array} \right),\, y = \left(\begin{array}{c} 2x_1\\2x_2\\2x_3 \end{array} \right) $$ この学習データをTensorFlowの機械学習モデルに入力し、$\boldsymbol{A}, \boldsymbol{b}$の値を学習させます。
ランダムに生成した$x_1,x_2,x_3$に対して$y_1=2x_1,\, y_2=2x_2,\, y_3=2x_3$を正解データとして組み合わせます。

ちなみに、筆者ははじめ $$ x = \left(\begin{array}{c} 1\\1\\1 \end{array} \right),\, x = \left(\begin{array}{c} 2\\2\\2 \end{array} \right),\, x = \left(\begin{array}{c} 3\\3\\3 \end{array} \right) $$ のように$x_1 = x_2 = x_3$とした単純な学習データを作り学習させて大失敗しました。
この状態で学習させるとどのような$\boldsymbol{A}, \boldsymbol{b}$が導かれるのか、 なぜその値になるのか時間のある人は考えてみてください。

今回利用するPythonのコードは下記のとおりです。
下記のコードの背景薄緑色の箇所がTensorBoardに関連するコードです。

import numpy as np
import tensorflow as tf

# セッションを生成
sess = tf.InteractiveSession()

# トレーニングするパラメータを定義
= tf.Variable(tf.zeros(shape=[3,3]), dtype=tf.float32, name="A")
= tf.Variable(tf.zeros(shape=[3,1]), dtype=tf.float32, name="b")
sess.run(tf.global_variables_initializer())

# 学習用入力データのプレースホルダの定義
= tf.placeholder(tf.float32, shape=[3,1], name="x")
# 学習用出力データのプレースホルダの定義
= tf.placeholder(tf.float32, shape=[3,1], name="y")

# 機械学習のモデルを定義
y_= tf.matmul(A, x)+ b

# 誤差を定義
with tf.name_scope('loss'):
    loss = tf.reduce_mean(tf.square(y_- y))

# トレーニングアルゴリズムを定義
with tf.name_scope('train'):
    learning_rate=1.0e-3
    train= tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)

# TensorBoardで追跡する変数を定義
with tf.name_scope('summary'):
    writer = tf.summary.FileWriter('/tmp/tensorboard-sample', sess.graph)
    tf.summary.scalar('A', tf.reshape(tf.matrix_determinant(A), []))
    tf.summary.scalar('b', tf.reshape(tf.norm(b), []))
    tf.summary.scalar('loss', loss)
    merged = tf.summary.merge_all()

# トレーニングデータを生成
samples_count = 100
x_input = np.ndarray(shape=[samples_count,3,1])
y_input = np.ndarray(shape=[samples_count,3,1])
forin range(samples_count):
    x_input[i][0= np.random.rand()
    x_input[i][1]= np.random.rand()
    x_input[i][2= np.random.rand()

    forin range(3):
        y_input[i][j] = x_input[i][j]* 2

# 学習開始
summary = None
total_epochs = 1000
forin range(total_epochs):
    forin range(samples_count):
        __, summary = sess.run([train, merged], feed_dict={x: x_input[i], y: y_input[i]})

    # 進捗10%毎に途中経過をコンソール出力、TensorBoardに監視対象変数の値を記録
    if _% (total_epochs/10== 0:
        curr_A, curr_b, curr_loss= sess.run([A, b, loss], feed_dict={x: x_input[_% samples_count], y: y_input[_% samples_count]})
        print('-----------------------------')
        print('y')
        print(y_input[_% samples_count])
        print('A')
        print(curr_A)
        print('x')
        print(x_input[_% samples_count])
        print('b')
        print(curr_b)
        print('loss')
        print(curr_loss)
        writer.add_summary(summary, _)

# 学習結果の確認
print('==========================')
# x = (1,2,3) を入力すると (2,4,6)に近いベクトルが出力されるはずだが、実際にどうなるか確認。
x_test= np.array([1,2,3]).reshape([3,1])
y_test = np.array([2,4,6]).reshape([3,1])
estimated_y, final_loss = sess.run([y_, loss], feed_dict={x:x_test, y: y_test})
print('Input x')
print(x_test)
print('Computed y')
print(estimated_y)
print('loss')
print(final_loss)

それでは緑の箇所をひとつずつ確認していきます。

# TensorBoardで追跡する変数を定義
with tf.name_scope('summary'):
    writer = tf.summary.FileWriter('/tmp/tensorboard-sample', sess.graph)
    tf.summary.scalar('A', tf.reshape(tf.matrix_determinant(A), []))
    tf.summary.scalar('b', tf.reshape(tf.norm(b), []))
    tf.summary.scalar('loss', loss)
    merged = tf.summary.merge_all()

まずは最初の緑です。writer = tf.summary.FileWriterによってグラフの可視化とログディレクトリの指定をしています。 FileWriterが実行される直前までのオペレーション、テンソルのグラフノードがTensorBoardに表示されるようになります。
FileWriterをこのブロックの末尾、tf.summary.scalar等のコードのあとに書いてしまうと、サマリーOPのノードも一緒に グラフに表示されてしまいグラフが見づらくなってしまうので、tf.summary.scalar等のOPの前にFileWriterを実行する処理を記述すると良いです。

FileWriterが実行されると即時にTensorBoard用のログディレクトリとイベントファイルが作成されます。このイベントファイルにはグラフイメージは 書き込まれていますが、まだtf.summary.scalarで指定した値のデータは書き込まれていません。 TensorBoardの実行に必要なファイル、ディレクトリは作成されているので、この時点でTensorBoard上でグラフを確認することが可能です。

tf.summary.scalarは上述したように指定したスカラー変数の値を時系列グラフに登録するOPです。OPであるため、セッション上で実行されなければ 時系列グラフも更新されません。 ここでは3つのtf.summary.scalar OPを登録していますが、馬鹿正直にこのOPをそれぞれsess.runで個別に実行するのではなく、通常は tf.summary.merge_allでこれらのOPを1つのOPにマージし、このマージしたOPを1度セッション上で走らせるようにします。

特にtf.name_scopeについては言及しませんでしたが、これはwithインデント内のOPを指定した名前でグルーピングするような処理であり、 特に必須の処理ではありません。グラフを見やすくするために煩雑でバラバラなOPをひとまとまりにするために頻繁に利用されます。

これでTensorBoard用グラフイメージの生成とサマリー記録用のOperationが出来上がりました。 次にサマリーOPのセッション上での実行と、実行結果のファイルへの書き出しをする必要があります。

        __, summary = sess.run([train, merged], feed_dict={x: x_input[i], y: y_input[i]})

サマリーOPの実行は上記のコードでtrainオペレーションと同時に行っています。
サマリーOPをsess.runで実行した結果は変数summaryにProtocol Bufferのフォーマットで入ってきます。

        writer.add_summary(summary, _)

セッションの実行により得られたサマリーOPのProtocol Bufferの値をTensorBoard用のイベントログに上記で追記しています。 TensorBoardは一定の間隔でイベントログを確認します。そのため、機械学習実行中の経過をTensorBoardで確認することも可能です。