Python
機械学習
MachineLearning
TensorFlow

TensorFlowのAPI,tf.dataとtf.estimatorの緩い関係を考察する

More than 1 year has passed since last update.

オープンソースのライブラリ利用者はわがままである.あまり更新のアクティビティが少ないと「大丈夫か?」と心配になるし,多すぎると「変化が激しくてついていけない」という嘆きが多くなる.TensorFlowは,明らかに後者であると感じているが,本記事では,TensorFlowの新しい(新しめの)APIである,tf.datatf.estimatorを調べた.

元ネタは,Google開発者ブログ.
Introduction to TensorFlow Datasets and Estimators - Google Developers Blog
https://developers.googleblog.com/2017/09/introducing-tensorflow-datasets.html

TensorFlowの状況を再確認

先日,TensorFlow 1.4.0-rc0 がリリースされている.今年2017年の初め,ver.1.0 リリースに合わせTensorFlow Dev summitが開催され,いくつかのRoadmapが発表されたが,TF 1.1 〜 1.4 でこのRoadmapにあった内容が新機能として実装されてきている.TF 1.4 で個人的に注目しているのは,以下の点である.

  • Keras model APIが,ようやくAPIのtop-levelに移った.(TF 1.3 までは,tf.contrib.keras のレベルでしたが, tf.keras となったようです.)
  • tf.data のAPIが整備された.(TF 1.3 では,tf.contrib.data の扱いでした.)

TesnforFlowの構成図を,上記ブログ記事から引用する.

Fig. TensorFlow Architecture (I added arrows)
TF_architect_s.png

Keras model API はすでに有名であるし,Layers (tf.layers)は以前記事にしたので,ここでは比較的新しいAPIである,tf.data, tf.estimatorについて説明してみたい.

tf.data は機械学習におけるデータ入力を支援するAPI

TensorFlowを使い始めてまずやってみるのがMNIST(手書き文字)データセットの分類である.サンプルコードでは,MNISTのデータを取り扱うモジュールが用意されていて,それをimportするところから処理が始まっている.

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

(中略)

for _ in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

ここで行っていることは,予め tf.placeholder(データの置き場所)'x", "y_" を用意して,モデル訓練時に実データをこれに当てはめるというものである.TensorFlowを始めた当初は難しいと感じたが,理解してしまえば特に問題なし.現在の私のコードでは,ほぼ100%このやり方でデータを入力している.tf.data はこのやり方を一般化するためのAPIである.(Chainerを使っている方は,chainer.training.Trainerのようなものという印象だろうか?)ユーザが扱うデータは,構造化されたもの,非構造化のもの(画像,テキスト, etc.)と多岐にわたるので,tf.data は,それに対応するようになっている(はずである).

以下は,TensorFlowドキュメントからの抜粋(少し変更)した,IRISデータの入力方法である.

# The CSV features in our training & test data
feature_names = [
    'SepalLength',
    'SepalWidth',
    'PetalLength',
    'PetalWidth']

# Create an input function reading a file using the Dataset API
# Then provide the results to the Estimator API

def my_input_fn(file_path, perform_shuffle=False, repeat_count=1):
    def decode_csv(line):
        parsed_line = tf.decode_csv(line, [[0.], [0.], [0.], [0.], [0]])
        label = parsed_line[-1:]  # Last element is the label
        del parsed_line[-1]  # Delete last element
        features = parsed_line  # Everything but last elements are the features
        d = dict(zip(feature_names, features)), label
        return d

    dataset = (tf.contrib.data.TextLineDataset(file_path)  # Read text file
               .skip(1)  # Skip header row
               .map(decode_csv))  # Transform each elem by applying decode_csv fn
    if perform_shuffle:
        # Randomizes input using a window of 256 elements (read into memory)
        dataset = dataset.shuffle(buffer_size=256)
    dataset = dataset.repeat(repeat_count)  # Repeats dataset this # times
    dataset = dataset.batch(32)  # Batch size to use
    iterator = dataset.make_one_shot_iterator()
    batch_features, batch_labels = iterator.get_next()
    return batch_features, batch_labels

next_batch = my_input_fn("./iris_training.csv", True)  # Will return 32 random elements

