ホーム » TensorFlow » TensorFlow : Guide : 高位 API : データをインポートする

TensorFlow : Guide : 高位 API : データをインポートする

TensorFlow : Guide : 高位 API : データをインポートする (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 4/10/2019 (1.13.1); 10/30 (1.11.0), 07/13/2018 (1.9.0)
作成日時 : 04/16/2018

* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページでは tf.data モジュールによるデータのインポートについて説明されています。
* 本ページは、TensorFlow 本家サイトの Programmer’s Guide – Importing Data を翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

本文

tf.data API は、単純で、再利用可能なピースから複雑な入力パイプラインを構築することを可能にします。例えば、画像モデルのためのパイプラインは分散ファイルシステムのファイルからデータを集め、各画像にランダムな摂動 (= perturbation、ずれ) を適用し、そしてランダムに選択された画像を訓練のためのバッチにマージするかもしません。テキスト・モデルのためのパイプラインは生テキスト・データからシンボルを抽出し、それらを検索テーブルを持つ埋め込み認識装置へと変換し、そして異なる長さのシークエンスをまとめてバッチ処理することを伴うかもしれません。tf.data API は巨大な総量のデータ、異なるデータフォーマット、そして複雑な変換を処理することを容易にします。

tf.data API は TensorFlow に2つの抽象を導入します :

  • tf.data.Dataset は要素のシークエンスを表わし、そこでは各要素は一つまたはそれ以上の Tensor オブジェクトを含みます。例えば画像パイプラインでは、(一つの) 要素は画像データとラベルを表わす Tensor のペアを持つ単一の訓練サンプルかもしれません。データセットを作成するために2つの異なる方法があります :
    • ソース (e.g. Dataset.from_tensor_slices()) の作成は一つまたそれ以上の tf.Tensor オブジェクトからデータセットを構築します。
    • 変換 (= transformation) (e.g. Dataset.batch()) の適用は一つまたはそれ以上の tf.data.Dataset オブジェクトから dataset を構築します。
  • tf.data.Iterator はデータセットから要素を抽出するための主要な方法を提供します。Iterator.get_next() で返される演算は実行されるときにデータセットの次の要素を生成し、典型的には入力パイプライン・コードと貴方のモデルの間のインターフェイスとして動作します。最も単純な iterator は “one-shot iterator” で、これは特定のデータセットに関連してそれを通して一度だけ iterate します。より洗練された利用のためには、Iterator.initializer 演算は異なるデータセットを持つ iterator を再初期化してパラメータ化します、その結果、例えば、同じプログラム内で訓練と検証データに渡り複数回 iterate できます。

 

基本メカニズム

(Programmer’s) ガイドのこのセクションは異なる種類の Dataset と Iterator オブジェクトの作成の基本と、それらからどのようにしてデータを抽出するかについて記述します。

入力パイプラインを開始するためには、ソースを定義しなければなりません。例えば、メモリ内の何某かの Tensor から Dataset を構築するために、tf.data.Dataset.from_tensors() か tf.data.Dataset.from_tensor_slices() を利用できます。あるいは、入力データが推奨される TFRecord フォーマットでディスク上にあるのであれば、tf.data.TFRecordDataset を構築できます。

Dataset オブジェクトをひとたび持てば、tf.data.Dataset オブジェクト上のメソッド呼び出しをチェインしてそれを新しい Dataset に変換することができます。例えば、(各要素に関数を適用するための) Dataset.map() のような要素毎の変換、そして Dataset.batch() のような複数要素変換を適用することができます。変換の完全なリストのためには tf.data.Dataset のドキュメントを見てください。

Dataset から値を消費する最も一般的な方法は、(例えば、Dataset.make_one_shot_iterator() を呼び出すことで) 一度にデータセットの一つの要素へのアクセスを提供する iterator オブジェクトを作成することです。tf.data.Iterator は2つの演算を提供します : Iterator.initializer, これは iterator の状態を (再) 初期化することを可能にします ; そして Iterator.get_next(), これは記号的に次の要素に相当する tf.Tensor オブジェクトを返します。貴方のユースケースに依拠して、異なるタイプの iterator を選択できます、そしてそのオプションは下で概説されます。

 

Dataset 構造

dataset は各々が同じ構造を持つ要素から成ります。(一つの) 要素は、コンポーネントと呼ばれる一つまたはそれ以上の tf.Tensor オブジェクトから成ります。各コンポーネントは要素の型を tensor で表わす tf.DType と、各要素の (多分部分的に指定された) 静的 shape を表わす tf.TensorShape を持ちます。Dataset.output_types と Dataset.output_shapes プロパティは dataset 要素の各コンポーネントの推論型と shape を探究することを可能にします。これらのプロパティのネストされた構造は要素の構造へとマップされ、これは単一の tensor、tensor のタプル, または tensor のネストされたタプルが取れます。例えば :

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random_uniform([4, 10]))
print(dataset1.output_types)  # ==> "tf.float32"
print(dataset1.output_shapes)  # ==> "(10,)"

dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random_uniform([4]),
    tf.random_uniform([4, 100], maxval=100, dtype=tf.int32)))
print(dataset2.output_types)  # ==> "(tf.float32, tf.int32)"
print(dataset2.output_shapes)  # ==> "((), (100,))"

dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
print(dataset3.output_types)  # ==> (tf.float32, (tf.float32, tf.int32))
print(dataset3.output_shapes)  # ==> "(10, ((), (100,)))"

要素の各コンポーネントに名前を与えることはしばしば便利です、例えばそれらが訓練サンプルの異なる特徴を表わす場合です。タプルに加えて、Dataset の単一の要素を表わすために collections.namedtuple か文字列を tensor にマップする辞書を使用することができます。

dataset = tf.data.Dataset.from_tensor_slices(
   {"a": tf.random_uniform([4]),
    "b": tf.random_uniform([4, 100], maxval=100, dtype=tf.int32)})
print(dataset.output_types)  # ==> "{'a': tf.float32, 'b': tf.int32}"
print(dataset.output_shapes)  # ==> "{'a': (), 'b': (100,)}"

Dataset 変換は任意の構造の dataset をサポートします。各要素に関数を適用する、Dataset.map(), Dataset.flat_map(), と Dataset.filter() 変換を使用するとき、要素構造は関数の引数を決定します :

dataset1 = dataset1.map(lambda x: ...)

dataset2 = dataset2.flat_map(lambda x, y: ...)

# Note: Argument destructuring is not available in Python 3.
dataset3 = dataset3.filter(lambda x, (y, z): ...)

 

iterator を作成する

ひとたび貴方の入力データを表わす Dataset を構築したのであれば、次のステップはその dataset から要素にアクセスするための Iterator を作成することです。tf.data API は現在次の iterator をサポートします、洗練レベルを上げながら :

  • one-shot,
  • initializable,
  • reinitializable, そして
  • feedable.

one-shot iterator は iterator の最も単純な形式で、これは dataset を通して一度だけ iterate することをサポートするだけで、明示的な初期化は必要ありません。one-shot iterator は既存の queue-ベースの入力パイプラインがサポートする殆ど総てのケースを扱いますが、それらはパラメータ化はサポートしません。Dataset.range() のサンプルを使用しています :

dataset = tf.data.Dataset.range(100)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()

for i in range(100):
  value = sess.run(next_element)
  assert i == value

Note: 現在、one-shot iterator は Estimator と共に簡単に利用可能な唯一のタイプです。

initializable iterator はそれを使用する前に明示的な iterator.initializer を実行することを要求します。不便さと引き換えに、それは (iterator を初期化するときに供給可能な) 一つまたはそれ以上の tf.placeholder() tensor を使用して dataset の定義をパラメータ化します。Dataset.range() サンプルで続けます :

max_value = tf.placeholder(tf.int64, shape=[])
dataset = tf.data.Dataset.range(max_value)
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()

# Initialize an iterator over a dataset with 10 elements.
sess.run(iterator.initializer, feed_dict={max_value: 10})
for i in range(10):
  value = sess.run(next_element)
  assert i == value

# Initialize the same iterator over a dataset with 100 elements.
sess.run(iterator.initializer, feed_dict={max_value: 100})
for i in range(100):
  value = sess.run(next_element)
  assert i == value

reinitializable iterator は複数の異なる Dataset オブジェクトか初期化できます。例えば、貴方は、一般化を改善するために入力画像にランダム摂動を使用する訓練入力パイプラインと変更されていないデータ上の予測を評価する検証入力パイプラインを持つかもしれません。これらのパイプラインは典型的には同じ構造を持つ (i.e. 各コンポーネントに対して同じタイプと互換な shape) 異なる Dataset オブジェクトを使用するでしょう。

