Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/guide/data.ipynb
25115 views
Kernel: Python 3

Licensed under the Apache License, Version 2.0 (the "License");

#@title Licensed under the Apache License, Version 2.0 (the "License"); { display-mode: "form" } # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

tf.data: criando pipelines de entrada no TensorFlow

A API tf.data permite a criação de pipelines de entrada complexos a partir de peças simples e reutilizáveis. Por exemplo, o pipeline para um modelo de imagem pode agregar dados de arquivos num 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 de embeddings numa tabela de pesquisa e agrupar sequências de comprimentos diferentes em um lote. A API tf.data torna possível lidar com grandes quantidades de dados, ler diferentes formatos de dados e realizar transformações complexas.

A API tf.data introduz uma abstração tf.data.Dataset que representa uma sequência de elementos, em que cada elemento consiste em um ou mais componentes. Por exemplo, num 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 dataset:

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

  • Uma transformação de dados constrói um dataset a partir de um ou mais objetos tf.data.Dataset.

import tensorflow as tf
import pathlib import os import matplotlib.pyplot as plt import pandas as pd import numpy as np np.set_printoptions(precision=4)

Mecanismo básico

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 seus dados de entrada estiverem armazenados em arquivo no formato TFRecord recomendado, você poderá usar tf.data.TFRecordDataset().

Assim que você tiver um objeto Dataset, você pode transformá-lo num novo Dataset encadeando chamadas de método no objeto tf.data.Dataset. Por exemplo, você pode aplicar transformações por elemento, como Dataset.map, e transformações multi-elemento, como Dataset.batch. Consulte a documentação de tf.data.Dataset para uma lista completa de transformações.

O objeto Dataset é um iterável (iterable) em Python. Isso possibilita o consumo dos seus elementos usando um loop for:

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1]) dataset
for elem in dataset: print(elem.numpy())

Ou criando explicitamente um iterador Python com iter e consumindo seus elementos usando next:

it = iter(dataset) print(next(it).numpy())

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

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

Estrutura do dataset

Um dataset produz uma sequência de elementos , onde cada elemento é a mesma estrutura (aninhada) de componentes. Os componentes individuais da estrutura podem ser de qualquer tipo representável por tf.TypeSpec, incluindo tf.Tensor, tf.sparse.SparseTensor, tf.RaggedTensor, tf.TensorArray ou tf.data.Dataset.

Os construtos do Python que podem ser usados para expressar a estrutura (aninhada) dos elementos incluem tuple, dict, NamedTuple e OrderedDict. No entanto, list não é considerado um construto válido para expressar a estrutura dos elementos do dataset. Isto ocorre devido aos primeiros usuários de tf.data se preocuparem com a possibilidade das entradas list (por exemplo, quando passadas para tf.data.Dataset.from_tensors) serem automaticamente empacotadas como tensores e as saídas list (por exemplo, valores de retorno de funções definidas pelo usuário) serem coagidas para uma tuple. Como consequência disso, se você quiser que uma entrada list seja tratada como uma estrutura, você precisa convertê-la numa tuple e se quiser que uma saída de list seja tratado como um único componente, então você precisará empacotá-la explicitamente usando tf.stack.

A propriedade Dataset.element_spec permite inspecionar o tipo de cada componente de um elemento. A propriedade retorna uma estrutura aninhada de objetos tf.TypeSpec, correspondendo à estrutura do elemento, que pode ser um único componente, uma tupla de componentes ou uma tupla aninhada de componentes. Por exemplo:

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10])) dataset1.element_spec
dataset2 = tf.data.Dataset.from_tensor_slices( (tf.random.uniform([4]), tf.random.uniform([4, 100], maxval=100, dtype=tf.int32))) dataset2.element_spec
dataset3 = tf.data.Dataset.zip((dataset1, dataset2)) dataset3.element_spec
# 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
# Use value_type to see the type of value represented by the element spec dataset4.element_spec.value_type

As transformações Dataset suportam datasets de qualquer estrutura. Ao usar as transformações Dataset.map e Dataset.filter, que aplicam uma função a 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
for z in dataset1: print(z.numpy())
dataset2 = tf.data.Dataset.from_tensor_slices( (tf.random.uniform([4]), tf.random.uniform([4, 100], maxval=100, dtype=tf.int32))) dataset2
dataset3 = tf.data.Dataset.zip((dataset1, dataset2)) dataset3
for a, (b,c) in dataset3: print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))

