Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

tf.data: crea pipeline di input TensorFlow

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza sorgente su GitHub Scarica notebook

L'API tf.data consente di creare pipeline di input complesse da parti semplici e riutilizzabili. Ad esempio, la pipeline per un modello di immagine potrebbe aggregare dati da file in un file system distribuito, applicare perturbazioni casuali a ciascuna immagine e unire immagini selezionate casualmente in un batch per l'addestramento. La pipeline per un modello di testo potrebbe comportare l'estrazione di simboli da dati di testo non elaborati, la loro conversione per incorporare identificatori con una tabella di ricerca e il raggruppamento in batch di sequenze di diverse lunghezze. L'API tf.data consente di gestire grandi quantità di dati, leggere da diversi formati di dati ed eseguire trasformazioni complesse.

L'API tf.data introduce un'astrazionetf.data.Dataset che rappresenta una sequenza di elementi, in cui ogni elemento è costituito da uno o più componenti. Ad esempio, in una pipeline di immagini, un elemento potrebbe essere un singolo esempio di addestramento, con una coppia di componenti tensoriali che rappresentano l'immagine e la sua etichetta.

Esistono due modi distinti per creare un set di dati:

  • Una sorgente di dati costruisce un Dataset dai dati memorizzati nella memoria o in uno o più file.

  • Una trasformazione dei dati costruisce un set di dati da uno o più oggettitf.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)

Meccanica di base

Per creare una pipeline di ingresso, è necessario iniziare con una sorgente di dati. Ad esempio, per costruire un Dataset dai dati in memoria, è possibile utilizzare tf.data.Dataset.from_tensors() o tf.data.Dataset.from_tensor_slices() . In alternativa, se i dati di input sono memorizzati in un file nel formato TFRecord consigliato, è possibile utilizzare tf.data.TFRecordDataset() .

Una volta che hai un oggetto Dataset , puoi trasformarlo in un nuovo Dataset concatenando le chiamate aitf.data.Dataset sull'oggettotf.data.Dataset . Ad esempio, è possibile applicare trasformazioni per elemento come Dataset.map() e trasformazioni Dataset.batch() come Dataset.batch() . Vedere la documentazione pertf.data.Dataset per un elenco completo delle trasformazioni.

L'oggetto Dataset è un iterabile Python. Ciò rende possibile consumare i suoi elementi utilizzando un ciclo 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

O creando esplicitamente un iteratore Python usando iter e consumando i suoi elementi usando next :

it = iter(dataset)

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

In alternativa, gli elementi del set di dati possono essere utilizzati utilizzando la trasformazione reduce , che riduce tutti gli elementi per produrre un unico risultato. L'esempio seguente illustra come utilizzare la trasformazione di reduce per calcolare la somma di un set di dati di numeri interi.

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

Struttura del set di dati

Un set di dati contiene elementi che hanno ciascuno la stessa struttura (annidata) e i singoli componenti della struttura possono essere di qualsiasi tipo rappresentabile da tf.TypeSpec , inclusi tf.Tensor , tf.sparse.SparseTensor ,tf.RaggedTensor , tf.TensorArray , otf.data.Dataset .

La proprietà Dataset.element_spec consente di controllare il tipo di ogni componente dell'elemento. La proprietà restituisce una struttura nidificata di oggetti tf.TypeSpec , corrispondente alla struttura dell'elemento, che può essere un singolo componente, una tupla di componenti o una tupla nidificata di componenti. Per esempio:

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

I Dataset trasformazioni supportano set di dati di qualsiasi struttura. Quando si utilizzano le Dataset.map() e Dataset.filter() , che applicano una funzione a ciascun elemento, la struttura dell'elemento determina gli argomenti della funzione:

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

dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[4 6 7 3 1 1 6 7 3 7]
[6 6 1 7 3 8 9 8 9 4]
[2 3 2 2 7 1 8 8 5 9]
[6 6 7 8 8 9 2 3 7 8]

dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

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

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

Lettura dei dati di input

Consumo di array NumPy

Vedi Caricamento di array NumPy per ulteriori esempi.

Se tutti i dati di input rientrano nella memoria, il modo più semplice per creare un Dataset da essi è convertirli in oggetti tf.Tensor e utilizzare 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)>

Consumare generatori Python

