Utilizzare reti neurali convoluzionali (CNN) con immagini complesse

1. Prima di iniziare

In questo codelab utilizzerai le convoluzioni per classificare le immagini di cavalli e umani. Utilizzerai TensorFlow in questo lab per creare un sistema di CNN addestrato a riconoscere immagini di cavalli e esseri umani e classificarli.

Prerequisiti

Se non hai mai creato convoluzioni con TensorFlow in passato, ti consigliamo di completare il codelab su Build convolution and run pooling, dove introduciamo contorzioni e pool, e Build convolutional neural Networks (CNN) per migliorare la visione artificiale, in cui parliamo di come rendere i computer più efficienti nel riconoscimento delle immagini.

Cosa imparerai a fare:

  • Come addestrare i computer a riconoscere le funzionalità di un'immagine in cui il soggetto non è chiaro

Cosa imparerai a realizzare

  • Una rete neurale convoluzionale in grado di distinguere tra foto di cavalli e immagini di esseri umani

Che cosa ti serve

Puoi trovare il codice per il resto del codelab in esecuzione in Colab.

Dovrai anche installare TensorFlow e le librerie installate nel codelab precedente.

2. Guida introduttiva: acquisire i dati

Per farlo, creerai un classificatore di cavalli o esseri umani che ti dirà se un'immagine specifica contiene un cavallo o un essere umano, dove la rete viene addestrata a riconoscere gli elementi che determinano cosa significa. Prima di addestrare i dati, dovrai elaborare alcuni dati.

Innanzitutto, scarica i dati:

!wget --no-check-certificate https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip  -O /tmp/horse-or-human.zip

Il codice Python seguente utilizzerà la libreria del sistema operativo per utilizzare le librerie del sistema operativo, offrendoti l'accesso al file system e alla libreria di file ZIP, permettendoti di decomprimere i dati.

import os
import zipfile
 
local_zip = '/tmp/horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('/tmp/horse-or-human')
zip_ref.close()

I contenuti del file ZIP vengono estratti nella directory di base /tmp/horse-or-human, che contiene cavalli e sottodirectory.

In breve, il set di addestramento è il tipo di dati utilizzati per indicare al modello di rete neurale "che aspetto ha un cavallo", e "come è simile a un essere umano".

3. Usa ImageGenerator per etichettare e preparare i dati

Le immagini non sono etichettate esplicitamente come cavalli o esseri umani.

In seguito vedrai qualcosa chiamato ImageDataGenerator. Legge le immagini delle sottodirectory e le etichetta automaticamente dal nome della sottodirectory. Ad esempio, hai una directory di addestramento che contiene una directory dei cavalli e una directory delle persone. ImageDataGenerator etichetta le immagini in modo appropriato, riducendo un passaggio di codifica.

Definisci ognuna di queste directory.

# Directory with our training horse pictures
train_horse_dir = os.path.join('/tmp/horse-or-human/horses')
 
# Directory with our training human pictures
train_human_dir = os.path.join('/tmp/horse-or-human/humans')

Ora, guarda l'aspetto dei nomi file nelle directory di addestramento di cavalli e umani:

train_horse_names = os.listdir(train_horse_dir)
print(train_horse_names[:10])
train_human_names = os.listdir(train_human_dir)
print(train_human_names[:10])

Trova il numero totale di immagini di cavalli e umani nelle directory:

print('total training horse images:', len(os.listdir(train_horse_dir)))
print('total training human images:', len(os.listdir(train_human_dir)))

4. Esplora i dati

Dai un'occhiata ad alcune immagini per farti un'idea più chiara.

Innanzitutto, configura i parametri matplot:

%matplotlib inline
 
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
 
# Parameters for our graph; we'll output images in a 4x4 configuration
nrows = 4
ncols = 4
 
# Index for iterating over images
pic_index = 0

Ora mostra un gruppo di otto immagini di cavalli e otto immagini umane. Puoi eseguire di nuovo la cella per visualizzare un nuovo batch ogni volta.

# Set up matplotlib fig, and size it to fit 4x4 pics
fig = plt.gcf()
fig.set_size_inches(ncols * 4, nrows * 4)
 
pic_index += 8
next_horse_pix = [os.path.join(train_horse_dir, fname) 
                for fname in train_horse_names[pic_index-8:pic_index]]
