¡El Día de la Comunidad de ML es el 9 de noviembre! Únase a nosotros para recibir actualizaciones de TensorFlow, JAX, y más Más información

Clasificación de imágenes

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Este tutorial muestra cómo clasificar imágenes de flores. Se crea una imagen usando un clasificador tf.keras.Sequential modelo y carga datos utilizando tf.keras.utils.image_dataset_from_directory . Obtendrá experiencia práctica con los siguientes conceptos:

  • Carga eficiente de un conjunto de datos fuera del disco.
  • Identificar el sobreajuste y aplicar técnicas para mitigarlo, incluido el aumento y el abandono de datos.

Este tutorial sigue un flujo de trabajo básico de aprendizaje automático:

  1. Examinar y comprender los datos
  2. Construye una canalización de entrada
  3. Construye el modelo
  4. Entrena el modelo
  5. Prueba el modelo
  6. Mejora el modelo y repite el proceso

Importar TensorFlow y otras bibliotecas

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

Descarga y explora el conjunto de datos

Este tutorial utiliza un conjunto de datos de aproximadamente 3700 fotos de flores. El conjunto de datos contiene cinco subdirectorios, uno por clase:

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 [==============================] - 6s 0us/step
228827136/228813984 [==============================] - 6s 0us/step

Después de la descarga, ahora debería tener una copia del conjunto de datos disponible. Hay 3670 imágenes en total:

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

Aquí hay algunas rosas:

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

png

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

png

Y unos tulipanes:

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

png

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

png

Cargar datos usando una utilidad de Keras

Carga de dejar que estas imágenes desde el disco utilizando el útil tf.keras.utils.image_dataset_from_directory utilidad. Esto le llevará a partir de un directorio de imágenes de disco a un tf.data.Dataset en tan sólo un par de líneas de código. Si lo desea, también puede escribir su propio código de carga de datos desde cero visitando la carga y las imágenes de preproceso tutorial.

Crea un conjunto de datos

Defina algunos parámetros para el cargador:

batch_size = 32
img_height = 180
img_width = 180

Es una buena práctica utilizar una división de validación al desarrollar su modelo. Usemos el 80% de las imágenes para entrenamiento y el 20% para validación.

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.

Puede encontrar los nombres de las clases en el class_names atributo en estos conjuntos de datos. Estos corresponden a los nombres de los directorios en orden alfabético.

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

Visualiza los datos

Aquí están las primeras nueve imágenes del conjunto de datos de entrenamiento:

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

Va a entrenar un modelo usando estos conjuntos de datos pasándolos a Model.fit en un momento. Si lo desea, también puede iterar manualmente sobre el conjunto de datos y recuperar lotes de imágenes:

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

El image_batch es un tensor de la forma (32, 180, 180, 3) . Este es un lote de 32 imágenes de forma 180x180x3 (la última dimensión se refiere a los canales de color RGB). El label_batch es un tensor de la forma (32,) , estos son etiquetas que corresponden a las 32 imágenes.

Puede llamar .numpy() en las image_batch y labels_batch tensores para convertirlos a un numpy.ndarray .

Configurar el conjunto de datos para el rendimiento

Asegurémonos de utilizar la captación previa en búfer para que pueda obtener datos del disco sin que la E / S se bloquee. Estos son dos métodos importantes que debe utilizar al cargar datos:

  • Dataset.cache mantiene las imágenes en la memoria después de que se cargan de disco durante la primera época. Esto asegurará que el conjunto de datos no se convierta en un cuello de botella mientras entrena su modelo. Si su conjunto de datos es demasiado grande para caber en la memoria, también puede usar este método para crear una caché en disco de alto rendimiento.
  • Dataset.prefetch se superpone datos de pre-procesamiento y la ejecución del modelo durante el entrenamiento.

Los lectores interesados pueden obtener más información sobre ambos métodos, así como la forma de datos de la caché en el disco en la sección de captura previa de la Mejor rendimiento con la API tf.data guía.

AUTOTUNE = tf.data.AUTOTUNE

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

Estandarizar los datos

Los valores de los canales RGB están en la [0, 255] gama. Esto no es ideal para una red neuronal; en general, debe tratar de hacer que sus valores de entrada sean pequeños.

A continuación, se le estandarizar los valores de estar en el [0, 1] rango utilizando tf.keras.layers.Rescaling :

