tf.data: crea pipeline di input TensorFlow

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHubScarica il taccuino

Il tf.data API consente di costruire oleodotti ingresso complessi da semplici pezzi 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 in identificatori incorporati con una tabella di ricerca e il raggruppamento in batch di sequenze di lunghezze diverse. Il tf.data API permette di gestire grandi quantità di dati, letti dalla diversi formati di dati, ed eseguire trasformazioni complesse.

Il tf.data API introduce un tf.data.Dataset astrazione 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 del tensore 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 di dati costruisce un set di dati da uno o più tf.data.Dataset oggetti.

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. Per 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 è memorizzato in un file nel formato TFRecord consigliata, è possibile utilizzare tf.data.TFRecordDataset() .

Una volta che si dispone di un Dataset oggetto, è possibile trasformarlo in un nuovo Dataset concatenando chiamate di metodo sul tf.data.Dataset oggetto. Ad esempio, è possibile applicare trasformazioni per-elemento, come Dataset.map() , e trasformazioni più elementi, come Dataset.batch() . Vedere la documentazione per tf.data.Dataset per un elenco completo di trasformazioni.

Il Dataset oggetto è un Python iterabile. 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 in modo esplicito un Python iteratore utilizzando iter e consumare i suoi elementi utilizzando next :

it = iter(dataset)

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

In alternativa, gli elementi del set di dati possono essere consumati usando l' reduce trasformazione, che riduce tutti gli elementi per produrre un singolo risultato. Il seguente esempio illustra come utilizzare il reduce trasformazione per calcolare la somma di un insieme 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 produce una sequenza di elementi, in cui ogni elemento è uguale (nested) Struttura di componenti. I singoli componenti della struttura potranno essere di qualsiasi tipo rappresentabile da tf.TypeSpec , includendo tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray o tf.data.Dataset .

I costrutti Python che possono essere usate per esprimere la (nested) Struttura di elementi includono tuple , dict , NamedTuple e OrderedDict . In particolare, list non è un costrutto valido per esprimere la struttura del set di dati elementi. Questo perché i primi utenti tf.data fortemente sentita merito list ingressi (es passato a tf.data.Dataset.from_tensors ) essendo confezionato automaticamente come tensori e list uscite (ad esempio valori di ritorno delle funzioni definite dall'utente) essendo costretto a una tuple . Di conseguenza, se si desidera una list di ingresso di essere trattato come una struttura, è necessario convertirlo in tuple e se vuoi un list di output per essere un singolo componente, quindi è necessario mettere in valigia in modo esplicito utilizzando tf.stack .

Il Dataset.element_spec proprietà consente di ispezionare il tipo di ogni componente elemento. La proprietà restituisce una struttura nidificata di tf.TypeSpec oggetti, 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 utilizza il Dataset.map() , e Dataset.filter() trasformazioni, che applicano una funzione per ogni elemento, la struttura di 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())
[8 1 7 9 5 5 3 3 9 5]
[1 3 4 4 6 1 3 2 9 7]
[3 8 3 1 3 6 2 3 9 4]
[7 3 3 2 9 5 5 6 9 1]
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

Vedere Caricamento array NumPy per ulteriori esempi.

Se tutti i dati di input si inserisce in memoria, il modo più semplice per creare un Dataset da loro è quello di convertirli in tf.Tensor oggetti e sull'uso Dataset.from_tensor_slices() .

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 1s 0us/step
26435584/26421880 [==============================] - 1s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

Consumo di generatori Python

Un'altra fonte comune di dati che possono essere facilmente ingerito come tf.data.Dataset è il generatore pitone.

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 Dataset.from_generator costruttore converte il generatore pitone ad una completamente funzionale tf.data.Dataset .

Il costruttore accetta un callable come input, non un iteratore. Ciò consente di riavviare il generatore quando raggiunge la fine. Ci vuole un optional args argomento, che viene passato come argomento della richiamabili.

output_types argomento è necessario perché tf.data costruisce un tf.Graph internamente e bordi grafico necessita di tf.dtype .

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

output_shapes argomento non è necessaria, ma è altamente raccomandato come molte operazioni tensorflow non supportano tensori con rango sconosciuto. Se la lunghezza di un asse particolare è sconosciuto o variabile, impostarla come None nelle output_shapes .

