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 Wyświetl źródło na 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óżnej długości. tf.data API tf.data umożliwia obsługę dużych ilości danych, odczyt 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 obiektu tf.data.Dataset .

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

np.set_printoptions(precision=4)

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 obiekcie tf.data.Dataset . Na przykład można zastosować transformacje na element, takie jak Dataset.map() , i transformacje wieloelementowe, takie jak Dataset.batch() . Pełną listę przekształceń można znaleźć w dokumentacji tf.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, 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 zawiera elementy, z których każdy ma taką samą (zagnieżdżoną) strukturę, a poszczególne komponenty struktury mogą być dowolnego typu reprezentowane przez tf.TypeSpec , w tym tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray , lub tf.data.Dataset .

Właściwość Dataset.element_spec umożliwia sprawdzenie typu każdego składnika elementu. Właściwość zwraca zagnieżdżoną strukturę obiektów tf.TypeSpec , dopasowaną 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())
[1 2 5 3 8 9 6 7 9 3]
[5 4 3 3 9 7 3 1 5 4]
[7 3 4 8 4 8 5 9 4 1]
[8 6 8 2 6 3 5 8 3 3]

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 łatwo tf.data.Dataset jako tf.data.Dataset jest generator tf.data.Dataset Python.

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

Konstruktor Dataset.from_generator konwertuje generator Dataset.from_generator Python na w pełni funkcjonalny tf.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 to wektor 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.2226]
1 : [ 0.4785  1.1887 -0.2828  0.6047  1.3367 -0.4387  0.1822]
2 : [ 1.1343 -0.2676  0.0224 -0.111  -0.1384 -1.9315]
3 : [-0.8651]
4 : [0.6275]
5 : [ 0.8034  2.0773  0.6183 -0.3746]
6 : [-0.9439 -0.6686]

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żna go używać jak zwykłego tf.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())
[18  1  8 22  5 13  6  2 14 28]

[[ 0.0196 -1.007   0.1843  0.0289  0.0735 -0.6279 -1.0877  0.    ]
 [ 0.8238 -1.0284  0.      0.      0.      0.      0.      0.    ]
 [ 0.544  -1.1061  1.2368  0.3975  0.      0.      0.      0.    ]
 [-1.1933 -0.7535  1.0497  0.1764  1.5319  0.9202  0.4027  0.6844]
 [-1.2025 -2.7148  1.0702  1.3893  0.      0.      0.      0.    ]
 [ 1.3787  0.6817  0.1197 -0.1178  0.9764  0.8895  0.      0.    ]
 [-1.523  -0.7722 -2.13    0.2761 -1.1094  0.      0.      0.    ]
 [ 0.203   2.1858 -0.722   1.2554  1.2208  0.1813 -2.3427  0.    ]
 [ 0.7685  1.7138 -1.2376 -2.6168  0.2565  0.0753  1.5653  0.    ]
 [ 0.0908 -0.4535  0.1257 -0.5122  0.      0.      0.      0.    ]]

Aby uzyskać bardziej realistyczny przykład, spróbuj tf.data.Dataset 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 [==============================] - 9s 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 zapoznać się z przykładem od końca do końca.

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 korzysta z serializowanych rekordów tf.train.Example w swoich plikach TFRecord. Należy je zdekodować, 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ębnienia 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, index_col=None)
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 odczytu zestawów plików csv. Obsługuje wnioskowanie o typach kolumn i wiele innych funkcji, takich jak grupowanie i tasowanie, aby ułatwić 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': [0 0 0 0]
features:
  'sex'               : [b'male' b'male' b'male' b'male']
  'age'               : [18. 19. 31. 34.]
  'n_siblings_spouses': [0 0 0 1]
  'parch'             : [0 0 0 0]
  'fare'              : [ 8.3    8.05   7.775 26.   ]
  'class'             : [b'Third' b'Third' b'Third' b'Second']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Southampton' b'Southampton']
  'alone'             : [b'y' b'y' b'y' b'n']

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 1]
  'fare'              : [ 20.25     9.     110.8833  59.4   ]
  'class'             : [b'Third' b'Third' b'First' b'First']

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/4933229095_f7e4218b28.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/18282528206_7fb3166041.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/3711723108_65247a3170.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4019748730_ee09b39a43.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/475936554_a2b38aaa8e.jpg'

