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 elementi 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 raggruppare sequenze di lunghezze diverse. 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'astrazione tf.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ù oggetti 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)

Meccanica di base

Per creare una pipeline di ingresso, è necessario iniziare con una sorgente di dati. Ad esempio, per costruire un Dataset di dati 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 archiviati in un file nel formato TFRecord consigliato, è possibile utilizzare tf.data.TFRecordDataset() .

Una volta che si dispone di un oggetto Dataset , è possibile trasformarlo in un nuovo Dataset concatenando le chiamate ai tf.data.Dataset sull'oggetto tf.data.Dataset . Ad esempio, è possibile applicare trasformazioni per elemento come Dataset.map() e trasformazioni Dataset.batch() come Dataset.batch() . Vedere la documentazione per tf.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 (nidificata) 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 , o tf.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())
[6 4 2 6 4 7 8 5 2 3]
[3 6 2 3 8 6 1 2 3 4]
[8 4 4 2 7 3 1 7 7 8]
[9 9 3 7 9 1 9 1 1 7]

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

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

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

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 facilmente tf.data.Dataset come tf.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 un tf.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 tf.Graph 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 : [-0.119  -0.1705  0.6298  1.6472]
1 : [0.4102 0.858 ]
2 : [ 1.1845 -0.4004 -0.5682  0.7648 -2.0806  1.2242 -2.0966  0.8446]
3 : [-2.2548  1.4521  0.8092]
4 : [ 0.8081  0.7604 -0.6913  0.5043  1.0684]
5 : [-0.1358  0.3046  0.0346]
6 : []

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 normale tf.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())
[16 19 15  2  0 23 12  6 27 20]

[[ 1.7982  0.6075  0.8935  1.5496 -0.9427  0.9026 -1.0912  0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3835 -0.7199  0.6697 -0.5535 -1.2751  1.0727 -1.6975 -1.8727  0.6132]
 [-0.7542  2.4467  1.6744  0.6795  0.3545  0.4416  0.      0.      0.    ]
 [-1.1485  0.2186 -0.5212 -0.6324 -1.1361 -2.0172  0.      0.      0.    ]
 [ 0.3903  0.0763  0.1203 -1.5849  0.      0.      0.      0.      0.    ]
 [-0.361   0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3172 -0.3045 -0.4404  1.0413 -2.2203 -0.9137  0.      0.      0.    ]
 [ 0.1888  1.432   1.4547  0.5166 -0.2852  2.3501 -0.3476 -0.1323 -0.0585]
 [ 0.0677  0.5017  1.5934  0.5202  0.      0.      0.      0.      0.    ]]

Per un esempio più realistico, prova a tf.data.Dataset wrapping di preprocessing.image.ImageDataGenerator come tf.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 [==============================] - 3s 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(
    img_gen.flow_from_directory, args=[flowers], 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds
<FlatMapDataset shapes: ((32, 256, 256, 3), (32, 5)), types: (tf.float32, tf.float32)>

Consumo di dati TFRecord

Vedere Caricamento di 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 filenames dell'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, index_col=None)
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 il raggruppamento in batch e lo shuffling, per semplificarne 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'female' b'male']
  'age'               : [30. 28. 28. 36.]
  'n_siblings_spouses': [0 0 0 0]
  'parch'             : [0 0 2 1]
  'fare'              : [ 13.       7.8958   7.75   512.3292]
  'class'             : [b'Second' b'Third' b'Third' b'First']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'B']
  'embark_town'       : [b'Southampton' b'Southampton' b'Queenstown' b'Cherbourg']
  'alone'             : [b'y' b'y' b'n' 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': [1 1 0 0]
  'fare'              : [ 7.925 32.5   13.     8.05 ]
  'class'             : [b'Third' b'Second' b'Second' 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. Devi invece 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/4746648726_e37a2de16d_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/2862944799_45bc8e7302.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/13513644515_a51470b899.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/506018088_4f7a15a7c5_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/15381511376_fd743b7330_n.jpg'

Leggi i dati utilizzando la funzione tf.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\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'sunflowers'

Batch di elementi del set di dati

Dosaggio semplice

La forma più semplice di batching impila n elementi consecutivi di un set di dati in un unico 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 del 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 ciascun 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

Misurazione 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())
[  6  41  35  53  36  50  52  80   1  26  22  29  38  55  84  18  47  68
 100 105]

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:

[594 456 585 615 536 627 514 297 617 620]
[495 530 356 622 395 602 505 593 611 492]
[573 597 575 528 561 616 609 494]
[51 85 41  4 37  9 91 74 89  8]
[ 13 107 104  65  52  83  71  16 101  92]

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

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:

[570 263 533  22  17  14 485 348 517 606]
[501 488 620 611 624 599 534 398 542 521]
[446 596 359 587   6 475 436 610   2 605]
[ 35 583  49  16 547  46  39 576  47   8]
[ 30  26  63  55 496  51   9 575  66 571]
[ 41  57  33 589  43 550 540 404 591 595]
[ 12 512  48 581 600  25  67  19  85  56]
[ 83  65 563 597  52  61  90 585  91  78]
[ 11  37 445 621 618 482  92 103  82  32]
[ 42  79  62  88 543  80  95 598 539 113]
[ 38  86 107 124  50 114  76 535   3  27]
[111  64  71 130  29 129 470 120  94 101]
[425 122 131  18  40  84  87  73 519  21]
[272  58 143 145 331  77  60 612 127  13]
[495  96  99 523 138 627 115 167 135 152]

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

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)

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 del 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 un tf.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 = 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]

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=' ')
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7fda544008c8> 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 0x7fda544008c8> 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 

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 che è molto squilibrato in base alla classe, è 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.9964 0.0036]

Un approccio comune alla formazione con un set di dati sbilanciato è 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 un data.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())
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7fda54400400> 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 0x7fda54400400> 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 0x7fda54440400> 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 0x7fda54440400> 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]

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

Ricampionamento del rifiuto

Un problema con l'approccio experimental.sample_from_datasets sopra è che ha bisogno di un tf.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 1 1 1 0 1 0 1 0]
[0 1 0 1 0 0 1 1 0 1]
[0 1 1 1 0 0 0 0 0 0]
[1 0 1 1 0 0 0 0 0 0]
[0 0 1 1 0 1 1 1 0 1]
[1 0 0 0 0 1 1 1 0 0]
[0 1 0 1 1 1 0 1 1 1]
[0 1 0 0 0 0 0 1 0 0]
[1 1 0 1 0 0 1 1 1 1]
[0 0 0 0 1 0 0 1 0 1]

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 eseguire il checkpoint delle variabili del modello, è anche possibile 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
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.6033 - accuracy: 0.7961
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4633 - accuracy: 0.8411

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

Se passi un set di dati 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.4781 - accuracy: 0.8313
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4285 - accuracy: 0.8516

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

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 [==============================] - 3s 1ms/step - loss: 0.4449 - accuracy: 0.8468
Loss : 0.4448782205581665
Accuracy : 0.846750020980835

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.4509 - accuracy: 0.8438
Loss : 0.45086926221847534
Accuracy : 0.84375

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)