tf.data: construir pipelines de entrada do TensorFlow

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHubBaixar caderno

O tf.data API permite construir dutos de entrada complexos de peças simples, reutilizáveis. Por exemplo, o pipeline para um modelo de imagem pode agregar dados de arquivos em um sistema de arquivos distribuído, aplicar perturbações aleatórias a cada imagem e mesclar imagens selecionadas aleatoriamente em um lote para treinamento. O pipeline para um modelo de texto pode envolver a extração de símbolos de dados de texto brutos, convertendo-os em identificadores incorporados com uma tabela de pesquisa e agrupando sequências de diferentes comprimentos. O tf.data API torna possível para lidar com grandes quantidades de dados, leia a partir de diferentes formatos de dados, e realizar transformações complexas.

O tf.data API introduz um tf.data.Dataset abstracção que representa uma sequência de elementos, em que cada elemento é constituído de um ou mais componentes. Por exemplo, em um pipeline de imagem, um elemento pode ser um único exemplo de treinamento, com um par de componentes tensores representando a imagem e seu rótulo.

Existem duas maneiras distintas de criar um conjunto de dados:

  • Uma fonte de dados constrói um Dataset a partir de dados armazenados na memória ou em um ou mais arquivos.

  • A transformação de dados constrói um conjunto de dados de um ou mais tf.data.Dataset objetos.

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)

Mecânica básica

Para criar um pipeline de entrada, você deve começar com uma fonte de dados. Por exemplo, para construir um Dataset a partir de dados na memória, você pode usar tf.data.Dataset.from_tensors() ou tf.data.Dataset.from_tensor_slices() . Alternativamente, se os dados de entrada é armazenado em um arquivo no formato TFRecord recomendado, você pode usar tf.data.TFRecordDataset() .

Depois de ter um Dataset objeto, você pode transformá-lo em um novo Dataset por encadeamento chamadas de método no tf.data.Dataset objeto. Por exemplo, você pode aplicar transformações por elementos tais como Dataset.map() e transformações multi-elemento como Dataset.batch() . Consulte a documentação para tf.data.Dataset para uma lista completa de transformações.

O Dataset objecto é um pitão iteráveis. Isso torna possível consumir seus elementos usando um loop 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

Ou criando explicitamente um Python iterator usando iter e consumir seus elementos usando next :

it = iter(dataset)

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

Alternativamente, conjuntos de dados elementos pode ser consumida usando a reduce transformação, o que reduz todos os elementos para produzir um único resultado. O exemplo a seguir ilustra como usar a reduce transformação para calcular a soma de um conjunto de dados de números inteiros.

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

Estrutura do conjunto de dados

Um conjunto de dados produz uma sequência de elementos, em que cada elemento é o mesmo (aninhados) estrutura de componentes. Os componentes individuais da estrutura pode ser de qualquer tipo representável por tf.TypeSpec , incluindo tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray , ou tf.data.Dataset .

As construções Python que podem ser utilizados para expressar a estrutura (aninhada) de elementos incluem tuple , dict , NamedTuple , e OrderedDict . Em particular, list não é uma construção válida para expressar a estrutura do conjunto de dados elementos. Isto é porque os utilizadores tf.data início fortemente sentida sobre list entradas (por exemplo, passada para tf.data.Dataset.from_tensors ) a ser embalado automaticamente como tensores e list produtos (por exemplo, valores de retorno de funções definidas pelo utilizador) a ser coagido a um tuple . Como conseqüência, se você gostaria de ter uma list de entrada a ser tratada como uma estrutura, você precisa convertê-lo em tuple e se você gostaria de ter uma list de saída a ser um único componente, então você precisa para embalar explicitamente usando tf.stack .

O Dataset.element_spec propriedade permite-lhe inspeccionar o tipo de cada componente elemento. A propriedade devolve uma estrutura aninhada de tf.TypeSpec objectos, correspondentes a estrutura do elemento, que pode ser um único componente, um tuplo de componentes, ou um tuplo aninhada dos componentes. Por exemplo:

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

Os Dataset transformações suportar conjuntos de dados de qualquer estrutura. Quando se utiliza o Dataset.map() , e Dataset.filter() transformações, que se aplicam uma função de cada elemento, a estrutura do elemento determina os argumentos da função:

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

Lendo dados de entrada

Consumindo matrizes NumPy

Veja Carregando matrizes Numpy para mais exemplos.

Se todos os seus acessos de dados de entrada na memória, a maneira mais simples de criar um Dataset a partir deles é convertê-los para tf.Tensor objetos e usá 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)>

Consumindo geradores Python

Outra fonte de dados comum que pode ser facilmente ingerida como uma tf.data.Dataset é o gerador de 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

O Dataset.from_generator construtor converte o gerador de python para um completamente funcional tf.data.Dataset .

O construtor recebe um chamável como entrada, não um iterador. Isso permite que ele reinicie o gerador quando chegar ao fim. Leva um opcional args argumento, que é passada como argumentos do exigíveis.

O output_types argumento é necessário porque tf.data constrói uma tf.Graph internamente, e as bordas do gráfico requerem um 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]

O output_shapes argumento não é obrigatório, mas é altamente recomendado como muitas operações tensorflow não suportam tensores com classificação desconhecida. Se o comprimento de um determinado eixo é desconhecida ou variável, defini-lo como None nas output_shapes .

Também é importante notar que os output_shapes e output_types seguem as mesmas regras de nidificação como outros métodos de conjuntos de dados.

Aqui está um gerador de exemplo que demonstra ambos os aspectos, ele retorna tuplas de matrizes, onde a segunda matriz é um vetor com comprimento desconhecido.

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]

A primeira saída é um int32 o segundo é um float32 .

O primeiro produto é uma escalar, forma () , e o segundo é um vector de comprimento desconhecido, 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)>

Agora ele pode ser usado como um regulares tf.data.Dataset . Note que quando dosadora de um conjunto de dados com uma forma variável, você precisa usar 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]]

Para um exemplo mais realista, tentar envolver preprocessing.image.ImageDataGenerator como um tf.data.Dataset .

Primeiro baixe os dados:

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

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

Consumir dados TFRecord

Veja Carregando TFRecords para um exemplo end-to-end.

O tf.data API suporta uma variedade de formatos de arquivo para que você possa processar grandes conjuntos de dados que não cabem na memória. Por exemplo, o formato de arquivo TFRecord é um formato binário orientado a registro simples que muitos aplicativos TensorFlow usam para dados de treinamento. O tf.data.TFRecordDataset classe permite transmitir sobre o conteúdo de um ou mais arquivos TFRecord como parte de um gasoduto de entrada.

Aqui está um exemplo usando o arquivo de teste da French Street Name Signs (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

O filenames argumento para o TFRecordDataset initializer pode ser uma string, uma lista de strings, ou um tf.Tensor de cordas. Portanto, se você tiver dois conjuntos de arquivos para fins de treinamento e validação, você pode criar um método de fábrica que produz o conjunto de dados, tomando nomes de arquivos como um argumento de entrada:

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

Muitos projetos TensorFlow usar serializados tf.train.Example registros em seus arquivos TFRecord. Eles precisam ser decodificados antes de serem inspecionados:

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

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

Consumindo dados de texto

Veja Carregando texto para o fim exemplo fim.

Muitos conjuntos de dados são distribuídos como um ou mais arquivos de texto. O tf.data.TextLineDataset fornece uma maneira fácil de extrair linhas de um ou mais arquivos de texto. Dado um ou mais nomes de arquivos, um TextLineDataset irá produzir um elemento valorizado-string por linha desses arquivos.

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)

Aqui estão as primeiras linhas do primeiro arquivo:

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

Para linhas alternadas entre ficheiros usar Dataset.interleave . Isso torna mais fácil embaralhar os arquivos. Aqui estão a primeira, segunda e terceira linhas de cada tradução:

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'

Por padrão, um TextLineDataset produz cada linha de cada arquivo, que pode não ser desejável, por exemplo, se o arquivo começa com uma linha de cabeçalho, ou contém comentários. Estas linhas podem ser removidos usando os Dataset.skip() ou Dataset.filter() transformações. Aqui, você pula a primeira linha e filtra para encontrar apenas sobreviventes.

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'

Consumir dados CSV

Veja Carregando arquivos CSV e carregar Pandas DataFrames para mais exemplos.

O formato de arquivo CSV é um formato popular para armazenar dados tabulares em texto simples.

Por exemplo:

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 os seus acessos de dados na memória os mesmos Dataset.from_tensor_slices método funciona em dicionários, permitindo que esses dados sejam facilmente importados:

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'

Uma abordagem mais escalonável é carregar do disco conforme necessário.

O tf.data módulo fornece métodos para registros de extrato de um ou mais arquivos CSV que estejam em conformidade com RFC 4180 .

O experimental.make_csv_dataset função é a interface de alto nível para leitura de conjuntos de arquivos CSV. Ele suporta inferência de tipo de coluna e muitos outros recursos, como envio em lote e embaralhamento, para simplificar o uso.

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

Você pode usar o select_columns argumento, se você só precisa de um subconjunto de colunas.

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

Há também um nível inferior experimental.CsvDataset classe que fornece mais fina controle de grão. Ele não suporta inferência de tipo de coluna. Em vez disso, você deve especificar o tipo de cada coluna.

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 algumas colunas estiverem vazias, esta interface de baixo nível permite que você forneça valores padrão em vez de tipos de coluna.

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

Por padrão, um CsvDataset produz cada coluna de cada linha do arquivo, que pode não ser desejável, por exemplo, se o arquivo começa com uma linha de cabeçalho que deve ser ignorado, ou se algumas colunas não são necessários na entrada. Estas linhas de campos e pode ser removido com os header e select_cols argumentos respectivamente.

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

Consumir conjuntos de arquivos

Existem muitos conjuntos de dados distribuídos como um conjunto de arquivos, onde cada arquivo é um exemplo.

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)

O diretório raiz contém um diretório para cada classe:

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

Os arquivos em cada diretório de classe são exemplos:

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'

Leia os dados usando o tf.io.read_file função e extrair o rótulo do caminho, retornando (image, label) pares:

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'

Elementos de conjunto de dados em lote

Lote simples

A forma mais simples de dosagem pilhas n elementos consecutivos de um conjunto de dados dentro de um único elemento. O Dataset.batch() transformação faz exactamente este, com as mesmas limitações que a tf.stack() operador, aplicada a cada componente dos elementos: ou seja, para cada componente i, todos os elementos devem ter um tensor de exata da mesma 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])]

Enquanto tf.data tentativas para propagar informações de forma, as configurações padrão de Dataset.batch resultar em um tamanho de lote desconhecido porque o último lote pode não ser completa. Observe as None s na forma:

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

Use o drop_remainder argumento para ignorar esse último lote, e tenha propagação forma completa:

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

Tensores de dosagem com preenchimento

A receita acima funciona para tensores que têm todos o mesmo tamanho. No entanto, muitos modelos (por exemplo, modelos de sequência) funcionam com dados de entrada que podem ter tamanhos variados (por exemplo, sequências de comprimentos diferentes). Para lidar com este caso, o Dataset.padded_batch transformação permite tensores de lote de forma diferente, especificando uma ou mais dimensões em que eles podem ser acolchoadas.

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

O Dataset.padded_batch transformação permite definir o preenchimento diferente para cada uma das dimensões de cada um dos componentes, e que podem ser de comprimento variável (representado por None no exemplo acima) ou constante de comprimento. Também é possível substituir o valor de preenchimento, que é padronizado como 0.

Fluxos de trabalho de treinamento

Processando várias épocas

Os tf.data ofertas API duas maneiras principais de processar várias épocas dos mesmos dados.

A maneira mais simples de interagir sobre um conjunto de dados em várias épocas é usar o Dataset.repeat() transformação. Primeiro, crie um conjunto de dados de dados titânicos:

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

Aplicando o Dataset.repeat() transformação sem argumentos vai repetir a entrada indefinidamente.

O Dataset.repeat transformação concatena seus argumentos sem sinalizar o final de uma época eo início da próxima época. Devido a isso um Dataset.batch aplicada após Dataset.repeat irá produzir lotes que os limites epoch de straddle:

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

png

Se você precisar de separação época claro, colocar Dataset.batch antes da repetição:

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

plot_batch_sizes(titanic_batches)

png

Se você gostaria de realizar um cálculo personalizado (por exemplo, para coletar estatísticas) no final de cada época, é mais simples reiniciar a iteração do conjunto de dados em cada época:

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

Ordem aleatória de dados de entrada

O Dataset.shuffle() transformação mantém um tampão de tamanho fixo e escolhe o próximo elemento uniformemente aleatoriamente em que o tampão.

Adicione um índice ao conjunto de dados para ver o efeito:

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

Uma vez que o buffer_size é 100, e o tamanho do lote é de 20, o primeiro lote não contém elementos com um índice de 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]

Tal como acontece com Dataset.batch a ordem relativa a Dataset.repeat assuntos.

Dataset.shuffle não sinaliza o fim de uma época até que o buffer baralhamento está vazia. Portanto, uma ordem aleatória colocada antes de uma repetição mostrará todos os elementos de uma época antes de passar para a próxima:

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

Mas uma repetição antes de um embaralhamento mistura os limites da época:

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

Dados de pré-processamento

O Dataset.map(f) transformação produz um novo conjunto de dados por aplicação de uma função f para cada elemento do conjunto de dados de entrada. Ele é baseado no map() função que é comumente aplicada a listas (e outras estruturas) em linguagens de programação funcional. A função f leva os tf.Tensor objetos que representam um único elemento na entrada, e retorna o tf.Tensor objetos que irá representar um único elemento no novo conjunto de dados. Sua implementação usa operações padrão do TensorFlow para transformar um elemento em outro.

Esta sessão cobre os exemplos comuns de como usar Dataset.map() .

Decodificando dados de imagem e redimensionando-os

Ao treinar uma rede neural em dados de imagem do mundo real, geralmente é necessário converter imagens de tamanhos diferentes em um tamanho comum, para que possam ser agrupadas em um tamanho fixo.

Reconstrua o conjunto de dados de nomes de arquivos de flores:

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

Escreva uma função que manipule os elementos do conjunto de dados.

# 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

Teste se funciona.

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

Mapeie-o no conjunto de dados.

images_ds = list_ds.map(parse_image)

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

png

png

Aplicação de lógica Python arbitrária

Por motivos de desempenho, use as operações do TensorFlow para pré-processar seus dados sempre que possível. No entanto, às vezes é útil chamar bibliotecas Python externas ao analisar seus dados de entrada. Você pode usar o tf.py_function() operação em uma Dataset.map() transformação.

Por exemplo, se você deseja aplicar uma rotação aleatória, a tf.image módulo só tem tf.image.rot90 , o que não é muito útil para o aumento da imagem.

Para demonstrar tf.py_function , tente usar o scipy.ndimage.rotate função em vez disso:

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

Para usar esta função com Dataset.map as mesmas ressalvas se aplicam como com Dataset.from_generator , você precisa para descrever as formas de retorno e tipos quando você aplica a função:

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

De análise tf.Example mensagens tampão protocolo

Muitas tubulações de entrada extrair tf.train.Example mensagens buffer de protocolo de um formato TFRecord. Cada tf.train.Example registro contém um ou mais "recursos", e do gasoduto de entrada normalmente converte esses recursos em tensores.

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>

Você pode trabalhar com tf.train.Example protos fora de uma tf.data.Dataset para entender os dados:

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

Janelas de séries temporais

Para uma ponta a ponta exemplo séries temporais ver: previsão de séries temporais .

Os dados da série temporal geralmente são organizados com o eixo do tempo intacto.

Use um simples Dataset.range para demonstrar:

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

Normalmente, os modelos baseados neste tipo de dados precisam de uma fração de tempo contígua.

A abordagem mais simples seria agrupar os dados em lote:

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

Ou, para fazer previsões densas um passo em direção ao futuro, você pode mudar os recursos e rótulos em uma etapa em relação uns aos outros:

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]

Para prever uma janela inteira em vez de um deslocamento fixo, você pode dividir os lotes em duas partes:

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]

Para permitir alguma sobreposição entre as características de um lote e os rótulos de outro, use 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]

usando window

Enquanto estiver usando Dataset.batch obras, há situações em que você pode precisar um controle mais fino. O Dataset.window método lhe dá controle total, mas requer alguns cuidados: ele retorna um Dataset de Datasets . Veja estrutura Dataset para mais detalhes.

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>

O Dataset.flat_map método pode levar um conjunto de dados de conjuntos de dados e achatar-lo em um único conjunto de dados:

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

