Carregar e pré-processar imagens

Veja no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial mostra como carregar e pré-processar um conjunto de dados de imagem de três maneiras:

Configurar

import numpy as np
import os
import PIL
import PIL.Image
import tensorflow as tf
import tensorflow_datasets as tfds
print(tf.__version__)
2.8.0-rc1

Baixe o conjunto de dados de flores

Este tutorial usa um conjunto de dados de vários milhares de fotos de flores. O conjunto de dados de flores contém cinco subdiretórios, um por classe:

flowers_photos/
  daisy/
  dandelion/
  roses/
  sunflowers/
  tulips/
import pathlib
dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file(origin=dataset_url,
                                   fname='flower_photos',
                                   untar=True)
data_dir = pathlib.Path(data_dir)

Após o download (218 MB), você deverá ter uma cópia das fotos das flores disponíveis. Há 3.670 imagens no total:

image_count = len(list(data_dir.glob('*/*.jpg')))
print(image_count)
3670

Cada diretório contém imagens desse tipo de flor. Aqui estão algumas rosas:

roses = list(data_dir.glob('roses/*'))
PIL.Image.open(str(roses[0]))

png

roses = list(data_dir.glob('roses/*'))
PIL.Image.open(str(roses[1]))

png

Carregar dados usando um utilitário Keras

Vamos carregar essas imagens do disco usando o útil utilitário tf.keras.utils.image_dataset_from_directory .

Criar um conjunto de dados

Defina alguns parâmetros para o carregador:

batch_size = 32
img_height = 180
img_width = 180

É uma boa prática usar uma divisão de validação ao desenvolver seu modelo. Você usará 80% das imagens para treinamento e 20% para validação.

train_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="training",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)
Found 3670 files belonging to 5 classes.
Using 2936 files for training.
val_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="validation",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)
Found 3670 files belonging to 5 classes.
Using 734 files for validation.

Você pode encontrar os nomes das classes no atributo class_names nesses conjuntos de dados.

class_names = train_ds.class_names
print(class_names)
['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']

Visualize os dados

Aqui estão as primeiras nove imagens do conjunto de dados de treinamento.

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1):
  for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])
    plt.axis("off")

png

Você pode treinar um modelo usando esses conjuntos de dados passando-os para model.fit (mostrado posteriormente neste tutorial). Se desejar, você também pode iterar manualmente no conjunto de dados e recuperar lotes de imagens:

for image_batch, labels_batch in train_ds:
  print(image_batch.shape)
  print(labels_batch.shape)
  break
(32, 180, 180, 3)
(32,)

O image_batch é um tensor da forma (32, 180, 180, 3) . Este é um lote de 32 imagens de formato 180x180x3 (a última dimensão refere-se aos canais de cores RGB). O label_batch é um tensor da forma (32,) , estes são os rótulos correspondentes às 32 imagens.

Você pode chamar .numpy() em qualquer um desses tensores para convertê-los em um numpy.ndarray .

Padronize os dados

Os valores do canal RGB estão na faixa [0, 255] . Isso não é ideal para uma rede neural; em geral, você deve procurar tornar seus valores de entrada pequenos.

Aqui, você padronizará os valores para estarem no intervalo [0, 1] usando tf.keras.layers.Rescaling :

normalization_layer = tf.keras.layers.Rescaling(1./255)

Existem duas maneiras de usar essa camada. Você pode aplicá-lo ao conjunto de dados chamando Dataset.map :

normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
image_batch, labels_batch = next(iter(normalized_ds))
first_image = image_batch[0]
# Notice the pixel values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))
0.0 0.96902645

Ou você pode incluir a camada dentro de sua definição de modelo para simplificar a implantação. Você usará a segunda abordagem aqui.

Configurar o conjunto de dados para desempenho

Vamos nos certificar de usar a pré-busca em buffer para que você possa produzir dados do disco sem que a E/S se torne um bloqueio. Estes são dois métodos importantes que você deve usar ao carregar dados:

  • Dataset.cache mantém as imagens na memória depois de serem carregadas fora do disco durante a primeira época. Isso garantirá que o conjunto de dados não se torne um gargalo ao treinar seu modelo. Se seu conjunto de dados for muito grande para caber na memória, você também poderá usar esse método para criar um cache em disco de alto desempenho.
  • Dataset.prefetch sobrepõe o pré-processamento de dados e a execução do modelo durante o treinamento.

Os leitores interessados ​​podem aprender mais sobre os dois métodos, bem como sobre como armazenar dados em cache no disco na seção Pré -busca do guia Melhor desempenho com a API tf.data .

AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

Treinar um modelo

Para completar, você mostrará como treinar um modelo simples usando os conjuntos de dados que acabou de preparar.

O modelo Sequencial consiste em três blocos de convolução ( tf.keras.layers.Conv2D ) com uma camada de agrupamento máximo ( tf.keras.layers.MaxPooling2D ) em cada um deles. Há uma camada totalmente conectada ( tf.keras.layers.Dense ) com 128 unidades em cima dela que é ativada por uma função de ativação ReLU ( 'relu' ). Este modelo não foi ajustado de forma alguma - o objetivo é mostrar a mecânica usando os conjuntos de dados que você acabou de criar. Para saber mais sobre classificação de imagens, visite o tutorial Classificação de imagens.

num_classes = 5

model = tf.keras.Sequential([
  tf.keras.layers.Rescaling(1./255),
  tf.keras.layers.Conv2D(32, 3, activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(32, 3, activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(32, 3, activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(num_classes)
])

Escolha o otimizador tf.keras.optimizers.Adam e a função de perda tf.keras.losses.SparseCategoricalCrossentropy . Para visualizar a precisão do treinamento e da validação para cada época de treinamento, passe o argumento de metrics para Model.compile .

model.compile(
  optimizer='adam',
  loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),
  metrics=['accuracy'])
model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=3
)
Epoch 1/3
92/92 [==============================] - 4s 21ms/step - loss: 1.3091 - accuracy: 0.4281 - val_loss: 1.0982 - val_accuracy: 0.5599
Epoch 2/3
92/92 [==============================] - 1s 12ms/step - loss: 1.0196 - accuracy: 0.5879 - val_loss: 0.9572 - val_accuracy: 0.6213
Epoch 3/3
92/92 [==============================] - 1s 12ms/step - loss: 0.8455 - accuracy: 0.6775 - val_loss: 0.8839 - val_accuracy: 0.6512
<keras.callbacks.History at 0x7ff10c168850>

Você pode notar que a precisão da validação é baixa em comparação com a precisão do treinamento, indicando que seu modelo está superajustado. Você pode aprender mais sobre overfitting e como reduzi-lo neste tutorial .

Usando tf.data para um controle mais preciso

O utilitário de pré-processamento Keras acima — tf.keras.utils.image_dataset_from_directory — é uma maneira conveniente de criar um tf.data.Dataset a partir de um diretório de imagens.

Para um controle de granulação mais preciso, você pode escrever seu próprio pipeline de entrada usando tf.data . Esta seção mostra como fazer exatamente isso, começando com os caminhos de arquivo do arquivo TGZ que você baixou anteriormente.

list_ds = tf.data.Dataset.list_files(str(data_dir/'*/*'), shuffle=False)
list_ds = list_ds.shuffle(image_count, reshuffle_each_iteration=False)
for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/roses/14267691818_301aceda07.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/2641151167_3bf1349606_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/6495554833_86eb8faa8e_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/4578030672_e6aefd45af.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/144686365_d7e96941ee_n.jpg'

A estrutura em árvore dos arquivos pode ser usada para compilar uma lista class_names .

class_names = np.array(sorted([item.name for item in data_dir.glob('*') if item.name != "LICENSE.txt"]))
print(class_names)
['daisy' 'dandelion' 'roses' 'sunflowers' 'tulips']

Divida o conjunto de dados em conjuntos de treinamento e validação:

val_size = int(image_count * 0.2)
train_ds = list_ds.skip(val_size)
val_ds = list_ds.take(val_size)

Você pode imprimir o comprimento de cada conjunto de dados da seguinte forma:

print(tf.data.experimental.cardinality(train_ds).numpy())
print(tf.data.experimental.cardinality(val_ds).numpy())
2936
734

Escreva uma função curta que converta um caminho de arquivo em um par (img, label) :

def get_label(file_path):
  # Convert the path to a list of path components
  parts = tf.strings.split(file_path, os.path.sep)
  # The second to last is the class-directory
  one_hot = parts[-2] == class_names
  # Integer encode the label
  return tf.argmax(one_hot)
def decode_img(img):
  # Convert the compressed string to a 3D uint8 tensor
  img = tf.io.decode_jpeg(img, channels=3)
  # Resize the image to the desired size
  return tf.image.resize(img, [img_height, img_width])
def process_path(file_path):
  label = get_label(file_path)
  # Load the raw data from the file as a string
  img = tf.io.read_file(file_path)
  img = decode_img(img)
  return img, label

Use Dataset.map para criar um conjunto de dados de image, label :

# Set `num_parallel_calls` so multiple images are loaded/processed in parallel.
train_ds = train_ds.map(process_path, num_parallel_calls=AUTOTUNE)
val_ds = val_ds.map(process_path, num_parallel_calls=AUTOTUNE)
for image, label in train_ds.take(1):
  print("Image shape: ", image.numpy().shape)
  print("Label: ", label.numpy())
Image shape:  (180, 180, 3)
Label:  1

Configurar conjunto de dados para desempenho

Para treinar um modelo com esse conjunto de dados, você precisará dos dados:

  • Para ser bem embaralhado.
  • A ser em lote.
  • Lotes a serem disponibilizados o mais rápido possível.

Esses recursos podem ser adicionados usando a API tf.data . Para obter mais detalhes, visite o guia Desempenho do pipeline de entrada .

def configure_for_performance(ds):
  ds = ds.cache()
  ds = ds.shuffle(buffer_size=1000)
  ds = ds.batch(batch_size)
  ds = ds.prefetch(buffer_size=AUTOTUNE)
  return ds

train_ds = configure_for_performance(train_ds)
val_ds = configure_for_performance(val_ds)

Visualize os dados

Você pode visualizar este conjunto de dados de forma semelhante ao que você criou anteriormente:

image_batch, label_batch = next(iter(train_ds))

plt.figure(figsize=(10, 10))
for i in range(9):
  ax = plt.subplot(3, 3, i + 1)
  plt.imshow(image_batch[i].numpy().astype("uint8"))
  label = label_batch[i]
  plt.title(class_names[label])
  plt.axis("off")
2022-01-26 06:29:45.209901: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

png

Continue treinando o modelo

Você agora construiu manualmente um tf.data.Dataset semelhante ao criado por tf.keras.utils.image_dataset_from_directory acima. Você pode continuar treinando o modelo com ele. Como antes, você treinará por apenas algumas épocas para manter o tempo de execução curto.

model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=3
)
Epoch 1/3
92/92 [==============================] - 3s 21ms/step - loss: 0.7305 - accuracy: 0.7245 - val_loss: 0.7311 - val_accuracy: 0.7139
Epoch 2/3
92/92 [==============================] - 1s 13ms/step - loss: 0.5279 - accuracy: 0.8069 - val_loss: 0.7021 - val_accuracy: 0.7316
Epoch 3/3
92/92 [==============================] - 1s 13ms/step - loss: 0.3739 - accuracy: 0.8644 - val_loss: 0.8266 - val_accuracy: 0.6948
<keras.callbacks.History at 0x7ff0ee071f10>