Un'altra origine dati comune che può essere facilmentetf.data.Dataset cometf.data.Dataset è il generatore di 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

Il costruttore Dataset.from_generator converte il generatore Python in untf.data.Dataset completamente funzionale.

Il costruttore accetta un chiamabile come input, non un iteratore. Ciò gli consente di riavviare il generatore quando raggiunge la fine. args argomento args facoltativo, che viene passato come argomenti del chiamabile.

L'argomento output_types è richiesto perché tf.data costruisce internamente un tf.Graph e gli spigoli del grafico richiedono un 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]

L'argomento output_shapes non è richiesto ma è altamente raccomandato poiché molte operazioni tensorflow non supportano tensori con rango sconosciuto. Se la lunghezza di un particolare asse è sconosciuta o variabile, output_shapes come None in output_shapes .

È anche importante notare che output_shapes e output_types seguono le stesse regole di nidificazione degli altri metodi del set di dati.

Ecco un generatore di esempio che dimostra entrambi gli aspetti, restituisce tuple di array, dove il secondo array è un vettore di lunghezza sconosciuta.

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [-1.8423 -0.1016  0.2763  0.815   0.0137  0.1228  0.0773]
1 : [ 0.4419  0.6819 -0.576 ]
2 : [-0.8961 -0.8613 -0.5917  0.7749 -0.2283  0.4406 -2.4833  0.1952  0.9962]
3 : []
4 : [0.2609 0.854  2.96  ]
5 : []
6 : [ 1.0899 -0.377   0.4295 -1.835  -0.4915 -0.0435 -0.6999 -0.9527]

Il primo output è un int32 il secondo è un float32 .

Il primo elemento è uno scalare, shape () , e il secondo è un vettore di lunghezza sconosciuta, forma (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)>

Ora può essere utilizzato come un normaletf.data.Dataset . Tieni presente che quando esegui il batch di un set di dati con una forma variabile, devi utilizzare Dataset.padded_batch .

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

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[12  0 21 20 19  2 13  6 16 15]

[[ 0.6409  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3158 -1.1566  0.5766  0.2067  0.2566 -0.7567  0.      0.      0.    ]
 [ 1.703   0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 1.577   0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.1427  1.779   1.5403  0.428  -0.0309  0.8038 -0.4779  0.3646 -0.3527]
 [-1.0069  0.6749 -1.4268  0.0887  0.4798  0.769   0.5454  0.      0.    ]
 [-0.3393  0.5725 -0.8578 -3.5323 -0.9053  0.261  -1.7785  0.5377 -0.4388]
 [ 0.5343  1.609  -0.9407  1.1031  0.4216  0.      0.      0.      0.    ]
 [ 1.1637  0.6195  1.6317 -0.759  -0.4261 -3.2933  1.9672 -0.2561  1.341 ]]

Per un esempio più realistico, prova atf.data.Dataset wrapping di preprocessing.image.ImageDataGenerator cometf.data.Dataset .

Prima scarica i dati:

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

Crea il file 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)

Consumo di dati TFRecord

Vedere Caricamento TFRecords per un esempio end-to-end.

L'API tf.data supporta una varietà di formati di file in modo da poter elaborare set di dati di grandi dimensioni che non si adattano alla memoria. Ad esempio, il formato di file TFRecord è un semplice formato binario orientato ai record che molte applicazioni TensorFlow utilizzano per l'addestramento dei dati. La classe tf.data.TFRecordDataset consente di eseguire lo streaming sui contenuti di uno o più file TFRecord come parte di una pipeline di input.

Di seguito è riportato un esempio che utilizza il file di prova dei segnali stradali francesi (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

L'argomento dei filenames per l'inizializzatore TFRecordDataset può essere una stringa, un elenco di stringhe o un tf.Tensor di stringhe. Pertanto, se si dispone di due set di file per scopi di addestramento e convalida, è possibile creare un metodo factory che produce il set di dati, prendendo i nomi dei file come argomento di input:

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

Molti progetti TensorFlow utilizzano record tf.train.Example serializzati nei file TFRecord. Questi devono essere decodificati prima di poter essere ispezionati:

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

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

Consumo di dati di testo

Vedere Caricamento del testo per un esempio dall'inizio alla fine.

Molti set di dati vengono distribuiti come uno o più file di testo. Il tf.data.TextLineDataset fornisce un modo semplice per estrarre le righe da uno o più file di testo. Dato uno o più nomi di file, un TextLineDataset produrrà un elemento con valori di stringa per riga di quei file.

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)