Em quase todos os casos, você vai querer .batch o conjunto de dados em primeiro lugar:

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]

Agora, você pode ver que os shift controles argumento quanto cada janela se move sobre.

Juntando isso, você pode escrever esta função:

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]

Então, é fácil extrair rótulos, como antes:

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]

Reamostragem

Ao trabalhar com um conjunto de dados que é muito desequilibrado por classes, você pode querer reamostrar o conjunto de dados. tf.data fornece dois métodos para fazer isso. O conjunto de dados de fraude de cartão de crédito é um bom exemplo desse tipo de 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()])

Agora, verifique a distribuição das classes, ela é altamente distorcida:

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]

Uma abordagem comum para treinar com um conjunto de dados desequilibrado é equilibrá-lo. tf.data inclui alguns métodos que permitem que este fluxo de trabalho:

Amostragem de conjuntos de dados

Uma abordagem para reamostragem um conjunto de dados é usar sample_from_datasets . Esta é mais aplicável quando você tem um separado data.Dataset para cada classe.

Aqui, basta usar o filtro para gerá-los a partir dos dados de fraude de cartão de crédito:

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]

Para usar tf.data.experimental.sample_from_datasets passar os conjuntos de dados e o peso para cada um:

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

Agora, o conjunto de dados produz exemplos de cada classe com probabilidade de 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]

Reamostragem de rejeição

Um problema com o acima experimental.sample_from_datasets abordagem é que ela precisa de um separado tf.data.Dataset por classe. Usando Dataset.filter funciona, mas os resultados em todos os dados que estão sendo carregados duas vezes.

O data.experimental.rejection_resample função pode ser aplicado a um conjunto de dados para reequilibrar que, enquanto apenas uma vez carregá-lo. Os elementos serão eliminados do conjunto de dados para atingir o equilíbrio.

data.experimental.rejection_resample leva um class_func argumento. Este class_func é aplicado a cada elemento do conjunto de dados, e é utilizado para determinar qual a classe de um exemplo pertence a para fins de equilíbrio.

Os elementos de creditcard_ds já são (features, label) pares. Assim, o class_func só precisa devolver esses rótulos:

def class_func(features, label):
  return label

O reamostrador também precisa de uma distribuição de destino e, opcionalmente, de uma estimativa de distribuição inicial:

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

Os nova amostragem lida com exemplos individuais, por isso você deve unbatch o conjunto de dados antes de aplicar o 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:

Os retornos nova amostragem cria (class, example) pares a partir da saída do class_func . Neste caso, o example já era um (feature, label) par, então uso map para soltar a cópia extra das etiquetas:

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

Agora, o conjunto de dados produz exemplos de cada classe com probabilidade de 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]

Ponto de verificação do iterador

Suportes Tensorflow tomar postos de controle para que, quando os seus processos de formação reinicia ele pode restaurar o último ponto de verificação para recuperar a maior parte do seu progresso. Além de marcar as variáveis ​​do modelo, você também pode marcar o andamento do iterador do conjunto de dados. Isso pode ser útil se você tiver um grande conjunto de dados e não quiser iniciar o conjunto de dados desde o início em cada reinicialização. Note no entanto que iteradoras postos de controle pode ser grande, já que transformações tais como shuffle e prefetch exigem elementos de tamponamento dentro do iterador.

Para incluir seu iterador em um posto de controle, passar o iterador para o tf.train.Checkpoint construtor.

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]

Usando tf.data com tf.keras

O tf.keras API simplifica muitos aspectos da criação e execução de modelos de aprendizagem de máquina. Sua .fit() e .evaluate() e .predict() APIs suportar conjuntos de dados como entradas. Aqui está um conjunto de dados rápido e configuração do modelo:

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 um conjunto de dados de (feature, label) pares é tudo o que é necessário para 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 você passar um conjunto de dados infinito, por exemplo, chamando Dataset.repeat() , você só precisa também passar a steps_per_epoch argumento:

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>

Para avaliação, você pode passar no número de etapas de avaliação:

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

Para conjuntos de dados longos, defina o número de etapas para avaliar:

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

Os rótulos não são necessários ao chamar 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)

Mas os rótulos são ignorados se você passar um conjunto de dados que os contenha:

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