tf.data:TensorFlow入力パイプラインを構築する

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロード

tf.data APIは、単純な、再利用可能なものから複雑な入力パイプラインを構築することができます。たとえば、画像モデルのパイプラインは、分散ファイルシステム内のファイルからデータを集約し、各画像にランダムな摂動を適用し、ランダムに選択された画像をトレーニング用のバッチにマージする場合があります。テキストモデルのパイプラインには、生のテキストデータからシンボルを抽出し、それらをルックアップテーブルを使用して埋め込み識別子に変換し、さまざまな長さのシーケンスをバッチ処理することが含まれる場合があります。 tf.data APIは、それが可能な、大量のデータを扱う異なるデータ形式から読み出され、複雑な変換を行うことが可能となります。

tf.data APIを導入A tf.data.Dataset抽象各要素は、1つのまたは複数のコンポーネントから構成されている要素のシーケンスを表します。たとえば、画像パイプラインでは、要素は単一のトレーニング例であり、画像とそのラベルを表すテンソルコンポーネントのペアがあります。

データセットを作成するには、2つの異なる方法があります。

  • データソースは、作成Datasetメモリ内に1つのまたは複数のファイルに格納されたデータから。

  • データ変換は、一つ以上のデータセットから構築tf.data.Datasetオブジェクトを。

import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

np.set_printoptions(precision=4)

基本的な仕組み

入力パイプラインを作成するには、データ・ソースで開始する必要があります。例えば、構築するためにDatasetメモリ内のデータからは、使用することができますtf.data.Dataset.from_tensors()またはtf.data.Dataset.from_tensor_slices()あなたの入力データが推奨TFRecord形式のファイルに保存されている場合あるいは、あなたが使用することができますtf.data.TFRecordDataset()

あなたが持ってたら、 Datasetオブジェクトを、あなたは新しいにそれを変換することができDatasetのメソッド呼び出しの連鎖によってtf.data.Datasetオブジェクトを。たとえば、次のような要素毎の変換を適用することができるDataset.map()およびなどの多要素変換Dataset.batch()ドキュメントを参照してくださいtf.data.Dataset変換の完全なリストについて。

Datasetオブジェクトは、Pythonの反復可能です。これにより、forループを使用してその要素を消費することが可能になります。

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

または明示的に使用してPythonのイテレータを作成することにより、 iterして使用してその要素を消費してnext

it = iter(dataset)

print(next(it).numpy())
8

あるいは、データセット要素が使用消費することができるreduce単一の結果を生成するためにすべての要素を減少させる変換を、。次の例では、使用する方法を示しreduce整数のセットの和を計算するための変換を。

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22

データセットの構造

データセットは、各要素が構成要素の同一の(ネストされた)構造である要素のシーケンスを生成します。構造体の個々の成分は、によって任意の種類の表現であってもよいtf.TypeSpec含む、 tf.Tensortf.sparse.SparseTensortf.RaggedTensortf.TensorArray 、又はtf.data.Dataset

要素の(ネストされた)構造を発現させるために使用することができるPythonの構築物は、 tupledictNamedTuple 、及びOrderedDict 。具体的には、 listデータセットの要素の構造を表現するために有効な構築物ではありません。早期tf.dataユーザーが強く感じたからですlist (例えばに渡された入力tf.data.Dataset.from_tensors )を自動的テンソルととしてパックされているlistに強制された出力(ユーザー定義関数の例戻り値) tuple 。結果として、あなたが希望の場合は、 list構造として処理されるべき入力を、あなたはに変換する必要があるtuple 、あなたが希望の場合はlist出力は、単一の成分であることが、あなたは明示的に使用し、それをパックする必要がtf.stack

Dataset.element_specプロパティを使用すると、各要素部品の種類を検査することができます。プロパティは、入れ子構造返しtf.TypeSpec単一成分、構成要素の組、または構成要素のネストされたタプルとすることができる素子の構造と一致する、オブジェクトを。例えば:

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))

dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None),
 TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None),
 (TensorSpec(shape=(), dtype=tf.float32, name=None),
  TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))

dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor

Dataset変換は、任意の構造のデータセットをサポートしています。使用する場合Dataset.map()及びDataset.filter()各要素に関数を適用する変換を、素子構造は、関数の引数を決定します。