Ecco le prime righe del primo file:

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

Per alternare le righe tra i file utilizzare Dataset.interleave . Ciò rende più facile mescolare i file insieme. Ecco la prima, la seconda e la terza riga di ogni traduzione:

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'

Per impostazione predefinita, un TextLineDataset restituisce ogni riga di ogni file, il che potrebbe non essere desiderabile, ad esempio, se il file inizia con una riga di intestazione o contiene commenti. Queste righe possono essere rimosse utilizzando le Dataset.skip() o Dataset.filter() . Qui, salti la prima riga, quindi filtri per trovare solo i sopravvissuti.

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'

Consumo di dati CSV

Vedere Caricamento di file CSV e Caricamento di frame di dati Pandas per ulteriori esempi.

Il formato di file CSV è un formato popolare per l'archiviazione di dati tabulari in testo normale.

Per esempio:

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

Se i tuoi dati Dataset.from_tensor_slices in memoria, lo stesso metodo Dataset.from_tensor_slices funziona sui dizionari, consentendo di importare facilmente questi dati:

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'

Un approccio più scalabile consiste nel caricare dal disco se necessario.

Il modulo tf.data fornisce metodi per estrarre record da uno o più file CSV conformi a RFC 4180 .

La funzione experimental.make_csv_dataset è l'interfaccia di alto livello per la lettura di set di file csv. Supporta l'inferenza del tipo di colonna e molte altre funzionalità, come batch e mescolamento, per semplificare l'utilizzo.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 0 0 1]
features:
  'sex'               : [b'male' b'male' b'male' b'female']
  'age'               : [11. 16. 28. 19.]
  'n_siblings_spouses': [5 4 0 0]
  'parch'             : [2 1 0 2]
  'fare'              : [46.9    39.6875  7.75   26.2833]
  'class'             : [b'Third' b'Third' b'Third' b'First']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'D']
  'embark_town'       : [b'Southampton' b'Southampton' b'Queenstown' b'Southampton']
  'alone'             : [b'n' b'n' b'y' b'n']

È possibile utilizzare l'argomento select_columns se è necessario solo un sottoinsieme di colonne.

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

Esiste anche una classe experimental.CsvDataset livello inferiore che fornisce un controllo granulare più fine. Non supporta l'inferenza del tipo di colonna. Invece è necessario specificare il tipo di ogni colonna.

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

Se alcune colonne sono vuote, questa interfaccia di basso livello consente di fornire valori predefiniti invece dei tipi di colonna.

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

Per impostazione predefinita, un CsvDataset restituisce ogni colonna di ogni riga del file, il che potrebbe non essere desiderabile, ad esempio se il file inizia con una riga di intestazione che deve essere ignorata o se alcune colonne non sono richieste nell'input. Queste righe e questi campi possono essere rimossi rispettivamente con gli argomenti header e 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]

Consumare set di file

Esistono molti set di dati distribuiti come un insieme di file, di cui ogni file è un esempio.

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)

La directory root contiene una directory per ogni classe:

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

I file in ciascuna directory di classe sono esempi:

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

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4868595281_1e58083785.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/5883162120_dc7274af76_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/12883412424_cb5086b43f_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/13264214185_d6aa79b3bd.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/6690926183_afedba9f15_n.jpg'

