tf.data: Buduj potoki wejściowe TensorFlow

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHubPobierz notatnik

tf.data API pozwala na tworzenie złożonych rurociągów wejściowe z prostych, wielokrotnego użytku kawałki. Na przykład potok dla modelu obrazu może agregować dane z plików w rozproszonym systemie plików, stosować losowe perturbacje do każdego obrazu i scalać losowo wybrane obrazy w partię w celu uczenia. Potok dla modelu tekstowego może obejmować wyodrębnianie symboli z nieprzetworzonych danych tekstowych, konwertowanie ich na osadzanie identyfikatorów w tabeli przeglądowej i grupowanie sekwencji o różnych długościach. tf.data API pozwala obsługiwać duże ilości danych, odczytać z różnych formatów danych i wykonywać złożone transformacje.

tf.data API wprowadza tf.data.Dataset poboru, który reprezentuje sekwencję elementów, w którym każdy z elementów zawiera jeden lub więcej składników. Na przykład w potoku obrazu element może być pojedynczym przykładem szkolenia z parą składników tensora reprezentujących obraz i jego etykietę.

Istnieją dwa różne sposoby tworzenia zestawu danych:

  • Źródło danych konstruuje Dataset z danymi przechowywanymi w pamięci lub w jednym lub więcej plików.

  • Transformacja danych konstruuje zbiór danych z jednego lub większej liczby tf.data.Dataset obiektów.

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)

Mechanika podstawowa

Aby utworzyć rurociąg wejściowego, należy zacząć od źródła danych. Na przykład, aby skonstruować Dataset z danymi w pamięci, można użyć tf.data.Dataset.from_tensors() lub tf.data.Dataset.from_tensor_slices() . Ewentualnie, jeśli dane wejściowe są zapisywane w pliku w formacie zalecanym TFRecord, można użyć tf.data.TFRecordDataset() .

Raz masz Dataset obiektu, można przekształcić go w nowym Dataset przez łańcuchowym wywołań metod na tf.data.Dataset obiektu. Na przykład, można zastosować transformacje per-elementu, takie jak Dataset.map() i transformacje wieloelementowe, takie jak Dataset.batch() . Zapoznać się z dokumentacją tf.data.Dataset pełną listę przekształceń.

Dataset obiekt jest Python iterable. Dzięki temu możliwe jest konsumowanie jego elementów za pomocą pętli 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

Lub jawnie tworzenia Pythona iterator użyciu iter i spożywania jej elementów za pomocą next :

it = iter(dataset)

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

Alternatywnie, w zestawie danych elementów może być wykorzystana za pomocą reduce transformacji, który zmniejsza wszystkie elementy, które wytwarzają jeden wynik. Poniższy przykład ilustruje, w jaki sposób korzystać z reduce transformację, aby obliczyć sumę w zbiorze liczb całkowitych.

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

Struktura zbioru danych

Zestaw danych generuje sekwencję elementów, przy czym każdy element ma takie same (zagnieżdżonych) Strukturę składników. Poszczególne elementy konstrukcji mogą być dowolnego typu, reprezentowana przez tf.TypeSpec , w tym tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray lub tf.data.Dataset .

Python konstrukty, które mogą być stosowane do ekspresji (zagnieżdżonych) obejmują strukturę elementów tuple , dict , NamedTuple i OrderedDict . W szczególności, list nie jest poprawnym konstrukt do wyrażania struktury zestawu danych elementów. Jest tak, ponieważ wczesne użytkowników tf.data czuł się mocno o list czynnikami (np przekazywane tf.data.Dataset.from_tensors ) jest automatycznie pakowane w Tensory list produktów (na przykład wartości powrotu funkcji przez użytkownika) jest zmuszany do tuple . W konsekwencji, jeśli chcieliby Państwo list wejście należy traktować jako struktury, trzeba przekonwertować go na tuple i jeśli chcesz się list wyjście za pojedynczy składnik, to trzeba wyraźnie zapakować go tf.stack .