# debug print (REM. these are out of tf.Session())
print('type of next_batch = ', type(next_batch))
print('type of next_batch[0] = ', type(next_batch[0]))
print('next_batch[0].values() = ', next_batch[0].values())

ご存知,IRISデータは4つの特徴量,全体で150サンプルの小さなデータセットであるが,この入力に上記のコードが必要となる.(厳密に言えば,入力だけでなく,反復計算のためのイテレータを準備していますが.)
まだ tf.data について勉強途中なので細かい解説は加えないが,全体を理解するには,しっかり関連ドキュメントを読み込む必要がありそうである.以下のドキュメント(スライド)が参考になる.

The tf.data API - tf.dataを紹介したGoogleドキュメント
https://t.co/kCmBsDoqh2

tf.estimator の採否は,コード全体の構成に影響を及ぼす

さて,tf.estimator を見てみる.まずは,ドキュメントを参照する.

tf.estimator

Estimators is a high-level API that reduces much of the boilerplate code you previously needed to write when training a TensorFlow model. Estimators are also very flexible, allowing you to override the default behavior if you have specific requirements for your model.

Esitimatorsは,高レベルAPIであり,TensorFlowモデルの学習に必要となる"熱々の"(boilerplate)コードを書かなくて済むようにする.Estimatorsはまた大変柔軟で,すでにあるモデルのディフォルトの挙動を
オーバーライドすることを可能にする.

"boilerplate code"が(俗語?)上手く訳せないので分かりにくいが,高レベルAPIであるので,Estimatorの導入でコードの抽象化に繋がることには違いないと思われる.

tf.estimator で覚えておきたいのが,以下の2種類があるという点である.

  1. Pre-made Estimator (既製服のようなもの)
  2. Custom Estimator (カスタム版のもの)

(上に示した,TensorFlow構成図において,一番上の段にあるのが,Pre-made Estimator, 二段目にある"Estimator"とのみ表記されているのが,Custom Estimatorである.)次に,ドキュメントより,Estimatorの構成図を引用する.

Fig. Estimator structure
TFEstimator_s.jpg

以下,2種類のEstimatorを紹介する.

1. Pre-made Estimator

ドキュメントによると...

  • tf.estimator.LinearClassifier: Constructs a linear classification model.
  • tf.estimator.LinearRegressor: Constructs a linear regression model.
  • tf.estimator.DNNClassifier: Construct a neural network classification model.
  • tf.estimator.DNNRegressor: Construct a neural network regression model.
  • tf.estimator.DNNLinearCombinedClassifier: Construct a neural network and linear combined classification model.
  • tf.estimator.DNNLinearCombinedRegressor: Construct a neural network and linear combined regression model.

のように6種類のEstimatorが用意されている.但し,個人的に断定してしまうが,この中で有用なのは,tf.estimator.DNNClassifiertf.estimator.DNNRegressor の2つである."Linear"がつくものは,Generalized Linear Model(GLM)のためのもので,"Combined"がつくものは,”Linear"モデルと"DNN"モデルを組み合わせたものである.TensorFlowというツールを選択した時点で"DNN"(Deep neural network)モデルを想定しているはずなので,上記2つをとりあえず知っていればいい,というのが私の意見である.

但し,TensorFlowサイトでは,それなりに詳しく紹介されているので,必要となるケースでは以下を参照のこと.
TensorFlow Wide & Deep Learning Tutorial
tensorflow models / wide_deep - GitHub

以下は,tf.estimator.DNNClassifierを用いたMNISTのコードである.

import numpy as np
import tensorflow as tf

N_DIGITS = 10       # Number of digits.
X_FEATURE = 'x'     # Name of the input feature.