E 'anche importante notare che i output_shapes e output_types seguono le stesse regole di nidificazione degli altri metodi di set di dati.

Ecco un generatore di esempio che dimostra entrambi gli aspetti, restituisce tuple di array, in cui il secondo array è un vettore con 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.5118  1.1338  0.4652 -0.1266  2.1672  0.6211]
1 : [-0.6128 -0.9794  0.8448 -2.0378  1.0311  0.3236 -1.2445]
2 : [-0.1676 -1.7922  0.9232  0.975   0.0882 -1.2209  2.9198]
3 : [ 1.3887  0.4012 -0.0488  0.6349  0.5027  0.445  -1.4106 -1.1119]
4 : [ 0.8127 -0.3852  2.5075 -0.9225 -1.3582  2.1959  0.3715  0.7565]
5 : [-0.7891  1.4259 -0.949  -0.7526 -1.6112  0.5935 -0.1808 -2.4721]
6 : [-0.2006  0.1787 -0.8684  0.7611  0.1345 -1.9513  0.0824 -0.2069]

La prima uscita è un int32 il secondo è un float32 .

Il primo elemento è uno scalare, forma () , 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 . Si noti che quando il dosaggio di un insieme di dati con una forma variabile, è necessario 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())
[18  3  9  2 21 24 10 16  1 25]

[[-0.1558  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3243  0.8113  1.3543  1.4365  1.7064  0.2669 -0.9977  0.      0.    ]
 [-1.0384  0.6793  0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.1779  0.7456 -0.6888  0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.3821  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3428 -1.3348  0.      0.      0.      0.      0.      0.      0.    ]
 [-1.7189 -0.9511  0.3775  0.3023 -0.8825 -1.002  -0.349  -0.2439 -0.6234]]

Per un esempio più realistico, provare avvolgendo 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 [==============================] - 8s 0us/step
228827136/228813984 [==============================] - 8s 0us/step

Creare 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.

Il tf.data API supporta una varietà di formati di file in modo da poter elaborare grandi insiemi di dati che non rientrano nella memoria. Ad esempio, il formato di file TFRecord è un semplice formato binario orientato ai record che molte applicazioni TensorFlow utilizzano per i dati di addestramento. Il tf.data.TFRecordDataset classe consente di riprodurre in streaming sul contenuto di uno o più file TFRecord come parte di una conduttura di ingresso.

Ecco un esempio che utilizza il file di prova della segnaletica stradale francese (FSNS).

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 0s 0us/step
7913472/7904079 [==============================] - 0s 0us/step

Il filenames argomento al TFRecordDataset inizializzatore può essere sia una stringa, una lista 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 di fabbrica 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 serializzati tf.train.Example record nelle loro 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 di testo per la fine esempio end.

Molti set di dati sono distribuiti come uno o più file di testo. Il tf.data.TextLineDataset fornisce un modo semplice per estrarre le linee da uno o più file di testo. Dato uno o più nomi di file, un TextLineDataset produrrà un elemento tipo stringa, per linea di tali 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
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

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 linee alternate tra i file utilizzare Dataset.interleave . Questo rende più facile mescolare i file insieme. Ecco la prima, la seconda e la terza riga di ciascuna 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 produce ogni riga di ogni file, che non può essere desiderabile, per esempio, se il file inizia con una riga di intestazione, o contiene i commenti. Tali linee possono essere rimosse con i Dataset.skip() o Dataset.filter() trasformazioni. 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
40960/30874 [=======================================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'

Consumo di dati CSV

Vedere Caricamento di file CSV , e carico Panda DataFrames per ulteriori esempi.

Il formato 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 gli stessi tuoi dati si inserisce in memoria Dataset.from_tensor_slices metodo funziona su dizionari, permettendo questi dati per essere facilmente importati:

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 tf.data modulo fornisce metodi per estrarre record da uno o più file CSV che rispettano RFC 4180 .

experimental.make_csv_dataset funzione è l'interfaccia di alto livello per la lettura dei gruppi di file CSV. Supporta l'inferenza del tipo di colonna e molte altre funzionalità, come batch e shuffle, 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 1 0 0]
features:
  'sex'               : [b'male' b'female' b'male' b'male']
  'age'               : [16. 38. 22. 28.]
  'n_siblings_spouses': [4 1 0 0]
  'parch'             : [1 5 0 0]
  'fare'              : [39.6875 31.3875  8.05    7.8958]
  'class'             : [b'Third' b'Third' b'Third' b'Third']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Southampton' b'Southampton']
  'alone'             : [b'n' b'n' b'y' b'y']