Como usar conjuntos de dados do TensorFlow

Até agora, este tutorial se concentrou no carregamento de dados do disco. Você também pode encontrar um conjunto de dados para usar explorando o grande catálogo de conjuntos de dados fáceis de baixar em TensorFlow Datasets .

Como você carregou anteriormente o conjunto de dados Flowers do disco, agora vamos importá-lo com os conjuntos de dados do TensorFlow.

Faça o download do conjunto de dados Flowers usando os conjuntos de dados do TensorFlow:

(train_ds, val_ds, test_ds), metadata = tfds.load(
    'tf_flowers',
    split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
    with_info=True,
    as_supervised=True,
)

O conjunto de dados de flores tem cinco classes:

num_classes = metadata.features['label'].num_classes
print(num_classes)
5

Recupere uma imagem do conjunto de dados:

get_label_name = metadata.features['label'].int2str

image, label = next(iter(train_ds))
_ = plt.imshow(image)
_ = plt.title(get_label_name(label))
2022-01-26 06:29:54.281352: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

png

Como antes, lembre-se de agrupar, embaralhar e configurar os conjuntos de treinamento, validação e teste para desempenho:

train_ds = configure_for_performance(train_ds)
val_ds = configure_for_performance(val_ds)
test_ds = configure_for_performance(test_ds)

Você pode encontrar um exemplo completo de como trabalhar com o conjunto de dados Flowers e o TensorFlow Datasets visitando o tutorial de aumento de dados .

Próximos passos

Este tutorial mostrou duas maneiras de carregar imagens do disco. Primeiro, você aprendeu a carregar e pré-processar um conjunto de dados de imagem usando camadas e utilitários de pré-processamento Keras. Em seguida, você aprendeu a escrever um pipeline de entrada do zero usando tf.data . Por fim, você aprendeu a baixar um conjunto de dados do TensorFlow Datasets.

Para seus próximos passos: