R&Dセンター 技術開発部 古林 隆宏

TensorFlow+Keras入門 ~ ウチでもできた⁉ ディープラーニング ~

第3回 TensorFlow 基礎から最新APIまで

第3回はTensorFlowの基礎を確認したうえで、最新の高レベルAPIを利用したディープラーニングの実装法をご紹介します。

記事内で必要に応じてPythonのコードを示すことがありますが、Python自体の機能や構文に関する解説は割愛させていただきますのでご了承ください。
なお、この記事にて掲載しているコードはすべてMITライセンスのもと利用を許諾するものとします。

TensorFlow, the TensorFlow logo and any related marks are trademarks of Google Inc.
この記事のTensorFlowロゴ画像を含む図は、CC-BY 3.0ライセンスのもと利用許諾されたTensorFlowの派生物です。

1) TensorFlowの基礎

TensorFlowはGoogle主導で開発されているOSSです。
「AI用ライブラリ」「ディープラーニング用ライブラリ」のように呼ばれることもありますが、
つくりとしては汎用的な数値計算基盤のうえに機械学習やディープラーニング関連の機能が豊富に搭載されているというイメージになっています。

TensorFlowの名前の由来となっているTensor(=テンソル)は数学の用語で、多次元配列を用いて表現できるデータ構造を示します。
TensorFlowを触るうえでは、テンソル ≒ n次元配列と理解しておけば充分です。

この節では、TensorFlowの数値計算基盤としての仕組みについてご紹介します。
能書きはいいからディープラーニングが見たい!という方は次の節までスキップしていただいても良いと思います。

インストール

とにかくまずはTensorFlowを使ってみましょう!
とにかく動けばよいというのであれば、pipコマンド一発でインストールすることができます。

$ pip install tensorflow

(GPUを使いたい場合はインストール対象をtensorflow-gpuにする必要があり、その他追加のシステム条件や手順が必要となります。詳しくは公式サイトをご参照ください)

1 + 2 = 3

以下のPythonスクリプトでは、TensorFlowで1 + 2 = 3を計算しています。

import tensorflow as tf
 
# 計算グラフの定義
x = tf.placeholder(tf.int32, shape=())
two = tf.constant(2)
x_plus_two = x + two
 
# 計算の実行
with tf.Session() as session:
    result_value = session.run(x_plus_two, {x: 1})
 
# 結果出力
print(result_value)
# => 3

「計算グラフの定義」部分では、

  • 任意の整数を指定できるx
  • 固定値2をもつtwo
  • xとtwoを足した値x_plus_two

の3つを定義しています。tf.placeholderは後で任意の値を指定する変数の定義を、tf.constantはその名の通り定数の定義を行います。
これらの値は全てTensorというクラスで表現されます。
実際に入る値としては1つの数字からなるスカラー値ですが、「0次元の配列で表現される値」と解釈すればテンソルと見なすこともできるわけです。

次に「計算の実行」部分においてxを1とした場合にx_plus_twoがどのような値をとるかを計算しています。
このように「まず計算の流れを定義し、その後求めたい値を指定して計算する」というスタイルになっているのがTensorFlowの特色です。

そうは言っても、普通にプログラミングをする場合にも「まずクラスや関数を定義し、その後それを利用してメインの処理を実装する」という手順で実行するかと思います。
たとえば上記の計算はxが引数でx_plus_twoが戻り値の関数を作るのに似ています。
TensorFlowの「計算の流れの定義」がクラスや関数の定義と少し違うのは、TensorFlowの計算は必ずSessionオブジェクトを通して行われるということです。

Sessionとは?

Sessionの機能は主に以下の2つです:

  • TensorFlowの計算を実行する
  • TensorFlowの計算における様々な状態を保持する
計算実行の主体としてのSession

この図は、2を足す計算の例を模式的に表したものです。起こることを時系列で並べると、

  1. x_plus_twoの値の計算を要求
  2. x_plus_twoがxとtwoを足したものということを確認
  3. 入力値としてxの中身が1であることを確認
  4. 1 + 2を計算し、結果の3を出力

