Zapisz datę! Google I / O powraca w dniach 18-20 maja Zarejestruj się teraz
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

tf.data: Twórz potoki wejściowe TensorFlow

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

tf.data API tf.data umożliwia tworzenie złożonych potoków wejściowych z prostych elementów wielokrotnego użytku. Na przykład potok modelu obrazu może agregować dane z plików w rozproszonym systemie plików, stosować losowe zakłócenia do każdego obrazu i łączyć losowo wybrane obrazy w partię na potrzeby uczenia. Potok modelu tekstowego może obejmować wyodrębnianie symboli z surowych danych tekstowych, przekształcanie ich w celu osadzania identyfikatorów za pomocą tabeli odnośników i grupowanie sekwencji o różnych długościach. tf.data API tf.data umożliwia obsługę dużych ilości danych, odczytywanie z różnych formatów danych i wykonywanie złożonych transformacji.

tf.data API tf.data wprowadza abstrakcjętf.data.Dataset która reprezentuje sekwencję elementów, w których każdy element składa się z co najmniej jednego składnika. Na przykład w potoku obrazu element może być pojedynczym przykładem szkoleniowym z parą składników tensora reprezentujących obraz i jego etykietę.

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

  • Źródło danych tworzy Dataset z danych przechowywanych w pamięci lub w co najmniej jednym pliku.

  • Transformacja danych tworzy zestaw danych z co najmniej jednego obiektutf.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)

Podstawowa mechanika

Aby utworzyć potok wejściowy, musisz rozpocząć od źródła danych. Na przykład, aby skonstruować zestaw Dataset z danych w pamięci, można użyć tf.data.Dataset.from_tensors() lub tf.data.Dataset.from_tensor_slices() . Alternatywnie, jeśli dane wejściowe są przechowywane w pliku w zalecanym formacie TFRecord, możesz użyć tf.data.TFRecordDataset() .

Gdy masz już obiekt Dataset , możesz przekształcić go w nowy Dataset , łącząc wywołania metod w obiekcietf.data.Dataset . Na przykład można zastosować transformacje poszczególnych elementów, takie jak Dataset.map() , i transformacje wieloelementowe, takie jak Dataset.batch() . Pełną listę przekształceń można znaleźć w dokumentacjitf.data.Dataset .

Obiekt Dataset jest iterowalny w Pythonie. 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 tworząc iterator Pythona za pomocą iter i konsumując jego elementy przy użyciu next :

it = iter(dataset)

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

Alternatywnie, elementy zestawu danych mogą być używane przy użyciu transformacji reduce , która redukuje wszystkie elementy w celu uzyskania jednego wyniku. Poniższy przykład ilustruje sposób użycia transformacji reduce do obliczenia sumy zestawu danych liczb całkowitych.

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

Struktura zbioru danych

Zestaw danych tworzy sekwencję elementów , w których każdy element jest tą samą (zagnieżdżoną) strukturą komponentów . Poszczególne komponenty struktury mogą być dowolnego typu reprezentowane przez tf.TypeSpec , w tym tf.Tensor , tf.sparse.SparseTensor ,tf.RaggedTensor , tf.TensorArray lubtf.data.Dataset .

Konstrukcje Pythona, których można użyć do wyrażenia (zagnieżdżonej) struktury elementów, obejmują tuple , dict , NamedTuple i OrderedDict . W szczególności list nie jest prawidłową konstrukcją do wyrażania struktury elementów zestawu danych. Dzieje się tak, ponieważ wcześni użytkownicy tf.data byli bardzo przekonani, że dane wejściowe list (np. Przekazane do tf.data.Dataset.from_tensors ) są automatycznie pakowane jako tensory, a wyjścia list (np. tf.data.Dataset.from_tensors wartości funkcji zdefiniowanych przez użytkownika) są przekształcane w tuple . W konsekwencji, jeśli chcesz, aby dane wejściowe list były traktowane jako struktura, musisz przekonwertować je na tuple a jeśli chcesz, aby wyjście list było pojedynczym komponentem, musisz jawnie spakować je za pomocą tf.stack .

