Cette page a été traduite par l'API Cloud Translation.
Switch to English

Classification des images

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

Ce tutoriel montre comment classer des images de fleurs. Il crée un classificateur d'image à l'aide d'un modèle keras.Sequential et charge les données à l'aide de preprocessing.image_dataset_from_directory . Vous acquerrez une expérience pratique avec les concepts suivants:

  • Chargement efficace d'un ensemble de données hors disque.
  • Identifier le surajustement et appliquer des techniques pour l'atténuer, y compris l'augmentation des données et l'abandon.

Ce didacticiel suit un workflow d'apprentissage automatique de base:

  1. Examiner et comprendre les données
  2. Construire un pipeline d'entrée
  3. Construisez le modèle
  4. Former le modèle
  5. Testez le modèle
  6. Améliorez le modèle et répétez le processus
pip install -q tf-nightly

Importer TensorFlow et d'autres bibliothèques

 import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
 

Téléchargez et explorez l'ensemble de données

Ce didacticiel utilise un ensemble de données d'environ 3700 photos de fleurs. L'ensemble de données contient 5 sous-répertoires, un par classe:

 flower_photo/
  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('flower_photos', origin=dataset_url, untar=True)
data_dir = pathlib.Path(data_dir)
 
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
228818944/228813984 [==============================] - 4s 0us/step

Après le téléchargement, vous devriez maintenant avoir une copie de l'ensemble de données disponible. Il y a 3670 images au total:

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

Voici quelques roses:

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

png

 PIL.Image.open(str(roses[1]))
 

png

Et quelques tulipes:

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

png

 PIL.Image.open(str(tulips[1]))
 

png

Charger à l'aide de keras.preprocessing

Chargez ces images hors du disque à l'aide de l'utilitaire utile image_dataset_from_directory . Cela vous mènera d'un répertoire d'images sur disque à un tf.data.Dataset en seulement quelques lignes de code. Si vous le souhaitez, vous pouvez également écrire votre propre code de chargement de données à partir de zéro en visitant le didacticiel de chargement d'images .

Créer un jeu 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 un fractionnement de validation lors du développement de votre modèle. Nous utiliserons 80% des images pour la formation et 20% pour la validation.

 train_ds = tf.keras.preprocessing.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.preprocessing.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. Ceux-ci correspondent aux noms des répertoires par ordre alphabétique.

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

Visualisez les données

Voici les 9 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 allez entraîner un modèle à l'aide de ces ensembles de données en les passant à model.fit dans un instant. 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 se réfère 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 les tenseurs image_batch et labels_batch pour les convertir en numpy.ndarray .

Configurer l'ensemble de données pour les performances

Veillons à utiliser la prélecture tamponnée afin que nous puissions générer des données à partir du disque sans que les E / S deviennent bloquantes. Ce sont deux méthodes importantes que vous devez utiliser lors du chargement de données.

Dataset.cache() garde les images en mémoire après leur chargement hors du disque au cours de la première époque. Cela garantira que l'ensemble de données ne deviendra pas un goulot d'étranglement lors de l'entraînement de votre modèle. Si votre ensemble de données est trop volumineux pour tenir dans la 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 l'entraînement.

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 le guide des performances des données .

 AUTOTUNE = tf.data.experimental.AUTOTUNE

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

Standardiser les données

Les valeurs du canal 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 à réduire vos valeurs d'entrée. Ici, nous normaliserons les valeurs pour qu'elles soient dans [0, 1] en utilisant une couche de redimensionnement.

 normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)
 

Il existe deux manières d'utiliser cette couche. Vous pouvez l'appliquer à l'ensemble de données en appelant 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 pixels values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image)) 
 
0.0 1.0

Vous pouvez également inclure la couche dans votre définition de modèle, ce qui peut simplifier le déploiement. Nous utiliserons ici la deuxième approche.

Créer le modèle

Le modèle se compose de trois blocs de convolution avec une couche de pool maximale dans chacun d'eux. Il y a une couche entièrement connectée avec 128 unités au-dessus qui est activée par une fonction d'activation relu . Ce modèle n'a pas été réglé pour une grande précision, le but de ce tutoriel est de montrer une approche standard.

 num_classes = 5

model = Sequential([
  layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(32, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(64, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes)
])
 

Compilez le modèle

Pour ce didacticiel, choisissez les optimizers.Adam optimizer and losses.SparseCategoricalCrossentropy loss function. Pour afficher la précision de la formation et de la validation pour chaque période de formation, transmettez l'argument metrics .

 model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
 

Résumé du modèle

Visualisez toutes les couches du réseau en utilisant la méthode summary du modèle:

 model.summary()
 
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
rescaling_1 (Rescaling)      (None, 180, 180, 3)       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 180, 180, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 90, 90, 16)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 90, 90, 32)        4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
flatten (Flatten)            (None, 30976)             0         
_________________________________________________________________
dense (Dense)                (None, 128)               3965056   
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 645       
=================================================================
Total params: 3,989,285
Trainable params: 3,989,285
Non-trainable params: 0
_________________________________________________________________