Lendo dados de entrada

Consumindo arrays NumPy

Consulte o tutorial Carregando arrays NumPy para mais exemplos.

Se todos os seus dados de entrada couberem na memória, a maneira mais simples de criar um Dataset a partir deles é convertê-los em objetos tf.Tensor e usar Dataset.from_tensor_slices.

train, test = tf.keras.datasets.fashion_mnist.load_data()
images, labels = train images = images/255 dataset = tf.data.Dataset.from_tensor_slices((images, labels)) dataset

Observação: o snippet de código acima incorporará os arrays features e labels no seu grafo do TensorFlow como operações tf.constant(). Isto funciona bem para um dataset pequeno, mas desperdiça memória --- porque o conteúdo do array será copiado múltiplas vezes --- e poderá atingir o limite de 2 GB para o buffer de protocolo tf.GraphDef.

Consumindo geradores Python

Outra fonte de dados comum que pode ser facilmente ingerida como tf.data.Dataset é o gerador python.

Cuidado: Embora esta seja uma abordagem conveniente, ela é limitada quanto à portabilidade e escalabilidade. Ela deve ser executado no mesmo processo python que criou o gerador, e ainda está sujeita ao Python GIL .

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

O construtor Dataset.from_generator converte o gerador python em um tf.data.Dataset totalmente funcional.

O construtor recebe um callable como entrada, não um iterator. Isto permite reiniciar o gerador quando ele chega ao fim. Ele recebe um argumento args opcional, que é passado como os argumentos do callable.

O argumento output_types é necessário porque tf.data constrói um tf.Graph internamente e as bordas do grafo 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())

O argumento output_shapes não é obrigatório, mas é altamente recomendado, pois muitas operações do TensorFlow não oferecem suporte a tensores com posto desconhecido. Se o comprimento de um eixo específico for desconhecido ou variável, defina-o como None em output_shapes.

Também é importante observar que output_shapes e output_types seguem as mesmas regras de aninhamento de outros métodos de um dataset.

Eis um exemplo de gerador que demonstra ambos os aspectos: ele retorna tuplas de arrays, onde o segundo array é 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

A primeira saída é um int32 e a segunda é um float32.

O primeiro item é um escalar, formato (), e o segundo é um vetor de comprimento desconhecido, formato (None,)

ds_series = tf.data.Dataset.from_generator( gen_series, output_types=(tf.int32, tf.float32), output_shapes=((), (None,))) ds_series

Agora ele pode ser usado como um tf.data.Dataset comum. Observe que ao construir um lote com um dataset de formato 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())

Para um exemplo mais realista, experimente empacotar 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)

Crie 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))
print(images.dtype, images.shape) print(labels.dtype, labels.shape)
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
for images, labels in ds.take(1): print('images.shape: ', images.shape) print('labels.shape: ', labels.shape)

Consumindo dados TFRecord

Consulte o tutorial Carregando TFRecords para um exemplo completo.

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

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

O argumento filenames (nomes de arquivos) passado para o inicializador TFRecordDataset pode ser uma string, uma lista de strings ou um tf.Tensor de strings. Portanto, se você tiver dois conjuntos de arquivos para fins de treinamento e validação, poderá criar um método de fábrica que produza o dataset, tomando filenames como argumento de entrada:

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file]) dataset

Muitos projetos do TensorFlow usam registros tf.train.Example serializados 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']

Consumindo dados de texto

Consulte o tutorial Carregando texto para um exemplo completo.

Muitos datasets 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 produzirá um elemento com valor de 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 ]
dataset = tf.data.TextLineDataset(file_paths)

Aqui estão as primeiras linhas do primeiro arquivo:

for line in dataset.take(5): print(line.numpy())

Para alternar linhas entre arquivos use Dataset.interleave. Isso facilita o processo de 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())

Por padrão, um TextLineDataset produz cada linha de cada arquivo, o que pode não ser desejável, por exemplo, se o arquivo começar com uma linha de cabeçalho ou contiver comentários. Essas linhas podem ser removidas usando as transformações Dataset.skip() ou Dataset.filter. Aqui, você pula a primeira linha e depois filtra para encontrar apenas linhas com 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)
for line in titanic_lines.take(10): print(line.numpy())
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())

Consumindo dados CSV