Dataset.element_spec właściwość pozwala sprawdzać typ każdego składnika elementu. Właściwość zwraca zagnieżdżonej strukturę tf.TypeSpec obiektów dopasowanie struktury elementu, który może być pojedynczym elementem, krotka składników lub zagnieżdżone krotką składników. Na przykład:

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

Do Dataset transformacje obsługują zestawów danych o dowolnej strukturze. Podczas korzystania z Dataset.map() i Dataset.filter() transformacje, które stosują funkcję do każdego elementu, struktura elementu określa argumenty funkcji:

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

Odczytywanie danych wejściowych

Zużywanie tablic NumPy

Patrz Ładowanie tablic numpy więcej przykładów.

Jeśli wszystkie wejściowych pasuje danych w pamięci, to najprostszy sposób, aby utworzyć Dataset z nich jest przekształcenie ich do tf.Tensor przedmioty i używać 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)>

Zużywanie generatorów Pythona

Innym częstym źródłem danych, które mogą być łatwo spożyta w tf.data.Dataset jest generatorem pyton.

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 konstruktor konwertuje generator Pythona do w pełni funkcjonalnego tf.data.Dataset .

Konstruktor przyjmuje wywoływalne jako dane wejściowe, a nie iterator. Pozwala to na ponowne uruchomienie generatora po osiągnięciu końca. Zajmuje opcjonalnego args argument, który jest przekazywany jako argumenty płatnych na żądanie użytkownika.

output_types argumentu jest konieczny, ponieważ tf.data tworzy tf.Graph wewnętrznej i krawędziach wykres wymagają 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 argument nie jest wymagane, ale jest wysoce zalecana jako wielu operacjach tensorflow nie obsługują tensorów o nieznanym rangi. Jeśli długość danej osi jest nieznany lub zmienny, ustawić ją jako None w output_shapes .

Ważne jest również, aby pamiętać, że output_shapes i output_types przestrzegać tych samych zasad co gniazdowania innych metod zestawu danych.

Oto przykładowy generator, który demonstruje oba aspekty, zwraca krotki tablic, gdzie druga tablica jest wektorem o nieznanej długości.

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]

Pierwsze wyjście jest int32 drugi to float32 .

Pierwszym punktem jest skalarne, kształt () , a drugi jest wektorem o nieznanej długości, kształt (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)>

Teraz może być używany jak zwykły tf.data.Dataset . Należy pamiętać, że podczas dozowania zestawu danych o zmiennym kształcie, trzeba użyć 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.    ]]

Dla bardziej realistyczny przykład spróbować owijania preprocessing.image.ImageDataGenerator jako tf.data.Dataset .

Najpierw pobierz dane:

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

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

Zużywanie danych TFRecord

Patrz Ładowanie TFRecords na przykład end-to-end.

tf.data API obsługuje wiele formatów plików, dzięki czemu można przetwarzać duże zbiory danych, które nie mieszczą się w pamięci. Na przykład format pliku TFRecord jest prostym, zorientowanym na rekordy formatem binarnym, którego wiele aplikacji TensorFlow używa do trenowania danych. tf.data.TFRecordDataset klasa pozwala przesyłać na zawartość jednego lub kilku plików TFRecord jako część rurociągu wejściowym.

Oto przykład wykorzystujący plik testowy z francuskich znaków nazw ulic (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 argument TFRecordDataset inicjator może albo być ciągiem, wykaz ciągów lub tf.Tensor strun. Dlatego jeśli masz dwa zestawy plików do celów uczenia i walidacji, możesz utworzyć metodę fabryczną, która generuje zestaw danych, przyjmując nazwy plików jako argument wejściowy:

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

Wiele projektów TensorFlow używać odcinkach tf.train.Example rekordy w swoich plikach TFRecord. Należy je rozszyfrować, zanim będzie można je sprawdzić:

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

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

Zużywanie danych tekstowych

Patrz Ładowanie Tekst do zakończenia przykład końcowym.

Wiele zestawów danych jest dystrybuowanych jako jeden lub więcej plików tekstowych. tf.data.TextLineDataset zapewnia łatwy sposób, aby wyodrębnić linii z jednego lub kilku plików tekstowych. Biorąc pod uwagę jeden lub więcej nazw plików, o TextLineDataset będzie produkować jeden element ciąg wycenione na linię tych plików.

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)