dataset1 = tf.data.Dataset.from_tensor_slices(
    tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))

dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[5 4 1 7 2 7 2 7 5 5]
[9 5 9 2 4 5 8 9 7 7]
[5 4 3 9 8 4 8 2 8 2]
[2 4 1 6 1 1 5 5 8 2]
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
for a, (b,c) in dataset3:
  print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)

入力データの読み取り

NumPy配列の消費

参照してくださいnumpyのアレイをロードする例については。

メモリ内の入力データのフィットのすべて、作成する最も簡単な方法ならばDataset彼らからは、それらを変換することですtf.Tensorオブジェクトおよび使用Dataset.from_tensor_slices()

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

Pythonジェネレーターの使用

簡単として摂取することができる別の共通のデータソースtf.data.Dataset Pythonのジェネレータです。

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
0
1
2
3
4

Dataset.from_generatorコンストラクタは完全に機能するには、Pythonのジェネレータを変換tf.data.Dataset

コンストラクターは、イテレーターではなく、呼び出し可能オブジェクトを入力として受け取ります。これにより、ジェネレーターが最後に到達したときにジェネレーターを再起動できます。これはオプションとるargs呼び出し可能なの引数として渡される引数を、。

output_typesための引数が必要であるtf.data構築tf.Graph内部、およびグラフのエッジは必要tf.dtype

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

output_shapes引数は必須ではありませんが、多くのtensorflow操作が不明ランクのテンソルをサポートしていないと非常にお奨めします。特定の軸の長さが不明または可変である場合、として設定Noneoutput_shapes

これは、ことに注意することも重要ですoutput_shapesoutput_types他のデータセットのメソッドと同じネストの規則に従ってください。

これは両方の側面を示すジェネレータの例です。配列のタプルを返します。2番目の配列は長さが不明なベクトルです。

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : []
1 : [0.018]
2 : [-0.1799 -0.4538 -1.7808  1.2569 -0.77  ]
3 : [ 0.1728  1.0476 -1.3632  1.2148  1.0274  0.7899]
4 : [-1.5616  0.8348 -0.7334]
5 : [ 0.0777  1.5326 -2.0872]
6 : [-0.0785 -0.3282  0.2665]

第一の出力は、 int32秒であるfloat32

最初の項目は、スカラー、形状である()及び第二は、未知の長さ、形状のベクトルで(None,)

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>

今では、通常のように使用することができますtf.data.Dataset 。変数形でデータセットをバッチ処理するとき、あなたが使用する必要があることを注意Dataset.padded_batch

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[13  7  5 17 19 14 12 26 16  6]

[[ 0.976   0.2999  1.1758 -0.7253  0.5655  0.    ]
 [-0.5697 -0.3878 -0.8197  0.8563  1.1502 -0.3431]
 [ 0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.    ]
 [-0.415   1.5524  0.0372  0.8279  0.      0.    ]
 [-2.1594  0.3963  0.5639 -0.1209  0.6403  0.7756]
 [-2.0557  0.3314  0.      0.      0.      0.    ]
 [-0.3955  0.0811 -0.4472  0.      0.      0.    ]
 [-0.7648 -0.8468  0.      0.      0.      0.    ]
 [ 0.3892  0.      0.      0.      0.      0.    ]]

より現実的な例については、ラッピングしてみてくださいpreprocessing.image.ImageDataGenerator通りtf.data.Dataset

最初にデータをダウンロードします。

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
228818944/228813984 [==============================] - 2s 0us/step
228827136/228813984 [==============================] - 2s 0us/step

作成image.ImageDataGenerator

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3)
float32 (32, 5)
ds = tf.data.Dataset.from_generator(
    lambda: img_gen.flow_from_directory(flowers), 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None),
 TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
  print('images.shape: ', images.shape)
  print('labels.shape: ', labels.shape)
Found 3670 images belonging to 5 classes.
images.shape:  (32, 256, 256, 3)
labels.shape:  (32, 5)

TFRecordデータの消費

参照してくださいロードTFRecordsをエンドツーエンド例えば。

