Usa redes neuronales convolucionales (CNN) con imágenes complejas

1. Antes de comenzar

En este codelab, usarás convoluciones para clasificar imágenes de caballos y humanos. En este lab, usará TensorFlow para crear una CNN que esté entrenada a fin de reconocer imágenes de caballos y humanos, y clasificarlos.

Requisitos previos

Si nunca antes creaste convoluciones con TensorFlow, te recomendamos que completes el codelab Build convolutions and perform pooling (Presentación de convoluciones y agrupaciones), en el que presentamos las convoluciones y agrupaciones, y Crea redes neuronales convolucionales (CNN) para mejorar la visión artificial, en las que analizamos cómo lograr que las computadoras sean más eficientes a la hora de reconocer imágenes.

Qué aprenderás

  • Cómo entrenar computadoras para que reconozcan características en una imagen cuyo objeto no está claro

Qué compilarás

  • Red neuronal convolucional que puede distinguir entre imágenes de caballos y de personas

Requisitos

Puedes encontrar el código para el resto del codelab que se ejecuta en Colab.

También deberás instalar TensorFlow y las bibliotecas que instalaste en el codelab anterior.

2. Cómo comenzar: adquirir los datos

Para ello, compilarás un clasificador de caballos o seres humanos que te dirá si una imagen determinada contiene un caballo o un ser humano, en el que la red está entrenada para reconocer características que determinan cuál es cuál. Deberás realizar el procesamiento de los datos para poder entrenarlos.

Primero, descargue los datos:

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

El siguiente código de Python utilizará la biblioteca de SO a fin de utilizar las bibliotecas del sistema operativo, lo que te brindará acceso al sistema de archivos y a la biblioteca de archivos ZIP, lo que te permitirá descomprimir los datos.

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

El contenido del archivo ZIP se extrae en el directorio base /tmp/horse-or-human, que contiene caballos y subdirectorios humanos.

En resumen, el conjunto de entrenamiento son los datos que se utilizan para indicarle al modelo de red neuronal que esto es lo que luce un caballo... y así se ve un ser humano.

3. Use ImageGenerator para etiquetar y preparar los datos

Las imágenes no se deben etiquetar explícitamente como caballos ni humanos.

Más adelante, verás que se usa un elemento ImageDataGenerator. Lee imágenes de los subdirectorios y las etiqueta automáticamente desde el nombre de ese subdirectorio. Por ejemplo, tienes un directorio de entrenamiento que contiene un directorio de caballos y un directorio de personas. ImageDataGenerator etiquetará las imágenes de forma adecuada y reducirá el paso de codificación.

Define cada uno de esos directorios.

# 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')

Ahora, observe cómo se ven los nombres de archivo en los directorios de entrenamiento de caballos y humanos:

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

Busca la cantidad total de imágenes humanas y de caballos en los directorios:

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

4. Explore los datos

Mira algunas fotos para tener una mejor idea de cómo se ven.

Primero, configura los parámetros 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

Ahora, muestra un lote de ocho imágenes de caballos y ocho imágenes humanas. Puedes volver a ejecutar la celda para ver un lote nuevo cada vez.

# 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()
 

Estas son algunas imágenes de ejemplo que muestran caballos y humanos en diferentes poses y orientaciones:

6b6ebbc6e694ccd2.png

5. Define el modelo

Comienza a definir el modelo.

Para comenzar, importa TensorFlow:

import tensorflow as tf

Luego, agrega capas convolucionales y aplana el resultado final para incorporarlo a las capas densamente conectadas. Por último, agrega las capas más densamente conectadas.

