Classificazione delle immagini

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza l'origine su GitHub Scarica quaderno

Questo tutorial mostra come classificare le immagini dei fiori. Crea un classificatore di immagini utilizzando un modello tf.keras.Sequential e carica i dati utilizzando tf.keras.utils.image_dataset_from_directory . Acquisirai esperienza pratica con i seguenti concetti:

  • Caricamento efficiente di un set di dati da disco.
  • Identificazione dell'overfitting e applicazione di tecniche per mitigarlo, inclusi data augmentation e dropout.

Questo tutorial segue un flusso di lavoro di apprendimento automatico di base:

  1. Esaminare e comprendere i dati
  2. Costruisci una pipeline di input
  3. Costruisci il modello
  4. Allena il modello
  5. Prova il modello
  6. Migliora il modello e ripeti il ​​processo

Importa TensorFlow e altre librerie

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

Scarica ed esplora il set di dati

Questo tutorial utilizza un set di dati di circa 3.700 foto di fiori. Il set di dati contiene cinque sottodirectory, una per 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)

Dopo il download, dovresti ora avere una copia del set di dati disponibile. Ci sono 3.670 immagini in totale:

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

Ecco alcune rose:

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

png

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

png

E qualche tulipano:

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

png

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

png

Carica i dati utilizzando un'utilità Keras

Carichiamo queste immagini dal disco usando l'utile utility tf.keras.utils.image_dataset_from_directory . Questo ti porterà da una directory di immagini su disco a un tf.data.Dataset in solo un paio di righe di codice. Se lo desideri, puoi anche scrivere da zero il tuo codice di caricamento dei dati visitando il tutorial Carica e preelabora le immagini .

Crea un set di dati

Definire alcuni parametri per il caricatore:

batch_size = 32
img_height = 180
img_width = 180

È buona norma utilizzare una divisione di convalida durante lo sviluppo del modello. Usiamo l'80% delle immagini per la formazione e il 20% per la convalida.

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.

Puoi trovare i nomi delle classi nell'attributo class_names su questi set di dati. Questi corrispondono ai nomi delle directory in ordine alfabetico.

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

Visualizza i dati

Ecco le prime nove immagini del set di dati di addestramento:

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

Addestrerai un modello utilizzando questi set di dati passandoli a Model.fit in un attimo. Se lo desideri, puoi anche scorrere manualmente il set di dati e recuperare batch di immagini:

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

image_batch è un tensore della forma (32, 180, 180, 3) . Si tratta di un batch di 32 immagini di forma 180x180x3 (l'ultima dimensione si riferisce ai canali colore RGB). Il label_batch è un tensore della forma (32,) , queste sono etichette corrispondenti alle 32 immagini.

Puoi chiamare .numpy() sui tensori image_batch ed labels_batch per convertirli in un numpy.ndarray .

Configura il set di dati per le prestazioni

Assicuriamoci di utilizzare il prelettura bufferizzata in modo da poter produrre dati dal disco senza che l'I/O si blocchi. Questi sono due metodi importanti da utilizzare durante il caricamento dei dati:

  • Dataset.cache mantiene le immagini in memoria dopo che sono state caricate dal disco durante la prima epoca. Ciò garantirà che il set di dati non diventi un collo di bottiglia durante l'addestramento del modello. Se il tuo set di dati è troppo grande per essere contenuto nella memoria, puoi anche utilizzare questo metodo per creare una cache su disco performante.
  • Dataset.prefetch si sovrappone alla preelaborazione dei dati e all'esecuzione del modello durante l'addestramento.

I lettori interessati possono saperne di più su entrambi i metodi e su come memorizzare nella cache i dati su disco nella sezione Prefetching della Guida all'API tf.data .

AUTOTUNE = tf.data.AUTOTUNE

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

Standardizzare i dati

I valori del canale RGB sono compresi nell'intervallo [0, 255] . Questo non è l'ideale per una rete neurale; in generale dovresti cercare di ridurre i tuoi valori di input.

Qui, standardizzerai i valori in modo che rientrino nell'intervallo [0, 1] usando tf.keras.layers.Rescaling :

normalization_layer = layers.Rescaling(1./255)

Esistono due modi per utilizzare questo livello. Puoi applicarlo al set di dati chiamando 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 1.0

In alternativa, puoi includere il livello all'interno della definizione del modello, il che può semplificare la distribuzione. Usiamo qui il secondo approccio.

Crea il modello

Il modello sequenziale è costituito da tre blocchi di convoluzione ( tf.keras.layers.Conv2D ) con uno strato di pooling massimo ( tf.keras.layers.MaxPooling2D ) in ciascuno di essi. C'è uno strato completamente connesso ( tf.keras.layers.Dense ) con 128 unità sopra che viene attivato da una funzione di attivazione ReLU ( 'relu' ). Questo modello non è stato ottimizzato per un'elevata precisione: l'obiettivo di questo tutorial è mostrare un approccio standard.

num_classes = len(class_names)

model = Sequential([
  layers.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)
])

Compila il modello

Per questo tutorial, scegli l'ottimizzatore tf.keras.optimizers.Adam e la funzione di perdita tf.keras.losses.SparseCategoricalCrossentropy . Per visualizzare l'accuratezza dell'addestramento e della convalida per ogni epoca di addestramento, passa l'argomento delle metrics a Model.compile .

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

Riepilogo del modello

Visualizza tutti i livelli della rete utilizzando il metodo Model.summary del modello:

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 (MaxPooling  (None, 45, 45, 32)       0         
 2D)                                                             
                                                                 
 conv2d_2 (Conv2D)           (None, 45, 45, 64)        18496     
                                                                 
 max_pooling2d_2 (MaxPooling  (None, 22, 22, 64)       0         
 2D)                                                             
                                                                 
 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
_________________________________________________________________

Allena il modello

epochs=10
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/10
92/92 [==============================] - 3s 16ms/step - loss: 1.2769 - accuracy: 0.4489 - val_loss: 1.0457 - val_accuracy: 0.5804
Epoch 2/10
92/92 [==============================] - 1s 11ms/step - loss: 0.9386 - accuracy: 0.6328 - val_loss: 0.9665 - val_accuracy: 0.6158
Epoch 3/10
92/92 [==============================] - 1s 11ms/step - loss: 0.7390 - accuracy: 0.7200 - val_loss: 0.8768 - val_accuracy: 0.6540
Epoch 4/10
92/92 [==============================] - 1s 11ms/step - loss: 0.5649 - accuracy: 0.7963 - val_loss: 0.9258 - val_accuracy: 0.6540
Epoch 5/10
92/92 [==============================] - 1s 11ms/step - loss: 0.3662 - accuracy: 0.8733 - val_loss: 1.1734 - val_accuracy: 0.6267
Epoch 6/10
92/92 [==============================] - 1s 11ms/step - loss: 0.2169 - accuracy: 0.9343 - val_loss: 1.3728 - val_accuracy: 0.6499
Epoch 7/10
92/92 [==============================] - 1s 11ms/step - loss: 0.1191 - accuracy: 0.9629 - val_loss: 1.3791 - val_accuracy: 0.6471
Epoch 8/10
92/92 [==============================] - 1s 11ms/step - loss: 0.0497 - accuracy: 0.9871 - val_loss: 1.8002 - val_accuracy: 0.6390
Epoch 9/10
92/92 [==============================] - 1s 11ms/step - loss: 0.0372 - accuracy: 0.9922 - val_loss: 1.8545 - val_accuracy: 0.6390
Epoch 10/10
92/92 [==============================] - 1s 11ms/step - loss: 0.0715 - accuracy: 0.9813 - val_loss: 2.0656 - val_accuracy: 0.6049

Visualizza i risultati dell'allenamento

Creare grafici di perdita e accuratezza sui set di addestramento e convalida:

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

I grafici mostrano che l'accuratezza dell'addestramento e l'accuratezza della convalida sono di gran lunga inferiori e il modello ha raggiunto solo un'accuratezza del 60% circa sul set di convalida.

Esaminiamo cosa è andato storto e cerchiamo di aumentare le prestazioni complessive del modello.

Sovraccarico

Nei grafici sopra, l'accuratezza dell'addestramento aumenta linearmente nel tempo, mentre l'accuratezza della convalida si ferma a circa il 60% nel processo di addestramento. Inoltre, è evidente la differenza di accuratezza tra addestramento e accuratezza di convalida, un segno di overfitting .

Quando è presente un numero limitato di esempi di addestramento, il modello a volte apprende da rumori o dettagli indesiderati dagli esempi di addestramento, in una misura tale da influire negativamente sulle prestazioni del modello su nuovi esempi. Questo fenomeno è noto come overfitting. Significa che il modello avrà difficoltà a generalizzare su un nuovo set di dati.

Esistono diversi modi per combattere l'overfitting nel processo di formazione. In questo tutorial utilizzerai l'aumento dei dati e aggiungerai Dropout al tuo modello.

Aumento dei dati

L'overfitting si verifica generalmente quando c'è un piccolo numero di esempi di formazione. L'aumento dei dati adotta l'approccio di generare dati di addestramento aggiuntivi dai tuoi esempi esistenti aumentandoli utilizzando trasformazioni casuali che producono immagini dall'aspetto credibile. Ciò consente di esporre il modello a più aspetti dei dati e di generalizzare meglio.

Implementerai l'aumento dei dati utilizzando i seguenti livelli di preelaborazione Keras: tf.keras.layers.RandomFlip , tf.keras.layers.RandomRotation e tf.keras.layers.RandomZoom . Questi possono essere inclusi all'interno del tuo modello come altri livelli ed essere eseguiti sulla GPU.

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

Visualizziamo l'aspetto di alcuni esempi aumentati applicando più volte l'aumento dei dati alla stessa immagine:

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

Utilizzerai l'aumento dei dati per addestrare un modello in un momento.

Ritirarsi

Un'altra tecnica per ridurre l' overfitting consiste nell'introdurre la regolarizzazione dell'abbandono nella rete.

Quando si applica il dropout a un livello, durante il processo di addestramento vengono eliminate casualmente (impostando l'attivazione su zero) un numero di unità di output dal livello. Dropout accetta un numero frazionario come valore di input, nella forma come 0.1, 0.2, 0.4, ecc. Ciò significa eliminare casualmente il 10%, 20% o 40% delle unità di output dal livello applicato.

Creiamo una nuova rete neurale con tf.keras.layers.Dropout prima di addestrarla utilizzando le immagini aumentate:

model = Sequential([
  data_augmentation,
  layers.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)
])

Compila e addestra il modello

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 (MaxPooling  (None, 90, 90, 16)       0         
 2D)                                                             
                                                                 
 conv2d_4 (Conv2D)           (None, 90, 90, 32)        4640      
                                                                 
 max_pooling2d_4 (MaxPooling  (None, 45, 45, 32)       0         
 2D)                                                             
                                                                 
 conv2d_5 (Conv2D)           (None, 45, 45, 64)        18496     
                                                                 
 max_pooling2d_5 (MaxPooling  (None, 22, 22, 64)       0         
 2D)                                                             
                                                                 
 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 14ms/step - loss: 1.3840 - accuracy: 0.3999 - val_loss: 1.0967 - val_accuracy: 0.5518
Epoch 2/15
92/92 [==============================] - 1s 12ms/step - loss: 1.1152 - accuracy: 0.5395 - val_loss: 1.1123 - val_accuracy: 0.5545
Epoch 3/15
92/92 [==============================] - 1s 12ms/step - loss: 1.0049 - accuracy: 0.6052 - val_loss: 0.9544 - val_accuracy: 0.6253
Epoch 4/15
92/92 [==============================] - 1s 12ms/step - loss: 0.9452 - accuracy: 0.6257 - val_loss: 0.9681 - val_accuracy: 0.6213
Epoch 5/15
92/92 [==============================] - 1s 12ms/step - loss: 0.8804 - accuracy: 0.6591 - val_loss: 0.8450 - val_accuracy: 0.6798
Epoch 6/15
92/92 [==============================] - 1s 12ms/step - loss: 0.8001 - accuracy: 0.6945 - val_loss: 0.8715 - val_accuracy: 0.6594
Epoch 7/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7736 - accuracy: 0.6965 - val_loss: 0.8059 - val_accuracy: 0.6935
Epoch 8/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7477 - accuracy: 0.7078 - val_loss: 0.8292 - val_accuracy: 0.6812
Epoch 9/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7053 - accuracy: 0.7251 - val_loss: 0.7743 - val_accuracy: 0.6989
Epoch 10/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6884 - accuracy: 0.7340 - val_loss: 0.7867 - val_accuracy: 0.6907
Epoch 11/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6536 - accuracy: 0.7469 - val_loss: 0.7732 - val_accuracy: 0.6785
Epoch 12/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6456 - accuracy: 0.7500 - val_loss: 0.7801 - val_accuracy: 0.6907
Epoch 13/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5941 - accuracy: 0.7735 - val_loss: 0.7185 - val_accuracy: 0.7330
Epoch 14/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5824 - accuracy: 0.7735 - val_loss: 0.7282 - val_accuracy: 0.7357
Epoch 15/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5771 - accuracy: 0.7851 - val_loss: 0.7308 - val_accuracy: 0.7343

Visualizza i risultati dell'allenamento

Dopo aver applicato l'aumento dei dati e tf.keras.layers.Dropout , c'è meno overfitting rispetto a prima e l'accuratezza della formazione e della convalida sono più allineate:

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

Prevedere nuovi dati

Infine, utilizziamo il nostro modello per classificare un'immagine che non è stata inclusa nei set di addestramento o convalida.

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 = tf.keras.utils.load_img(
    sunflower_path, target_size=(img_height, img_width)
)
img_array = tf.keras.utils.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
131072/117948 [=================================] - 0s 0us/step
This image most likely belongs to sunflowers with a 89.13 percent confidence.