Oto kilka pierwszych linijek pierwszego pliku:

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

Do alternatywnych linii między plikami używać Dataset.interleave . Ułatwia to wspólne mieszanie plików. Oto pierwszy, drugi i trzeci wiersz z każdego tłumaczenia:

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'

Domyślnie TextLineDataset daje każdy wiersz każdego pliku, który nie może być pożądane, na przykład, jeśli plik zaczyna się od linii nagłówka, lub zawiera komentarze. Linie te mogą być usunięte za pomocą Dataset.skip() lub Dataset.filter() transformacji. Tutaj pomijasz pierwszą linię, a następnie filtrujesz, aby znaleźć tylko ocalałych.

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'

Zużywam dane CSV

Patrz Ładowanie CSV Files i ładuje pandy DataFrames więcej przykładów.

Format pliku CSV to popularny format przechowywania danych tabelarycznych w postaci zwykłego tekstu.

Na przykład:

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

Jeśli twoje napady dane w pamięci te same Dataset.from_tensor_slices metoda działa na słownikach, pozwalające te dane mają być łatwo importowane:

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'

Bardziej skalowalnym podejściem jest ładowanie z dysku w razie potrzeby.

tf.data moduł dostarcza metod do ewidencji wyciąg z jednego lub więcej plików CSV, które są zgodne z RFC 4180 .

experimental.make_csv_dataset funkcja jest interfejs wysokiego poziomu do czytania zestawy plików CSV. Obsługuje wnioskowanie o typie kolumn i wiele innych funkcji, takich jak grupowanie i tasowanie, aby ułatwić korzystanie.

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']

Można użyć select_columns argument, jeśli trzeba tylko podzbiór kolumn.

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']

Istnieje również niższy poziom experimental.CsvDataset klasa, która zapewnia lepszą kontrolę szczegółową. Nie obsługuje wnioskowania o typie kolumny. Zamiast tego musisz określić typ każdej kolumny.

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']

Jeśli niektóre kolumny są puste, ten niskopoziomowy interfejs umożliwia podanie wartości domyślnych zamiast typów kolumn.

%%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]

Domyślnie CsvDataset daje każda kolumna każdej linii pliku, który nie może być pożądane, na przykład, jeśli plik zaczyna się od linii nagłówka, które powinny być ignorowane, lub jeśli niektóre kolumny nie są wymagane na wejściu. Te linie i pola mogą zostać usunięte z header i select_cols argumentów odpowiednio.

# 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]

Zużywanie zestawów plików

Istnieje wiele zestawów danych dystrybuowanych jako zestaw plików, gdzie każdy plik jest przykładem.

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)

Katalog główny zawiera katalog dla każdej klasy:

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

Pliki w każdym katalogu klas są przykładami:

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'

Odczytuje dane przy użyciu tf.io.read_file funkcję i wyodrębnić etykietę od ścieżki powrotu (image, label) parach

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'

Grupowanie elementów zbioru danych

Proste dozowanie

Najprostsza postać dozowania stosy n kolejnych elementów zestawu danych do pojedynczego elementu. Dataset.batch() transformacja robi dokładnie to, z tymi samymi ograniczeniami jak tf.stack() operatora, stosowanych do każdego składnika z następujących elementów: czyli dla każdego i komponentów, wszystkie elementy muszą mieć tensor dokładnie w tym samym kształcie.

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

