HELLO CYBERNETICS

深層学習、機械学習、強化学習、信号処理、制御工学、量子計算などをテーマに扱っていきます

TensorFlowのEstimatorsチュートリアルを日本語で【といっても翻訳してません】

 

 

follow us in feedly

https://cdn.blog.st-hatena.com/images/theme/og-image-1500.png

はじめに

TensorFlowのEstimatorに関するチュートリアルは、既に公式のGet Startedに挙がっております。 しかし、なんというか、機能がてんこ盛り故に触りづらい雰囲気が出てしまっています。

 

きっと初心者にとって見たいチュートリアルというのは、KerasやPyTorchなどがそうであるように、 Numpyのデータをどうやって渡していけば良いのかという点にあるように思います。

Estimatorのチュートリアルをなぞる

ここでは公式のGet Started

Get Started with Graph Execution  |  TensorFlow

をなぞって行きます。ただ、ここのコードの通りに忠実にやっても上手くいかない場面などもあったので、適宜修正しております。 (例えば辞書型で渡さなければならないところなどで、dict()をやっていないためにエラーが出てしまうなどが起こりました)

  細かいことが気になるようでしたら、公式の方と見比べながら見てください。 恐らく、この記事の通りにやればとりあえず動きます。動かしてから、少しずつ理解を深めていきましょう。

データの読み込み

まずはデータを読み込みます。 ここは公式の通りで、みんな大好きirisのデータセットを使います。

TRAIN_URL = "http://download.tensorflow.org/data/iris_training.csv"
TEST_URL = "http://download.tensorflow.org/data/iris_test.csv"
CSV_COLUMN_NAMES = ['SepalLength', 'SepalWidth',
                    'PetalLength', 'PetalWidth', 'Species']

 

csvからpandasのデータフレームにする部分です。 これはまあ、一例と言いますか、どのようなデータ型でデータを持っているのかはそのときによるので、適宜、 次で見るようなpandasのデータ型にしましょう(本当はnumpyでもやれるのですが、とりあえず)。

def load_data(label_name='Species'):
    """Parses the csv file in TRAIN_URL and TEST_URL."""

    # Create a local copy of the training set.
    train_path = tf.keras.utils.get_file(fname=TRAIN_URL.split('/')[-1],
                                         origin=TRAIN_URL)
    # train_path now holds the pathname: ~/.keras/datasets/iris_training.csv

    # Parse the local CSV file.
    train = pd.read_csv(filepath_or_buffer=train_path,
                        names=CSV_COLUMN_NAMES,  # list of column names
                        header=0  # ignore the first row of the CSV file.
                       )
    # train now holds a pandas DataFrame, which is data structure
    # analogous to a table.

    # 1. Assign the DataFrame's labels (the right-most column) to train_label.
    # 2. Delete (pop) the labels from the DataFrame.
    # 3. Assign the remainder of the DataFrame to train_features
    train_features, train_label = train, train.pop(label_name)

    # Apply the preceding logic to the test set.
    test_path = tf.keras.utils.get_file(TEST_URL.split('/')[-1], TEST_URL)
    test = pd.read_csv(test_path, names=CSV_COLUMN_NAMES, header=0)
    test_features, test_label = test, test.pop(label_name)

    # Return four DataFrames.
    return (train_features, train_label), (test_features, test_label)

  データをロードします。

(train_feature, train_label), (test_feature, test_label) = load_data()

  train_featureの最初の数行を見てみましょう。

train_feature.head()

 

f:id:s0sem0y:20180606010552p:plain

  訓練データ(とテストデータ)に関しては上記のような感じ。 下記は同様にtrain_label.head()を見た場合です。 多クラス分類の場合、one-hot表現を使うことも多々あると思われますが、estimatorではクラスラベルを格納した一次元配列(pandas.Series)で渡します。

f:id:s0sem0y:20180606201015p:plain

下準備

ではモデル構築前の下準備をしていきましょう。 とりあえず思考停止して以下を実行してみます。

 

これは多次元データの各特徴量に名前を付けているようなものだと思ってください。 このコードは若干テクニカルに見えますが、