Consulte os tutoriais Carregando arquivos CSV e Carregando DataFrames Pandas 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 seus dados couberem na memória o mesmo método Dataset.from_tensor_slices 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))

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

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

A função tf.data.experimental.make_csv_dataset é a interface de alto nível para leitura de conjuntos de arquivos CSV. Ela suporta inferência de tipo de coluna e muitos outros recursos, como 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))

Você pode usar o argumento select_columns se precisar apenas de um subconjunto das 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))

Há também uma classe experimental.CsvDataset de nível inferior que oferece um controle mais refinado. Mas ela não suporta inferência de tipo de coluna. Em vez disso, você precisa 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])

Se algumas colunas estiverem vazias, esta interface de baixo nível permite fornecer valores padrão em vez de tipos de colunas.

%%writefile missing.csv 1,2,3,4 ,2,3,4 1,,3,4 1,2,,4 1,2,3, ,,,
# 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
for line in dataset: print(line.numpy())

Por padrão, um CsvDataset produz cada coluna de cada linha do arquivo, o que pode não ser desejável, por exemplo, se o arquivo começar com uma linha de cabeçalho que deve ser ignorada ou se algumas colunas não forem obrigatórias na entrada. Essas linhas e campos podem ser removidos com os argumentos header e select_cols 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
for line in dataset: print(line.numpy())

Consumindo conjuntos de arquivos

Existem muitos datasets 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)

Observação: essas imagens são licenciadas de acordo com a CC-BY. Veja LICENSE.txt para mais detalhes.

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

for item in flowers_root.glob("*"): print(item.name)

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

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

def process_path(file_path): label = tf.strings.split(file_path, os.sep)[-2] return tf.io.read_file(file_path), label labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1): print(repr(image_raw.numpy()[:100])) print() print(label_text.numpy())

Lotes de elementos de um dataset

Lotes simples

A forma mais simples de criar um lote é empilhando n elementos consecutivos de um dataset num único elemento. A transformação Dataset.batch() faz exatamente isso, com as mesmas restrições do operador tf.stack(), aplicadas a cada componente dos elementos: ou seja, para cada componente i, todos os elementos devem ter um tensor do mesmo formato.

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

Embora tf.data tente propagar informações de formato, as configurações padrão de Dataset.batch resultam num tamanho de lote desconhecido porque o último lote pode não estar cheio. Observe os None no formato:

batched_dataset

Use o argumento drop_remainder para ignorar o último lote e obter a propagação completa do formato:

batched_dataset = dataset.batch(7, drop_remainder=True) batched_dataset

Lotes de tensores com preenchimento

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

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

A transformação Dataset.padded_batch permite definir preenchimentos diferentes para cada dimensão de cada componente e pode ter comprimento variável (representado por None no exemplo acima) ou comprimento constante. Também é possível substituir o valor do preenchimento, cujo padrão é 0.

Workflows de treinamento

Processando múltiplas épocas

A API tf.data oferece duas formas principais de processar múltiplas épocas dos mesmos dados.

A maneira mais simples de iterar sobre um dataset em múltiplas épocas é usar a transformação Dataset.repeat(). Primeiro, crie um conjunto de dados do Titanic:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv") titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds): batch_sizes = [batch.shape[0] for batch in ds] plt.bar(range(len(batch_sizes)), batch_sizes) plt.xlabel('Batch number') plt.ylabel('Batch size')

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

A transformação Dataset.repeat concatena seus argumentos sem sinalizar o fim de uma época e o início da época seguinte. Por causa disso, um Dataset.batch aplicado após Dataset.repeat produzirá lotes que ultrapassam os limites da época:

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

Se você precisar de uma separação clara de épocas, coloque Dataset.batch antes do repeat:

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

Se você quiser realizar uma computação personalizada (por exemplo, para coletar estatísticas) ao final de cada época, é mais simples reiniciar a iteração do dataset 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)

Embaralhando dados de entrada aleatoriamente

A transformação Dataset.shuffle() mantém um buffer de tamanho fixo e escolhe o próximo elemento de maneira uniforme e aleatória a partir desse buffer.

Observação: embora um buffer_size grande seja embaralhado de maneira mais completa, ele pode consumir muita memória e demorar um tempo considerável para ser preenchido. Considere usar Dataset.interleave entre arquivos se isso se tornar um problema.

Adicione um índice ao dataset 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

Já que o buffer_size é 100 e o tamanho do lote é 20, o primeiro lote não contém elementos com índice superior a 120.