Choć tf.data stara się propagować informacje kształtu, ustawienia domyślne Dataset.batch wyniku w nieznanej wielkości partii, ponieważ ostatnia partia nie może być pełna. Należy zwrócić uwagę na None S w kształcie:

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

Użyj drop_remainder argumentu zignorować tę ostatnią partię i uzyskać pełną propagację kształt:

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

Tensory dozujące z wyściółką

Powyższy przepis działa dla tensorów, które mają ten sam rozmiar. Jednak wiele modeli (np. modele sekwencyjne) pracuje z danymi wejściowymi, które mogą mieć różną wielkość (np. sekwencje o różnych długościach). Do obsługi tej sprawy, Dataset.padded_batch transformacja pozwala tensorów wsadowych o różnym kształcie, określając jeden lub więcej wymiarów, w których mogą być wyściełane.

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 przetwarzania pozwala ustawić różne wyściółkę w odniesieniu do każdego z poszczególnych składników, a to może być zmienną długość (oznaczało przez None w powyższym przykładzie) lub stałej długości. Możliwe jest również nadpisanie wartości dopełnienia, która domyślnie wynosi 0.

Przepływy pracy szkoleniowe

Przetwarzanie wielu epok

W tf.data oferty API dwa główne sposoby przetwarzania wielu epok tych samych danych.

Najprostszym sposobem iteracyjne nad zestawu danych w wielu epok jest użycie Dataset.repeat() transformacji. Najpierw utwórz zbiór danych tytanicznych:

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

Stosując Dataset.repeat() transformację bez argumentów powtórzy wejście w nieskończoność.

Dataset.repeat transformacja skleja swoje argumenty bez sygnalizacji koniec jednej epoki i początek następnej epoki. Z tym, ponieważ Dataset.batch stosowane po Dataset.repeat przyniesie partii które przekraczają granice epoki:

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

png

Jeśli potrzebujesz wyraźne oddzielenie epoka, umieścić Dataset.batch przed powtórzeniem:

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

plot_batch_sizes(titanic_batches)

png

Jeśli chcesz wykonać niestandardowe obliczenia (np. zbierać statystyki) na końcu każdej epoki, najprościej jest ponownie uruchomić iterację zestawu danych w każdej epoce:

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

Losowe tasowanie danych wejściowych

Dataset.shuffle() przekształcenie utrzymuje bufor o stałym rozmiarze i wybiera kolejny element jednolity losowo z tym buforem.

Dodaj indeks do zbioru danych, aby zobaczyć efekt:

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

Ponieważ buffer_size 100, a wielkość wsadu 20, pierwsza grupa nie zawiera pierwiastków o wskaźniku 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]

Jak z Dataset.batch kolejność względem Dataset.repeat sprawach.

Dataset.shuffle nie sygnalizuje koniec pewnej epoki, aż bufor shuffle jest pusty. Tak więc tasowanie umieszczone przed powtórzeniem pokaże każdy element jednej epoki przed przejściem do następnej:

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

Ale powtórzenie przed przetasowaniem miesza granice epoki:

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

Wstępne przetwarzanie danych

Dataset.map(f) przekształcenie powoduje nowy zestaw danych poprzez zastosowanie określonej funkcji f każdego z elementów zestawu danych wejściowych. Opiera się ona na map() funkcji, która jest powszechnie stosowana na listach (i innych budowli) w językach programowania funkcjonalnych. Funkcja f przyjmuje tf.Tensor obiektów, które reprezentują pojedynczy element na wejściu i zwraca tf.Tensor obiektów, które reprezentują pojedynczy element w nowym zbiorze. Jego implementacja wykorzystuje standardowe operacje TensorFlow do przekształcenia jednego elementu w inny.

Sekcja ta obejmuje typowe przykłady sposobu korzystania Dataset.map() .

Dekodowanie danych obrazu i zmiana jego rozmiaru