Leggi i dati utilizzando la funzionetf.io.read_file ed estrai l'etichetta dal percorso, restituendo le coppie (image, label) :

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

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xfe\x00\x0cAppleMark\n\xff\xe2\x05(ICC_PROFILE\x00\x01\x01\x00\x00\x05\x18appl\x02 \x00\x00scnrRGB XYZ \x07\xd3\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00acspAPPL\x00\x00\x00\x00'

b'sunflowers'

Batch di elementi del set di dati

Dosaggio semplice

La forma più semplice di batch impila n elementi consecutivi di un set di dati in un singolo elemento. La trasformazione Dataset.batch() fa esattamente questo, con gli stessi vincoli dell'operatore tf.stack() , applicato a ogni componente degli elementi: cioè per ogni componente i , tutti gli elementi devono avere un tensore della stessa identica forma.

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

Mentre tf.data tenta di propagare le informazioni sulla forma, le impostazioni predefinite di Dataset.batch danno Dataset.batch risultato una dimensione batch sconosciuta perché l'ultimo batch potrebbe non essere pieno. Nota il None nella forma:

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

Utilizza l'argomento drop_remainder per ignorare l'ultimo batch e ottenere la propagazione completa della forma:

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

Tensori di dosaggio con imbottitura

La ricetta sopra funziona per tensori che hanno tutti la stessa dimensione. Tuttavia, molti modelli (ad es. Modelli di sequenza) funzionano con dati di input che possono avere dimensioni variabili (ad es. Sequenze di lunghezze diverse). Per gestire questo caso, la trasformazione Dataset.padded_batch consente di Dataset.padded_batch in batch tensori di forma diversa specificando una o più dimensioni in cui possono essere riempiti.

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


La trasformazione Dataset.padded_batch consente di impostare una spaziatura interna diversa per ogni dimensione di ogni componente e può essere di lunghezza variabile (indicata da None nell'esempio sopra) o di lunghezza costante. È anche possibile sovrascrivere il valore di riempimento, il cui valore predefinito è 0.

Flussi di lavoro di formazione

Elaborazione di più epoche

L'API tf.data offre due modi principali per elaborare più epoche degli stessi dati.

Il modo più semplice per iterare su un set di dati in più epoche è utilizzare la trasformazione Dataset.repeat() . Innanzitutto, crea un set di dati di dati titanici:

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

L'applicazione della trasformazione Dataset.repeat() senza argomenti ripeterà l'input indefinitamente.

La trasformazione Dataset.repeat concatena i suoi argomenti senza segnalare la fine di un'epoca e l'inizio dell'epoca successiva. Per questo Dataset.batch un Dataset.batch applicato dopo Dataset.repeat produrrà batch che si Dataset.repeat a cavallo dei confini dell'epoca:

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

png

Se è necessaria una chiara separazione delle epoche, inserire Dataset.batch prima della ripetizione:

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

plot_batch_sizes(titanic_batches)

png

Se desideri eseguire un calcolo personalizzato (ad esempio per raccogliere statistiche) alla fine di ogni epoca, è più semplice riavviare l'iterazione del set di dati su ciascuna epoca:

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

Mescolamento casuale dei dati di input

La trasformazione Dataset.shuffle() mantiene un buffer di dimensioni fisse e sceglie l'elemento successivo in modo uniforme e casuale da quel buffer.

Aggiungi un indice al set di dati in modo da poter vedere l'effetto:

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

Poiché buffer_size è 100 e la dimensione del batch è 20, il primo batch non contiene elementi con un indice superiore a 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 90  75  39  84 102   5  98 101  51  72  54  33 104  59 110  29  92  50
  36 103]

Come con Dataset.batch l'ordine relativo a Dataset.repeat importante.

Dataset.shuffle non segnala la fine di un'epoca fino a quando il buffer di shuffle non è vuoto. Quindi uno shuffle posizionato prima di una ripetizione mostrerà ogni elemento di un'epoca prima di passare a quella successiva:

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

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

[550 618 614 435 556 530 578 451 590 604]
[464 453 610 412 282 596 601 612 584 606]
[368 469 575 607 586 537 444 300]
[ 15  98  65  26  40  39 101  54  32  10]
[  8 102  68 108  12  96   2  87  80  37]

shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7f3f083eebe0>

png

Ma una ripetizione prima di uno shuffle mescola insieme i confini dell'epoca:

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

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

[576 618 527   9 602 612  21 574 504 622]
[623  26  32 616 626 482 617 598   0 614]
[476   1 473  14  10 267  29  31  43  48]
[588  13 470 467  12 596 619  46  28 528]
[609   2  52 542 607  23  35  38 620 523]
[509 477 571  15  56  74 565 525  58  19]
[359  40  22 627 317  54 526  16 562  33]
[ 67 500 584 531  49  86  51  81  78 583]
[ 24 557 452  47 124 485 610  45  27  17]
[379  66  85  91 599  97 499 112 108  11]
[ 39 164 101  96 543  64 109 564  82  18]
[533 120  30  63 115  88  95  75 133  34]
[ 92  65 102 132  76 119 131 475 572  50]
[ 94 145 144 603 152 505 621 140 448 122]
[ 70 159 146  84  71 160  42  72  41 139]

repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

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

png

Pre-elaborazione dei dati

La Dataset.map(f) produce un nuovo set di dati applicando una determinata funzione f a ciascun elemento del set di dati di input. Si basa sulla funzione map() comunemente applicata agli elenchi (e ad altre strutture) nei linguaggi di programmazione funzionale. La funzione f accetta gli oggetti tf.Tensor che rappresentano un singolo elemento nell'input e restituisce gli oggetti tf.Tensor che rappresenteranno un singolo elemento nel nuovo dataset. La sua implementazione utilizza operazioni TensorFlow standard per trasformare un elemento in un altro.

Questa sezione copre esempi comuni di come utilizzare Dataset.map() .

Decodificare i dati dell'immagine e ridimensionarli

Quando si addestra una rete neurale su dati di immagini del mondo reale, è spesso necessario convertire immagini di dimensioni diverse in una dimensione comune, in modo che possano essere raggruppate in una dimensione fissa.

Ricostruisci il set di dati dei nomi dei file dei fiori:

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

Scrivi una funzione che manipoli gli elementi del set di dati.

# 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

Verifica che funzioni.

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

Mappalo sul set di dati.

images_ds = list_ds.map(parse_image)

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

png

png

Applicazione della logica Python arbitraria

Per motivi di prestazioni, utilizza le operazioni TensorFlow per la preelaborazione dei dati ogni volta che è possibile. Tuttavia, a volte è utile chiamare librerie Python esterne durante l'analisi dei dati di input. È possibile utilizzare l'operazione tf.py_function() in una trasformazione Dataset.map() .

Ad esempio, se si desidera applicare una rotazione casuale, il tf.image modulo ha solo tf.image.rot90 , che non è molto utile per un'immagine aumento.

Per dimostrare tf.py_function , prova invece a utilizzare la funzione 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

Per utilizzare questa funzione con Dataset.map applicano le stesse avvertenze di Dataset.from_generator , è necessario descrivere le forme ei tipi di ritorno quando si applica la funzione:

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

Parsing tf.Example protocol buffer messages

Molte pipeline di input estraggono i messaggi del buffer di protocollo tf.train.Example da un formato TFRecord. Ogni record tf.train.Example contiene una o più "caratteristiche" e la pipeline di input converte tipicamente queste caratteristiche in tensori.

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>

Puoi lavorare con tf.train.Example protos al di fuori di untf.data.Dataset per comprendere i dati:

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

Finestra delle serie temporali

Per un esempio di serie temporale end-to-end, vedere: Previsione delle serie temporali .

I dati delle serie temporali sono spesso organizzati con l'asse temporale intatto.

Usa un semplice Dataset.range per dimostrare:

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

In genere, i modelli basati su questo tipo di dati richiedono una porzione di tempo contigua.

L'approccio più semplice sarebbe quello di raggruppare i dati:

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

Oppure, per fare previsioni dense un passo nel futuro, potresti spostare le caratteristiche e le etichette di un passo l'una rispetto all'altra:

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]