È possibile utilizzare l' select_columns argomento 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 0 0 1]
  'fare'              : [26.25   13.      6.8583 55.    ]
  'class'             : [b'Second' b'Second' b'Third' b'First']

C'è anche un livello inferiore experimental.CsvDataset classe che fornisce più fine controllo a grana fine. Non supporta l'inferenza del tipo di colonna. È invece necessario specificare il tipo di ciascuna 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 default, un CsvDataset produce ogni colonna di ogni riga del file, che non può essere desiderabile, per esempio se il file inizia con una riga di intestazione che deve essere ignorato, o se alcune colonne non sono necessari in ingresso. Queste linee ei campi possono essere rimossi con le header e select_cols argomenti rispettivamente.

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

Consumo di set di file

Esistono molti set di dati distribuiti come un insieme di file, in 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 principale 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 ogni 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/daisy/54377391_15648e8d18.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/18474740346_ffdaa18032.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/212720516_df4965ebda_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/14554906452_35f066ffe9_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/3393564906_f2df184b76_n.jpg'

Leggere i dati utilizzando la tf.io.read_file funzione ed estrarre l'etichetta dal percorso, ritornando (image, label) coppie:

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

Batch di elementi del set di dati

Dosaggio semplice

La forma più semplice di dosaggio pile n elementi consecutivi di un insieme di dati in un unico elemento. Il Dataset.batch() trasformazione fa esattamente questo, con gli stessi vincoli del tf.stack() operatore, applicate a ciascun componente degli elementi: vale a dire per ogni i componenti, tutti gli elementi devono avere un tensore dell'esatto stessa 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 cerca di propagare le informazioni di forma, le impostazioni di default di Dataset.batch risultato in una dimensione del lotto sconosciuto poiché l'ultimo lotto potrebbe non essere completa. Si notino le None s nella forma:

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

Utilizzare il drop_remainder tesi di ignorare che ultimo lotto, e ottenere pieno di propagazione di 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, il Dataset.padded_batch trasformazione consente di tensori lotti di forma diversa specificando una o più dimensioni in cui possono essere imbottiti.

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