Podczas uczenia sieci neuronowej na danych obrazu ze świata rzeczywistego często konieczne jest przekonwertowanie obrazów o różnych rozmiarach do wspólnego rozmiaru, aby można je było pogrupować w ustalony rozmiar.

Odbuduj zbiór danych nazw plików kwiatów:

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

Napisz funkcję, która manipuluje elementami zestawu danych.

# 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

Sprawdź, czy to działa.

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

Zamapuj go na zbiór danych.

images_ds = list_ds.map(parse_image)

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

png

png

Stosowanie dowolnej logiki Pythona

Ze względu na wydajność używaj operacji TensorFlow do wstępnego przetwarzania danych, gdy tylko jest to możliwe. Czasami jednak przydatne jest wywoływanie zewnętrznych bibliotek Pythona podczas analizowania danych wejściowych. Można użyć tf.py_function() pracę w Dataset.map() transformacji.

Na przykład, jeśli chcesz zastosować losowy obrót The tf.image moduł ma tylko tf.image.rot90 , który nie jest bardzo przydatna do powiększania obrazu.

Aby zademonstrować tf.py_function , spróbuj użyć scipy.ndimage.rotate funkcję zamiast:

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

Aby skorzystać z tej funkcji z Dataset.map te same zastrzeżenia stosuje się z Dataset.from_generator , trzeba opisać kształty i rodzaje powrotne po zastosowaniu funkcji:

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

Parsowanie tf.Example wiadomości buforowe protokół

Wiele rurociągi wejściowe wyodrębnić tf.train.Example wiadomości buforowe protokół z formatu TFRecord. Każdy tf.train.Example rekord zawiera jeden lub więcej „możliwości”, a rurociąg wejście zazwyczaj przetwarza te funkcje do tensorów.

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>

Można pracować z tf.train.Example Protos zewnętrznej strony tf.data.Dataset zrozumieć dane:

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

Okienkowanie szeregów czasowych

Na koniec do końca serii czas zobaczyć przykład: seria prognozowania czasu .

Dane szeregów czasowych są często organizowane z nienaruszoną osią czasu.

Użyj prostego Dataset.range wykazać:

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

Zazwyczaj modele oparte na tego rodzaju danych wymagają ciągłego wycinka czasu.

Najprostszym podejściem byłoby zgrupowanie danych:

Korzystanie z 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]

Lub, aby gęste przewidywania o jeden krok w przyszłość, możesz przesunąć cechy i etykiety o jeden krok względem siebie:

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]

Aby przewidzieć całe okno zamiast stałego przesunięcia, możesz podzielić partie na dwie części:

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]

Aby umożliwić pewne nakładanie się funkcji jednej partii i etykietach inny, użyj 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]

Korzystanie z window

Podczas korzystania Dataset.batch prace, istnieją sytuacje, w których może być konieczne dokładniejsze sterowanie. Dataset.window metoda daje pełną kontrolę, ale wymaga opieki: to zwraca Dataset od Datasets . Zobacz strukturę zestawach danych, aby poznać szczegóły.

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 metoda może mieć zestaw danych o zbiorach danych i spłaszczyć je w jednym zbiorze:

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

W prawie wszystkich przypadkach, będziemy chcieli, aby .batch zbioru danych pierwszy:

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]

Teraz widać, że shift kontrole argumentów, ile każdy porusza się okno.

Składając to razem, możesz napisać tę funkcję:

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]

Następnie łatwo wyodrębnić etykiety, tak jak poprzednio:

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]

Ponowne próbkowanie

Podczas pracy z zestawem danych, który jest bardzo niezrównoważony klasowo, możesz chcieć ponownie próbkować zestaw danych. tf.data udostępnia dwa sposoby, aby to zrobić. Dobrym przykładem tego rodzaju problemu jest zbiór danych dotyczących oszustw związanych z kartami kredytowymi.

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()])

Teraz sprawdź rozkład klas, jest mocno wypaczony:

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]