next_human_pix = [os.path.join(train_human_dir, fname) 
                for fname in train_human_names[pic_index-8:pic_index]]
 
for i, img_path in enumerate(next_horse_pix+next_human_pix):
  # Set up subplot; subplot indices start at 1
  sp = plt.subplot(nrows, ncols, i + 1)
  sp.axis('Off') # Don't show axes (or gridlines)
 
  img = mpimg.imread(img_path)
  plt.imshow(img)
 
plt.show()
 

Ecco alcuni esempi di immagini che mostrano cavalli e esseri umani in pose e orientamenti diversi:

6b6ebbc6e694ccd2.png

5. Definisci il modello

Definisci innanzitutto il modello.

Inizia importando TensorFlow:

import tensorflow as tf

Quindi, aggiungi livelli convoluzionali e uniforma il risultato finale in modo da inserire i livelli ad alta densità di connessione. Infine, aggiungi i livelli densamente collegati.

Tieni presente che stai riscontrando un problema di classificazione in due classi (un problema di classificazione binaria) terminerai la rete con un'attivazione sigmoid in modo che l'output della tua rete sia un unico scalare compreso tra 0 e 1, codificando la probabilità che l'immagine corrente sia la classe 1 (anziché la classe 0).

model = tf.keras.models.Sequential([
    # Note the input shape is the desired size of the image 300x300 with 3 bytes color
    # This is the first convolution
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300, 300, 3)),
    tf.keras.layers.MaxPooling2D(2, 2),
    # The second convolution
    tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The third convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fourth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # The fifth convolution
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # Flatten the results to feed into a DNN
    tf.keras.layers.Flatten(),
    # 512 neuron hidden layer
    tf.keras.layers.Dense(512, activation='relu'),
    # Only 1 output neuron. It will contain a value from 0-1 where 0 for 1 class ('horses') and 1 for the other ('humans')
    tf.keras.layers.Dense(1, activation='sigmoid')
])

La chiamata del metodo model.summary() stampa un riepilogo della rete.

model.summary()

Puoi vedere i risultati qui:

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 298, 298, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 149, 149, 16)      0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 147, 147, 32)      4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 73, 73, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 71, 71, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 35, 35, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 33, 33, 64)        36928     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 16, 16, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 14, 14, 64)        36928     
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 3136)              0         
_________________________________________________________________
dense (Dense)                (None, 512)               1606144   
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 513       
=================================================================
Total params: 1,704,097
Trainable params: 1,704,097
Non-trainable params: 0

La colonna Forma di output indica l'evoluzione delle dimensioni della mappa delle caratteristiche in ciascun livello successivo. I livelli di voluzione riducono leggermente le dimensioni delle mappe delle caratteristiche a causa della spaziatura interna e ogni livello di raggruppamento dimezza le dimensioni.

6. Compila il modello

Successivamente, configura le specifiche per l'addestramento del modello. Addestra il tuo modello con la perdita binary_crossentropy perché è un problema di classificazione binaria e l'attivazione finale è un SIGMOID. Per un ripasso delle metriche di perdita, consulta Decrescente in ML. Utilizza lo strumento di ottimizzazione rmsprop con una percentuale di apprendimento pari a 0,001. Durante l'addestramento, monitora la precisione della classificazione.

from tensorflow.keras.optimizers import RMSprop
 
model.compile(loss='binary_crossentropy',
              optimizer=RMSprop(lr=0.001),
              metrics=['acc'])

7. Addestrare il modello dai generatori

Configura i generatori di dati che leggono le immagini nelle cartelle di origine, le converteno in tensori float32 e le inviano (con le relative etichette) alla rete.

avrai un generatore per le immagini di addestramento e uno per le immagini di convalida. I generatori produrranno gruppi di immagini di dimensioni 300 x 300 e le relative etichette (binario).

Come probabilmente sai già, i dati inseriti nelle reti neurali dovrebbero in genere essere normalizzati in qualche modo per renderli più facilmente elaborabili dalla rete. Non è comune alimentare i pixel non elaborati in una CNN. Nel tuo caso, pre-elabora le immagini normalizzando i valori dei pixel per essere nell'intervallo [0, 1] (originariamente tutti i valori sono nell'intervallo [0, 255]).

In Keras, puoi farlo tramite la classe keras.preprocessing.image.ImageDataGenerator utilizzando il parametro di riscalabilità. Tale classe ImageDataGenerator consente di creare istanze dei generatori di batch di immagini migliorate (e relative etichette) tramite .flow(dati, etichette) o .flow_from_directory(directory). Questi generatori possono essere utilizzati con i metodi del modello Keras che accettano i generatori di dati come input: fit_generator, evaluate_generator e predict_generator.

from tensorflow.keras.preprocessing.image import ImageDataGenerator
 
# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
 
# Flow training images in batches of 128 using train_datagen generator
train_generator = train_datagen.flow_from_directory(
        '/tmp/horse-or-human/',  # This is the source directory for training images
        target_size=(300, 300),  # All images will be resized to 150x150
        batch_size=128,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

8. Completa la formazione

Treno di 15 periodi. (l'esecuzione potrebbe richiedere alcuni minuti).

history = model.fit(
      train_generator,
      steps_per_epoch=8,  
      epochs=15,
      verbose=1)

Prendi nota dei valori per periodo.

La perdita e l'accuratezza sono un ottimo indicatore del progresso della formazione. Fa ipotesi sulla classificazione dei dati di addestramento, poi li misura sull'etichetta nota, calcolando il risultato. L'accuratezza è la parte dell'ipotesi corretta.

Epoch 1/15
9/9 [==============================] - 9s 1s/step - loss: 0.8662 - acc: 0.5151
Epoch 2/15
9/9 [==============================] - 8s 927ms/step - loss: 0.7212 - acc: 0.5969
Epoch 3/15
9/9 [==============================] - 8s 921ms/step - loss: 0.6612 - acc: 0.6592
Epoch 4/15
9/9 [==============================] - 8s 925ms/step - loss: 0.3135 - acc: 0.8481
Epoch 5/15
9/9 [==============================] - 8s 919ms/step - loss: 0.4640 - acc: 0.8530
Epoch 6/15
9/9 [==============================] - 8s 896ms/step - loss: 0.2306 - acc: 0.9231
Epoch 7/15
9/9 [==============================] - 8s 915ms/step - loss: 0.1464 - acc: 0.9396
Epoch 8/15
9/9 [==============================] - 8s 935ms/step - loss: 0.2663 - acc: 0.8919
Epoch 9/15
9/9 [==============================] - 8s 883ms/step - loss: 0.0772 - acc: 0.9698
Epoch 10/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0403 - acc: 0.9805
Epoch 11/15
9/9 [==============================] - 8s 891ms/step - loss: 0.2618 - acc: 0.9075
Epoch 12/15
9/9 [==============================] - 8s 902ms/step - loss: 0.0434 - acc: 0.9873
Epoch 13/15
9/9 [==============================] - 8s 904ms/step - loss: 0.0187 - acc: 0.9932
Epoch 14/15
9/9 [==============================] - 9s 951ms/step - loss: 0.0974 - acc: 0.9649
Epoch 15/15
9/9 [==============================] - 8s 877ms/step - loss: 0.2859 - acc: 0.9338

9. Testa il modello

Ora puoi eseguire una previsione utilizzando il modello. Il codice ti consentirà di scegliere uno o più file dal tuo file system. In seguito, li carica ed esegue il modello, indicando se l'oggetto è un cavallo o un essere umano.

Puoi scaricare immagini da Internet nel tuo file system per provarle. Potresti notare che la rete commette molti errori nonostante la precisione dell'addestramento sia superiore al 99%.

Questo è dovuto a qualcosa che è chiamato overfitting, ossia la rete neurale è addestrata con dati molto limitati (ci sono solo circa 500 immagini per classe). Quindi è molto bravo a riconoscere immagini che sembrano quelle del set di addestramento, ma può mancare molto per le immagini che non fanno parte del set.

Si tratta di un punto dati che dimostra che più dati vengono addestrati, migliore sarà la tua rete finale.

Esistono molte tecniche che possono essere utilizzate per migliorare la formazione, nonostante i dati limitati, tra cui l'aumento delle immagini, ma questo non rientra nell'ambito di questo codelab.

import numpy as np
from google.colab import files
from keras.preprocessing import image
 
uploaded = files.upload()
 
for fn in uploaded.keys():
 
  # predicting images
  path = '/content/' + fn
  img = image.load_img(path, target_size=(300, 300))
  x = image.img_to_array(img)
  x = np.expand_dims(x, axis=0)
 
  images = np.vstack([x])
  classes = model.predict(images, batch_size=10)
  print(classes[0])
  if classes[0]>0.5:
    print(fn + " is a human")
  else:
    print(fn + " is a horse")

Ad esempio, supponiamo che tu voglia eseguire il test con questa immagine:

9e07a57ff3be7a82.jpeg

Ecco cosa produce il cioccolata:

77b678e70b00862a.png

Nonostante si tratti di un'immagine di un fumetto, questo classifica comunque correttamente.

Anche l'immagine seguente presenta la corretta classificazione:

c9213173d9f3d83c.jpeg

f2844da737a1a2f2.png

Prova alcune delle tue immagini ed esplorale.

10. Visualizza rappresentazioni intermedie

Per farti un'idea del tipo di funzionalità apprese dalla CNN, una cosa divertente da fare è visualizzare in che modo un input si trasforma mentre passa alla CNN.

Scegli un'immagine casuale dal set di addestramento, quindi genera una figura in cui ogni riga rappresenta l'output di un livello e ciascuna immagine nella riga rappresenta un filtro specifico nella mappa delle caratteristiche di output. Riesegui la cella per generare rappresentazioni intermedie per una varietà di immagini di addestramento.

import numpy as np
import random
from tensorflow.keras.preprocessing.image import img_to_array, load_img
 
# Let's define a new Model that will take an image as input, and will output
# intermediate representations for all layers in the previous model after
# the first.
successive_outputs = [layer.output for layer in model.layers[1:]]
#visualization_model = Model(img_input, successive_outputs)
visualization_model = tf.keras.models.Model(inputs = model.input, outputs = successive_outputs)
# Let's prepare a random input image from the training set.
horse_img_files = [os.path.join(train_horse_dir, f) for f in train_horse_names]
human_img_files = [os.path.join(train_human_dir, f) for f in train_human_names]
img_path = random.choice(horse_img_files + human_img_files)
 
img = load_img(img_path, target_size=(300, 300))  # this is a PIL image
x = img_to_array(img)  # Numpy array with shape (150, 150, 3)
x = x.reshape((1,) + x.shape)  # Numpy array with shape (1, 150, 150, 3)
 
# Rescale by 1/255
x /= 255
 
# Let's run our image through our network, thus obtaining all
# intermediate representations for this image.
successive_feature_maps = visualization_model.predict(x)
 
# These are the names of the layers, so can have them as part of our plot
layer_names = [layer.name for layer in model.layers]
 
# Now let's display our representations
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
  if len(feature_map.shape) == 4:
    # Just do this for the conv / maxpool layers, not the fully-connected layers
    n_features = feature_map.shape[-1]  # number of features in feature map
    # The feature map has shape (1, size, size, n_features)
    size = feature_map.shape[1]
    # We will tile our images in this matrix
    display_grid = np.zeros((size, size * n_features))
    for i in range(n_features):
      # Postprocess the feature to make it visually palatable
      x = feature_map[0, :, :, i]
      x -= x.mean()
      if x.std()>0:
        x /= x.std()
      x *= 64
      x += 128
      x = np.clip(x, 0, 255).astype('uint8')
      # We'll tile each filter into this big horizontal grid
      display_grid[:, i * size : (i + 1) * size] = x
    # Display the grid
    scale = 20. / n_features
    plt.figure(figsize=(scale * n_features, scale))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')

Ecco alcuni risultati di esempio:

e078d1bc9662c93f.png

Come puoi vedere, si passa dai pixel non elaborati delle immagini a rappresentazioni sempre più astratte e compatte. Le rappresentazioni downstream iniziano a mettere in evidenza ciò a cui la rete presta attenzione e mostrano un numero sempre meno basso di funzionalità "attivate." La maggior parte è impostata su zero. Questa è chiamata parsità. La sparsità della rappresentazione è una caratteristica fondamentale del deep learning.

Queste rappresentazioni forniscono sempre più informazioni sui pixel originali dell'immagine, ma informazioni sempre più raffinate sulla classe dell'immagine. Puoi considerare una CNN (o una rete profonda in generale) come una pipeline di distillazione delle informazioni.

11. Complimenti

Hai imparato a usare le CNN per migliorare immagini complesse. Per scoprire come migliorare ulteriormente i tuoi modelli di visione artificiale, consulta Utilizzare reti neurali convoluzionali (CNN) con grandi set di dati per evitare l'overfitting.