my_feature_columns = []
for key in train_feature.keys():
    my_feature_columns.append(tf.feature_column.numeric_column(key=key))

下記のコードと全く同じです。 特徴量が4つくらいなら手で打ってもいいでしょうし、もっと増えたら上のようにしましょう。

my_feature_columns = [
    tf.feature_column.numeric_column(key='SepalLength'),
    tf.feature_column.numeric_column(key='SepalWidth'),
    tf.feature_column.numeric_column(key='PetalLength'),
    tf.feature_column.numeric_column(key='PetalWidth')
]

 

モデルの構築

今回は10個のユニットを持つ隠れ層を2つ並べたシンプルな多層パーセプトロンを使います。 これはhidden_units=[10, 10]で指定されており、この数字を変えればもっと幅を持たせることも可能ですし、深くすることも可能です。

 

optimizerにはとりあえずtf.train.AdamOptimizerを利用しますが、他のものでも構いません。また、ここでは dropoutの指定もできます。後にTensorBoardで確認しますが、隠れ層の全てにこのドロップアウト率が採用されます。 n_classesは分類クラス数です(出力層のユニットも3になります)。

 

model_dirには学習のcheckpointや、TensorBoardで表示できる情報が保存されます。 ここではカレントディレクトリにあるmodel_dirに保存することにしていますが、お好きなようにしてください。

classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    hidden_units=[10, 10],
    optimizer=tf.train.AdamOptimizer(1e-4),
    dropout=0.1,
    n_classes=3,
    model_dir='./model_dir')

 

上記でモデルが簡単に構築できました。 次は、憎きtf.placeholderの代わりに、モデルへデータを供給するための関数を作ります。 ここで注意が必要なのが、tf.data.Dataset.from_tensor_slicesには特徴量の辞書とラベルのタプルを渡すことです。 特徴量の方を辞書にし忘れるということがあると思いますので注意が必要です。

 

tf.data.Datasetはいろいろ便利なメソッドがあります。shuffle(N)メソッドはN個の塊でデータの順番を入れ替えます。 今回の訓練データの数は120なので、これより大きくしとけば問題ありません。超巨大なデータセットの場合は、PCの処理能力に合わせて適宜十分にシャッフルされるように設定しましょう。 repeat()メソッドによってデータが一巡した場合にはシャッフルして再度データの供給を続けます(これがない場合、一巡して止まってしまうと思われる)。

def train_input_fn(features, labels, batch_size):
    dataset = tf.data.Dataset.from_tensor_slices(
        (dict(features), labels))
    dataset = dataset.shuffle(1000).repeat().batch(batch_size)
    return dataset.make_one_shot_iterator().get_next()

 

モデルの学習

先ほど作ったモデルであるclassifiertrain()メソッドを使えば簡単に学習が行えます。 input_fnにはデータを供給する関数を無名関数で渡します(なんでこういう形式なのかは知らぬ…)。 stepsにはパラメータ更新回数を渡します(iterationの回数でありepochの回数ではない。なんでこういう形式なのかは知らぬ…)。

batch_size = 32
train_steps = 1000
classifier.train(
    input_fn=lambda:train_input_fn(train_feature, 
                                   train_label, 
                                   batch_size),
    steps=train_steps)

モデルの評価

モデルを評価する場合にも下記のようにデータを供給する関数を準備してやらねばなりません。 ここではrepeat()メソッドを噛ませてる必要はありません(データ一巡したら終わって欲しいので)。 batch_sizeは訓練時と同じ値にしていますが、GPUに乗り切るならば多いほうが早く終わるでしょう(評価の結果は変わりませんが)。

 

ちなみにlabelsif文に関しては、「テストデータセットで評価する場合」と、「特徴量だけが手元にあるときに予測をする場合」の両方で以下の関数を利用できるようにする意図があります。

def eval_input_fn(features, labels=None, batch_size=None):
    """An input function for evaluation or prediction"""
    if labels is None:
        # No labels, use only features.
        inputs = features
    else:
        inputs = (dict(features), labels)

    # Convert inputs to a tf.dataset object.
    dataset = tf.data.Dataset.from_tensor_slices(inputs)

    # Batch the examples
    assert batch_size is not None, "batch_size must not be None"
    dataset = dataset.batch(batch_size)

    # Return the read end of the pipeline.
    return dataset.make_one_shot_iterator().get_next()

 