def main(unused_args):
    ### Load MNIST dataset, prep. input funcs
    mnist = tf.contrib.learn.datasets.DATASETS['mnist']('../MNIST_data')
    train_input_fn = tf.estimator.inputs.numpy_input_fn(
        x={X_FEATURE: mnist.train.images},
        y=mnist.train.labels.astype(np.int32),
        batch_size=100,
        num_epochs=None,
        shuffle=True)
    test_input_fn = tf.estimator.inputs.numpy_input_fn(
        x={X_FEATURE: mnist.train.images},
        y=mnist.train.labels.astype(np.int32),
        num_epochs=1,
        shuffle=False)

    ### DNNClassifier (MLP network)
    feature_columns = [
        tf.feature_column.numeric_column(
            X_FEATURE, shape=np.array(mnist.train.images).shape[1:])]
    classifier = tf.estimator.DNNClassifier(
        feature_columns=feature_columns,
        hidden_units=[512, 256],
        n_classes=N_DIGITS,
        optimizer=tf.train.ProximalAdagradOptimizer(
            learning_rate=0.1,
            l1_regularization_strength=0.001
    ))

    print('Training...')
    classifier.train(input_fn=train_input_fn, steps=400)
    print('Done.\nEvaluating...')
    scores = classifier.evaluate(input_fn=test_input_fn)
    print('Accuracy (conv_model): {0:f}'.format(scores['accuracy']))

if __name__ == '__main__':
    tf.app.run()

確かにコードは短くなり,学習ループや,TensorFlowの決まり事,"tf.placeholder"の設定や,"tf.Session"の立ち上げも隠されている.しかし,代わりに覚えなくてはいけない事柄が増えている気もする.使いこなすには,もう少し調べる必要がありそうである.(特に,tf.feature_column.xxxxx の関数は,重要な部分に見える.)

2. Custom Estimator

前記の通り,Pre-made Estimatorでは,"DNNClassifier", "DNNRegresor"が使えるが,これらは,MLP(Multi-layer Perceptron)モデルである.より複雑なモデル,例えばCNN,RNN等を使いたければ,Custom Estimatorの枠組みを使う必要がある.ここで使うtf.estimator.Estimator は,上記Pre-made Estimatorのベース・クラスであるので,より柔軟なモデル作製ができるようになっている.(もちろん,その分,書くコードの分量は増えますが.)

Custom Estimatorについては,以下のドキュメント,サンプルコードが参考になる.
Creating Estimators in tf.estimator
Estimator Examples - GitHub
(特に,この中のMNISTコード(CNN版): https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/learn/mnist.py

(追加) Creating Estimators from Keras models

これもどれほどの需要があるのかわからないが,すでにあるKeras modelを使って,Estimatorが作れるとのことである.(TensorFlow 1.4より.)
https://www.tensorflow.org/versions/r1.4/programmers_guide/estimators

少しドキュメントより引用させていただく.

You can convert existing Keras models to Estimators. Doing so enables your Keras model to access Estimator's strengths, such as distributed training. Call tf.keras.estimator.model_to_estimator as in the following sample:

# Instantiate a Keras inception v3 model.
keras_inception_v3 = tf.keras.applications.inception_v3.InceptionV3(weights=None)
# Compile model with the optimizer, loss, and metrics you'd like to train with.
keras_inception_v3.compile(optimizer=tf.keras.optimizers.SGD(lr=0.0001, momentum=0.9),
                          loss='categorical_crossentropy',
                          metric='accuracy')
# Create an Estimator from the compiled Keras model.
est_inception_v3 = tf.keras.estimator.model_to_estimator(keras_model=keras_inception_v3)
# Treat the derived Estimator as you would any other Estimator. For example,
# the following derived Estimator calls the train method:
est_inception_v3.train(input_fn=my_training_set, steps=2000)

なるほど,Kerasのpre-trainedモデルを使うケースでは便利かも知れない.

感想

現時点で(2017年10月時点で),どれほどの割合のTensorFlowユーザが,tf.data, tf.estimatorを使っているか,使ってみようと考えているか,やや疑問である.但し,これからユーザを増やしていこうとGoogleのTenforFlowチームが考えているのは,間違いないと思われる.個人的な意見として,「よく分からないから使わない」のではなく,これらAPIのざっくりした知識を持ったうえで「使う/使わない」を選択する姿勢が大切なのではないか,と考えている.
また本記事のタイトルtf.datatf.estimatorの関係であるが,正直,まだ全体像を理解できていない.tf.estimatorコードのデータ入力関数では,tf.data の使用が推奨されると思われるが,これら2つのモジュールを別々なものしても使える,という”緩い”関係性であると理解している.

参考文献,web site