normalization_layer = layers.Rescaling(1./255)

Hay dos formas de utilizar esta capa. Se puede aplicar al conjunto de datos llamando 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

O puede incluir la capa dentro de la definición de su modelo, lo que puede simplificar la implementación. Usemos el segundo enfoque aquí.

Crea el modelo

El secuencial modelo consiste en tres bloques de convolución ( tf.keras.layers.Conv2D ) con una capa de puesta en común max ( tf.keras.layers.MaxPooling2D ) en cada uno de ellos. Hay una capa completamente conectado ( tf.keras.layers.Dense ) con 128 unidades en la parte superior de la misma que se activa mediante una función de activación relu ( 'relu' ). Este modelo no se ha ajustado para una alta precisión; el objetivo de este tutorial es mostrar un enfoque estándar.

num_classes = 5

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

Para este tutorial, elegir el tf.keras.optimizers.Adam optimizador y tf.keras.losses.SparseCategoricalCrossentropy función de pérdida. A la vista de la formación y la precisión de validación para cada época de entrenamiento, pasar la metrics argumento para Model.compile .

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

Resumen Modelo

Ver todas las capas de la red utilizando el modelo de Model.summary método:

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
_________________________________________________________________

Entrena el modelo

epochs=10
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/10
92/92 [==============================] - 4s 17ms/step - loss: 1.2993 - accuracy: 0.4407 - val_loss: 1.0307 - val_accuracy: 0.5995
Epoch 2/10
92/92 [==============================] - 1s 10ms/step - loss: 0.9722 - accuracy: 0.6161 - val_loss: 0.9224 - val_accuracy: 0.6226
Epoch 3/10
92/92 [==============================] - 1s 10ms/step - loss: 0.7757 - accuracy: 0.7078 - val_loss: 0.8981 - val_accuracy: 0.6431
Epoch 4/10
92/92 [==============================] - 1s 10ms/step - loss: 0.5768 - accuracy: 0.7837 - val_loss: 1.0015 - val_accuracy: 0.6240
Epoch 5/10
92/92 [==============================] - 1s 10ms/step - loss: 0.3632 - accuracy: 0.8723 - val_loss: 1.0516 - val_accuracy: 0.6335
Epoch 6/10
92/92 [==============================] - 1s 10ms/step - loss: 0.2129 - accuracy: 0.9353 - val_loss: 1.2665 - val_accuracy: 0.6553
Epoch 7/10
92/92 [==============================] - 1s 10ms/step - loss: 0.1241 - accuracy: 0.9615 - val_loss: 1.4287 - val_accuracy: 0.6267
Epoch 8/10
92/92 [==============================] - 1s 10ms/step - loss: 0.0991 - accuracy: 0.9680 - val_loss: 1.5253 - val_accuracy: 0.6063
Epoch 9/10
92/92 [==============================] - 1s 10ms/step - loss: 0.0514 - accuracy: 0.9888 - val_loss: 1.8721 - val_accuracy: 0.6540
Epoch 10/10
92/92 [==============================] - 1s 10ms/step - loss: 0.0180 - accuracy: 0.9959 - val_loss: 1.9763 - val_accuracy: 0.6540

Visualice los resultados del entrenamiento

Cree gráficos de pérdida y precisión en los conjuntos de entrenamiento y validación:

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

Los gráficos muestran que la precisión del entrenamiento y la precisión de la validación están fuera de lugar por grandes márgenes, y el modelo ha logrado solo alrededor del 60% de precisión en el conjunto de validación.

Inspeccionemos qué salió mal e intentemos aumentar el rendimiento general del modelo.

Sobreajuste

En los gráficos anteriores, la precisión del entrenamiento aumenta linealmente con el tiempo, mientras que la precisión de la validación se detiene alrededor del 60% en el proceso de entrenamiento. Además, la diferencia de precisión entre la formación y la precisión de validación es notable, una señal de sobreajuste .

Cuando hay una pequeña cantidad de ejemplos de entrenamiento, el modelo a veces aprende de ruidos o detalles no deseados de ejemplos de entrenamiento, hasta el punto que impacta negativamente el desempeño del modelo en nuevos ejemplos. Este fenómeno se conoce como sobreajuste. Significa que el modelo tendrá dificultades para generalizar en un nuevo conjunto de datos.