Il Dataset.padded_batch trasformazione permette di impostare imbottitura diverso per ogni dimensione di ogni componente, e può essere di lunghezza variabile (significata dal None nell'esempio precedente) o costante di lunghezza. È anche possibile sovrascrivere il valore di riempimento, che per impostazione predefinita è 0.

Flussi di lavoro di formazione

Elaborazione di più epoche

Le tf.data offerte API due metodi principali per elaborare più epoche degli stessi dati.

Il modo più semplice per scorrere un set di dati in più epoche è utilizzare il Dataset.repeat() trasformazione. 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')

Applicando la Dataset.repeat() trasformazione senza argomenti ripeterà l'ingresso a tempo indeterminato.

Il Dataset.repeat trasformazione concatena suoi argomenti senza segnalare la fine di un'epoca e l'inizio del successivo epoca. A causa di questo un Dataset.batch applicato dopo Dataset.repeat produrrà lotti che i confini d'epoca a cavaliere:

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

png

Se avete bisogno di una chiara separazione epoca, mettere 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 ogni 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

Mescolando casualmente i dati di input

Il Dataset.shuffle() trasformazione mantiene un buffer di dimensione fissa e sceglie il successivo elemento uniformemente a caso 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
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/counter.py:66: scan (from tensorflow.python.data.experimental.ops.scan_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.scan(...) instead
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

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

n,line_batch = next(iter(dataset))
print(n.numpy())
[49 10 47  5 41 22 24 12 63 95  6 81 93 33 32 46 62 38 34 59]

Come con Dataset.batch l'ordine relativo alla Dataset.repeat questioni.

Dataset.shuffle non segnala la fine di un'epoca finché il buffer è vuoto casuale. Quindi un shuffle posizionato prima di una ripetizione mostrerà ogni elemento di un'epoca prima di passare alla 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:

[547 510 586 552  73 585 615 601 619 621]
[374 450 574 459 533 505 593 526 604 572]
[627 475 590 512 611 520 610 618]
[12 50 97 88 15 63 26 32 95  1]
[ 6 24 44 49 47 92 70 13 16 96]
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 0x7f97d44e5590>

png

Ma una ripetizione prima di un riordino 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:

[ 70 591   8 621 617   3  15  28 367 597]
[599 514 500 604  13 532 580  32 381 616]
[371  16 458 553 625 567   7 478 627 602]
[ 37 571  24   5  42 605 601  17 341 321]
[ 34 484  18  39 576 626 551 412 578 618]
[ 43  33 610  20  21 563  46 603  19 590]
[ 71  73  55  25 492 483  14 515  59  67]
[ 81  65   4 577  57  58  86 513  90 583]
[100 556 534  70 619  48 459  12 537  68]
[ 38  45  87 102  97 101 442  77 111 554]
[ 99  78 377  93  26 564  98 614  49  10]
[ 35  72  79 611 624 122  96 594   1 579]
[  2 542 581 125  83  23  51 139  36  91]
[124 486 575 129 152  92 134 592  89 133]
[ 82 144 109 164 159  61 429 607 522 150]
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 0x7f97d41d3610>

png

Pre-elaborazione dei dati

Il Dataset.map(f) trasformazione produce un nuovo insieme di dati applicando una data funzione f a ciascun elemento del set di dati di ingresso. Essa si basa sulla map() funzione che viene comunemente applicato alle liste (e altre strutture) in linguaggi di programmazione funzionale. La funzione f prende le tf.Tensor oggetti che rappresentano un unico elemento in ingresso, e restituisce la tf.Tensor oggetti che rappresentare un singolo elemento nel nuovo insieme di dati. La sua implementazione utilizza operazioni TensorFlow standard per trasformare un elemento in un altro.

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

Decodifica dei dati dell'immagine e ridimensionamento

Quando si addestra una rete neurale su dati di immagine 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/'*/*'))

Scrivere una funzione che manipola 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

Prova che funziona.

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, utilizzare le operazioni di TensorFlow per la preelaborazione dei dati quando possibile. Tuttavia, a volte è utile chiamare librerie Python esterne durante l'analisi dei dati di input. È possibile utilizzare il tf.py_function() il funzionamento in un Dataset.map() trasformazione.

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 , provare a utilizzare lo scipy.ndimage.rotate funzione invece:

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 gli stessi avvertimenti si applicano come con Dataset.from_generator , è necessario descrivere le forme di ritorno e tipi 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 messaggi di buffer protocollo

Molti gasdotti di ingresso estrarre tf.train.Example messaggi di buffer di protocollo da un formato TFRecord. Ogni tf.train.Example record contiene uno o più "caratteristiche", e la tubazione di ingresso 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>

È possibile lavorare con tf.train.Example Protos al di fuori di un tf.data.Dataset di 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])

Finestratura delle serie temporali

Per un capo all'altro esempio serie storiche vedere: previsione delle serie temporali .

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

Utilizzare un semplice Dataset.range per dimostrare:

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

In genere, i modelli basati su questo tipo di dati richiedono un intervallo di tempo contiguo.

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, puoi dividere i batch in due parti:

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
  return (batch[:-5],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

for features, label in predict_5_steps.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]  =>  [25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42 43 44]

Per consentire una certa sovrapposizione tra le caratteristiche di un lotto e le etichette di un altro, utilizzare 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 Dataset.batch opere, ci sono situazioni in cui potrebbe essere necessario un controllo più preciso. Il Dataset.window metodo offre un controllo completo, ma richiede una certa attenzione: restituisce un Dataset di Datasets . Vedere Struttura dataset 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 Dataset.flat_map metodo può prendere un set di dati di set di dati e appiattire 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, si vuole .batch il set di dati prima:

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, si può vedere che i shift controlli argomento quanto ogni finestra si muove sopra.

