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
と教師データlabels
をTensor
オブジェクトとして返すこと」です。
(正確には、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
メソッドの返り値は各出力のイテレータであることに注意してください。
※TensorFlow, the TensorFlow logo and any related marks are trademarks of Google Inc.
その他、本コンテンツ内で利用させて頂いた各プロダクト名やサービス名などは、各社もしくは各団体の商標または登録商標です。