Hay varias formas de luchar contra el sobreajuste en el proceso de formación. En este tutorial, vamos a usar el aumento de datos y deserción de su modelo.

Aumento de datos

El sobreajuste generalmente ocurre cuando hay una pequeña cantidad de ejemplos de entrenamiento. El aumento de datos adopta el enfoque de generar datos adicionales de capacitación de sus ejemplos existentes mediante el aumento de ellos al azar usando transformaciones que producen imágenes de aspecto creíbles. Esto ayuda a exponer el modelo a más aspectos de los datos y a generalizar mejor.

Va a poner en práctica el aumento de datos utilizando las siguientes capas de pre-procesamiento Keras: tf.keras.layers.RandomFlip , tf.keras.layers.RandomRotation y tf.keras.layers.RandomZoom . Estos pueden incluirse dentro de su modelo como otras capas y ejecutarse en la GPU.

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

Visualicemos cómo se ven algunos ejemplos aumentados aplicando el aumento de datos a la misma imagen varias veces:

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

Utilizará el aumento de datos para entrenar un modelo en un momento.

Abandonar

Otra técnica para reducir el sobreajuste es introducir deserción regularización a la red.

Cuando aplica la omisión a una capa, elimina aleatoriamente (estableciendo la activación en cero) una cantidad de unidades de salida de la capa durante el proceso de entrenamiento. El abandono toma un número fraccionario como valor de entrada, en la forma como 0,1, 0,2, 0,4, etc. Esto significa eliminar el 10%, 20% o 40% de las unidades de salida al azar de la capa aplicada.

Vamos a crear una nueva red neuronal con tf.keras.layers.Dropout antes de entrenar utilizando las imágenes aumentadas:

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

Compilar y entrenar el modelo

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 13ms/step - loss: 1.2856 - accuracy: 0.4414 - val_loss: 1.0649 - val_accuracy: 0.5804
Epoch 2/15
92/92 [==============================] - 1s 12ms/step - loss: 1.0023 - accuracy: 0.6022 - val_loss: 0.9583 - val_accuracy: 0.6308
Epoch 3/15
92/92 [==============================] - 1s 12ms/step - loss: 0.8958 - accuracy: 0.6536 - val_loss: 0.9973 - val_accuracy: 0.6117
Epoch 4/15
92/92 [==============================] - 1s 12ms/step - loss: 0.8465 - accuracy: 0.6771 - val_loss: 0.8394 - val_accuracy: 0.6948
Epoch 5/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7807 - accuracy: 0.7139 - val_loss: 0.8387 - val_accuracy: 0.6635
Epoch 6/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7545 - accuracy: 0.7183 - val_loss: 0.7879 - val_accuracy: 0.7016
Epoch 7/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7294 - accuracy: 0.7166 - val_loss: 0.8693 - val_accuracy: 0.6839
Epoch 8/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6805 - accuracy: 0.7401 - val_loss: 0.7669 - val_accuracy: 0.7125
Epoch 9/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6433 - accuracy: 0.7599 - val_loss: 0.8579 - val_accuracy: 0.6839
Epoch 10/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6081 - accuracy: 0.7681 - val_loss: 0.8400 - val_accuracy: 0.7125
Epoch 11/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5916 - accuracy: 0.7718 - val_loss: 0.7557 - val_accuracy: 0.7003
Epoch 12/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5757 - accuracy: 0.7851 - val_loss: 0.7170 - val_accuracy: 0.7193
Epoch 13/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5395 - accuracy: 0.7919 - val_loss: 0.7218 - val_accuracy: 0.7343
Epoch 14/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5170 - accuracy: 0.8048 - val_loss: 0.7128 - val_accuracy: 0.7330
Epoch 15/15
92/92 [==============================] - 1s 12ms/step - loss: 0.4897 - accuracy: 0.8178 - val_loss: 0.7013 - val_accuracy: 0.7302

Visualice los resultados del entrenamiento

Después de aplicar el aumento de datos y tf.keras.layers.Dropout , hay menos overfitting que antes, y la formación y la precisión de validación están más cerca de alineado:

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

Predecir sobre nuevos datos

Finalmente, usemos nuestro modelo para clasificar una imagen que no se incluyó en los conjuntos de entrenamiento o validación.

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 99.98 percent confidence.