![]() | ![]() | ![]() | ![]() |
tf.data
APIを使用すると、単純で再利用可能な部分から複雑な入力パイプラインを構築できます。たとえば、画像モデルのパイプラインは、分散ファイルシステム内のファイルからデータを集約し、各画像にランダムな摂動を適用し、ランダムに選択された画像をトレーニング用のバッチにマージする場合があります。テキストモデルのパイプラインには、生のテキストデータからシンボルを抽出し、それらをルックアップテーブルを使用して埋め込み識別子に変換し、異なる長さのシーケンスをバッチ処理することが含まれる場合があります。 tf.data
APIを使用すると、大量のデータを処理したり、さまざまなデータ形式から読み取ったり、複雑な変換を実行したりできます。
tf.data
APIは、要素のシーケンスを表すtf.data.Dataset
抽象化を導入します。各要素は、1つ以上のコンポーネントで構成されます。たとえば、画像パイプラインでは、要素は単一のトレーニング例であり、画像とそのラベルを表すテンソルコンポーネントのペアがあります。
データセットを作成するには、2つの異なる方法があります。
データソースは、メモリまたは1つ以上のファイルに格納されている
Dataset
からデータ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
からデータDataset
を構築するには、 tf.data.Dataset.from_tensors()
またはtf.data.Dataset.from_tensor_slices()
使用できます。または、入力データが推奨されるTFRecord形式のファイルに保存されている場合は、 tf.data.TFRecordDataset()
使用できます。
Dataset
オブジェクトをtf.data.Dataset
、tf.data.Dataset
オブジェクトのメソッド呼び出しをチェーンすることで、それを新しい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
または、 iter
を使用してPythonイテレータを明示的に作成し、 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.Tensor
、 tf.sparse.SparseTensor
、tf.RaggedTensor
、 tf.TensorArray
、またはtf.data.Dataset
。
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())
[4 6 7 3 1 1 6 7 3 7] [6 6 1 7 3 8 9 8 9 4] [2 3 2 2 7 1 8 8 5 9] [6 6 7 8 8 9 2 3 7 8]
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()
を使用すること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 Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz 26427392/26421880 [==============================] - 1s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz 8192/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
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
として簡単に取り込むことができるもう1つの一般的なデータソースは、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
引数は必須ではありませんが、多くのテンソルフロー操作はランクが不明なテンソルをサポートしていないため、強くお勧めします。特定の軸の長さが不明または可変である場合は、 output_shapes
None
に設定します。
output_shapes
とoutput_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.8423 -0.1016 0.2763 0.815 0.0137 0.1228 0.0773] 1 : [ 0.4419 0.6819 -0.576 ] 2 : [-0.8961 -0.8613 -0.5917 0.7749 -0.2283 0.4406 -2.4833 0.1952 0.9962] 3 : [] 4 : [0.2609 0.854 2.96 ] 5 : [] 6 : [ 1.0899 -0.377 0.4295 -1.835 -0.4915 -0.0435 -0.6999 -0.9527]
最初の出力はint32
、2番目の出力はfloat32
です。
最初の項目はスカラーであるshape ()
であり、2番目の項目は未知の長さのベクトルであるshape (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
を使用する必要があることに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())
[12 0 21 20 19 2 13 6 16 15] [[ 0.6409 0. 0. 0. 0. 0. 0. 0. 0. ] [-0.3158 -1.1566 0.5766 0.2067 0.2566 -0.7567 0. 0. 0. ] [ 1.703 0. 0. 0. 0. 0. 0. 0. 0. ] [ 1.577 0. 0. 0. 0. 0. 0. 0. 0. ] [ 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [-1.1427 1.779 1.5403 0.428 -0.0309 0.8038 -0.4779 0.3646 -0.3527] [-1.0069 0.6749 -1.4268 0.0887 0.4798 0.769 0.5454 0. 0. ] [-0.3393 0.5725 -0.8578 -3.5323 -0.9053 0.261 -1.7785 0.5377 -0.4388] [ 0.5343 1.609 -0.9407 1.1031 0.4216 0. 0. 0. 0. ] [ 1.1637 0.6195 1.6317 -0.759 -0.4261 -3.2933 1.9672 -0.2561 1.341 ]]
より現実的な例については、ラッピングしてみてください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 [==============================] - 11s 0us/step
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データの消費
エンドツーエンドの例については、 TFRecordのロードを参照してください。
tf.data
APIはさまざまなファイル形式をサポートしているため、メモリに収まらない大きなデータセットを処理できます。たとえば、TFRecordファイル形式は、多くのTensorFlowアプリケーションがデータのトレーニングに使用する単純なレコード指向のバイナリ形式です。 tf.data.TFRecordDataset
クラスを使用すると、入力パイプラインの一部として1つ以上の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
filenames
引数TFRecordDataset
いずれかの文字列、文字列のリスト、またはすることができイニシャライザtf.Tensor
文字列の。したがって、トレーニングと検証の目的で2セットのファイルがある場合は、ファイル名を入力引数として使用して、データセットを生成するファクトリメソッドを作成できます。
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>
多くのTensorFlowプロジェクトは、TFRecordファイルでシリアル化されたtf.train.Example
レコードを使用します。これらは、検査する前にデコードする必要があります。
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つ以上のテキストファイルから行を抽出する簡単な方法を提供します。 1つ以上のファイル名を指定すると、 TextLineDataset
はそれらのファイルの行ごとに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 Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt 811008/809730 [==============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt 811008/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
使用し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
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ファイルの読み込みとPandasDataFrameの読み込みを参照してください。
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
モジュールは、 tf.data
準拠する1つ以上のCSVファイルからレコードを抽出するメソッドを提供します。
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 0 0 1] features: 'sex' : [b'male' b'male' b'male' b'female'] 'age' : [11. 16. 28. 19.] 'n_siblings_spouses': [5 4 0 0] 'parch' : [2 1 0 2] 'fare' : [46.9 39.6875 7.75 26.2833] 'class' : [b'Third' b'Third' b'Third' b'First'] 'deck' : [b'unknown' b'unknown' b'unknown' b'D'] 'embark_town' : [b'Southampton' b'Southampton' b'Queenstown' b'Southampton'] 'alone' : [b'n' b'n' b'y' b'n']
列のサブセットのみが必要な場合は、 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': [0 1 1 0] 'fare' : [ 0. 12.2875 30. 7.75 ] 'class' : [b'Second' b'Third' b'First' b'Third']
よりきめ細かい制御を提供する低レベルの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
はファイルのすべての行のすべての列を生成します。これは、たとえば、ファイルが無視する必要のあるヘッダー行で始まる場合や、入力に一部の列が不要な場合など、望ましくない場合があります。これらのラインおよびフィールドを用いて除去することができheader
とselect_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/sunflowers/4868595281_1e58083785.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/daisy/5883162120_dc7274af76_n.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/tulips/12883412424_cb5086b43f_n.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/roses/13264214185_d6aa79b3bd.jpg' b'/home/kbuilder/.keras/datasets/flower_photos/roses/6690926183_afedba9f15_n.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\x00\x00\x01\x00\x01\x00\x00\xff\xfe\x00\x0cAppleMark\n\xff\xe2\x05(ICC_PROFILE\x00\x01\x01\x00\x00\x05\x18appl\x02 \x00\x00scnrRGB XYZ \x07\xd3\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00acspAPPL\x00\x00\x00\x00' b'sunflowers'
データセット要素のバッチ処理
簡単なバッチ処理
バッチ処理の最も単純な形式は、データセットのn
連続する要素を1つの要素にスタックします。 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
トランスフォーメーションを使用すると、 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
トランスフォーメーションは、あるエポックの終了と次のエポックの開始を通知せずに、引数を連結します。このため、 Dataset.batch
後に適用されたDataset.repeat
は、エポック境界にまたがるバッチを生成します。
titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)
明確なエポック分離が必要な場合は、繰り返しの前にDataset.batch
配置しDataset.batch
。
titanic_batches = titanic_lines.batch(128).repeat(3)
plot_batch_sizes(titanic_batches)
各エポックの最後にカスタム計算(統計の収集など)を実行する場合は、各エポックでデータセットの反復を再開するのが最も簡単です。
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
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>
buffer_size
が100で、バッチサイズが20であるため、最初のバッチには、インデックスが120を超える要素は含まれていません。
n,line_batch = next(iter(dataset))
print(n.numpy())
[ 90 75 39 84 102 5 98 101 51 72 54 33 104 59 110 29 92 50 36 103]
Dataset.batch
同様に、 Dataset.batch
に関連するDataset.repeat
重要です。
Dataset.shuffle
は、シャッフルバッファーが空になるまで、エポックの終了を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: [550 618 614 435 556 530 578 451 590 604] [464 453 610 412 282 596 601 612 584 606] [368 469 575 607 586 537 444 300] [ 15 98 65 26 40 39 101 54 32 10] [ 8 102 68 108 12 96 2 87 80 37]
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 0x7f3f083eebe0>
しかし、シャッフルの前に繰り返すと、エポックの境界が混ざり合います。
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: [576 618 527 9 602 612 21 574 504 622] [623 26 32 616 626 482 617 598 0 614] [476 1 473 14 10 267 29 31 43 48] [588 13 470 467 12 596 619 46 28 528] [609 2 52 542 607 23 35 38 620 523] [509 477 571 15 56 74 565 525 58 19] [359 40 22 627 317 54 526 16 562 33] [ 67 500 584 531 49 86 51 81 78 583] [ 24 557 452 47 124 485 610 45 27 17] [379 66 85 91 599 97 499 112 108 11] [ 39 164 101 96 543 64 109 564 82 18] [533 120 30 63 115 88 95 75 133 34] [ 92 65 102 132 76 119 131 475 572 50] [ 94 145 144 603 152 505 621 140 448 122] [ 70 159 146 84 71 160 42 72 41 139]
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 0x7f3f0838c860>
データの前処理
Dataset.map(f)
変換は、入力データセットの各要素に特定の関数f
を適用することにより、新しいデータセットを生成します。これは、関数型プログラミング言語のリスト(およびその他の構造)に一般的に適用されるmap()
関数に基づいてい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)
データセットにマッピングします。
images_ds = list_ds.map(parse_image)
for image, label in images_ds.take(2):
show(image, label)
任意のPythonロジックを適用する
パフォーマンス上の理由から、可能な限りTensorFlow操作を使用してデータを前処理します。ただし、入力データを解析するときに外部Pythonライブラリを呼び出すと便利な場合があります。 Dataset.map()
変換でtf.py_function()
操作を使用できます。
たとえば、ランダムな回転を適用する場合、 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).
で、この機能を使用するには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).
tf.Example
プロトコルバッファメッセージの解析
多くの入力パイプラインは、TFRecord形式からtf.train.Example
プロトコルバッファメッセージを抽出します。各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
を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])
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
を使用して、 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つのステップで密な予測を行うには、フィーチャとラベルを相互に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], # Take the first 5 steps
batch[-5:]) # take the remainder
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]
あるバッチの機能と別のバッチのラベルの重複を許可するには、 Dataset.zip
使用し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.batch
ますが、より細かい制御が必要になる場合があります。 Dataset.window
メソッドは完全な制御を提供しますが、注意が必要です。 Dataset
のDatasets
Dataset
を返します。詳細については、データセットの構造を参照してください。
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
メソッドは、データセットのデータセットを取得し、それを単一のデータセットにフラット化できます。
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
は、これを行うための2つの方法を提供します。クレジットカード詐欺データセットは、この種の問題の良い例です。
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 [==============================] - 3s 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.9952 0.0048]
不均衡なデータセットを使用したトレーニングの一般的なアプローチは、バランスを取ることです。 tf.data
は、このワークフローを可能にするいくつかのメソッドが含まれています。
データセットのサンプリング
データセットをリサンプリングする1つの方法は、 sample_from_datasets
を使用すること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)
これで、データセットは50/50の確率で各クラスの例を生成します。
for features, labels in balanced_ds.take(10):
print(labels.numpy())
[1 0 1 0 1 1 0 0 1 0] [0 0 1 1 1 1 0 0 1 1] [1 1 0 1 1 0 1 1 0 0] [1 0 1 1 0 0 0 0 0 1] [1 1 0 1 1 0 0 0 1 0] [1 0 1 1 1 0 0 0 1 1] [0 1 1 0 0 0 1 0 1 0] [0 1 1 1 1 0 1 1 1 0] [0 0 1 1 1 1 0 0 1 1] [0 0 0 0 1 0 0 1 0 0]
拒否のリサンプリング
上記のexperimental.sample_from_datasets
アプローチの問題の1つは、クラスごとに個別のtf.data.Dataset
が必要なことです。 Dataset.filter
使用はDataset.filter
ますが、すべてのデータが2回読み込まれます。
data.experimental.rejection_resample
関数をデータセットに適用して、データセットを1回だけロードしながらリバランスすることができます。バランスをとるために、要素はデータセットから削除されます。
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.6/site-packages/tensorflow/python/data/experimental/ops/resampling.py:156: 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_func
出力からcreates (class, example)
ペアを返します。この場合、 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())
[0 1 0 1 1 1 0 1 1 1] [0 1 1 1 1 0 1 0 0 1] [1 0 1 1 0 1 0 1 1 1] [1 0 0 1 1 0 0 0 1 0] [1 1 1 1 1 0 0 0 1 0] [0 0 0 0 1 0 1 1 0 1] [0 1 0 1 1 1 0 1 1 0] [1 0 0 0 0 1 0 1 0 0] [0 1 1 1 0 1 1 1 1 0] [0 1 1 1 1 0 1 1 1 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'])
Model.fit
とModel.evaluate
必要なのは(feature, label)
ペアのデータセットを渡すことModel.evaluate
です。
model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2 1875/1875 [==============================] - 4s 2ms/step - loss: 0.7804 - accuracy: 0.7374 Epoch 2/2 1875/1875 [==============================] - 3s 2ms/step - loss: 0.4711 - accuracy: 0.8393 <tensorflow.python.keras.callbacks.History at 0x7f3ef05b1390>
たとえば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.4312 - accuracy: 0.8562 Epoch 2/2 20/20 [==============================] - 0s 2ms/step - loss: 0.4509 - accuracy: 0.8344 <tensorflow.python.keras.callbacks.History at 0x7f3ef062e198>
評価のために、いくつかの評価ステップを渡すことができます。
loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4347 - accuracy: 0.8516 Loss : 0.43466493487358093 Accuracy : 0.8515999913215637
長いデータセットの場合、評価するステップ数を設定します。
loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4131 - accuracy: 0.8750 Loss : 0.41311272978782654 Accuracy : 0.875
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)