Ten en cuenta que dado que tienes un problema de clasificación de dos clases (un problema de clasificación binaria), finalizarás tu red con una activación sigmoidea de modo que el resultado de tu red sea un único escalar entre 0 y 1, lo que codifica la probabilidad de que la imagen actual sea de clase 1 (a diferencia de la clase 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 llamada de método model.summary() muestra un resumen de la red.

model.summary()

Puede ver los resultados aquí:

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

En la columna de forma de salida, se muestra cómo evoluciona el tamaño de tu mapa de atributos en cada capa sucesiva. Las capas de convolución reducen el tamaño de los mapas de atributos un poco debido al padding y cada capa de reducción se divide a la mitad.

6. Compila el modelo

A continuación, configura las especificaciones para el entrenamiento de modelos. Entrena tu modelo con la pérdida de binary_crossentropy porque es un problema de clasificación binaria y tu activación final es una sigmoidea. (Para obtener un repaso sobre las métricas de pérdida, consulta Descender en AA). Usa el optimizador rmsprop con una tasa de aprendizaje de 0.001. Durante el entrenamiento, supervisa la exactitud de la clasificación.

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

7. Entrenar el modelo con generadores

Configura generadores de datos que lean imágenes de tus carpetas fuente, conviértelos en tensores float32 y transmítelos (con sus etiquetas) a tu red.

Tendrás un generador para las imágenes de entrenamiento y otro para las imágenes de validación. Tus generadores generarán lotes de imágenes de 300 x 300 y sus etiquetas (binarios).

Como ya debes saber, los datos que se incorporan a las redes neuronales, por lo general, deben normalizarse de alguna manera para que la red pueda procesarlas mejor. (No es común introducir píxeles sin procesar en una CNN). En tu caso, procesarás previamente tus imágenes normalizando los valores de píxeles para que estén en el rango [0, 1] (originalmente, todos los valores están en el rango [0, 255]).

En Keras, esto se puede hacer a través de la clase keras.preprocessing.image.ImageDataGenerator con el parámetro rescale. Esa clase ImageDataGenerator te permite crear instancias de generadores de lotes de imágenes aumentadas (y sus etiquetas) a través de .flow(data, labels) o .flow_from_directory(directory). Esos generadores se pueden usar con los métodos de modelo de Keras que aceptan generadores de datos como entradas: fit_generator, evaluate_generator y 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. Realice la capacitación

Entrena por 15 ciclos. (que puede tardar unos minutos en ejecutarse).

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

Toma nota de los valores por ciclo de entrenamiento.

La Pérdida y la Precisión son un excelente indicador del progreso del entrenamiento. Para hacer una conjetura en cuanto a la clasificación de los datos de entrenamiento y, luego, medirlos con la etiqueta conocida, calcular el resultado. La exactitud es la parte de adivinaciones correctas.

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. Prueba el modelo

Ahora, en realidad, ejecuta una predicción con el modelo. El código le permitirá elegir uno o más archivos de su sistema. Luego, los subirá y los ejecutará en el modelo, lo que indicará si el objeto es un caballo o un ser humano.

Puedes descargar imágenes de Internet en tu sistema de archivos para probarlas. Ten en cuenta que podrías ver que la red comete muchos errores a pesar de que la precisión del entrenamiento es superior al 99%.

Esto se debe a que se denomina sobreajuste, lo que significa que la red neuronal se entrena con datos muy limitados (solo hay aproximadamente 500 imágenes de cada clase). Así que es muy bueno para reconocer imágenes que se ven como las del conjunto de entrenamiento, pero puede fallar mucho en las imágenes que no están en el conjunto de entrenamiento.

Este es un dato que demuestra que cuantos más datos entrenes, mejor será la red final.

Existen muchas técnicas que se pueden usar para mejorar tu entrenamiento, a pesar de tener datos limitados, incluido el aumento de imágenes, pero está fuera del alcance de este 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")

Por ejemplo, supongamos que quieres probar con esta imagen:

9e07a57ff3be7a82.jpeg

Esto es lo que produce el Colab:

77b678e70b00862a.png

A pesar de ser un gráfico de dibujos animados, igualmente clasifica correctamente.

La siguiente imagen también clasifica correctamente:

c9213173d9f3d83c.jpeg

f2844da737a1a2f2.png

¡Prueba tus propias imágenes y explora!

10. Visualiza representaciones intermedias

Para familiarizarte con el tipo de funciones que aprendió CNN, diviértete viendo cómo se transforman las entradas mientras pasan por la CNN.

Elige una imagen aleatoria del conjunto de entrenamiento y, luego, genera una figura en la que cada fila sea el resultado de una capa y cada imagen en la fila sea un filtro específico en ese mapa de atributos de salida. Vuelve a ejecutar esa celda a fin de generar representaciones intermedias para una variedad de imágenes de entrenamiento.

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

Estos son ejemplos de resultados:

e078d1bc9662c93f.png

Como puedes ver, se pasa de los píxeles sin procesar de las imágenes a representaciones cada vez más abstractas y compactas. Las representaciones descendentes comienzan a destacar a qué está pendiente la red y muestran que cada vez menos funciones se están activando. La mayoría están en cero. Esto se denomina dispersión. La dispersión de la representación es una característica clave del aprendizaje profundo.

Esas representaciones contienen cada vez menos información sobre los píxeles originales de la imagen, pero información cada vez más refinada sobre la clase de la imagen. Puedes pensar en una CNN (o una red profunda en general) como una canalización de destilación de información.

11. Felicitaciones

Aprendiste a usar CNN para mejorar las imágenes complejas. Para obtener más información sobre cómo mejorar aún más los modelos de visión artificial, consulta Usa redes neuronales convolucionales (CNN) con grandes conjuntos de datos para evitar el sobreajuste.