# Define training and validation datasets with the same structure.
training_dataset = tf.data.Dataset.range(100).map(
    lambda x: x + tf.random_uniform([], -10, 10, tf.int64))
validation_dataset = tf.data.Dataset.range(50)

# A reinitializable iterator is defined by its structure. We could use the
# `output_types` and `output_shapes` properties of either `training_dataset`
# or `validation_dataset` here, because they are compatible.
iterator = tf.data.Iterator.from_structure(training_dataset.output_types,
                                           training_dataset.output_shapes)
next_element = iterator.get_next()

training_init_op = iterator.make_initializer(training_dataset)
validation_init_op = iterator.make_initializer(validation_dataset)

# Run 20 epochs in which the training dataset is traversed, followed by the
# validation dataset.
for _ in range(20):
  # Initialize an iterator over the training dataset.
  sess.run(training_init_op)
  for _ in range(100):
    sess.run(next_element)

  # Initialize an iterator over the validation dataset.
  sess.run(validation_init_op)
  for _ in range(50):
    sess.run(next_element)

feedable iterator は、良く知られた feed_dict メカニズムを通して tf.Session.run への各呼び出しにおいてどの iterator を使用するかを選択するために tf.placeholder と一緒に利用可能です。それは reinitializable iterator と同じ機能を提供しますが、iterator 間で切り替えるときに dataset の最初から iterator を初期化することを要求しません。例えば、上からの同じ訓練と検証サンプルを使用して、2つの dataset 間で切り替えることを可能にする feedable iterator を定義するために tf.data.Iterator.from_string_handle を使用できます :

# Define training and validation datasets with the same structure.
training_dataset = tf.data.Dataset.range(100).map(
    lambda x: x + tf.random_uniform([], -10, 10, tf.int64)).repeat()
validation_dataset = tf.data.Dataset.range(50)

# A feedable iterator is defined by a handle placeholder and its structure. We
# could use the `output_types` and `output_shapes` properties of either
# `training_dataset` or `validation_dataset` here, because they have
# identical structure.
handle = tf.placeholder(tf.string, shape=[])
iterator = tf.data.Iterator.from_string_handle(
    handle, training_dataset.output_types, training_dataset.output_shapes)
next_element = iterator.get_next()

# You can use feedable iterators with a variety of different kinds of iterator
# (such as one-shot and initializable iterators).
training_iterator = training_dataset.make_one_shot_iterator()
validation_iterator = validation_dataset.make_initializable_iterator()

# The `Iterator.string_handle()` method returns a tensor that can be evaluated
# and used to feed the `handle` placeholder.
training_handle = sess.run(training_iterator.string_handle())
validation_handle = sess.run(validation_iterator.string_handle())

# Loop forever, alternating between training and validation.
while True:
  # Run 200 steps using the training dataset. Note that the training dataset is
  # infinite, and we resume from where we left off in the previous `while` loop
  # iteration.
  for _ in range(200):
    sess.run(next_element, feed_dict={handle: training_handle})

  # Run one pass over the validation dataset.
  sess.run(validation_iterator.initializer)
  for _ in range(50):
    sess.run(next_element, feed_dict={handle: validation_handle})

 

iterator からの値を消費する

Iterator.get_next() メソッドは iterator のシンボリックな次の要素に相当する一つまたはそれ以上の tf.Tensor オブジェクトを返します。これらの tensor が評価されるたびに、それらは基礎となる dataset の次の要素の値を取ります。(TensorFlow の他のステートフル・オブジェクトのように、Iterator.get_next() の呼び出しは iterator を直ちには進めません。代わりに貴方は TensorFlow 式の返された tf.Tensor オブジェクトを使用して、そして次の要素を取り iterator を進めるためにその式の結果を tf.Session.run() に渡さなければなりません。)

iterator が dataset の終わりに達した場合は、Iterator.get_next() 演算の実行は tf.errors.OutOfRangeError を上げるでしょう。このポイント以後、iterator は利用負荷の状態にあり、それを更に使用することを望む場合にはそれをサイド初期化しなければなりません。

dataset = tf.data.Dataset.range(5)
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()