というイメージになります。
仮にxの値が未指定であった場合、計算結果を導くことができませんのでエラーとなります。

状態を保持する器としてのSession

「様々な状態の保持」に関する例として下記のコードを用いて考えます。

import tensorflow as tf
 
# 計算グラフの定義
x = tf.placeholder(tf.int32, shape=())
accum_var = tf.Variable(0) # accum_varの初期値は0
 
# accum_varの値にxを足して保持する。accumでは足した後の値を取得できる
accum = tf.assign(accum_var, x + accum_var)
 
# 計算の実行
with tf.Session() as session:
    session.run(tf.global_variables_initializer())
    print(session.run(accum, {x: 1}))
    # => 1
    print(session.run(accum, {x: 1}))
    # => 2
    print(session.run(accum, {x: 2}))
    # => 4
    print(session.run(accum, {x: 2}))
    # => 6
 

tf.Variableは、セットした値がSessionの中に保持され、次回以降の計算にも使用される変数を定義します。
xの定義に使用しているplaceholderとVariableはどちらも変数ではありますが、placeholderの値は毎回使い捨てでSessionの中に保存されないという大きな違いがあります。

2を足す計算の例は入力値が同じであれば出力値も同じであったのに対し、今回は入力の累積値が状態として保持され、同じ入力値に対しても異なる値が出力されます。
この処理を模式的に表したのが下の図です。

入力の累積値accum_varの中身がSessionの中に保持されており、入力があるごとにその値が更新されていくイメージです。
tf.assignは、第1引数で指定したVariableに第2引数で指定した値をセットしたうえで、第2引数の値を返す処理です。

Variableと機械学習

Variableが想定する主な用途は、ディープラーニングやその他の機械学習におけるモデルのパラメータの保持です。

例えばあるデータの集合を機械学習してy=ax+bという1次関数に落とし込む場合、
xとyの正しい組み合わせを複数用意して、このモデルのパラメータである傾きaと切片bを調整していくということをやります。
具体的には、xの値からax+bの値を計算し、あるべきyの値との誤差を計算したうえで、誤差が小さくなるように傾きaと切片bの値を更新します。
また学習が終われば傾きaと切片bの値は動かさずに定数のように扱います。

このように、常にどんな値が来るかわからないxと違い、学習モデルのパラメータであるaおよびbには「学習時には変化させたいが学習が終わったら与えずとも保持しておいて欲しい」という性質があります。
この性質の違いが、毎回使い捨てのplaceholderと、変化はするが保持もされるVariableの役割の違いに表れているのです。

Variableの初期化

機械学習をまっさらな状態からはじめる場合はVariableは初期値のままでよいのですが、学習済みモデルを利用したい場合や、一度中断した学習処理を再開するような場合には、ファイルに保存しておいたモデルの内部状態をVariableに展開する必要があります。

どちらにしても、新しくSessionを作った場合まずVariableを初期化する必要があります。
上記のコードで利用しているtf.global_variables_initializer()はすべてのVariableをまっさらな状態で初期化する処理です。

2) 高レベルAPIでディープラーニングを実装する

さて、ここまでTensorFlowの数値計算機能の基礎についてご説明してきましたが、
placeholderがこうで、Variableがこうで、Sessionがこうなっているから……というような話はあくまでTensorFlow側の事情であって、
とにかくディープラーニングの処理を組んで流したいんだ!という目的のためには、率直に言って少し遠回りをさせられている感もないではありません。

そのような人々のためにTensorFlowには、そういった泥臭い部分をある程度スキップできる高レベルのAPIも提供されています。
ここからは、前回作成した犬と猫の画像を分類するDNNを題材とし、TensorFlowの高レベルAPIを用いて実装する例をご紹介していきます。

ちなみに前回までにご紹介したKerasは、思い切った抽象化によるスリムな書き方を実現した一方で、もう一歩踏み込んでカスタマイズしたいというときのハードルが高くなっています。
実際のところとしては、まずはKerasを使ってみてその枠からはみ出したくなったらTensorFlowに移行するというのがセオリーといえそうです。

ディープラーニングモデルの定義

