Charger et prétraiter les images

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Ce didacticiel montre comment charger et prétraiter un jeu de données d'image de trois manières :

Installer

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

Téléchargez le jeu de données sur les fleurs

Ce tutoriel utilise un jeu de données de plusieurs milliers de photos de fleurs. Le jeu de données flowers contient cinq sous-répertoires, un par 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)

Après le téléchargement (218 Mo), vous devriez maintenant avoir une copie des photos de fleurs disponibles. Il y a 3 670 images au total :

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

Chaque répertoire contient des images de ce type de fleur. Voici quelques roses :

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

Charger des données à l'aide d'un utilitaire Keras

Chargeons ces images hors disque à l'aide de l'utilitaire utile tf.keras.utils.image_dataset_from_directory .

Créer un ensemble de données

Définissez quelques paramètres pour le chargeur :

batch_size = 32
img_height = 180
img_width = 180

Il est recommandé d'utiliser une division de validation lors du développement de votre modèle. Vous utiliserez 80% des images pour la formation et 20% pour la validation.

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.

Vous pouvez trouver les noms de classe dans l'attribut class_names sur ces ensembles de données.

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

Visualisez les données

Voici les neuf premières images de l'ensemble de données d'entraînement.

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

Vous pouvez entraîner un modèle à l'aide de ces jeux de données en les transmettant à model.fit (illustré plus loin dans ce didacticiel). Si vous le souhaitez, vous pouvez également parcourir manuellement l'ensemble de données et récupérer des lots d'images :

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

L' image_batch est un tenseur de la forme (32, 180, 180, 3) . Il s'agit d'un lot de 32 images de forme 180x180x3 (la dernière dimension fait référence aux canaux de couleur RVB). Le label_batch est un tenseur de la forme (32,) , ce sont des labels correspondants aux 32 images.

Vous pouvez appeler .numpy() sur l'un de ces tenseurs pour les convertir en numpy.ndarray .

Standardiser les données

Les valeurs des canaux RVB sont dans la plage [0, 255] . Ce n'est pas idéal pour un réseau neuronal ; en général, vous devriez chercher à rendre vos valeurs d'entrée petites.

Ici, vous allez normaliser les valeurs pour qu'elles soient dans la plage [0, 1] en utilisant tf.keras.layers.Rescaling :

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

Il existe deux façons d'utiliser cette couche. Vous pouvez l'appliquer au jeu de données en appelant 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, vous pouvez inclure la couche dans la définition de votre modèle pour simplifier le déploiement. Vous utiliserez ici la deuxième approche.

Configurer l'ensemble de données pour les performances

Assurons-nous d'utiliser la prélecture tamponnée afin que vous puissiez récupérer des données à partir du disque sans que les E/S ne deviennent bloquantes. Voici deux méthodes importantes que vous devez utiliser lors du chargement des données :

  • Dataset.cache conserve les images en mémoire après leur chargement hors disque au cours de la première époque. Cela garantira que l'ensemble de données ne devienne pas un goulot d'étranglement lors de la formation de votre modèle. Si votre jeu de données est trop volumineux pour tenir en mémoire, vous pouvez également utiliser cette méthode pour créer un cache sur disque performant.
  • Dataset.prefetch chevauche le prétraitement des données et l'exécution du modèle pendant la formation.

Les lecteurs intéressés peuvent en savoir plus sur les deux méthodes, ainsi que sur la mise en cache des données sur le disque dans la section Prélecture du guide Meilleures performances avec l'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)

Former un modèle

Pour être complet, vous montrerez comment entraîner un modèle simple à l'aide des ensembles de données que vous venez de préparer.

Le modèle séquentiel se compose de trois blocs de convolution ( tf.keras.layers.Conv2D ) avec une couche de regroupement maximum ( tf.keras.layers.MaxPooling2D ) dans chacun d'eux. Il y a une couche entièrement connectée ( tf.keras.layers.Dense ) avec 128 unités dessus qui est activée par une fonction d'activation ReLU ( 'relu' ). Ce modèle n'a été ajusté d'aucune façon. L'objectif est de vous montrer les mécanismes à l'aide des ensembles de données que vous venez de créer. Pour en savoir plus sur la classification des images, consultez le didacticiel Classification des images .

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