classifierにはevaluate()というメソッドがあるので、データを供給する関数を渡せば評価を行ってくれます。

# Evaluate the model.
eval_result = classifier.evaluate(
    input_fn=lambda:eval_input_fn(test_feature, 
                                  test_label, 
                                  batch_size))

print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))

ちなみにeval_resultは辞書型で以下のようにデータを格納しております(記事を書きながら何度も学習していたので'global_step'がすごい数になっています。 このようにclassifierはパラメータだけでなく学習回数も覚えています)。

{'accuracy': 0.93333334,
 'average_loss': 0.10345762,
 'global_step': 21000,
 'loss': 3.1037288}

 

学習済モデルを用いた予測

学習済モデルを使っていく場合は、predict()メソッドを使います。 こちらの場合は予測することが目的であり、教師データは持っていない状況のはずなので、このメソッドに渡すのも特徴量だけになります。

 

例えば特徴量は以下のように辞書型で渡します。今回は人工的なデータを使ってみましょう。 ここで注意が必要なのは、下記の場合、4つの特徴量を持つ3つのデータを渡すことになるという点です。 見た目上の問題ですが、上下方向が特徴量で左右方向がデータ数になっています。お間違いの無いように…。

 

label_nameは後で予測結果を表示するために準備しているだけなので、クラスを番号で表示するだけで良ければ不要です。

predict_x = {
    'SepalLength': [5.1, 5.9, 6.9],
    'SepalWidth': [3.3, 3.0, 3.1],
    'PetalLength': [1.7, 4.2, 5.4],
    'PetalWidth': [0.5, 1.5, 2.1],}

label_name = {0: 'Setora',
              1: 'Versicolor',
              2: 'Virginica'}

 

下記のコードで予測ができます。 eval_input_fnでは、labelが無いときには特徴量だけを渡すようにコードを書いていたのでこれをそのまま使います。 もちろん別途コードを書いても構いません。

predictions = classifier.predict(
    input_fn=lambda:eval_input_fn(predict_x,
                                  labels=None,
                                  batch_size=batch_size))

 

predictionsは辞書を吐き出すgenerator objectとなっているので、下記のようにして結果を吐き出させます。

pred_dictのキーとしてlogits,probabilities,class_idsがあります。このうち必要なのだけをprint文で表示してみます。 ここでは先程準備した辞書label_nameclass_idsを食わせて、予測結果を表示しています。番号のままでいい場合は、class_idを直接出力して構いません。

for pred_dict in predictions:
    template = ('\nPrediction is class "{}" ({:.1f}%)')

    class_id = pred_dict['class_ids'][0]
    probability = pred_dict['probabilities'][class_id]
    print(template.format(label_name[class_id], 100 * probability))
Prediction is class "Setora" (99.6%)

Prediction is class "Versicolor" (99.7%)

Prediction is class "Virginica" (94.7%)

TensorBoard

学習後は、ターミナルで TensorBoard --logdir=./model_dirと打ち込みましょう。その後表示されるURLにブラウザからアクセスするとTensorBoardを見ることができます。

 

今回のモデルは以下のようになっていました。 f:id:s0sem0y:20180606205610p:plain

 

dnnの中身を表示してみます。2つの隠れ層が確かにありますね。 他にも学習を管理したり保存したりするいろいろな計算グラフが含まれています(TensorFlowではできる限り全ての操作を計算グラフで書きます)。 これらを自動で包含してくれるなんて便利ですねぇ。

f:id:s0sem0y:20180606205622p:plain

ちなみにDISTRIBUTIONSやHISTGRAMSも見ることができます。

おわりに

本当はチュートリアルをなぞった後に、

  • データをnumpyとした場合の渡し方
  • オリジナルのモデルをestimatorで使う方法
  • Kerasのモデルをestimatorにする方法

なども書こうと思ったんですけど、意外と長くなったので一旦切ります。

時間があって気が向いたら書くかもしれません!!

 

s0sem0y.hatenablog.com

s0sem0y.hatenablog.com