n,line_batch = next(iter(dataset)) print(n.numpy())

Tal como acontece com Dataset.batch, a ordem relativa a Dataset.repeat é importante.

Dataset.shuffle não sinaliza o fim de uma época até que o buffer aleatório esteja vazio. Portanto, um shuffle colocado antes de um repeat mostrará todos os elementos de uma época antes de passar para a seguinte:

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

Mas um repeat antes de um shuffle 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())
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()

Pré-processamento de dados

A transformação Dataset.map(f) produz um novo dataset aplicando uma determinada função f a cada elemento do dataset de entrada. É baseado na função map() que é frequentemente aplicada a listas (e outras estruturas) em linguagens de programação funcionais. A função f pega os objetos tf.Tensor que representam um único elemento na entrada e retorna os objetos tf.Tensor que representarão um único elemento no novo dataset. Sua implementação usa operações padrão do TensorFlow para transformar um elemento em outro.

Esta seção cobre exemplos comuns de como usar Dataset.map().

Decodificando e redimensionando dados de imagem

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

Reconstrua o dataset 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 dataset.

# 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.io.decode_jpeg(image) image = tf.image.convert_image_dtype(image, tf.float32) image = tf.image.resize(image, [128, 128]) return image, label

Teste para ver 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)

Faça o mapeamento com o dataset.

images_ds = list_ds.map(parse_image) for image, label in images_ds.take(2): show(image, label)

Aplicando lógica Python arbitrária

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

Por exemplo, se você quiser aplicar uma rotação aleatória, o módulo tf.image possui apenas tf.image.rot90, o que não é muito útil para aumento de imagem.

Observação: O tensorflow_addons tem uma rotate compatível com TensorFlow em tensorflow_addons.image.rotate.

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

import scipy.ndimage as ndimage def random_rotate_image(image): image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False) return image
image, label = next(iter(images_ds)) image = random_rotate_image(image) show(image, label)

Para usar esta função com Dataset.map as mesmas ressalvas se aplicam a Dataset.from_generator, você precisa descrever os formatos e tipos de retorno ao aplicar 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)

Processando mensagens de buffer de protocolo tf.Example

Muitos pipelines de entrada extraem mensagens de buffer de protocolo tf.train.Example de um formato TFRecord. Cada registro tf.train.Example contém uma ou mais "características", e o pipeline de entrada normalmente converte essas características 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

Você pode trabalhar com protos tf.train.Example fora de um 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])
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]), "...")
decoded = dataset.map(tf_parse) decoded
image_batch, text_batch = next(iter(decoded.batch(10))) image_batch.shape

Janelas de série temporal

Para um exemplo de série temporal de ponta a ponta, consulte: Previsão em séries temporais .

Os dados de séries temporais geralmente são organizados com o eixo temporal intacto.

Use um Dataset.range simples para demonstrar:

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

Normalmente, os modelos baseados neste tipo de dados desejarão um intervalo de tempo contíguo.

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

Ou, para fazer previsões densas um passo no futuro, você pode mudar as características e os rótulos um passo em relação um ao outro:

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

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

Para permitir alguma sobreposição entre os recursos 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())

Usando window

Embora o uso Dataset.batch funcione, há situações em que você poderá precisar de um controle mais preciso. O método Dataset.window garante controle total, mas requer alguns cuidados: ele retorna um Dataset de Datasets. Vá para a seção Estrutura do 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)

O método Dataset.flat_map pode pegar um dataset de datasets e achatá-lo num único dataset:

for x in windows.flat_map(lambda x: x).take(30): print(x.numpy(), end=' ')

Em praticamente todas as situações, você vai querer primeiro fazer o Dataset.batch do dataset:

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

Agora, você pode ver que o argumento shift controla o quanto cada janela se move.

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

Depois disso fica 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())

Reamostragem

Ao trabalhar com um dataset que é pouco balanceado quanto a classes, você poderá querer fazer uma reamostragem do dataset. tf.data fornece dois métodos para fazer isso. O dataset credit card fraud é um ótimo exemplo desse tipo de problema.

Observação: Vá para Classificação de dados não balanceados para um tutorial completo.

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')
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 tem um grande desvio:

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)

Uma abordagem comum para treinar com um dataset desbalanceado é balanceá-lo. tf.data inclui alguns métodos que permitem realizar este workflow:

Reamostragem de datasets