Choisissez l'optimiseur tf.keras.optimizers.Adam et la fonction de perte tf.keras.losses.SparseCategoricalCrossentropy . Pour afficher la précision de la formation et de la validation pour chaque époque de formation, transmettez l'argument metrics à 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>

Vous remarquerez peut-être que la précision de la validation est faible par rapport à la précision de l'entraînement, ce qui indique que votre modèle est surajusté. Vous pouvez en savoir plus sur le surajustement et comment le réduire dans ce didacticiel .

Utiliser tf.data pour un contrôle plus fin

L'utilitaire de prétraitement Keras ci-dessus - tf.keras.utils.image_dataset_from_directory - est un moyen pratique de créer un tf.data.Dataset à partir d'un répertoire d'images.

Pour un contrôle plus précis du grain, vous pouvez écrire votre propre pipeline d'entrée à l'aide tf.data . Cette section montre comment faire exactement cela, en commençant par les chemins de fichier du fichier TGZ que vous avez téléchargé précédemment.

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'

L'arborescence des fichiers peut être utilisée pour compiler une liste de class_names de classes.

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

Divisez l'ensemble de données en ensembles d'apprentissage et de validation :

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

Vous pouvez imprimer la longueur de chaque ensemble de données comme suit :

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

Écrivez une fonction courte qui convertit un chemin de fichier en une paire (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

Utilisez Dataset.map pour créer un ensemble de données de paires d' 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

Configurer l'ensemble de données pour les performances

Pour former un modèle avec cet ensemble de données, vous aurez besoin des données :

  • Être bien mélangé.
  • A mettre en lot.
  • Les lots seront disponibles dès que possible.

Ces fonctionnalités peuvent être ajoutées à l'aide de l'API tf.data . Pour plus de détails, consultez le guide sur les performances du pipeline d'entrée .

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)

Visualisez les données

Vous pouvez visualiser cet ensemble de données de la même manière que celui que vous avez créé précédemment :

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

Continuer la formation du modèle

Vous avez maintenant créé manuellement un tf.data.Dataset similaire à celui créé par tf.keras.utils.image_dataset_from_directory ci-dessus. Vous pouvez continuer à entraîner le modèle avec. Comme auparavant, vous vous entraînerez pendant quelques époques seulement pour réduire le temps de course.

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>

Utiliser des ensembles de données TensorFlow

Jusqu'à présent, ce didacticiel s'est concentré sur le chargement de données hors disque. Vous pouvez également trouver un ensemble de données à utiliser en explorant le vaste catalogue d'ensembles de données faciles à télécharger sur TensorFlow Datasets .

Comme vous avez précédemment chargé le jeu de données Flowers hors disque, importons-le maintenant avec les jeux de données TensorFlow.

Téléchargez l'ensemble de données Flowers à l'aide des ensembles de données 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,
)

Le jeu de données flowers a cinq classes :

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

Récupérez une image de l'ensemble de données :

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

Comme précédemment, n'oubliez pas de regrouper, de mélanger et de configurer les ensembles d'entraînement, de validation et de test pour les performances :

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

Vous pouvez trouver un exemple complet d'utilisation de l'ensemble de données Flowers et des ensembles de données TensorFlow en consultant le didacticiel d'augmentation des données .

Prochaines étapes

Ce didacticiel a montré deux manières de charger des images à partir du disque. Tout d'abord, vous avez appris à charger et à prétraiter un jeu de données d'image à l'aide des couches et des utilitaires de prétraitement Keras. Ensuite, vous avez appris à écrire un pipeline d'entrée à partir de zéro en utilisant tf.data . Enfin, vous avez appris à télécharger un ensemble de données à partir des ensembles de données TensorFlow.

Pour vos prochaines étapes :