# Typically `result` will be the output of a model, or an optimizer's
# training operation.
result = tf.add(next_element, next_element)

sess.run(iterator.initializer)
print(sess.run(result))  # ==> "0"
print(sess.run(result))  # ==> "2"
print(sess.run(result))  # ==> "4"
print(sess.run(result))  # ==> "6"
print(sess.run(result))  # ==> "8"
try:
  sess.run(result)
except tf.errors.OutOfRangeError:
  print("End of dataset")  # ==> "End of dataset"

一般的なパターンは「訓練ループ」を try-except ブロックでラップします :

sess.run(iterator.initializer)
while True:
  try:
    sess.run(result)
  except tf.errors.OutOfRangeError:
    break

もし dataset の各要素がネストされた構造を持つのであれば、Iterator.get_next() の返し値は同じネストされた構造内の一つまたはそれ以上の tf.Tensor オブジェクトになるでしょう :

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random_uniform([4, 10]))
dataset2 = tf.data.Dataset.from_tensor_slices((tf.random_uniform([4]), tf.random_uniform([4, 100])))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

iterator = dataset3.make_initializable_iterator()

sess.run(iterator.initializer)
next1, (next2, next3) = iterator.get_next()

next1, next2, と next3 は (Iterator.get_next() で作成された) 同じ op/ノードにより生成された tensor であることに注意してください。従って、これらの tensor のどれかを評価すれば総てのコンポーネントのために iterator を進めるでしょう。iterator の典型的な消費者は単一の式で総てのコンポーネントを含むでしょう。

 

iterator 状態をセーブする

tf.contrib.data.make_saveable_from_iterator 関数は iterator からSaveableObject を作成します、これは iterator の (そして、効果的に、入力パイプライン全体の) 現在の状態をセーブしてリストアするために使用できます。こうして作成されたセーブ可能なオブジェクトは tf.Variable と同じ方法でセーブしてリストアするために tf.train.Saver 変数リストか tf.GraphKeys.SAVEABLE_OBJECT コレクションに追加されます。変数をどのようにセーブしてリストアするかについての詳細は セーブとリストア を参照してください。

# Create saveable object from iterator.
saveable = tf.contrib.data.make_saveable_from_iterator(iterator)

# Save the iterator state by adding it to the saveable objects collection.
tf.add_to_collection(tf.GraphKeys.SAVEABLE_OBJECTS, saveable)
saver = tf.train.Saver()

with tf.Session() as sess:

  if should_checkpoint:
    saver.save(path_to_checkpoint)

# Restore the iterator state.
with tf.Session() as sess:
  saver.restore(sess, path_to_checkpoint)

 

入力データを読む

NumPy 配列を消費する

貴方の入力データ総てがメモリにおさまる場合には、それらから Dataset を作成する最も単純な方法はそれらを tf.Tensor オブジェクトに変換して Dataset.from_tensor_slices() を使用することです。

# Load the training data into two NumPy arrays, for example using `np.load()`.
with np.load("/var/data/training_data.npy") as data:
  features = data["features"]
  labels = data["labels"]

# Assume that each row of `features` corresponds to the same row as `labels`.
assert features.shape[0] == labels.shape[0]

dataset = tf.data.Dataset.from_tensor_slices((features, labels))

上のコード・スニペットは features と labels 配列を TensorFlow グラフ内に tf.constant() 演算として埋め込むことに注意してください。これは小さなデータセットに対しては上手く動作しますが、メモリを浪費して — 何故ならば配列の内容が複数回コピーされるので — そして tf.GraphDef protocol buffer の 2 GB 制限に達するかもしれません。

代わりに、tf.placeholder() tensor の観点から Dataset を定義して、そして dataset に渡り Iterator を初期化するとき NumPy 配列を供給することができます。

# Load the training data into two NumPy arrays, for example using `np.load()`.
with np.load("/var/data/training_data.npy") as data:
  features = data["features"]
  labels = data["labels"]

# Assume that each row of `features` corresponds to the same row as `labels`.
assert features.shape[0] == labels.shape[0]

features_placeholder = tf.placeholder(features.dtype, features.shape)
labels_placeholder = tf.placeholder(labels.dtype, labels.shape)

dataset = tf.data.Dataset.from_tensor_slices((features_placeholder, labels_placeholder))
# [Other transformations on `dataset`...]
dataset = ...
iterator = dataset.make_initializable_iterator()

sess.run(iterator.initializer, feed_dict={features_placeholder: features,
                                          labels_placeholder: labels})

 

TFRecord データを消費する

tf.data API は、メモリにおさまらない巨大なデータセットを処理できるように様々なフォーマットをサポートしています。例えば、TFRecord ファイル・フォーマットは単純なレコード指向バイナリ・フォーマットで、多くの TensorFlow アプリケーションが訓練データのために使用します。tf.data.TFRecordDataset クラスは一つまたはそれ以上の TFRecord ファイルの内容に渡って入力パイプラインの一部として流れることを可能にします。

# Creates a dataset that reads all of the examples from two files.
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)

TFRecordDataset initializer への filenames 引数は文字列、文字列のリスト、あるいは文字列の tf.Tensor のいずれかです。従って訓練と検証目的の2セットのファイルを持つ場合には、filenames を表わすために tf.placeholder(tf.string) を使用して適切な filenames から iterator を初期化することができます :

filenames = tf.placeholder(tf.string, shape=[None])
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...)  # Parse the record into tensors.
dataset = dataset.repeat()  # Repeat the input indefinitely.
dataset = dataset.batch(32)
iterator = dataset.make_initializable_iterator()

# You can feed the initializer with the appropriate filenames for the current
# phase of execution, e.g. training vs. validation.

# Initialize `iterator` with training data.
training_filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
sess.run(iterator.initializer, feed_dict={filenames: training_filenames})

# Initialize `iterator` with validation data.
validation_filenames = ["/var/data/validation1.tfrecord", ...]
sess.run(iterator.initializer, feed_dict={filenames: validation_filenames})

 

テキストデータを消費する

多くの dataset は一つまたはそれ以上のテキストファイルとして分散されます。tf.data.TextLineDataset は一つまたはそれ以上のテキストファイルから行を抽出するための簡単な方法を提供します。一つまたはそれ以上の filenames が与えられた場合、TextLineDataset はそれらのファイルの行毎の一つの文字列値の要素を生成するでしょう。TFRecordDataset のように、TextLineDataset は tf.Tensor として filenames を受け取り、tf.placeholder(tf.string) を渡すことによりそれをパラメータ化できます。

filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]
dataset = tf.data.TextLineDataset(filenames)

デフォルトでは、TextLineDataset は各ファイルの総ての行を生成しますが、これは望ましくはないかもしれません、例えばファイルがヘッダ行で始まったり、コメントを含む場合です。これらの行は Dataset.skip() と Dataset.filter() 変換を使用して除去できます。これらの変換を各ファイルに別々に適用するために、各ファイルのためにネストされた Dataset を作成するために Dataset.flat_map() を使用します。

filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]

dataset = tf.data.Dataset.from_tensor_slices(filenames)

# Use `Dataset.flat_map()` to transform each file as a separate nested dataset,
# and then concatenate their contents sequentially into a single "flat" dataset.
# * Skip the first line (header row).
# * Filter out lines beginning with "#" (comments).
dataset = dataset.flat_map(
    lambda filename: (
        tf.data.TextLineDataset(filename)
        .skip(1)
        .filter(lambda line: tf.not_equal(tf.substr(line, 0, 1), "#"))))

 

Dataset.map() でデータを前処理する

Dataset.map(f) 変換は与えられた関数 f を入力 dataset の各要素に適用することにより新しい dataset を生成します。それは map() 関数 をベースとし、これは関数型プログラミング言語において一般にリスト (そして他の構造) に適用されます。関数 f は入力の単一要素を表わす tf.Tensor オブジェクトを取り、新しい dataset の単一の要素を表わす tf.Tensor オブジェクトを返します。その実装は一つの要素を他のひとつに変換するために標準的な TensorFlow 演算を使用します。

このセクションは Dataset.map() をどのように使用するかの一般的なサンプルをカバーします。

 

tf.Example protocol buffer メッセージを解析する

多くの入力パイプラインは (例えば、tf.python_io.TFRecordWriter を使用して書かれた) TFRecord フォーマットファイルから tf.train.Example protocol buffer メッセージを抽出します。各 tf.train.Example レコードは一つまたそれ以上の “features” を含み、入力パイプラインは典型的にはこれらの features を tensor に変換します。

# Transforms a scalar string `example_proto` into a pair of a scalar string and
# a scalar integer, representing an image and its label, respectively.
def _parse_function(example_proto):
  features = {"image": tf.FixedLenFeature((), tf.string, default_value=""),
              "label": tf.FixedLenFeature((), tf.int32, default_value=0)}
  parsed_features = tf.parse_single_example(example_proto, features)
  return parsed_features["image"], parsed_features["label"]

# Creates a dataset that reads all of the examples from two files, and extracts
# the image and label features.
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(_parse_function)

 

画像データをデコードしてそれをリサイズする

現実世界の画像データ上でニューラルネットワークを訓練するとき、異なるサイズの画像をそれらが固定サイズにバッチ処理されるように共通サイズに変換することはしばしば必要です。

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def _parse_function(filename, label):
  image_string = tf.read_file(filename)
  image_decoded = tf.image.decode_image(image_string)
  image_resized = tf.image.resize_images(image_decoded, [28, 28])
  return image_resized, label

# A vector of filenames.
filenames = tf.constant(["/var/data/image1.jpg", "/var/data/image2.jpg", ...])

# `labels[i]` is the label for the image in `filenames[i].
labels = tf.constant([0, 37, ...])

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.map(_parse_function)

 

任意の Python ロジックを tf.py_func() で適用する

パフォーマンス的な理由で、可能なときはいつでも貴方のデータの前処理に TensorFlow 演算を使用することを勧めます。けれども、貴方の入力データを解析するときに外部の Python ライブラリに頼むことは時々有用です。それを行なうためには、Dataset.map() 変換内で tf.py_func() 演算を呼び起こします。

import cv2

# Use a custom OpenCV function to read the image, instead of the standard
# TensorFlow `tf.read_file()` operation.
def _read_py_function(filename, label):
  image_decoded = cv2.imread(filename.decode(), cv2.IMREAD_GRAYSCALE)
  return image_decoded, label

# Use standard TensorFlow operations to resize the image to a fixed shape.
def _resize_function(image_decoded, label):
  image_decoded.set_shape([None, None, None])
  image_resized = tf.image.resize_images(image_decoded, [28, 28])
  return image_resized, label

filenames = ["/var/data/image1.jpg", "/var/data/image2.jpg", ...]
labels = [0, 37, 29, 1, ...]

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.map(
    lambda filename, label: tuple(tf.py_func(
        _read_py_function, [filename, label], [tf.uint8, label.dtype])))
dataset = dataset.map(_resize_function)

 

dataset 要素をバッチ処理する

単純なバッチ処理

バッチ処理の最も単純な形式は dataset の n 連続的な要素を単一の要素にスタックすることです。Dataset.batch() 変換は正確にこれを行ないます、tf.stack(0 演算子と同じ制約を伴い、要素の各コンポーネントに適用されます : i.e. 各コンポーネント i について、総ての要素は正確な同じ shape の tensor を持たなければなりません。

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

iterator = batched_dataset.make_one_shot_iterator()
next_element = iterator.get_next()

print(sess.run(next_element))  # ==> ([0, 1, 2,   3],   [ 0, -1,  -2,  -3])
print(sess.run(next_element))  # ==> ([4, 5, 6,   7],   [-4, -5,  -6,  -7])
print(sess.run(next_element))  # ==> ([8, 9, 10, 11],   [-8, -9, -10, -11])

 

パディングを持つ tensor をバッチ処理

上のレシピは同じサイズを持つ tensor 総てのためには動作します。けれども、多くのモデル (e.g. シークエンス・モデル) は変化するサイズを持てる入力データ (e.g. 異なる長さのシークエンス) で動作します。このケースを処理するために、Dataset.padded_batch() 変換は、(そこでパッドされるかもしれない) 一つまたはそれ以上に次元を指定することにより異なる shape の tensor をバッチ処理することを可能にします。

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=[None])

iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()

print(sess.run(next_element))  # ==> [[0, 0, 0], [1, 0, 0], [2, 2, 0], [3, 3, 3]]
print(sess.run(next_element))  # ==> [[4, 4, 4, 4, 0, 0, 0],
                               #      [5, 5, 5, 5, 5, 0, 0],
                               #      [6, 6, 6, 6, 6, 6, 0],
                               #      [7, 7, 7, 7, 7, 7, 7]]

Dataset.padded_batch() 変換は各コンポーネントの各次元に対する異なるパディングを設定することを可能にし、そしてそれは (上のサンプルでは None で表わさている) 可変長または定数長かもしれません。デフォルトが 0 のパディング値を override することもまた可能です。

 

訓練ワークフロー

複数エポックを処理する

tf.data API は同じデータの複数のエポックを処理するために2つの主要な方法を提供します。

複数のエポックで dataset に渡り iterate するための最も単純な方法は Dataset.repeat() 変換を使用することです。例えば、入力を 10 エポック繰り返す dataset を作成するためには :

filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...)
dataset = dataset.repeat(10)
dataset = dataset.batch(32)

Dataset.repeat() 変換を引数なしで適用することは入力を無期限に繰り返すでしょう。Dataset.repeat() は一つのエポックの終わりと次のエポックの最初をシグナルすることなくその引数を結合します。

各エポックの終わりでシグナルを受け取ることを望むのであれば、dataset の終わりに tf.errors.OutOfRangeError を catch する訓練ループを書くことができます。そのポイントで貴方は各エポックのための統計情報 (e.g., 検証エラー) を収集することができます。

filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...)
dataset = dataset.batch(32)
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()

# Compute for 100 epochs.
for _ in range(100):
  sess.run(iterator.initializer)
  while True:
    try:
      sess.run(next_element)
    except tf.errors.OutOfRangeError:
      break

  # [Perform end-of-epoch calculations here.]

 

入力データをランダムにシャッフルする

Dataset.shuffle() 変換は tf.RandomShuffleQueue に類似したアルゴリズムを使用して入力 dataset をランダムにシャッフルします : それは固定長バッファを保持してバッファから次の要素を一様にランダムに選択します。

filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...)
dataset = dataset.shuffle(buffer_size=10000)
dataset = dataset.batch(32)
dataset = dataset.repeat()

 

高位 API を使用する

tf.train.MonitoredTrainingSession API は分散設定で TensorFlow を実行する多くの局面を単純化します。MonitoredTrainingSession は訓練が完了したことをシグナルするために tf.errors.OutOfRangeError を使用し、それを tf.data API で使用するためには、Dataset.make_one_shot_iterator() を使用することを推奨します。例えば :

filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...)
dataset = dataset.shuffle(buffer_size=10000)
dataset = dataset.batch(32)
dataset = dataset.repeat(num_epochs)
iterator = dataset.make_one_shot_iterator()

next_example, next_label = iterator.get_next()
loss = model_function(next_example, next_label)

training_op = tf.train.AdagradOptimizer(...).minimize(loss)

with tf.train.MonitoredTrainingSession(...) as sess:
  while not sess.should_stop():
    sess.run(training_op)

tf.estimator.Estimator の input_fn で Dataset を使用するためにもまた、Dataset.make_one_shot_iterator() を使用することを推奨します。例えば :

def dataset_input_fn():
  filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
  dataset = tf.data.TFRecordDataset(filenames)

  # Use `tf.parse_single_example()` to extract data from a `tf.Example`
  # protocol buffer, and perform any additional per-record preprocessing.
  def parser(record):
    keys_to_features = {
        "image_data": tf.FixedLenFeature((), tf.string, default_value=""),
        "date_time": tf.FixedLenFeature((), tf.int64, default_value=""),
        "label": tf.FixedLenFeature((), tf.int64,
                                    default_value=tf.zeros([], dtype=tf.int64)),
    }
    parsed = tf.parse_single_example(record, keys_to_features)

    # Perform additional preprocessing on the parsed data.
    image = tf.image.decode_jpeg(parsed["image_data"])
    image = tf.reshape(image, [299, 299, 1])
    label = tf.cast(parsed["label"], tf.int32)

    return {"image_data": image, "date_time": parsed["date_time"]}, label

  # Use `Dataset.map()` to build a pair of a feature dictionary and a label
  # tensor for each example.
  dataset = dataset.map(parser)
  dataset = dataset.shuffle(buffer_size=10000)
  dataset = dataset.batch(32)
  dataset = dataset.repeat(num_epochs)
  iterator = dataset.make_one_shot_iterator()

  # `features` is a dictionary in which each value is a batch of values for
  # that feature; `labels` is a batch of labels.
  features, labels = iterator.get_next()
  return features, labels
 

以上



AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com