Per prevedere un'intera finestra invece di un offset fisso è possibile dividere i batch in due parti:

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]

Per consentire una certa sovrapposizione tra le caratteristiche di un batch e le etichette di un altro, utilizza 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]

Utilizzando window

Durante l'utilizzo di Dataset.batch , in alcune situazioni potrebbe essere necessario un controllo più preciso. Il metodo Dataset.window ti dà il controllo completo, ma richiede una certa attenzione: restituisce un Dataset di Datasets . Vedi Struttura del set di dati per i dettagli.

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>

Il metodo Dataset.flat_map può prendere un set di dati di set di dati e appiattirlo in un singolo set di dati:

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 

In quasi tutti i casi, vorrai prima .batch il .batch del set di dati:

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]

Ora, puoi vedere che l'argomento shift controlla quanto si sposta ogni finestra.

Mettendo questo insieme potresti scrivere questa funzione:

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]

Quindi è facile estrarre le etichette, come prima:

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]

Ricampionamento

Quando si lavora con un set di dati molto sbilanciato in classi, è possibile ricampionare il set di dati. tf.data fornisce due metodi per farlo. Il set di dati sulle frodi con carta di credito è un buon esempio di questo tipo di problema.

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

Ora, controlla la distribuzione delle classi, è molto distorta:

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]

