TensorFlow : Guide : Estimators : Estimator のためのデータセット (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 07/14/2018
作成日時 : 03/18/2018
* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページは、TensorFlow の本家サイトの Guide – Datasets for Estimators を翻訳した上で適宜、補足説明したものです:
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
tf.data モジュールはクラスのコレクションを含み、これらは簡単にデータをロードし、それを操作し、それを貴方のモデルに運ぶことを可能にします。このドキュメントは2つの単純なサンプルを一通り説明することで API を紹介します :
- numpy 配列から in-memory データを読む。
- csv ファイルから行を読む。
基本入力
配列からスライスを取ることは tf.data で始めるために最も単純な方法です。
Premade Estimator の章ではデータを Estimator にパイプするために iris_data.py からの次の train_input_fn を記述しています :
def train_input_fn(features, labels, batch_size): """An input function for training""" # Convert the inputs to a Dataset. dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels)) # Shuffle, repeat, and batch the examples. dataset = dataset.shuffle(1000).repeat().batch(batch_size) # Build the Iterator, and return the read end of the pipeline. return dataset.make_one_shot_iterator().get_next()
これをより密接に見てみましょう。
引数
この関数は3つの引数を想定しています。
「配列」を想定する引数は numpy.array で配列に変換できるどのようなものでも殆ど受け取ることができます。
一つの例外は タプル で、これは Datasets のために特別な意味を持ちます。
- features: 生の入力特徴を含む {‘feature_name’:array} 辞書 (または DataFrame) です。
- labels : 各サンプルのための ラベル を含む配列です。
- batch_size : 望まれるバッチサイズを示す整数。
premade_estimator.py では iris_data.load_data() 関数を使用してアイリス・データを回収します。貴方はそれを実行して、次のように結果をアンパックすることができます :
import iris_data # Fetch the data train, test = iris_data.load_data() features, labels = train
それからこれに類似した行でこのデータを入力関数に渡しました :
batch_size=100 iris_data.train_input_fn(features, labels, batch_size)
train_input_fn() を一通り見てみましょう。
スライス
最も単純なケースでは、tf.data.Dataset.from_tensor_slices 関数は配列を取り配列のスライスを表わす tf.data.Dataset を返します。例えば、mnist 訓練データ を含む配列は shape (60000, 28, 28) を持ちます。これを from_tensor_slices に渡すと 60000 スライスを含む Dataset オブジェクトを返し、各々の一つは 28×28 画像です。
Dataset を返すコードは次のようなものです :
train, test = tf.keras.datasets.mnist.load_data() mnist_x, mnist_y = train mnist_ds = tf.data.Dataset.from_tensor_slices(mnist_x) print(mnist_ds)
これは次の行をプリントし、dataset の項目の shape と 型 を示します。dataset はそれが幾つの項目を含むかは知らないことに注意してください。
<TensorSliceDataset shapes: (28,28), types: tf.uint8>
上の dataset は単純な配列のコレクションを表しますが、dataset はこれよりも遥かによりパワフルです。dataset は辞書かタプルのネストされた組み合わせを透過的に処理します。例えば、次のように features が標準的な辞書であることを確かなものとしてから、配列の辞書を辞書の dataset に変換することができます :
dataset = tf.data.Dataset.from_tensor_slices(dict(features)) print(dataset)
<TensorSliceDataset shapes: { SepalLength: (), PetalWidth: (), PetalLength: (), SepalWidth: ()}, types: { SepalLength: tf.float64, PetalWidth: tf.float64, PetalLength: tf.float64, SepalWidth: tf.float64} >
ここで Dataset が構造化された要素を含むとき、Dataset の shape と型は同じ構造を持つことを見ることができます。この dataset は スカラー の辞書を含み、総てが tf.float64 型です。
train_input_fn の最初の行は同じ機能を使用しますが、構造の他のレベルも追加します。それは (features, labels) ペアを含む dataset を作成します。
次のコードは label が int64 型のスカラーであることを示します。
# Convert the inputs to a Dataset. dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels)) print(dataset)
<TensorSliceDataset shapes: ( { SepalLength: (), PetalWidth: (), PetalLength: (), SepalWidth: ()}, ()), types: ( { SepalLength: tf.float64, PetalWidth: tf.float64, PetalLength: tf.float64, SepalWidth: tf.float64}, tf.int64)>
操作
現在 Dataset はデータに渡り固定された順序で一度だけ iterate します、そして一度に単一の要素を生成するだけです。訓練のために使用可能となる前に更なる処理を必要とします。幸い、tf.data.Dataset クラスは訓練のためのデータをより良い準備をするためのメソッドを提供します。入力関数の次の行はこれらのメソッドの幾つかを活用します :
# Shuffle, repeat, and batch the examples. dataset = dataset.shuffle(1000).repeat().batch(batch_size)
shuffle メソッドは項目をシャッフルするために (それらが通過するとき) 固定長のバッファを使用します。Dataset のサンプル数よりも大きい buffer_size の設定はデータが完全にシャッフルされることを確かなものにします。アイリス・データセットは 150 サンプルを含むだけです。
repeat メソッドは Dataset をそれが最後に達したとき再スタートさせます。エポック数を制限するためには、count 引数を設定します。
batch メソッドはバッチを作成するために、多数のサンプルを集めてそれらをスタックします。これはそれらの shape に次元を追加します。新しい次元は最初の次元として追加されます。次のコードは (前からの) MNIST Dataset 上で batch メソッドを使用します。これは (28, 28) 画像のスタックを表わす 3D 配列を含む Dataset になります :
print(mnist_ds.batch(100))
<BatchDataset shapes: (?, 28, 28), types: tf.uint8>
最後のバッチがより少ない要素を持つでしょうから dataset は未知のバッチサイズを持つことに注意してください。train_input_fn では、バッチ化の後 Dataset は要素の 1D ベクトルを含みます、そこでは各スカラーは以前は :
print(dataset)
<TensorSliceDataset shapes: ( { SepalLength: (?,), PetalWidth: (?,), PetalLength: (?,), SepalWidth: (?,)}, (?,)), types: ( { SepalLength: tf.float64, PetalWidth: tf.float64, PetalLength: tf.float64, SepalWidth: tf.float64}, tf.int64)>
Return
総ての Estimator の train, evaluate, と predict メソッドは tensorflow テンソル を含む (features, label) ペアを返す入力関数を必要とします。train_input_fn は Dataset を想定されるフォーマットに変換するために次の行を使用します :
# Build the Iterator, and return the read end of the pipeline. features_result, labels_result = dataset.make_one_shot_iterator().get_next()
結果は TensorFlow テンソル の構造です、Dataset の項目のレイアウトに適合します。これらのオブジェクトが何かそしてそれらとどのように作業するかへのイントロダクションのためには、Introduction を見てください。
print((features_result, labels_result))
({ 'SepalLength':, 'PetalWidth': , 'PetalLength': , 'SepalWidth': }, Tensor("IteratorGetNext_1:4", shape=(?,), dtype=int64))
CSV ファイルを読む
Dataset クラスのための最も一般的な現実世界のユースケースはディスク上のファイルからデータをストリームします。tf.data モジュールは様々なファイル・リーダーを含みます。Dataset を利用した csv ファイルからのアイリス・データセットの解析がどのようなものか見てみましょう。
iris_data.maybe_download 関数への次の呼び出しは必要であればデータをダウンロードし、結果としてのファイルのパス名を返します :
import iris_data train_path, test_path = iris_data.maybe_download()
iris_data.csv_input_fn 関数は Dataset を使用して csv ファイルを解析する代わりの実装を含みます。
ローカル・ファイルから読む Estimator 互換の入力関数をどのように構築するかを見てみましょう。
Dataset を構築する
一度にファイルの一行を読むための TextLineDataset オブジェクトを構築することから始めます。それから、サンプルではなくヘッダを含む、ファイルの最初の行をスキップするために skip メソッドを呼び出します :
ds = tf.data.TextLineDataset(train_path).skip(1)
csv 行パーサーを構築する
結局のところは必要な (features, label) ペアを生成するためには dataset の各行を解析する必要があるでしょう。
単一行を解析するための関数を構築することから始めます。
次の iris_data.parse_line 関数は tf.decode_csv と幾つかの単純な python コードを使用してこのタスクを成し遂げます :
必要な (features, label) ペアを生成するためには dataset の各行を解析しなければなりません。次の _parse_line 関数は単一行をその特徴とラベルに解析するために tf.decode_csv を呼び出します。Estimator はその特徴が辞書として表わされることを要求しますので、その辞書を構築するために Python の組み込み辞書と zip 関数に依拠します。特徴名はその辞書のキーです。それから特徴辞書から label フィールドを取り除くために辞書の pop メソッドを呼び出します :
# Metadata describing the text columns COLUMNS = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth', 'label'] FIELD_DEFAULTS = [[0.0], [0.0], [0.0], [0.0], [0]] def _parse_line(line): # Decode the line into its fields fields = tf.decode_csv(line, FIELD_DEFAULTS) # Pack the result into a dictionary features = dict(zip(COLUMNS,fields)) # Separate the label from the features label = features.pop('label') return features, label
行を解析する
Dataset は (モデルにパイプされている) データを操作するために多くのメソッドを持ちます。最も頻繁に使用されるメソッドは map で、これは Dataset の各要素に変換を適用します。
map メソッドは map_func 引数を取ります、これは Dataset の各項目がどのように変換されるべきかを記述します。
行を解析するためにはそれらが csv ファイルからストリームされるとき、_parse_line 関数を map メソッドに渡します :
ds = ds.map(_parse_line) print(ds)
<MapDataset shapes: ( {SepalLength: (), PetalWidth: (), ...}, ()), types: ( {SepalLength: tf.float32, PetalWidth: tf.float32, ...}, tf.int32)>
今では単純なスカラー文字列の代わりに、dataset は (features, label) ペアを含みます。
iris_data.csv_input_fn 関数の残りは、基本入力のセクションでカバーされた iris_data.train_input_fn と同一です。
試してみましょう
この関数は iris_data.train_input_fn の置き換えとして使用できます。それは次のように estimator に供給するために使用されます :
train_path, test_path = iris_data.maybe_download() # All the inputs are numeric feature_columns = [ tf.feature_column.numeric_column(name) for name in iris_data.CSV_COLUMN_NAMES[:-1]] # Build the estimator est = tf.estimator.LinearClassifier(feature_columns, n_classes=3) # Train the estimator batch_size = 100 est.train( steps=1000, input_fn=lambda : iris_data.csv_input_fn(train_path, batch_size))
Estimator は input_fn が引数を取らないことを想定しています。この制限を回避するために、引数を捕捉するために lambda を使用して想定するインターフェイスを提供します。
以上