Właściwość Dataset.element_spec umożliwia sprawdzenie typu każdego komponentu elementu. Właściwość zwraca zagnieżdżoną strukturę obiektów tf.TypeSpec , pasującą do struktury elementu, który może być pojedynczym komponentem, krotką komponentów lub zagnieżdżoną krotką komponentó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

Transformacje Dataset obsługują zestawy danych o dowolnej strukturze. Podczas korzystania z Dataset.map() i Dataset.filter() , 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())
[8 6 2 1 5 3 8 8 3 6]
[6 8 8 8 5 4 5 1 3 3]
[2 9 9 1 9 2 8 9 4 6]
[1 1 4 1 5 7 1 9 6 7]
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,)

Czytanie danych wejściowych

Zużywanie tablic NumPy

Więcej przykładów znajdziesz w temacie Ładowanie tablic NumPy .

Jeśli wszystkie dane wejściowe mieszczą się w pamięci, najprostszym sposobem utworzenia z nich Dataset jest przekonwertowanie ich na obiekty tf.Tensor i użycie 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)>

Zużywanie generatorów Pythona

Innym popularnym źródłem danych, które można łatwotf.data.Dataset jakotf.data.Dataset jest generatortf.data.Dataset Python.

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

Konstruktor Dataset.from_generator konwertuje generator Dataset.from_generator Python na w pełni funkcjonalnytf.data.Dataset .

Konstruktor przyjmuje wywoływalne jako dane wejściowe, a nie iterator. Pozwala to na ponowne uruchomienie generatora, gdy osiągnie koniec. Pobiera opcjonalny argument args , który jest przekazywany jako argument wywoływalny.

Argument output_types jest wymagany, ponieważ tf.data buduje tf.Graph wewnętrznie, a krawędzie wykresu 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]

Argument output_shapes nie jest wymagany, ale jest wysoce zalecany, ponieważ wiele operacji tensorflow nie obsługuje tensorów o nieznanej randze. Jeśli długość określonej osi jest nieznana lub zmienna, ustaw ją jako None w output_shapes .

Należy również zauważyć, że output_shapes i output_types tym samym regułom zagnieżdżania, co inne metody 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 : [-0.7193  0.1489  0.3015]
1 : [ 0.9257 -0.8073  1.1848 -0.4203 -0.5188 -1.7014 -1.0934  2.6323]
2 : [-0.2845 -0.3942  0.8429 -0.2441  0.8038]
3 : [ 0.0553 -0.2246]
4 : [ 0.5449 -0.0552 -0.4308 -0.5846  0.2252  0.475  -0.6507  1.3336 -0.6148]
5 : []
6 : [-0.0788 -0.2671 -0.5868 -1.038  -0.145 ]

Pierwsze wyjście to int32 drugie to float32 .

Pierwsza pozycja to skalar, kształt () , a druga to wektor o nieznanej długości, kształcie (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łytf.data.Dataset . Zwróć uwagę, że podczas grupowania zestawu danych o zmiennym kształcie musisz 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())
[14  2 16 18  5 13  3  4  0 25]