Uma abordagem para fazer a reamostragem de um conjunto de dados é usar sample_from_datasets. Isto é mais útil quando você tem um tf.data.Dataset separado para cada classe.

Aqui, basta usar o filtro para gerá-los a partir dos dados do dataset credit card fraud:

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

Para usar tf.data.Dataset.sample_from_datasets, passe os datasets e o peso de cada um:

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

Agora o dataset produz exemplos de cada classe com probabilidade de 50/50:

for features, labels in balanced_ds.take(10): print(labels.numpy())

Reamostragem de rejeição

Um problema com a abordagem Dataset.sample_from_datasets acima é que ela precisa de um tf.data.Dataset separado por classe. Você poderia usar Dataset.filter para criar esses dois datasets, mas isso resulta no carregamento de todos os dados duas vezes.

O método tf.data.Dataset.rejection_resample pode ser aplicado a um dataset para rebalanceá-lo, carregando-o apenas uma vez. Os elementos serão eliminados ou repetidos para alcançar o balanceamento.

O método rejection_resample recebe um argumento class_func. Este class_func é aplicado a cada elemento do dataset e é usado para determinar a qual classe um exemplo pertence para fins de balanceamento.

O objetivo aqui é balancear a distribuição dos rótulos, e os elementos de creditcard_ds já são pares (features, label). Então class_func só precisa retornar esses rótulos:

def class_func(features, label): return label

O método de reamostragem lida com exemplos individuais, portanto, neste caso, você deve fazer unbatch do lote do dataset antes de aplicar esse método.

O método precisa de uma distribuição-alvo e, opcionalmente, de uma estimativa de distribuição inicial como entradas.

resample_ds = ( creditcard_ds .unbatch() .rejection_resample(class_func, target_dist=[0.5,0.5], initial_dist=fractions) .batch(10))

O método rejection_resample retorna pares (class, example) onde a class é a saída de class_func. Neste caso, o example já era um par (feature, label), então use map para descartar a cópia extra dos rótulos:

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

Agora o dataset produz exemplos de cada classe com probabilidade de 50/50:

for features, labels in balanced_ds.take(10): print(labels.numpy())

Checkpoints do iterador

O Tensorflow oferece suporte à tomada de checkpoints para que, quando o processo de treinamento for reiniciado, ele possa restaurar o checkpoint mais recente para recuperar a maior parte de seu progresso. Além de fazer checkpointing das variáveis ​​do modelo, você também pode fazer checkpointing do progresso do iterador do dataset. Isso pode ser útil se você tiver um dataset grande e não quiser iniciá-lo do zero a cada reinicialização. Observe, entretanto, que os checkpoints do iterador podem ser grandes, pois transformações como Dataset.shuffle e Dataset.prefetch requerem elementos de buffer dentro do iterador.

Para incluir seu iterador num checkpoint, passe o iterador para o construtor tf.train.Checkpoint.

range_ds = tf.data.Dataset.range(20) iterator = iter(range_ds) ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator) manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3) print([next(iterator).numpy() for _ in range(5)]) save_path = manager.save() print([next(iterator).numpy() for _ in range(5)]) ckpt.restore(manager.latest_checkpoint) print([next(iterator).numpy() for _ in range(5)])

Observação: Não é possível aplicar um checkpoint em um iterador que dependa de um estado externo, como tf.py_function. Tentar fazer isso gerará uma exceção reclamando do estado externo.

Usando tf.data com tf.keras

A API tf.keras simplifica muitos aspectos da criação e execução de modelos de aprendizado de máquina. Suas APIs Model.fit, Model.evaluate e Model.predict suportam datasets como entradas. Aqui está a configuração rápida de um dataset e 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'])

Passar um dataset de pares (feature, label) é tudo o que você precisa para Model.fit e Model.evaluate:

model.fit(fmnist_train_ds, epochs=2)

Se você passar um dataset infinito, por exemplo, ao chamar Dataset.repeat, você só precisa passar também o argumento steps_per_epoch:

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)

Para a avaliação você pode passar o número de passos de avaliação:

loss, accuracy = model.evaluate(fmnist_train_ds) print("Loss :", loss) print("Accuracy :", accuracy)

Para datasets longos, defina o número de passos a serem avaliados:

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10) print("Loss :", loss) print("Accuracy :", accuracy)

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)

Mas os rótulos serão ignorados se você passar um dataset que os contenha:

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