Un approccio comune alla formazione con un set di dati sbilanciato consiste nel bilanciarlo. tf.data include alcuni metodi che abilitano questo flusso di lavoro:

Campionamento di set di dati

Un approccio al ricampionamento di un set di dati consiste nell'utilizzare sample_from_datasets . Questo è più applicabile quando si dispone di undata.Dataset separato per ogni classe.

Qui, usa il filtro per generarli dai dati di frode della carta di credito:

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]

Per utilizzare tf.data.experimental.sample_from_datasets passare i set di dati e il peso per ciascuno:

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

Ora il set di dati produce esempi di ciascuna classe con probabilità 50/50:

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

Ricampionamento del rifiuto

Un problema con l'approccio experimental.sample_from_datasets sopra è che necessita di untf.data.Dataset separato per classe. L'utilizzo di Dataset.filter funziona, ma tutti i dati vengono caricati due volte.

La funzione data.experimental.rejection_resample può essere applicata a un set di dati per ribilanciarlo, caricandolo solo una volta. Gli elementi verranno eliminati dal set di dati per raggiungere l'equilibrio.

data.experimental.rejection_resample accetta un argomento class_func . Questa funzione class_func viene applicata a ciascun elemento del set di dati e viene utilizzata per determinare a quale classe appartiene un esempio ai fini del bilanciamento.

Gli elementi di creditcard_ds sono già coppie (features, label) . Quindi class_func deve solo restituire quelle etichette:

def class_func(features, label):
  return label

Il ricampionatore necessita anche di una distribuzione target e, facoltativamente, di una stima della distribuzione iniziale:

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

Il ricampionatore si occupa di singoli esempi, quindi devi unbatch il set di dati prima di applicare il ricampionatore:

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:


Il ricampionatore restituisce le coppie create (class, example) dall'output di class_func . In questo caso, l' example era già una coppia (feature, label) , quindi usa map per rilasciare la copia extra delle etichette:

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

Ora il set di dati produce esempi di ciascuna classe con probabilità 50/50:

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

Checkpoint dell'iteratore

Tensorflow supporta l' acquisizione di checkpoint in modo che al riavvio del processo di addestramento possa ripristinare l'ultimo checkpoint per recuperare la maggior parte dei suoi progressi. Oltre a controllare le variabili del modello, puoi anche controllare lo stato di avanzamento dell'iteratore del set di dati. Ciò potrebbe essere utile se si dispone di un set di dati di grandi dimensioni e non si desidera avviare il set di dati dall'inizio a ogni riavvio. Si noti tuttavia che i punti di controllo dell'iteratore possono essere grandi, poiché trasformazioni come shuffle e prefetch richiedono elementi di buffering all'interno dell'iteratore.

Per includere il tuo iteratore in un checkpoint, passa l'iteratore al costruttore 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]

Utilizzo di tf.data con tf.keras

L'API tf.keras semplifica molti aspetti della creazione e dell'esecuzione di modelli di machine learning. Le sue .fit() e .evaluate() e .predict() supportano i set di dati come input. Ecco un rapido set di dati e configurazione del modello:

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

Il passaggio di un set di dati di coppie (feature, label) è tutto ciò che è necessario per Model.fit e Model.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.7804 - accuracy: 0.7374
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4711 - accuracy: 0.8393

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

Se passi un dataset infinito, ad esempio chiamando Dataset.repeat() , devi solo passare anche l'argomento steps_per_epoch :

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4312 - accuracy: 0.8562
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4509 - accuracy: 0.8344

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

Per la valutazione puoi superare il numero di passaggi di valutazione:

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

Per set di dati lunghi, imposta il numero di passaggi da valutare:

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4131 - accuracy: 0.8750
Loss : 0.41311272978782654
Accuracy : 0.875

Le etichette non sono necessarie quando si chiama 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)

Ma le etichette vengono ignorate se passi un set di dati che le contiene:

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