Former le modèle

 epochs=10
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
 
Epoch 1/10
92/92 [==============================] - 4s 47ms/step - loss: 1.9328 - accuracy: 0.2896 - val_loss: 1.1022 - val_accuracy: 0.5245
Epoch 2/10
92/92 [==============================] - 1s 10ms/step - loss: 1.0441 - accuracy: 0.5761 - val_loss: 1.0057 - val_accuracy: 0.5913
Epoch 3/10
92/92 [==============================] - 1s 10ms/step - loss: 0.8640 - accuracy: 0.6763 - val_loss: 0.8951 - val_accuracy: 0.6499
Epoch 4/10
92/92 [==============================] - 1s 10ms/step - loss: 0.7106 - accuracy: 0.7472 - val_loss: 0.8992 - val_accuracy: 0.6621
Epoch 5/10
92/92 [==============================] - 1s 10ms/step - loss: 0.4817 - accuracy: 0.8285 - val_loss: 0.8997 - val_accuracy: 0.6662
Epoch 6/10
92/92 [==============================] - 1s 10ms/step - loss: 0.3131 - accuracy: 0.8903 - val_loss: 1.0014 - val_accuracy: 0.6567
Epoch 7/10
92/92 [==============================] - 1s 10ms/step - loss: 0.1782 - accuracy: 0.9436 - val_loss: 1.2140 - val_accuracy: 0.6431
Epoch 8/10
92/92 [==============================] - 1s 10ms/step - loss: 0.1024 - accuracy: 0.9703 - val_loss: 1.5144 - val_accuracy: 0.6240
Epoch 9/10
92/92 [==============================] - 1s 10ms/step - loss: 0.0736 - accuracy: 0.9815 - val_loss: 1.7651 - val_accuracy: 0.5926
Epoch 10/10
92/92 [==============================] - 1s 10ms/step - loss: 0.0761 - accuracy: 0.9775 - val_loss: 2.0429 - val_accuracy: 0.5967

Visualisez les résultats de l'entraînement

Créez des graphiques de perte et de précision sur les ensembles de formation et de validation.

 acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss=history.history['loss']
val_loss=history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
 

png

Comme vous pouvez le voir sur les graphiques, la précision de l'entraînement et la précision de la validation sont largement faussées et le modèle n'a atteint qu'une précision d'environ 60% sur l'ensemble de validation.

Regardons ce qui ne va pas et essayons d'augmenter les performances globales du modèle.

Surapprentissage

Dans les graphiques ci-dessus, la précision de la formation augmente linéairement au fil du temps, tandis que la précision de la validation stagne autour de 60% dans le processus de formation. De plus, la différence de précision entre la précision de la formation et celle de la validation est perceptible - signe de surajustement .

Lorsqu'il y a un petit nombre d'exemples de formation, le modèle apprend parfois des bruits ou des détails indésirables des exemples de formation, dans une mesure où cela a un impact négatif sur les performances du modèle sur les nouveaux exemples. Ce phénomène est connu sous le nom de surajustement. Cela signifie que le modèle aura du mal à se généraliser sur un nouvel ensemble de données.

Il existe plusieurs façons de lutter contre le surajustement dans le processus de formation. Dans ce didacticiel, vous allez utiliser l'augmentation des données et ajouter Dropout à votre modèle.

Augmentation des données

Le surajustement se produit généralement lorsqu'il existe un petit nombre d'exemples d'entraînement. L'augmentation des données adopte l'approche consistant à générer des données d'entraînement supplémentaires à partir de vos exemples existants en augmentant puis en utilisant des transformations aléatoires qui donnent des images crédibles. Cela permet d'exposer le modèle à plus d'aspects des données et de mieux généraliser.

Nous implémenterons l'augmentation des données à l'aide de couches de prétraitement Keras expérimentales. Ceux-ci peuvent être inclus dans votre modèle comme les autres couches et s'exécuter sur le GPU.

 data_augmentation = keras.Sequential(
  [
    layers.experimental.preprocessing.RandomFlip("horizontal", 
                                                 input_shape=(img_height, 
                                                              img_width,
                                                              3)),
    layers.experimental.preprocessing.RandomRotation(0.1),
    layers.experimental.preprocessing.RandomZoom(0.1),
  ]
)
 

Visualisons à quoi ressemblent quelques exemples augmentés en appliquant plusieurs fois l'augmentation des données à la même image:

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

png

Nous utiliserons l'augmentation des données pour entraîner un modèle dans un instant.

Abandonner

Une autre technique pour réduire le surajustement consiste à introduire le Dropout dans le réseau, une forme de régularisation .

Lorsque vous appliquez Dropout à un calque, il supprime de manière aléatoire (en définissant l'activation sur zéro) un certain nombre d'unités de sortie du calque pendant le processus d'apprentissage. L'abandon prend un nombre fractionnaire comme valeur d'entrée, sous la forme telle que 0,1, 0,2, 0,4, etc. Cela signifie supprimer 10%, 20% ou 40% des unités de sortie de manière aléatoire de la couche appliquée.

Créons un nouveau réseau de neurones à l'aide de layers.Dropout , layers.Dropout , puis entraînez-le à l'aide d'images augmentées.

 model = Sequential([
  data_augmentation,
  layers.experimental.preprocessing.Rescaling(1./255),
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(32, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(64, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Dropout(0.2),
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes)
])
 

Compiler et former le modèle

 model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
 
 model.summary()
 
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
sequential_1 (Sequential)    (None, 180, 180, 3)       0         
_________________________________________________________________
rescaling_2 (Rescaling)      (None, 180, 180, 3)       0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 180, 180, 16)      448       
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 90, 90, 16)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 90, 90, 32)        4640      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
dropout (Dropout)            (None, 22, 22, 64)        0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 30976)             0         
_________________________________________________________________
dense_2 (Dense)              (None, 128)               3965056   
_________________________________________________________________
dense_3 (Dense)              (None, 5)                 645       
=================================================================
Total params: 3,989,285
Trainable params: 3,989,285
Non-trainable params: 0
_________________________________________________________________

 epochs = 15
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
 
Epoch 1/15
92/92 [==============================] - 2s 20ms/step - loss: 1.4842 - accuracy: 0.3279 - val_loss: 1.0863 - val_accuracy: 0.5640
Epoch 2/15
92/92 [==============================] - 1s 12ms/step - loss: 1.1215 - accuracy: 0.5284 - val_loss: 1.0374 - val_accuracy: 0.6022
Epoch 3/15
92/92 [==============================] - 1s 12ms/step - loss: 0.9680 - accuracy: 0.6117 - val_loss: 0.9200 - val_accuracy: 0.6485
Epoch 4/15
92/92 [==============================] - 1s 12ms/step - loss: 0.8538 - accuracy: 0.6753 - val_loss: 0.9206 - val_accuracy: 0.6417
Epoch 5/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7744 - accuracy: 0.6977 - val_loss: 0.8169 - val_accuracy: 0.6839
Epoch 6/15
92/92 [==============================] - 1s 13ms/step - loss: 0.7758 - accuracy: 0.7093 - val_loss: 0.7743 - val_accuracy: 0.6880
Epoch 7/15
92/92 [==============================] - 1s 13ms/step - loss: 0.6805 - accuracy: 0.7481 - val_loss: 0.8598 - val_accuracy: 0.6540
Epoch 8/15
92/92 [==============================] - 1s 13ms/step - loss: 0.7132 - accuracy: 0.7278 - val_loss: 0.7177 - val_accuracy: 0.7207
Epoch 9/15
92/92 [==============================] - 1s 13ms/step - loss: 0.6634 - accuracy: 0.7580 - val_loss: 0.7152 - val_accuracy: 0.7166
Epoch 10/15
92/92 [==============================] - 1s 13ms/step - loss: 0.6562 - accuracy: 0.7538 - val_loss: 0.7251 - val_accuracy: 0.7248
Epoch 11/15
92/92 [==============================] - 1s 13ms/step - loss: 0.5798 - accuracy: 0.7840 - val_loss: 0.7016 - val_accuracy: 0.7357
Epoch 12/15
92/92 [==============================] - 1s 13ms/step - loss: 0.5635 - accuracy: 0.7913 - val_loss: 0.7755 - val_accuracy: 0.7248
Epoch 13/15
92/92 [==============================] - 1s 13ms/step - loss: 0.5361 - accuracy: 0.7982 - val_loss: 0.7575 - val_accuracy: 0.7153
Epoch 14/15
92/92 [==============================] - 1s 13ms/step - loss: 0.5420 - accuracy: 0.8022 - val_loss: 0.7375 - val_accuracy: 0.7302
Epoch 15/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5132 - accuracy: 0.8120 - val_loss: 0.7561 - val_accuracy: 0.7289

Visualisez les résultats de l'entraînement

Après avoir appliqué l'augmentation des données et l'abandon, il y a moins de surajustement qu'auparavant, et la précision de l'entraînement et de la validation est plus proche.

 acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
 

png

Prédire sur de nouvelles données

Enfin, utilisons notre modèle pour classer une image qui n'était pas incluse dans les ensembles de formation ou de validation.

 sunflower_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg"
sunflower_path = tf.keras.utils.get_file('Red_sunflower', origin=sunflower_url)

img = keras.preprocessing.image.load_img(
    sunflower_path, target_size=(img_height, img_width)
)
img_array = keras.preprocessing.image.img_to_array(img)
img_array = tf.expand_dims(img_array, 0) # Create a batch

predictions = model.predict(img_array)
score = tf.nn.softmax(predictions[0])

print(
    "This image most likely belongs to {} with a {:.2f} percent confidence."
    .format(class_names[np.argmax(score)], 100 * np.max(score))
)
 
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg
122880/117948 [===============================] - 0s 0us/step
This image most likely belongs to sunflowers with a 89.39 percent confidence.