tf.dataあなたがメモリに収まらない大規模なデータセットを処理できるように、APIは、さまざまなファイル形式をサポートしています。たとえば、TFRecordファイル形式は、多くのTensorFlowアプリケーションがデータのトレーニングに使用する単純なレコード指向のバイナリ形式です。 tf.data.TFRecordDatasetクラスは、入力パイプラインの一部として、一つ以上のTFRecordファイルの内容経由でストリーミングすることができます。

これは、フランスの道路標識(FSNS)のテストファイルを使用した例です。

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 0s 0us/step
7913472/7904079 [==============================] - 0s 0us/step

filenames引数TFRecordDatasetいずれかの文字列、文字列のリスト、またはすることができイニシャライザtf.Tensor文字列の。したがって、トレーニングと検証の目的で2セットのファイルがある場合は、ファイル名を入力引数として使用して、データセットを生成するファクトリメソッドを作成できます。

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

多くのTensorFlowプロジェクトは、シリアライズ使用tf.train.Example彼らのTFRecordファイル内のレコードを。これらは、検査する前にデコードする必要があります。

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

parsed.features.feature['image/text']
bytes_list {
  value: "Rue Perreyon"
}

テキストデータの消費

参照してくださいロードテキストを終了例に終わりを。

多くのデータセットは、1つ以上のテキストファイルとして配布されます。 tf.data.TextLineDataset 1つまたは複数のテキストファイルから行を抽出するための簡単な方法を提供します。一の以上のファイル名が与えられ、 TextLineDatasetそれらのファイルの1行に1つの文字列値の要素を生成します。

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

最初のファイルの最初の数行は次のとおりです。

for line in dataset.take(5):
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b'His wrath pernicious, who ten thousand woes'
b"Caused to Achaia's host, sent many a soul"
b'Illustrious into Ades premature,'
b'And Heroes gave (so stood the will of Jove)'

ファイル間の代替ラインに使用Dataset.interleave 。これにより、ファイルをまとめてシャッフルするのが簡単になります。各翻訳の1行目、2行目、3行目は次のとおりです。

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

b'His wrath pernicious, who ten thousand woes'
b'The vengeance, deep and deadly; whence to Greece'
b'countless ills upon the Achaeans. Many a brave soul did it send'

b"Caused to Achaia's host, sent many a soul"
b'Unnumbered ills arose; which many a soul'
b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

デフォルトによって、 TextLineDatasetファイルがヘッダ行で始まり、またはコメントを含む場合、例えば、望ましくないかもしれない各ファイルのすべての行をもたらします。これらの線は、使用して除去することができるDataset.skip()またはDataset.filter()変換を。ここでは、最初の行をスキップしてから、フィルターをかけて生存者のみを検索します。

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
32768/30874 [===============================] - 0s 0us/step
40960/30874 [=======================================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'

CSVデータの消費

参照してください。 CSVファイルをロードし、ロードパンダデータフレームの詳細例について。

CSVファイル形式は、表形式のデータをプレーンテキストで保存するための一般的な形式です。

例えば:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file)
df.head()

メモリ内のデータのフィットが同じであればDataset.from_tensor_slices方法は、このデータを簡単にインポートすることができ、辞書上で動作します:

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived'          : 0
  'sex'               : b'male'
  'age'               : 22.0
  'n_siblings_spouses': 1
  'parch'             : 0
  'fare'              : 7.25
  'class'             : b'Third'
  'deck'              : b'unknown'
  'embark_town'       : b'Southampton'
  'alone'             : b'n'

よりスケーラブルなアプローチは、必要に応じてディスクからロードすることです。

tf.dataモジュールを遵守一つ以上のCSVファイルから抽出レコードにする方法を提供RFC 4180

experimental.make_csv_dataset機能は、CSVファイルのセットを読み取るための高レベルのインタフェースです。使用を簡単にするために、列タイプの推論や、バッチ処理やシャッフルなどの他の多くの機能をサポートしています。

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 1 0 0]
features:
  'sex'               : [b'male' b'female' b'male' b'male']
  'age'               : [30. 26. 31. 20.]
  'n_siblings_spouses': [1 0 0 0]
  'parch'             : [0 0 0 0]
  'fare'              : [24.      7.925  10.5     7.8542]
  'class'             : [b'Second' b'Third' b'Second' b'Third']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Cherbourg' b'Southampton' b'Southampton' b'Southampton']
  'alone'             : [b'n' b'y' b'y' b'y']

あなたは使用することができますselect_columnsあなただけの列のサブセットが必要な場合は、引数を。

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [1 1 0 0]
  'fare'              : [110.8833  41.5792   8.05    10.5   ]
  'class'             : [b'First' b'Second' b'Third' b'Second']

低レベルもありますexperimental.CsvDatasetより細かい制御を提供するクラスが。列タイプの推測はサポートしていません。代わりに、各列のタイプを指定する必要があります。

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
[1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
[1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
[0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
[0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
[1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
[0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

一部の列が空の場合、この低レベルのインターフェースを使用すると、列タイプの代わりにデフォルト値を提供できます。

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[1 2 3 4]
[999   2   3   4]
[  1 999   3   4]
[  1   2 999   4]
[  1   2   3 999]
[999 999 999 999]

デフォルトによって、 CsvDatasetファイルは無視されるべきヘッダ行で始まる場合、またはいくつかの列が入力に必要とされていない場合、例えば、望ましくないかもしれないファイルのすべての行のすべての列が得られます。これらのラインおよびフィールドを用いて除去することができheaderselect_colsそれぞれ引数。

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[2 4]
[2 4]
[999   4]
[2 4]
[  2 999]
[999 999]

ファイルのセットを消費する

ファイルのセットとして配布される多くのデータセットがあり、各ファイルは例です。

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

ルートディレクトリには、各クラスのディレクトリが含まれています。

for item in flowers_root.glob("*"):
  print(item.name)
sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion

各クラスディレクトリのファイルは例です。

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/4560613196_91a04f8dcf_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/8223949_2928d3f6f6_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/15951588433_c0713cbfc6_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/2960610406_b61930727f_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/4809566219_88f9a1aea3.jpg'

使用してデータを読み取るtf.io.read_file関数と戻り、経路からラベルを抽出する(image, label)対。

def process_path(file_path):
  label = tf.strings.split(file_path, os.sep)[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xe2\x0cXICC_PROFILE\x00\x01\x01\x00\x00\x0cHLino\x02\x10\x00\x00mntrRGB XYZ \x07\xce\x00\x02\x00\t\x00\x06\x001\x00\x00acspMSFT\x00\x00\x00\x00IEC sRGB\x00\x00\x00\x00\x00\x00'

b'daisy'

データセット要素のバッチ処理

簡単なバッチ処理

スタックバッチ処理の最も単純な形態n単一の要素へのデータセットの連続した要素。 Dataset.batch()変換が正確にこれは、同じ制約をんtf.stack()演算子、要素の各コンポーネントに適用される:すなわち、各成分iについて、全ての要素がまったく同じ形状のテンソルを有していなければなりません。

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)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
[array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
[array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
[array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

一方でtf.data試みは、形状情報を伝播するために、デフォルトの設定Dataset.batch未知のバッチサイズでの結果は、最後のバッチが満杯ではないかもしれないので。注意None形状で秒:

batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>

使用drop_remainderその最後のバッチを無視し、完全な形の伝播を取得するには、引数を:

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>

パディングを使用したテンソルのバッチ処理

上記のレシピは、すべて同じサイズのテンソルで機能します。ただし、多くのモデル(シーケンスモデルなど)は、さまざまなサイズ(長さの異なるシーケンスなど)の入力データを処理します。このような場合に対処するために、 Dataset.padded_batch変換は、彼らが埋められている1つ以上のディメンションを指定することにより、異なる形状のバッチテンソルにできます。

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,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()
[[0 0 0]
 [1 0 0]
 [2 2 0]
 [3 3 3]]

[[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であるパディング値をオーバーライドすることもできます。

トレーニングワークフロー

複数のエポックの処理

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

複数のエポックでデータセットを反復処理する最も簡単な方法は、使用することであるDataset.repeat()変換を。まず、タイタニックデータのデータセットを作成します。

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

適用Dataset.repeat()引数なしで変換すると、無期限の入力を繰り返します。

Dataset.repeat変換は、1つのエポックの終わりと次のエポックの始まりを知らせることなく、その引数を連結します。このためDataset.batch後に適用Dataset.repeatストラドルエポック境界というバッチを得られます:

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

png

あなたは明確なエポック分離が必要な場合は、入れDataset.batch繰り返し前に:

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

png

各エポックの最後にカスタム計算(統計の収集など)を実行する場合は、各エポックでデータセットの反復を再開するのが最も簡単です。

epochs = 3
dataset = titanic_lines.batch(128)

for epoch in range(epochs):
  for batch in dataset:
    print(batch.shape)
  print("End of epoch: ", epoch)
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  0
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  1
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  2

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

Dataset.shuffle()変換は、固定サイズのバッファを維持し、そのバッファからランダムに一様に次の要素を選択します。

効果を確認できるように、データセットにインデックスを追加します。

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/counter.py:66: scan (from tensorflow.python.data.experimental.ops.scan_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.scan(...) instead
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

以来buffer_size 100であり、そしてバッチサイズは20であり、最初のバッチは、120以上のインデックスを持つ要素が含まれていません。

n,line_batch = next(iter(dataset))
print(n.numpy())
[75  3 49 55 24 30  8 10 91 94 88 40 52 67 89 41 33 63 79 26]

同様にDataset.batchへの相対的な順序Dataset.repeat事項。

Dataset.shuffleシャッフルバッファが空になるまでエポックの終了を通知しません。したがって、繰り返しの前に配置されたシャッフルは、次のエポックに移動する前に、あるエポックのすべての要素を表示します。

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[502 531 625 473 532 600 585 607 381 598]
[544 341 583 586 577 603 377 468 492 512]
[588 562 584 602 596 592 550 286]
[ 40  94   2  58  53  95  77 105  79  20]
[ 16  19  46  72   9 114 108  30  50 103]
shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fd46416f590>

png

しかし、シャッフルの前に繰り返すと、エポックの境界が混ざり合います。

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[579 613 483 542 370   9 622 201  26  30]
[595 431 584  20 419 406  18 606 626 562]
[603  39  10 464  25  12 601  43 620  40]
[598  13 581 540  44  15  54 568 478 591]
[ 36  52  53  34   8  62 597 550 599 614]
[593  68 565 617 520 619 604 611  28 572]
[546  71 233  45  29  38  83  80 605 434]
[556  77 575 526  51 552  55  33  56  58]
[559 502  91  61 618  23  66 103 498 609]
[ 76  81   4  83 446 113   1 100 107 474]
[ 21 596 578 104 602  69 126 588  78 102]
[112  19  64  93 131 531 580  60 409   0]
[  7  97  14 138 115  42 141  17 623  95]
[117   2  70  16 114  31 157 151 133 499]
[111 140 144  30 544 105 135 148 506 167]
repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fd42079ab10>

png

データの前処理

Dataset.map(f)変換は、所定の関数を適用することにより、新しいデータセットを生成f入力データセットの各要素に。それは基づいているmap()関数型プログラミング言語では、一般的にリストに適用される機能(およびその他の構造)。関数fかかりtf.Tensor入力における単一の要素を表すオブジェクトを、そして返しtf.Tensorそれが新しいデータセット内の単一の要素を表現するオブジェクト。その実装では、標準のTensorFlow操作を使用して、ある要素を別の要素に変換します。

このセクションでは、使用方法の一般的な例について説明しDataset.map()

画像データのデコードとサイズ変更

実世界の画像データでニューラルネットワークをトレーニングする場合、固定サイズにバッチ処理できるように、さまざまなサイズの画像を共通のサイズに変換する必要があることがよくあります。

花のファイル名データセットを再構築します。

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

データセット要素を操作する関数を記述します。

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
  parts = tf.strings.split(filename, os.sep)
  label = parts[-2]

  image = tf.io.read_file(filename)
  image = tf.image.decode_jpeg(image)
  image = tf.image.convert_image_dtype(image, tf.float32)
  image = tf.image.resize(image, [128, 128])
  return image, label

それが機能することをテストします。

file_path = next(iter(list_ds))
image, label = parse_image(file_path)

def show(image, label):
  plt.figure()
  plt.imshow(image)
  plt.title(label.numpy().decode('utf-8'))
  plt.axis('off')

show(image, label)

png

データセットにマッピングします。

images_ds = list_ds.map(parse_image)

for image, label in images_ds.take(2):
  show(image, label)

png

png

任意のPythonロジックを適用する

パフォーマンス上の理由から、可能な限りTensorFlow操作を使用してデータを前処理します。ただし、入力データを解析するときに外部Pythonライブラリを呼び出すと便利な場合があります。あなたは使用することができますtf.py_function()での動作をDataset.map()変換。

あなたはランダムな回転を適用する場合たとえば、 tf.imageモジュールのみを持っているtf.image.rot90画像増大のために非常に有用ではありません。

実証するためにtf.py_function 、使用してみてくださいscipy.ndimage.rotate代わりに機能を:

import scipy.ndimage as ndimage

def random_rotate_image(image):
  image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
  return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

で、この機能を使用するにはDataset.map同じ警告を持つように適用Dataset.from_generator 、あなたが関数を適用する場合、リターン形状や種類を記述する必要があります。

def tf_random_rotate_image(image, label):
  im_shape = image.shape
  [image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
  image.set_shape(im_shape)
  return image, label
rot_ds = images_ds.map(tf_random_rotate_image)

for image, label in rot_ds.take(2):
  show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

png

解析tf.Exampleプロトコルバッファメッセージ

多くの入力パイプラインは、抽出tf.train.Example TFRecord形式からプロトコルバッファメッセージを。各tf.train.Exampleレコードは1つ以上の「機能」を含んでおり、入力されたパイプラインは、一般的テンソルにこれらの機能を変換します。

fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

あなたはで動作することができますtf.train.Exampleのプロト外tf.data.Datasetデータを理解するために:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])

png

raw_example = next(iter(dataset))
def tf_parse(eg):
  example = tf.io.parse_example(
      eg[tf.newaxis], {
          'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
          'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
      })
  return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon'
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...
decoded = dataset.map(tf_parse)
decoded
<MapDataset shapes: ((), ()), types: (tf.string, tf.string)>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])

時系列ウィンドウ処理

終了時系列の例に終わりを参照:時系列予測を

時系列データは、多くの場合、時間軸をそのままにして編成されます。

簡単な使用Dataset.range実証します:

range_ds = tf.data.Dataset.range(100000)

通常、この種のデータに基づくモデルでは、連続したタイムスライスが必要になります。

最も簡単なアプローチは、データをバッチ処理することです。

使用してbatch

batches = range_ds.batch(10, drop_remainder=True)

for batch in batches.take(5):
  print(batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
[40 41 42 43 44 45 46 47 48 49]

または、将来に向けて密な予測を行うために、フィーチャとラベルを相互に1ステップずつシフトすることができます。

def dense_1_step(batch):
  # Shift features and labels one step relative to each other.
  return batch[:-1], batch[1:]

predict_dense_1_step = batches.map(dense_1_step)

for features, label in predict_dense_1_step.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8]  =>  [1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18]  =>  [11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28]  =>  [21 22 23 24 25 26 27 28 29]

固定オフセットではなくウィンドウ全体を予測するには、バッチを2つの部分に分割します。

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
  return (batch[:-5],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

for features, label in predict_5_steps.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]  =>  [25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42 43 44]

1回分の特徴や他のラベルの間にいくつかの重複を許可するには、使用Dataset.zip

feature_length = 10
label_length = 3

features = range_ds.batch(feature_length, drop_remainder=True)
labels = range_ds.batch(feature_length).skip(1).map(lambda labels: labels[:label_length])

predicted_steps = tf.data.Dataset.zip((features, labels))

for features, label in predicted_steps.take(5):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42]
[40 41 42 43 44 45 46 47 48 49]  =>  [50 51 52]

使用してwindow

使用している間Dataset.batch作品を、あなたはより細かい制御を必要とするかもしれない状況があります。 Dataset.window方法はあなたに完全に制御できますが、いくつか注意が必要です。それが返すDatasetDatasets 。参照データセットの構造を詳細については。

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>

Dataset.flat_map方法は、データセットのデータセットを取り、1つのデータセットにそれを平らにすることができます:

for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9

ほぼすべてのケースでは、あなたがしたいと思うでしょう.batch最初のデータセットを:

def sub_to_batch(sub):
  return sub.batch(window_size, drop_remainder=True)

for example in windows.flat_map(sub_to_batch).take(5):
  print(example.numpy())
[0 1 2 3 4]
[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]
[4 5 6 7 8]

さて、あなたは見ることができますshift引数を制御どのくらいの各ウィンドウの移動の上に。

これをまとめると、次の関数を作成できます。

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
  windows = ds.window(window_size, shift=shift, stride=stride)

  def sub_to_batch(sub):
    return sub.batch(window_size, drop_remainder=True)

  windows = windows.flat_map(sub_to_batch)
  return windows
ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)

for example in ds.take(10):
  print(example.numpy())
[ 0  3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34 37]
[15 18 21 24 27 30 33 36 39 42]
[20 23 26 29 32 35 38 41 44 47]
[25 28 31 34 37 40 43 46 49 52]
[30 33 36 39 42 45 48 51 54 57]
[35 38 41 44 47 50 53 56 59 62]
[40 43 46 49 52 55 58 61 64 67]
[45 48 51 54 57 60 63 66 69 72]

次に、前と同じように、ラベルを簡単に抽出できます。

dense_labels_ds = ds.map(dense_1_step)

for inputs,labels in dense_labels_ds.take(3):
  print(inputs.numpy(), "=>", labels.numpy())
[ 0  3  6  9 12 15 18 21 24] => [ 3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]

リサンプリング

クラスが非常に不均衡なデータセットを操作する場合は、データセットをリサンプリングすることをお勧めします。 tf.dataこれを行うには二つの方法が用意されています。クレジットカード詐欺データセットは、この種の問題の良い例です。

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
    fname='creditcard.zip',
    extract=True)

csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip
69156864/69155632 [==============================] - 1s 0us/step
69165056/69155632 [==============================] - 1s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

ここで、クラスの分布を確認してください。非常に偏っています。

def count(counts, batch):
  features, labels = batch
  class_1 = labels == 1
  class_1 = tf.cast(class_1, tf.int32)

  class_0 = labels == 0
  class_0 = tf.cast(class_0, tf.int32)

  counts['class_0'] += tf.reduce_sum(class_0)
  counts['class_1'] += tf.reduce_sum(class_1)

  return counts
counts = creditcard_ds.take(10).reduce(
    initial_state={'class_0': 0, 'class_1': 0},
    reduce_func = count)

counts = np.array([counts['class_0'].numpy(),
                   counts['class_1'].numpy()]).astype(np.float32)

fractions = counts/counts.sum()
print(fractions)
[0.9958 0.0042]

不均衡なデータセットを使用したトレーニングの一般的なアプローチは、バランスを取ることです。 tf.dataこのワークフローを有効にするいくつかのメソッドが含まれています。

データセットのサンプリング

データセットをリサンプリングするための一つのアプローチは、使用することですsample_from_datasets 。あなたは別の持っているとき、これは、より適用されdata.Dataset各クラスのを。

ここでは、フィルターを使用して、クレジットカード詐欺データからそれらを生成します。

negative_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==0)
    .repeat())
positive_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==1)
    .repeat())
for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

使用するにtf.data.experimental.sample_from_datasets 、データセット、およびそれぞれの重みを渡します。

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/interleave_ops.py:260: RandomDataset.__init__ (from tensorflow.python.data.ops.dataset_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.random(...)`.

これで、データセットは50/50の確率で各クラスの例を生成します。

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[0 1 1 1 0 1 0 1 0 1]
[1 0 0 0 1 0 1 0 0 0]
[1 1 1 0 0 0 1 0 1 0]
[0 0 1 0 0 0 0 1 0 1]
[0 1 0 0 0 1 1 1 1 1]
[0 1 0 1 0 1 1 0 0 1]
[1 0 0 1 1 1 0 0 1 1]
[1 0 0 1 1 0 1 1 0 1]
[1 1 1 0 1 1 0 1 1 1]
[1 1 0 0 0 1 1 1 0 0]

拒否のリサンプリング

上記の問題の一つexperimental.sample_from_datasetsアプローチは、それが別の必要があることですtf.data.Datasetクラスあたりを。使用Dataset.filter動作しますが、すべてのデータの結果が二回ロードされています。

data.experimental.rejection_resample関数は一度だけそれをロードしている間、それをリバランスするためにデータセットに適用することができます。バランスをとるために、要素はデータセットから削除されます。

data.experimental.rejection_resampleかかるclass_func引数を。このclass_func各データセット要素に印加され、実施例は分散の目的のために属するクラスを決定するために使用されます。

要素creditcard_ds 、すでにある(features, label)のペア。だから、 class_funcちょうどそれらのラベルを返す必要があります:

def class_func(features, label):
  return label

リサンプラーには、ターゲット分布と、オプションで初期分布推定も必要です。

resampler = tf.data.experimental.rejection_resample(
    class_func, target_dist=[0.5, 0.5], initial_dist=fractions)

個々の例でリサンプラ取引は、あなたがしなければならないunbatchリサンプラを適用する前に、データセットを:

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/resampling.py:159: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:

リサンプラ戻る作成(class, example)の出力から、ペアをclass_func 。この場合、 exampleすでにでした(feature, label)のペアなので、使用mapラベルの余分なコピーを削除するには:

balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)

これで、データセットは50/50の確率で各クラスの例を生成します。

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
Proportion of examples rejected by sampler is high: [0.995800793][0.995800793 0.00419921894][0 1]
[1 0 0 1 0 1 0 0 1 1]
[0 1 0 1 0 0 0 1 0 1]
[0 0 0 0 0 0 1 1 0 0]
[1 0 0 1 0 1 0 1 0 1]
[1 0 1 1 1 0 1 1 0 0]
[0 1 0 0 0 0 1 1 0 1]
[1 1 1 1 1 0 0 1 0 0]
[0 0 1 1 0 1 1 1 0 0]
[0 0 1 1 1 0 1 0 0 0]
[0 0 1 1 0 1 0 0 0 0]

イテレータチェックポイント

Tensorflowサポートチェックポイントを取っているので、その進捗状況のほとんどを回復するために、最新のチェックポイントを復元することができたときにあなたのトレーニングのプロセスが再起動していること。モデル変数のチェックポイントに加えて、データセットイテレーターの進行状況をチェックポイントすることもできます。これは、大規模なデータセットがあり、再起動するたびにデータセットを最初から開始したくない場合に役立ちます。反復子チェックポイントのような変換ので、大きくてもよいことに注意してくださいshuffle及びprefetchイテレータ内の緩衝要素を必要とします。

チェックポイントであなたのイテレータを含めるには、へのイテレータを渡しtf.train.Checkpointコンストラクタ。

range_ds = tf.data.Dataset.range(20)

iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)

print([next(iterator).numpy() for _ in range(5)])

save_path = manager.save()

print([next(iterator).numpy() for _ in range(5)])

ckpt.restore(manager.latest_checkpoint)

print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]

tf.kerasでtf.dataを使用する

tf.keras APIは、機械学習モデルを作成し、実行の多くの側面を簡素化します。その.fit().evaluate().predict() APIは、入力としてデータセットをサポートしています。簡単なデータセットとモデルの設定は次のとおりです。

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)

model = tf.keras.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              metrics=['accuracy'])

データセットを渡す(feature, label)のペアは、のために必要だことすべてでModel.fitModel.evaluate

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.5909 - accuracy: 0.8016
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4605 - accuracy: 0.8416
<keras.callbacks.History at 0x7fd4203a1790>

あなたが呼び出すことにより、例えば、無限のデータセットを、渡すとDataset.repeat()あなただけのも渡す必要がsteps_per_epoch引数を:

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.5205 - accuracy: 0.8109
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4074 - accuracy: 0.8641
<keras.callbacks.History at 0x7fd420221350>

評価のために、あなたは評価ステップの数を渡すことができます:

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4352 - accuracy: 0.8516
Loss : 0.4352473020553589
Accuracy : 0.8516333103179932

長いデータセットの場合、評価するステップ数を設定します。

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.3755 - accuracy: 0.8875
Loss : 0.3755129277706146
Accuracy : 0.887499988079071

ラベルは、呼び出すときに必要とされていないModel.predict

predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)

ただし、ラベルを含むデータセットを渡すと、ラベルは無視されます。

result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)