前回と同様に、ディープラーニングモデルを別ファイルとして定義するところからはじめましょう。
前回と全く同じ構造のDNNをTensorFlowを用いて定義するとコードは下記のとおりになります;

import tensorflow as tf
 
def MyDNN(input_shape=(32, 32, 1), output_size=10, learning_rate=0.001,
          keep_prob=0.5, model_dir='tfmodel'):
    def mydnn_fn(features, labels, mode):
        # tf.LayersによるDNN構造の定義
        input_layer = tf.reshape(features["img"], [-1] + list(input_shape))
        labels = tf.reshape(labels, [-1, output_size])
 
        layer = tf.layers.conv2d(filters=20, kernel_size=5, strides=2,
                                 activation=tf.nn.relu,
                                 inputs=input_layer)
        layer = tf.layers.max_pooling2d(pool_size=3, strides=2,
                                        inputs=layer)
        layer = tf.layers.batch_normalization(inputs=layer)
 
        layer = tf.layers.conv2d(filters=50, kernel_size=5, strides=2,
                                 activation=tf.nn.relu,
                                 inputs=layer)
        layer = tf.layers.max_pooling2d(pool_size=3, strides=2,
                                        inputs=layer)
        layer = tf.layers.batch_normalization(inputs=layer)
 
        layer = tf.contrib.layers.flatten(layer)
        layer = tf.layers.dense(units=100, activation=tf.nn.relu,
                                inputs=layer)
        layer = tf.layers.dropout(rate=(1 - keep_prob),
                                  training=(mode == tf.estimator.ModeKeys.TRAIN),
                                  inputs=layer)
        output_layer = tf.layers.dense(units=output_size,
                                       inputs=layer)
 
        # tf.Estimatorを用いる準備
        # 学習済みモデルを利用した出力の取得に必要な値
        if mode == tf.estimator.ModeKeys.PREDICT:
            probabilities = tf.nn.softmax(output_layer)
            return tf.estimator.EstimatorSpec(mode=mode, predictions=probabilities)
 
        # 学習に必要な値
        labels = tf.reshape(labels, [-1, output_size])
        loss = tf.losses.softmax_cross_entropy(onehot_labels=labels,
                                               logits=output_layer)
        optimizer = tf.train.AdamOptimizer(learning_rate, epsilon=1e-1)
        train_op = optimizer.minimize(loss=loss, global_step=tf.train.get_global_step())
 
        # 評価に必要な値
        classes = tf.argmax(input=output_layer, axis=1)
        eval_metric_ops = {
          "accuracy": tf.metrics.accuracy(labels=tf.argmax(labels, axis=1),
                                          predictions=classes)
        }
 
        return tf.estimator.EstimatorSpec(mode=mode, loss=loss,
                                          train_op=train_op,
                                          eval_metric_ops=eval_metric_ops)
 
 
 
    estimator = tf.estimator.Estimator(model_fn=mydnn_fn, model_dir=model_dir)
 
    return estimator

Layers: DNNの構造を定義

DNNの各層を実装するにあたっては、LayersというTensorFlowのAPIが使えます。
上記コードの「LayersによるDNN構造の定義」というコメントの部分です。

Layersを用いると、Kerasを用いた時とかなり近い感覚でDNNの構造を書くことができます。
比較のため、畳み込み層→プーリング層→正規化層というまとまりの部分を双方から引用してみます:

ayer = tf.layers.conv2d(filters=20, kernel_size=5, strides=2,
         activation=tf.nn.relu,
         inputs=input_layer)
layer = tf.layers.max_pooling2d(pool_size=3, strides=2,
            inputs=layer)
layer = tf.layers.batch_normalization(inputs=layer)
model.add(Conv2D(20, kernel_size=5, strides=2,
                 activation='relu',
                 input_shape=input_shape))
model.add(MaxPooling2D(3, strides=2))
model.add(BatchNormalization())

Layersを用いずにTensorFlowで実装すると……という話はやめておくことにします。 世の中には知らない方がいい苦労というものがあるものです。