[[ 2.2655e-03 -1.1356e+00 -6.3269e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 5.9163e-01  1.1532e-01 -9.4578e-01 -5.7305e-01  5.8756e-01 -8.3086e-01
   1.0705e+00  4.6149e-01 -7.8259e-01]
 [ 8.7261e-01 -1.4031e+00  2.1707e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 2.2473e-01 -4.9448e-02 -5.0318e-01 -7.6812e-01 -5.9628e-01  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [-5.1477e-01  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [-1.3577e+00  7.6869e-02  1.3755e+00  1.1951e-01  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 1.4727e+00  5.2939e-01  1.1665e-01  6.6326e-01  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [-1.5769e+00  7.4631e-01  8.4916e-01  5.3781e-01 -2.3621e+00  1.0438e-01
   9.3234e-01  0.0000e+00  0.0000e+00]
 [-1.7867e+00 -1.5707e+00  1.7925e-02  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]]

Aby uzyskać bardziej realistyczny przykład, spróbujtf.data.Dataset preprocessing.image.ImageDataGenerator jakotf.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 [==============================] - 6s 0us/step

Utwórz 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

Zobacz Ładowanie TFRecords, aby uzyskać kompleksowy przykład.

tf.data API tf.data obsługuje różne formaty plików, dzięki czemu można przetwarzać duże tf.data danych, które nie mieszczą się w pamięci. Na przykład format pliku TFRecord jest prostym formatem binarnym zorientowanym na rekordy, z którego korzysta wiele aplikacji TensorFlow do obsługi danych szkoleniowych. Klasa tf.data.TFRecordDataset umożliwia przesyłanie strumieniowe zawartości jednego lub większej liczby plików TFRecord w ramach potoku wejściowego.

Oto przykład wykorzystujący plik testowy z francuskich znaków z nazwami 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

Argument filenames dla inicjatora TFRecordDataset może być łańcuchem, listą ciągów lub tf.Tensor ciągów. Dlatego jeśli masz dwa zestawy plików do celów szkoleniowych i walidacyjnych, możesz utworzyć metodę fabryczną, która tworzy 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 zserializowanych rekordów tf.train.Example rekordy w swoich plikach TFRecord. Należy je odszyfrować, 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

Zobacz Ładowanie tekstu, aby zapoznać się z przykładem końca do końca.

Wiele zbiorów danych jest dystrybuowanych jako jeden lub więcej plików tekstowych. Zestaw tf.data.TextLineDataset zapewnia łatwy sposób wyodrębniania wierszy z jednego lub większej liczby plików tekstowych. Biorąc pod uwagę jedną lub więcej nazw plików, TextLineDataset wygeneruje jeden element o wartości łańcuchowej w każdym wierszu 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
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)

Oto kilka pierwszych wierszy 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)'

Aby Dataset.interleave wiersze między plikami, użyj Dataset.interleave . Ułatwia to mieszanie plików razem. Oto pierwsza, druga i trzecia linijka 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 zwraca każdą linię każdego pliku, co może nie być pożądane, na przykład, jeśli plik zaczyna się od linii nagłówka lub zawiera komentarze. Te wiersze można usunąć za pomocą Dataset.skip() lub Dataset.filter() . 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
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żywanie danych CSV

Więcej przykładów znajdziesz w sekcjach Ładowanie plików CSV i Ładowanie ramek danych Pandas .

Format pliku CSV jest popularnym formatem do 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 dane Dataset.from_tensor_slices w pamięci, ta sama metoda Dataset.from_tensor_slices działa na słownikach, umożliwiając łatwe importowanie tych danych:

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 skalowalne podejście polega na ładowaniu z dysku w razie potrzeby.

Moduł tf.data zapewnia metody wyodrębniania rekordów z jednego lub większej liczby plików CSV, które są zgodne z RFC 4180 .

Funkcja experimental.make_csv_dataset to interfejs wysokiego poziomu do odczytywania zestawów plików csv. Obsługuje wnioskowanie o typach kolumn i wiele innych funkcji, takich jak grupowanie i tasowanie, aby uprościć użytkowanie.

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': [1 1 0 1]
features:
  'sex'               : [b'female' b'male' b'male' b'female']
  'age'               : [27. 48. 28. 50.]
  'n_siblings_spouses': [0 1 0 0]
  'parch'             : [2 0 0 0]
  'fare'              : [11.1333 76.7292  7.8958 10.5   ]
  'class'             : [b'Third' b'First' b'Third' b'Second']
  'deck'              : [b'unknown' b'D' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Cherbourg' b'Southampton' b'Southampton']
  'alone'             : [b'n' b'n' b'y' b'y']

Możesz użyć argumentu select_columns jeśli potrzebujesz tylko podzbioru 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 0 0 0]
  'fare'              : [29.7     7.8958 15.0458  7.75  ]
  'class'             : [b'First' b'Third' b'Second' b'Third']

Istnieje również klasa experimental.CsvDataset niższego poziomu experimental.CsvDataset która zapewnia dokładniejszą kontrolę. 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 interfejs niskiego poziomu 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 zwraca każdą kolumnę w każdym wierszu pliku, co może nie być pożądane, na przykład jeśli plik zaczyna się od wiersza nagłówka, który należy zignorować, lub jeśli niektóre kolumny nie są wymagane w danych wejściowych. Te wiersze i pola można usunąć odpowiednio za pomocą argumentów header i 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]

Zużywanie zestawów plików

Istnieje wiele zbiorów danych rozpowszechnianych 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 to przykłady:

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/7652532108_01ef94c476.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/3276552939_8c31b22d3e.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/4363734507_5cc4ed6e01.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/20754920332_53b995fc63_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/15219268336_f2460fca88_m.jpg'

Odczytaj dane za pomocą funkcjitf.io.read_file i wyodrębnij etykietę ze ścieżki, zwracając pary (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\xed\x00\x1cPhotoshop 3.0\x008BIM\x04\x04\x00\x00\x00\x00\x00\x00\xff\xdb\x00C\x00\x03\x02\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\n\x07\x07\x06\x08\x0c\n\x0c\x0c\x0b\n\x0b\x0b\r\x0e\x12\x10\r\x0e\x11\x0e\x0b\x0b\x10'

b'daisy'

Wsadowe elementy zbioru danych

Proste dozowanie

Najprostsza forma grupowania grupuje n kolejnych elementów zbioru danych w jeden element. Dataset.batch() robi dokładnie to, z tymi samymi ograniczeniami, co operator tf.stack() , zastosowanymi do każdego komponentu elementów: tj. Dla każdego komponentu i wszystkie elementy muszą mieć tensor o dokładnie 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])]

Podczas gdy tf.data próbuje propagować informacje o kształcie, domyślne ustawienia Dataset.batch powodują nieznany rozmiar wsadu, ponieważ ostatnia partia może nie być pełna. Zwróć uwagę na None w kształcie:

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

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

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 sekwencji) działa z danymi wejściowymi, które mogą mieć różną wielkość (np. Sekwencje o różnej długości). Aby obsłużyć ten przypadek, transformacja Dataset.padded_batch umożliwia Dataset.padded_batch tensorów o różnych kształtach przez określenie jednego lub więcej wymiarów, w których mogą być wypełnione.

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

Transformacja Dataset.padded_batch umożliwia ustawienie innego wypełnienia dla każdego wymiaru każdego komponentu i może mieć zmienną długość (oznaczoną jako None w powyższym przykładzie) lub stałą. Możliwe jest również przesłonięcie wartości wypełnienia, która domyślnie wynosi 0.

Szkolenia

Przetwarzanie wielu epok

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

Najprostszym sposobem iteracji zestawu danych w wielu epokach jest użycie transformacji Dataset.repeat() . Najpierw utwórz zbiór danych Titanic:

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

Zastosowanie transformacji Dataset.repeat() bez argumentów spowoduje powtarzanie danych wejściowych w nieskończoność.

Transformacja Dataset.repeat łączy swoje argumenty bez sygnalizowania końca jednej epoki i początku następnej epoki. Z tego powodu Dataset.batch zastosowany po Dataset.repeat przyniesie partie wykraczające poza granice epok:

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

png

Jeśli potrzebujesz wyraźnego oddzielenia epok, umieść Dataset.batch przed powtórzeniem:

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

plot_batch_sizes(titanic_batches)

png

Jeśli chcesz wykonać obliczenia niestandardowe (np. W celu zebrania statystyk) 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() zachowuje bufor o stałym rozmiarze i wybiera następny element równomiernie losowo z tego bufora.

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
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

Ponieważ rozmiar buffer_size wynosi 100, a rozmiar partii to 20, pierwsza partia nie zawiera elementów o indeksie większym niż 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 36  72  81  39  19  32  70  11  64  18  56  86  30  40 111  80  87  13
 110 116]

Podobnie jak w przypadku Dataset.batch kolejność względem Dataset.repeat znaczenie.

Dataset.shuffle nie sygnalizuje końca epoki, dopóki bufor shuffle nie jest pusty. Tak więc przetasowanie 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:

[622 295 617 552 465 394 592 593 442 488]
[469 543 329 600 359 471 554 627 620 456]
[565 550 563 464 556 618 587 449]
[20  0 71  8 25 86 10 57 72  2]
[ 78  99  85  36  62  13 108  46  56  97]
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 0x7fcccbcea860>

png

Ale powtórka przed przetasowaniem miesza ze sobą 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:

[607 519 555 627  23 469   9 501   2 500]
[546 470  14  21 583 552  15 539 340  30]
[620  29 586 570 250   6 599 498 618 610]
[ 16  25 518 505 465 418  12  22  59  51]
[ 10 558  47 553  52   7 616 454 561 597]
[363  68 441 530  75 603  57 339  64  37]
[ 46 388 626  39 600 577 609 467 451  44]
[601  78 537  69 514  54  11 613 502  32]
[ 62  76  85 391  34  65  36 105 541  96]
[548 576 107  61 543  19 527  72 111  38]
[ 98  26  43  70 463 581 115 559 590  86]
[ 50 594  77 490  55 108 370  63  87  94]
[122  27 136  73 617 535  40 144 112  24]
[ 67  99 151 128   5 154 120   4 343 102]
[625 132  83  35 356  80 130  60 156 100]
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 0x7fccb44c5f28>

png

Wstępne przetwarzanie danych

Dataset.map(f) tworzy nowy zestaw danych przez zastosowanie danej funkcji f do każdego elementu wejściowego zestawu danych. Opiera się na funkcji map() która jest powszechnie stosowana do list (i innych struktur) w funkcjonalnych językach programowania. Funkcja f przyjmuje obiekty tf.Tensor które reprezentują pojedynczy element w danych wejściowych i zwraca obiekty tf.Tensor które będą reprezentować pojedynczy element w nowym zbiorze danych. Jego implementacja wykorzystuje standardowe operacje TensorFlow do przekształcania jednego elementu w inny.

W tej sekcji omówiono typowe przykłady użycia Dataset.map() .

Dekodowanie danych obrazu i zmiana ich rozmiaru

Podczas uczenia sieci neuronowej na rzeczywistych danych obrazu często konieczne jest przekonwertowanie obrazów o różnych rozmiarach do wspólnego rozmiaru, tak aby można je było grupować do stałego rozmiaru.

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 zbiorze 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. Jednak czasami przydaje się wywołanie 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 zamiast tego scipy.ndimage.rotate funkcji scipy.ndimage.rotate :

import scipy.ndimage as ndimage

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

png

Aby użyć tej funkcji z Dataset.map te same zastrzeżenia, co w przypadku Dataset.from_generator , należy opisać zwracane kształty i typy podczas stosowania 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

tf.Example komunikaty bufora protokołu

Wiele potoków wejściowych wyodrębnia tf.train.Example komunikaty bufora protokołu z formatu TFRecord. Każdy rekord tf.train.Example zawiera co najmniej jedną „funkcję”, a potok wejściowy zazwyczaj przekształca te funkcje w tensory.

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żesz pracować z tf.train.Example protos pozatf.data.Dataset aby 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

Aby zapoznać się z przykładem szeregów czasowych od końca do końca, zobacz: Prognozowanie szeregów czasowych .

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

Użyj prostego Dataset.range aby zademonstrować:

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

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

Najprostszym podejściem byłoby grupowanie 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]

Aby zagęścić prognozy o krok w przyszłość, możesz przesunąć funkcje 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],   # 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]

Aby umożliwić pewne nakładanie się cech jednej partii i etykiet innej, 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 z Dataset.batch działa, są sytuacje, w których możesz potrzebować dokładniejszej kontroli. Metoda Dataset.window zapewnia pełną kontrolę, ale wymaga pewnej ostrożności: zwraca zestaw Dataset Datasets . Aby uzyskać szczegółowe informacje, zobacz Struktura zestawu danych.

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>

Metoda Dataset.flat_map może pobrać zestaw danych ze zbiorów danych i spłaszczyć go do jednego zestawu danych:

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ędziesz chciał .batch zestaw danych:

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 możesz zobaczyć, że argument shift kontroluje, o ile shift każde 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]

Wtedy łatwo wyodrębnić etykiety, 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]

Resampling

Podczas pracy ze zbiorem danych, który jest bardzo niezrównoważony pod względem klas, może być konieczne ponowne próbkowanie zestawu danych. tf.data udostępnia dwie metody, aby to zrobić. Zbiór danych dotyczących oszustw związanych z kartami kredytowymi jest dobrym przykładem tego rodzaju problemu.

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

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.9952 0.0048]

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

Próbkowanie zbiorów danych

Jednym ze sposobów ponownego próbkowania zbioru danych jest użycie sample_from_datasets . Jest to bardziej przydatne, gdy masz oddzielnedata.Dataset dla każdej klasy.

Tutaj wystarczy użyć filtra, aby wygenerować je na podstawie danych dotyczących oszustw 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 użyć tf.data.experimental.sample_from_datasets zestawy danych i wagę każdego z nich:

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)

Teraz zbiór danych tworzy przykłady każdej klasy z prawdopodobieństwem 50/50:

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

Odrzucenie ponownego próbkowania

Jednym z problemów z powyższym podejściem experimental.sample_from_datasets jest to, że wymaga on osobnegotf.data.Dataset każdej klasy. Korzystanie z Dataset.filter działa, ale powoduje dwukrotne załadowanie wszystkich danych.

Funkcję data.experimental.rejection_resample można zastosować do zbioru danych w celu jego zrównoważenia, przy jednoczesnym ładowaniu tylko raz. Elementy zostaną usunięte ze zbioru danych, aby osiągnąć równowagę.

data.experimental.rejection_resample przyjmuje argument class_func . Ta class_func jest stosowana do każdego elementu zestawu danych i służy do określania, do której klasy należy przykład na potrzeby równoważenia.

Elementy creditcard_ds są już parami (features, label) . Więc class_func musi po prostu 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)

Resampler zajmuje się pojedynczymi przykładami, więc przed zastosowaniem resamplera należy unbatch zestaw danych:

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:

Funkcja ponownego próbkowania zwraca tworzy pary (class, example) podstawie danych wyjściowych funkcji class_func . W tym przypadku example był już parą (feature, label) , więc użyj map aby upuścić dodatkową kopię etykiet:

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

Teraz zbiór danych tworzy przykłady każdej klasy z prawdopodobieństwem 50/50:

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

Punkty kontrolne iteratora

Tensorflow obsługuje przyjmowanie punktów kontrolnych, dzięki czemu po ponownym uruchomieniu procesu szkolenia można przywrócić najnowszy punkt kontrolny i odzyskać większość jego postępów. Oprócz określania punktów kontrolnych 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ć go od początku przy każdym ponownym uruchomieniu. Należy jednak pamiętać, że punkty kontrolne iteratora mogą być duże, ponieważ transformacje, takie jak shuffle i prefetch wymagają buforowania elementów w iteratorze.

Aby dołączyć iterator do punktu kontrolnego, przekaż iterator do konstruktora 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]

Korzystanie z tf.data z tf.keras

tf.keras API tf.keras 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 szybki zestaw danych i konfiguracja 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'])

Przekazanie zestawu danych par (feature, label) to wszystko, czego potrzeba do Model.fit i Model.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.7767 - accuracy: 0.7407
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4709 - accuracy: 0.8387
<tensorflow.python.keras.callbacks.History at 0x7fcca40a2080>

Jeśli przekazujesz nieskończony zestaw danych, na przykład przez wywołanie Dataset.repeat() , wystarczy również przekazać argument 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.4301 - accuracy: 0.8609
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4454 - accuracy: 0.8281
<tensorflow.python.keras.callbacks.History at 0x7fccb416bd30>

W celu oceny możesz przejść kilka etapów oceny:

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4484 - accuracy: 0.8435
Loss : 0.4483910799026489
Accuracy : 0.843500018119812

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.4643 - accuracy: 0.8531
Loss : 0.4643370509147644
Accuracy : 0.8531249761581421

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 zawierający je:

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