Odczytaj dane za pomocą funkcji tf.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\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'roses'

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 składnika elementów: tj. Dla każdego składnika 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 wypełnieniem

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óżnym kształcie 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ż zastąpienie 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())
[ 29  39  94  10  81  85  74   3  91  53  87  17   1  64  54 107  20  63
 116  62]

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

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:

[469 560 614 431 557 558 618 616 571 532]
[464 403 509 589 365 610 596 428 600 605]
[539 467 163 599 624 545 552 548]
[92 30 73 51 72 71 56 83 32 46]
[ 74 106  75  78  26  21   3  54  80  50]

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 0x7f79e43368d0>

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:

[618 517  22  13 602 608  25 621  12 579]
[568 620 534 547 319   1  17 346  29 581]
[537 395 560  33 617 557 561 610 585 627]
[428 607  32   0 455 496 605 624   6 596]
[511 588  14 572   4  21  66 471  41  36]
[583 622  49  54 616  75  65  57  76   7]
[ 77  37 574 586  74  56  28 419  73   5]
[599  83  31  61  91 481 577  98  26  50]
[  2 625  85  23 606 102 550  20 612  15]
[ 24  63 592 615 388  39  45  51  68  97]
[ 46  34  67  55  79  94 548  47  72 436]
[127  89  30  90 126 601 360  87 104  52]
[ 59 130 106 584 118 265  48  70 597 121]
[626 105 135  10  38  53 132 136 101   9]
[512 542 109 154 108  42 114  11  58 166]

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 0x7f79e42b60b8>

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 korzystania z 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 było je 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 poza tf.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 = 5

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

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

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]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22 23 24]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32 33 34]

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=' ')
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f79e44309d8> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f79e44309d8> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
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 przesuwa się 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 z zestawem danych, który jest bardzo niezrównoważony pod względem klas, możesz chcieć ponownie próbkować zestaw 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.9957 0.0043]

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:

Pobieranie próbek danych

Jednym ze sposobów ponownego próbkowania zbioru danych jest użycie sample_from_datasets . Jest to bardziej przydatne, gdy masz oddzielne data.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())
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f79e43ce510> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f79e43ce510> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7f79e43ceae8> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7f79e43ceae8> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert

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 ich wagę:

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 1 1 1 1 0 0 1 0 1]
[0 0 0 0 1 1 1 0 0 1]
[1 0 1 0 1 1 0 0 1 0]
[0 0 0 0 1 0 1 1 0 0]
[0 1 1 1 0 1 1 1 1 1]
[0 1 0 1 1 0 1 1 0 1]
[0 0 1 1 1 0 1 1 1 1]
[1 1 0 0 1 1 0 1 1 0]
[0 1 0 0 1 0 0 0 0 0]
[0 1 1 1 0 1 0 1 1 0]

Odrzucenie ponownego próbkowania

Jednym z problemów z powyższym podejściem experimental.sample_from_datasets jest to, że wymaga on osobnego tf.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ślenia, 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 tylko 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, dlatego 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 (class, example) pary z 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 1 1 0 0 0 0 0 0]
[1 0 1 0 1 1 0 1 0 1]
[0 1 1 0 0 1 0 0 0 0]
[1 0 1 0 0 1 0 1 0 0]
[1 1 1 1 0 0 0 0 0 1]
[1 0 1 1 1 0 1 0 1 1]
[0 0 1 1 1 1 0 0 1 1]
[0 0 1 0 0 1 1 1 0 1]
[1 0 0 0 1 0 0 0 0 0]
[0 0 0 0 1 0 0 0 1 1]

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ż sprawdzić 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 uwzględnić swój iterator w punkcie kontrolnym, 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 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'])

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
WARNING:tensorflow:Layer flatten is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because its dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

1875/1875 [==============================] - 3s 2ms/step - loss: 0.5973 - accuracy: 0.7969
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4629 - accuracy: 0.8407

<tensorflow.python.keras.callbacks.History at 0x7f79e43d8710>

Jeśli przekazujesz nieskończony zestaw danych, na przykład wywołując 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.4800 - accuracy: 0.8594
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4529 - accuracy: 0.8391

<tensorflow.python.keras.callbacks.History at 0x7f79e43d84a8>

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4386 - accuracy: 0.8508
Loss : 0.4385797381401062
Accuracy : 0.8507500290870667

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.3737 - accuracy: 0.8656
Loss : 0.37370163202285767
Accuracy : 0.8656250238418579

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)