1点だけ注意すべきなのは、Layersでドロップアウトを行う場合、引数で指定するドロップアウト率の意味がKerasと逆になっているところです。
Layersでは引数rateはデータがドロップアウトの対象と なる割合 を意味しますので、「全くドロップアウトしない」場合は0.0を指定します。
一方KerasではDropoutの引数はデータがロップアウトの対象と ならない割合 を意味しますので、「全くドロップアウトしない」場合は1.0を指定します。

Estimator: モデルの学習・評価・利用

MyDNN関数の実装においてKeras版と大きく違うのは、学習の基準値や評価値の定義の方法です。
KerasのSequentialオブジェクトに相当するものとして、TensorFlowにはEstimatorが用意されています。

def mydnn_fn(features, labels, mode):
    ...
 
estimator = tf.estimator.Estimator(model_fn=mydnn_fn, model_dir=model_dir)

Estimatorオブジェクトはtrainevaluatepredictといったメソッドを持ち、それらを正しく呼び出すことで学習・評価・学習済みモデルを利用した出力の取得が可能です。
また学習中は学習結果を自動的に保存し、保存された学習結果がある場合は学習開始時にそれを自動でロードしてくれます。
Estimatorを用いない場合は、学習の計算や評価値の計算などをSession.runを通じて直接実行し、結果を自分で保存して……おっと、 知らないでいい苦労については話さない ことにしたのでしたね。

Estimatorの作成に必要なのは、基本的にmodel_fnというパラメータのみです。このパラメータには、処理に必要な設定を保持するEstimatorSpecというオブジェクトを返す関数を渡します。
従って、DNNの実装はmodel_fnの内部で行うことになります。

また、model_dirというパラメータには学習済みモデルや学習途中の各種評価値の記録を保存するディレクトリ名を指定することができます。
API上は指定しなくてもよいパラメータですが、実用上は明示的に指定することになるでしょう。

model_fn内でのEstimatorSpecの作成

model_fnとして渡す関数は最低でもfeatureslabelsmodeという3つの引数を持っている必要があります。
featuresは入力データ、labelsは教師データに相当します。modeには、これから行うのが学習・評価・出力取得のいずれであるかを示す定数が渡されます。

EstimatorSpecのコンストラクタにおいて必要な引数は、modeによって違います:

mode 必要な引数
学習 mode, loss, train_op
評価 mode, loss
出力取得 mode, predictions

modeは渡されたmodeそのままです。
lossは、学習データと教師データの差異を表す値、いわゆる損失値です。
train_opは、損失値に基いてパラメータを最適化する処理のオブジェクトを渡します。
predictionは、出力取得処理で取得したい値を指定します。

使わない引数をセットしても支障はないようですので、上記に掲載したコードでは学習時と評価時のEstimatorSpecは共通としています。
しかし出力取得のみを行う場合については、教師データlabelsが空であることによるエラーが発生したため、labelsを扱う前にmodeをチェックし、EstimatorSpecを作成してリターンする実装としています。

3) ディープラーニング実行部の実装

次に、MyDNNを呼び出す側のコードを示します。ほぼ前回のKerasの例を踏襲した構造になっています。

再度仕様をご説明しますと、
コマンドライン引数なしで実行すると特定のディレクトリから学習用画像を読み込んでディープラーニングを行い、
コマンドライン引数として–inferオプションとともに判定したい画像のファイルパスを指定すると、学習済みモデルをもとにその画像に写っているのが犬か猫かを判定するという想定です。

import argparse
import tensorflow as tf
import os, sys
import numpy as np
from datetime import datetime
from mydnn_tf_layers import MyDNN
import tensorflow as tf
 
ImageDataGenerator = tf.keras.preprocessing.image.ImageDataGenerator
load_img = tf.keras.preprocessing.image.load_img
img_to_array = tf.keras.preprocessing.image.img_to_array
 
input_size_x = 224
input_size_y = 224
batch_size = 20
input_size_c = 3
output_size = 2
model_dir = 'tfmodel/'
 
parser = argparse.ArgumentParser()
parser.add_argument("--infer", action="store", nargs="?", type=str,
                    help="学習は行わず、このオプションで指定した画像ファイルの判定処理を行う")
parser.add_argument("--epochs", action="store", nargs="?", default=10, type=int,
                    help="学習データ全体を何周するか")
args = parser.parse_args()
 
epochs = args.epochs
 
model = MyDNN(input_shape=(input_size_x, input_size_y, input_size_c),
              output_size=output_size, model_dir=model_dir)
 
if not args.infer:
    print("学習モード")
    # 学習データの読み込み
    keras_idg = ImageDataGenerator(rescale=1.0 / 255)
    train_generator = keras_idg.flow_from_directory('data/train',
                              target_size=(input_size_x, input_size_y),
                          batch_size=1,
                          class_mode='categorical',
                          shuffle=True)
    valid_generator = keras_idg.flow_from_directory('data/valid',
                          target_size=(input_size_x, input_size_y),
                         batch_size=1,
                          class_mode='categorical')
 
    # 学習の実行
    num_data_train_dog = len(os.listdir('data/train/dog'))
    num_data_train_cat = len(os.listdir('data/train/cat'))
    num_data_train = num_data_train_dog + num_data_train_cat
 
    num_data_valid_dog = len(os.listdir('data/valid/dog'))
    num_data_valid_cat = len(os.listdir('data/valid/cat'))
    num_data_valid = num_data_valid_dog + num_data_valid_cat
 
    steps_per_epoch = num_data_train / batch_size
    validation_steps = num_data_valid / batch_size
    
    # ImageDataGeneratorをDatasetに変換
    def my_input_fn(generator):
        gen_fn = lambda: generator
        dataset = tf.data.Dataset.from_generator(gen_fn, (tf.float32, tf.float32))
        dataset = dataset.map(lambda f, l: ({"img": f}, l)).batch(batch_size)
        return dataset.make_one_shot_iterator().get_next()
 
    for epoch in range(epochs):
        print("epoch ",  epoch)
        print("training...")
        model.train(input_fn=lambda: my_input_fn(train_generator),
                    steps=steps_per_epoch)
        print("evaluation:")
        eval_results = model.evaluate(input_fn=lambda: my_input_fn(valid_generator),
                                      steps=validation_steps)
        print(eval_results)
 
else:
    print("判定モード")
    # 判定する画像の読み込み
    image_infer = load_img(args.infer, target_size=(input_size_x, input_size_y))
    data_infer = img_to_array(image_infer)
    data_infer = np.expand_dims(data_infer, axis=0)
    data_infer = data_infer / 255.0
 
    # 判定処理の実行
    predict_input_fn = tf.estimator.inputs.numpy_input_fn(x={"img": data_infer},
                                                          batch_size=1,
                                                          shuffle=False)
    result_generator = model.predict(predict_input_fn)
    result = next(result_generator) * 100
 
    # 判定結果の出力
    if result[0] > result[1]:
      print('Cat (%.1f%%)' % result[0])
    else:
      print('Dog (%.1f%%)' % result[1])

ImageDataGeneratorに関して

一見して「keras」という文字列が紛れ込んでいることに気づかれたアナタ、鋭いですね。
実は2017年11月3日にリリースされたばかりのTensorFlow 1.4では、Kerasの機能がTensorFlowの正式なAPIの中に取り込まれています。
前回の記事で画像を読み込む際に使用したImageDataGeneratorは非常に強力な機能でありながら、TensorFlowのKerasモジュール以外の部分には同等の機能が見当たりません。

そこで今回はImageDataGeneratorもTensorFlowの一部ということでご容赦いただき、前回同様ImageDataGeneratorを使用して画像を読み込むコードになっています。
このコードはkerasを別途インストールしなくても動作しますし、一応コードの冒頭でtf.kerasモジュールから(つまりTensorFlowの一部として)ImageDataGeneratorと関連関数を読み込んでいることがご確認いただけます。

これにより、「学習モード」のブロックは「ImageDataGeneratorをDatasetに変換」というコメントの前までは前回のコードと共通となっています。

Dataset: 効率的にデータを投入する

「TensorFlowの基礎」でご紹介した通り、TensorFlowで大量のデータを処理する場合、
「データを用意→placeholderとして指定し計算を実行→結果を処理」という流れを繰り返し行うことになり、またデータの集合に対して分割やシャッフルをはじめとした下処理を行うことも頻繁にあります。
そこでその一連の操作をまとめたうえで効率的な実装を提供するのがDatasetです。

今回のコードではデータの読み出しにImageDataGeneratorを使用しているため、Datasetを最大限活用しているというわけではないのですが、基本的な使い方の例としてご参照ください。

Datasetクラスにはfrom_〇〇というメソッドが複数あり、手元のデータからDatasetを作成するにはこれらのメソッドを使用します。
また、テキストファイルを直接読み込むことのできるTextLineDatasetをはじめとした、ファイルシステム上のデータを簡単に参照できるDatasetのサブクラスも存在します。

Datasetの作成

今回はImageDataGeneratorのオブジェクトをもとにDatasetを作成しますので、from_generatorというメソッドを使います。

gen_fn = lambda: generator
dataset = tf.data.Dataset.from_generator(gen_fn, (tf.float32, tf.float32))

tf.data.Dataset.from_generatorの第1引数はデータのジェネレータ関数を指定することが期待されています。
しかし今回は、ImageDataGeneratorの機能よりすでにイテレータを取得していますので、そのイテレータを返す擬似的なジェネレータ関数を作ってお茶を濁しています。

第2引数にはジェネレータから得られるデータの型を指定します。今回は、学習データとして画像をfloatで数値化したものと、教師データとしてラベルをfloatで表したもののがそれぞれ得られますので、(tf.float32, tf.float32)を指定します。

DatasetとEstimatorの連携

Estimatorで学習・評価・結果取得を行う際には、入力データはEstimatorが指定する仕様を持つ関数input_fnという形で与えます。
その仕様とは「入力データfeaturesと教師データlabelsTensorオブジェクトとして返すこと」です。
(正確には、TensorオブジェクトもしくはTensorオブジェクトを値として持つ辞書という2つの選択肢があります)

このfeaturesとlabelsは、EstimatorSpecの作成に使用したmodel_fnにそのまま与えられるイメージです。

ちょうどDatasetにはデータをTensorオブジェクトとして出力する機能がありますので、そちらを使って実装を行っています。

# ImageDataGeneratorをDatasetに変換
def my_input_fn(generator):
    gen_fn = lambda: generator
    dataset = tf.data.Dataset.from_generator(gen_fn, (tf.float32, tf.float32))
    dataset = dataset.map(lambda f, l: ({"img": f}, l))
    dataset = dataset.batch(batch_size)
    iterator = dataset.make_one_shot_iterator()
    features, labels = iterator.get_next()
    return features, labels
    
...
 
model.train(input_fn=lambda: my_input_fn(train_generator),
            steps=steps_per_epoch)

from_generatorメソッドによりDatasetオブジェクトを作成した続きから見ていきましょう。

dataset.mapは、Datasetが参照する各データに加工をほどこすメソッドです。
今回はfeaturesの値を、imgというキーをともなう辞書の中に格納しています。
Estimatorは基本的に辞書の形でデータを授受することを想定して作られているようなので、それに倣っています。

dataset.batchは、Datasetが参照する各データを指定数分まとめて入力するようにするメソッドです。

dataset.make_one_shot_iteratorは、Datasetから値を読み出すIteratorオブジェクトを作成するメソッドです。
Iteratorオブジェクトは、その名前に反して通常のPythonのイテレータとしては使用できません。
get_nextメソッドで得たTensorオブジェクトをSession.runで読み込むと、Datasetが参照する各データを順に計算対象としてくれるという仕組みになっています。

従ってここではiterator.get_next()の返り値がEstimatorのinput_fnが返すべきTensorであるということになります。

さらに、このTensorはあらかじめ取得してEstimatorに渡すという手順ではうまくいかず、あくまでもinput_fnで、つまりEstimatorの中で取得される必要があるようです。
そのためmy_input_fnをラムダ関数で包み、Estimatorの中でmy_input_fnが実行されるようにしています。

モデルの評価についても、使用するメソッドがevaluateである以外はほぼ同様です。重複となりますので今一度の説明は割愛いたします。

判定モード:1件のデータを入れる

判定モードでは学習済みモデルを利用して判定を行います。
といってもEstimatorの利用法は、メソッドがpredictになるだけでinput_fnを渡すことに代わりはありません。

ただしinput_fnの生成方法について、このプログラムの判定モードでは1件だけデータが入ればよいので、Datasetなしでできる軽量なやりかたで実装しています。

predict_input_fn = tf.estimator.inputs.numpy_input_fn(x={"img": data_infer},
                                                      batch_size=1,
                                                      shuffle=False)
result_generator = model.predict(predict_input_fn)

データが入ったNumPy Arrayが手元にあれば、それをtf.estimator.inputs.numpy_input_fnという関数でinput_fnとして渡せるものに変換できます。
また入力データは引数xに指定しますが、これは入力データそのものではなく、入力データを値とする辞書である必要があります。

さらに、predictメソッドの返り値は各出力のイテレータであることに注意してください。

4) TensorBoardで学習の経過を見守る

上記のコードで学習を実行してみると、「training…」の表示のところで長いこと沈黙してしまうことと思います。
これは一生懸命学習処理を行っているからなのですが、頑張ってくれているとはわかっていてもレスポンスがないと不安になってしまうのが人情というもの。
工夫すればコンソールに進捗状況を表示することもできるはずですが、公式の可視化ツール TensorBoard を用いればブラウザからインタラクティブに経過を確認することができます。

学習を実行しているのとは別のターミナルを開くなどして、tensorboardコマンドを実行しましょう。
tensorboardコマンドはTensorFlowをインストールすると付随的にインストールされます。

$ tensorboard --logdir=./tfmodel
 
TensorBoard 0.4.0rc2 at http://(実行中のホスト名):6006 (Press CTRL+C to quit)

logdir引数は、TensorBoardに表示するSummaryと呼ばれるデータの保存されているディレクトリを指定します。
Estimatorを使っている場合、model_dirで指定したディレクトリに学習済みモデル等といっしょに保存されますので、そのディレクトリを指定すればOKです。

上記のようなメッセージが表示されたら、ブラウザからhttp://(TensorBoardを実行中のホスト名):6006を開いてみましょう。
下図のような画面が開くと思います。

オレンジの系列は学習時の値、青の系列は評価時の値になります。
これは、画面左下の「Runs」という部分に対応しています。
logdirの中にさらにディレクトリを作って別々にSummaryを保存しておけば、TensorBoardはそれをディレクトリごとに別の系列として表示してくれます。
Estimatorは評価時のSummaryはeval以下に保存してくれるのでこのような表示になっています。

グラフのうち「loss」に注目してください。
lossはDNNが算出した結果と教師データの差異を表し、基本的には低いほどよい値です。
学習時のlossが下がっているということからは、とりあえず学習が進捗していることが確認できます。
一方評価時のlossが下がっているということからは、有効に学習が進んでいる(精度が上がっている)ということが確認できます。

一般的に評価時のlossは、最初急激に減少し、だんだん横ばいに近づいていき、ある程度を過ぎると逆に増え始めるという動きを見せます。
したがって、評価時のlossが横ばいになったなというあたりで学習を完了したいところです。この傾向をつかむのにはグラフを確認するのが適しているというわけです。

このようにEstimatorを使用すると全く追加コーディングなしでTensorBoardを利用できます。

おわりに

今回はTensorFlowの高レベルAPIであるLayers、Estimator、Datasetを用いたDNNの実装法をご紹介しました。
これらは一度使い方を覚えれば不必要な手間を避けることができ、またバグを埋め込む恐怖を緩和してくれますので、ぜひうまく使いこなしていきましょう。

さて、これでTensorFlowでディープラーニングモデルを作ることができるようになったわけですが、
実際のシステムへの応用を考えると、Pythonコマンド直打ちではなくWebアプリ等からモデルを利用できるようにしたいところです。
次回はそのための方策を考えていきます。

今回もお付き合い頂きありがとうございました。次回をお楽しみに。

1 2

3

4 5
PAGE TOP