Mettendo insieme questo 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 presenta un forte squilibrio di classe, potrebbe essere necessario ricampionare il set di dati. tf.data fornisce due metodi per fare questo. 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 [==============================] - 2s 0us/step
69165056/69155632 [==============================] - 2s 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.9951 0.0049]

Un approccio comune all'addestramento con un set di dati sbilanciato consiste nel bilanciarlo. tf.data include alcuni metodi che consentono di questo flusso di lavoro:

Campionamento di set di dati

Un approccio per il ricampionamento un set di dati è quello di utilizzare sample_from_datasets . Questo è più applicabile quando si dispone di un separato data.Dataset per ogni classe.

Qui, basta usare il filtro per generarli dai dati delle frodi con 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 passano i set di dati, e il peso per ogni:

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/interleave_ops.py:260: RandomDataset.__init__ (from tensorflow.python.data.ops.dataset_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.random(...)`.

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

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

Ricampionamento del rifiuto

Un problema con il sopra experimental.sample_from_datasets approccio è che ha bisogno di una separata tf.data.Dataset per classe. Utilizzando Dataset.filter funziona, ma i risultati in tutti i dati vengono caricati due volte.

La data.experimental.rejection_resample funzione può essere applicata ad un insieme di dati di equilibratura, mentre solo il caricamento di una volta. Gli elementi verranno eliminati dal set di dati per raggiungere l'equilibrio.

data.experimental.rejection_resample prende un class_func argomento. Questo class_func viene applicato a ciascun elemento di dati, ed è utilizzato per determinare quale classe appartiene un esempio a fini di bilanciamento.

Gli elementi di creditcard_ds le sezioni (features, label) coppie. Così il class_func ha solo bisogno di tornare 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)

Le offerte ricampionamento con i singoli esempi, quindi è necessario unbatch il set di dati prima di applicare la resampler:

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/resampling.py:159: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:

I rendimenti ricampionamento crea (class, example) coppie dall'uscita del class_func . In questo caso, l' example era già una (feature, label) coppia, quindi l'uso map di abbandonare 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 ogni classe con probabilità 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
[0 1 0 0 1 1 0 0 1 1]
[0 0 0 1 1 1 0 1 1 1]
[1 0 0 0 1 0 1 1 0 1]
[0 0 0 0 0 0 0 0 1 1]
[0 1 1 0 1 0 1 0 0 1]
[1 0 1 1 0 1 1 1 0 1]
[1 1 0 0 0 1 0 1 1 0]
[1 0 1 0 1 0 1 0 0 1]
[0 0 0 0 0 1 1 0 1 0]
[1 0 1 1 0 1 0 0 1 0]

Checkpoint dell'iteratore

Supporti tensorflow prendendo posti di blocco in modo che quando il riavvio del processo di formazione è in grado di ripristinare l'ultimo checkpoint di recuperare la maggior parte del suo progresso. Oltre a controllare le variabili del modello, puoi anche controllare l'avanzamento dell'iteratore del set di dati. Questo 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 comunque che iteratore posti di blocco possono essere grandi, in quanto le trasformazioni come la shuffle e prefetch richiedono elementi stabilizzatori all'interno del iteratore.

Per includere il vostro iteratore in un posto di blocco, passare l'iteratore alla tf.train.Checkpoint costruttore.

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

Il tf.keras API semplifica molti aspetti della creazione e l'esecuzione modelli di apprendimento automatico. La sua .fit() e .evaluate() e .predict() API supportano set di dati come input. Ecco un set di dati rapido e una 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'])

Passando un set di dati di (feature, label) coppie è 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.6006 - accuracy: 0.7965
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4616 - accuracy: 0.8414
<keras.callbacks.History at 0x7f9857337dd0>

Se si passa un set di dati infinito, per esempio chiamando Dataset.repeat() , è sufficiente anche passare il steps_per_epoch argomento:

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4131 - accuracy: 0.8609
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4523 - accuracy: 0.8281
<keras.callbacks.History at 0x7f97a4425590>

Per la valutazione è possibile 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.4349 - accuracy: 0.8489
Loss : 0.43494418263435364
Accuracy : 0.8488500118255615

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.5229 - accuracy: 0.8188
Loss : 0.5228594541549683
Accuracy : 0.8187500238418579

Le etichette non sono necessari 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)