Typowym podejściem do trenowania z niezrównoważonym zestawem danych jest jego zrównoważenie. tf.data zawiera kilka metod, które pozwolą ten przepływ pracy:

Próbkowanie zbiorów danych

Jedno podejście do resampling zestawu danych jest użycie sample_from_datasets . To jest bardziej zastosowanie, jeśli masz oddzielną data.Dataset dla każdej klasy.

Tutaj wystarczy użyć filtra, aby wygenerować je z danych o oszustwach związanych z kartą kredytową:

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]

Aby korzystać tf.data.experimental.sample_from_datasets przekazać zbiory danych, a ciężar dla każdego:

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(...)`.

Teraz zbiór danych generuje przykłady każdej klasy z prawdopodobieństwem 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]

Ponowne próbkowanie odrzucenia

Jeden problem z powyższych experimental.sample_from_datasets podejścia jest to, że wymaga odrębnego tf.data.Dataset na klasy. Korzystanie Dataset.filter działa, ale powoduje wszystkie dane są ładowane dwukrotnie.

data.experimental.rejection_resample Funkcja ta może być stosowana do zbioru danych, aby je zrównoważyć, a tylko ładuje go raz. Elementy zostaną usunięte ze zbioru danych, aby osiągnąć równowagę.

data.experimental.rejection_resample zajmuje class_func argument. Ten class_func jest stosowana do każdego elementu zbioru danych i służy do określenia, jakiej klasy należy do przykładem dla celów bilansowania.

Elementy creditcard_ds już (features, label) pary. Więc class_func prostu musi zwrócić te etykiety:

def class_func(features, label):
  return label

Resampler potrzebuje również dystrybucji docelowej i opcjonalnie wstępnego oszacowania dystrybucji:

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

W Resampler dotyczy pojedynczych przykładów, więc trzeba unbatch zestawu danych przed nałożeniem Resampler:

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:

W Resampler powraca tworzy (class, example) pary z wyjściem class_func . W tym przypadku, example był już (feature, label) para, więc wykorzystanie map upuścić dodatkową kopię etykiety:

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

Teraz zbiór danych generuje przykłady każdej klasy z prawdopodobieństwem 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]

Punkt kontrolny iteratora

Tensorflow podpory biorąc punktów kontrolnych , tak aby po ponownym uruchomieniu procesu szkolenia może przywrócić ostatni punkt kontrolny, aby odzyskać większość jego postępów. Oprócz sprawdzania zmiennych modelu można również sprawdzać postęp iteratora zestawu danych. Może to być przydatne, jeśli masz duży zestaw danych i nie chcesz rozpoczynać zestawu danych od początku przy każdym ponownym uruchomieniu. Należy jednak pamiętać, że iterator punkty kontrolne mogą być duże, ponieważ przekształceń, takich jak shuffle i prefetch wymagają elementy buforujące wewnątrz iteracyjnej.

Aby umieścić swój iterator w punkcie kontrolnym, przekazać iterator do tf.train.Checkpoint konstruktora.

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]

Używanie tf.data z tf.keras

W tf.keras interfejsie API upraszcza wiele aspektów tworzenia i wykonywania modeli uczenia maszynowego. Jego .fit() i .evaluate() i .predict() API obsługują zestawów danych, jak wejść. Oto szybka konfiguracja zestawu danych i modelu:

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'])

Przechodząc zestawu danych z (feature, label) par to wszystko, co jest potrzebne do Model.fit i Model.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>

Jeśli zdasz nieskończony zbiór danych, na przykład poprzez wywołanie Dataset.repeat() , po prostu trzeba też zdać steps_per_epoch argumentu:

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>

Do oceny możesz przekazać liczbę kroków oceny:

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

W przypadku długich zbiorów danych ustaw liczbę kroków do oceny:

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

Etykiety nie są wymagane podczas wywoływania 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)

Ale etykiety są ignorowane, jeśli przekażesz zbiór danych, który je zawiera:

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