Clasificación de imágenes con PyTorch
Los libros de texto de aprendizaje profundo están llenos de terminología profesional e incomprensible. Intento mantenerlo al mínimo y siempre proporciono un ejemplo que se puede ampliar fácilmente a medida que se acostumbre a trabajar con PyTorch. Usamos este ejemplo en todo el libro para demostrar cómo depurar un modelo (Capítulo 7) o implementarlo en producción (Capítulo 8).
Desde ahora hasta el final del Capítulo 4, compilaremos el clasificador de imágenes. Las redes neuronales se utilizan comúnmente como clasificadores de imágenes; Las redes ofrecen una imagen y hacen una pregunta sencilla: "¿Qué es esto?"
Comencemos creando nuestra aplicación en PyTorch.
Problema de clasificación
Aquí crearemos un clasificador simple que puede distinguir un pez de un gato. Repetiremos el proceso de diseño y desarrollo de nuestro modelo para hacerlo más preciso.
En la Fig. 2.1 y 2.2 representan un pez y un gato en todo su esplendor. No estoy seguro de si el pez tiene un nombre, pero el nombre del gato es Helvetica.
Comencemos discutiendo algunos de los problemas de clasificación estándar.
Dificultades estándar
¿Cómo escribir un programa que pueda distinguir un pez de un gato? Quizás escribirías un conjunto de reglas que describan si un gato tiene cola o que un pez tiene escamas, y aplicarías esas reglas a la imagen para que el programa pueda clasificar la imagen. Pero esto requerirá tiempo, esfuerzo y habilidad. ¿Qué pasa si te encuentras con un gato Manx? Aunque claramente es un gato, no tiene cola.
Estas reglas se vuelven cada vez más complejas cuando intenta describir todos los escenarios posibles usándolas. Además, debo admitir que la programación visual es terrible para mí, por lo que la idea de tener que escribir manualmente el código para todas estas reglas es aterradora.
Necesita una función que devuelva un gato o un pez cuando ingresa una imagen. Es difícil construir tal función simplemente enumerando todos los criterios en su totalidad. Pero el aprendizaje profundo esencialmente obliga a la computadora a hacer el trabajo duro de crear todas estas reglas de las que acabamos de hablar, siempre que creemos la estructura, proporcionemos a la red una gran cantidad de datos y le hagamos saber si dio la respuesta correcta. Eso es lo que vamos a hacer. Además, aprenderá algunas técnicas básicas para usar PyTorch.
Pero primero los datos
Primero, necesitamos datos. Cuantos datos? Depende de varios factores. Como verá en el Capítulo 4, la idea de que cualquier técnica de aprendizaje profundo requiere grandes cantidades de datos para entrenar una red neuronal no es necesariamente cierta. Sin embargo, ahora vamos a empezar desde cero, lo que normalmente requiere acceso a una gran cantidad de datos. Se requieren muchas imágenes de peces y gatos.
Se podría dedicar algún tiempo a descargar un montón de imágenes de una búsqueda de imágenes en Google, pero hay una manera más fácil: la colección estándar de imágenes que se usa para entrenar redes neuronales es ImageNet. Contiene más de 14 millones de imágenes y 20 mil categorías de imágenes. Este es el estándar por el cual se comparan todos los clasificadores de imágenes. Por tanto, tomo imágenes desde allí, aunque puedes elegir otras opciones si quieres.
Además de los datos, PyTorch debería tener una forma de definir qué es un gato y qué es un pez. Es bastante fácil para nosotros, pero es más difícil para una computadora (¡por eso creamos un programa!). Usamos etiquetas adjuntas a los datos y esto se llama aprendizaje supervisado. (Si no tiene acceso a ninguna de las etiquetas, entonces lo adivinó, se usa el aprendizaje automático no supervisado).
Si usamos datos de ImageNet, sus etiquetas no serán útiles porque contienen demasiada información. Marcar un gato atigrado o una trucha para una computadora no es lo mismo que un gato o un pez.
Es necesario volver a etiquetarlos. Dado que ImageNet es una gran colección de imágenes, he compilado la imagen y las URL de etiquetado de peces y gatos (https://oreil.ly/NbtEU).
Puede ejecutar el script download.py en este directorio y descargará las imágenes de las URL y las colocará en las ubicaciones de entrenamiento apropiadas. Volver a etiquetar es simple; el script almacena imágenes de gatos en el directorio train / cat e imágenes de peces en el directorio train / fish. Si no desea utilizar un script para descargar, simplemente cree estos directorios y coloque las imágenes correspondientes en los lugares correctos. Ahora tenemos datos, pero necesitamos convertirlos a un formato que PyTorch pueda entender.
PyTorch y cargadores de datos
La carga y transformación de datos en formatos listos para entrenamiento es a menudo un área de la ciencia de datos que lleva demasiado tiempo. PyTorch ha desarrollado requisitos de interacción de datos establecidos que lo hacen bastante sencillo ya sea que esté trabajando con imágenes, texto o audio.
Las dos condiciones principales para trabajar con datos son los conjuntos de datos y los cargadores de datos. Un conjunto de datos es una clase de Python que nos permite obtener los datos que enviamos a la red neuronal.
Un cargador de datos es lo que transfiere datos de un conjunto de datos a la red. (Esto puede incluir información como: ¿Cuántos procesos de trabajo están cargando datos en la red? ¿Cuántas imágenes estamos cargando al mismo tiempo?)
Primero, echemos un vistazo al conjunto de datos. Cada conjunto de datos, ya sea que contenga imágenes, audio, texto, paisajes 3D, información del mercado de valores o cualquier otra cosa, puede interactuar con PyTorch siempre que cumpla con los requisitos de esta clase abstracta de Python:
class Dataset(object):
def __getitem__(self, index):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
Es bastante simple: tenemos que usar un método que devuelva el tamaño de nuestro conjunto de datos (len) y uno que pueda extraer un elemento del conjunto de datos en un par (etiqueta, tensor). Esto lo llama el cargador de datos, ya que alimenta los datos a la red neuronal para su entrenamiento. Así que tenemos que escribir un cuerpo para el método getitem que pueda tomar una imagen, convertirla en un tensor y volver a colocarla y marcarla para que PyTorch pueda trabajar con ella. Todo está claro, pero obviamente este escenario es bastante común, por lo que tal vez PyTorch facilite la tarea.
Crear un conjunto de datos de entrenamiento
El paquete torchvision incluye una clase ImageFolder que hace prácticamente todo, asumiendo que nuestras imágenes están en una estructura donde cada directorio es una etiqueta (por ejemplo, todos los gatos están en un directorio llamado cat). Esto es lo que necesita para el ejemplo del gato y el pez:
import torchvision
from torchvision import transforms
train_data_path = "./train/"
transforms = transforms.Compose([
transforms.Resize(64),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225] )
])
train_data = torchvision.datasets.ImageFolder
(root=train_data_path,transform=transforms)
Aquí se agrega algo más porque Torchvision también le permite especificar una lista de transformaciones que se aplicarán a la imagen antes de que ingrese a la red neuronal. La transformación predeterminada es tomar los datos de la imagen y convertirlos en un tensor (el método transforms.ToTensor () que se muestra en el código anterior), pero también hace algunas otras cosas que pueden no ser tan obvias.
Primero, las GPU están diseñadas para realizar cálculos rápidos de tamaño estándar. Pero probablemente tengamos una variedad de imágenes en muchas resoluciones. Para mejorar el rendimiento del procesamiento, escalamos cada imagen de entrada a la misma resolución de 64x64 utilizando la transformación Resize (64). Luego convertimos las imágenes a un tensor y finalmente normalizamos el tensor alrededor de un conjunto específico de puntos de desviación estándar y media.
La normalización es importante porque se espera que se realice una gran cantidad de multiplicaciones a medida que la entrada pasa a través de las capas de la red neuronal; mantener los valores de entrada entre 0 y 1 evita grandes aumentos de valores durante la fase de aprendizaje (conocido como problema de gradiente explosivo). Esta encarnación mágica es solo la desviación media y estándar del conjunto de datos de ImageNet en su conjunto. Puede calcularlo específicamente para un subconjunto de peces y gatos, pero estos valores son bastante fiables. (Si estuviera trabajando en un conjunto de datos completamente diferente, esta media y varianza tendrían que calcularse, aunque muchos simplemente usan las constantes de ImageNet e informan resultados aceptables).
Las transformaciones componibles también facilitan la realización de acciones como la rotación de imágenes y el desplazamiento de imágenes para el aumento de datos, a lo que volveremos en el Capítulo 4.
En este ejemplo, estamos cambiando el tamaño de las imágenes a 64 x 64. Hice esta elección aleatoria para acelerar el cálculo en nuestra primera red. La mayoría de las arquitecturas existentes, que verá en el Capítulo 3, usan 224x224 o 299x299 para sus imágenes de entrada. Generalmente, cuanto mayor es el tamaño del archivo de entrada, más datos puede aprender la red. La otra cara de la moneda es que normalmente puede colocar un lote más pequeño de imágenes en la memoria de la GPU.
Hay mucha más información sobre conjuntos de datos, y eso no es todo. Pero, ¿por qué necesitamos saber más de lo necesario si ya conocemos el conjunto de datos de entrenamiento?
Conjuntos de datos de validación y referencia
Nuestro conjunto de datos de entrenamiento está configurado, pero ahora necesitamos repetir los mismos pasos con el conjunto de datos de validación. Cual es la diferencia aqui? Una de las trampas del aprendizaje profundo (y de hecho todo el aprendizaje automático) es el sobreajuste: el modelo es realmente bueno para reconocer en qué se entrenó, pero no funciona con ejemplos que no ha visto. El modelo ve una foto de un gato, y si todas las demás fotos de gatos no son muy similares a esta, la modelo decide que no es un gato, aunque es obvio lo contrario. Para evitar que la red neuronal se comporte así, cargamos la muestra de control en download.py, es decir, en una serie de imágenes de gatos y peces que no están en el conjunto de datos de entrenamiento. Al final de cada ciclo de entrenamiento (también conocido como época), comparamos este conjunto para asegurarnos de que la red no sea incorrecta. No se alarme, el código para esta verificación es increíblemente simple:este es el mismo código con varios nombres de variable cambiados:
val_data_path = "./val/"
val_data = torchvision.datasets.ImageFolder(root=val_data_path,
transform=transforms)
Solo usamos la cadena de transformaciones en lugar de definirla nuevamente.
Además del conjunto de datos de validación, también necesitamos crear un conjunto de datos de validación. Se utiliza para probar el modelo después de completar todo el entrenamiento:
test_data_path = "./test/"
test_data = torchvision.datasets.ImageFolder(root=test_data_path,
transform=transforms)
A primera vista, los diferentes tipos de conjuntos pueden ser complejos y confusos, así que armé una tabla para indicar qué parte del entrenamiento usa cada conjunto (Tabla 2.1).
Ahora podemos crear cargadores de datos con algunas líneas más de código Python:
batch_size=64
train_data_loader = data.DataLoader(train_data, batch_size=batch_size)
val_data_loader = data.DataLoader(val_data, batch_size=batch_size)
test_data_loader = data.DataLoader(test_data, batch_size=batch_size)
Nuevo y digno de mención en este código es el comando batch_size. Ella dice cuántas imágenes pasarán por la red antes de entrenarla y actualizarla. En teoría, podríamos asignar batch_size a una serie de imágenes en los conjuntos de datos de prueba y entrenamiento para que la red vea cada imagen antes de actualizar. En la práctica, esto generalmente no se hace porque los paquetes más pequeños (más comúnmente conocidos en la literatura como mini-paquetes) requieren menos memoria y no es necesario almacenar toda la información sobre cada imagen en el conjunto de datos, y un tamaño de paquete más pequeño conduce a un aprendizaje más rápido desde la red. se actualiza mucho más rápido. En el caso de los cargadores de datos de PyTorch, batch_size se establece de forma predeterminada en 1. Es probable que desee cambiarlo. Aunque elegí 64, puedes experimentar para entendercuántos mini paquetes se pueden usar sin quedarse sin memoria de la GPU. Experimente con algunos parámetros adicionales: por ejemplo, puede especificar cómo se obtienen los conjuntos de datos, si el conjunto de datos completo se baraja cada vez que se ejecuta y cuántos flujos de trabajo están involucrados para recuperar datos del conjunto de datos. Todo esto se puede encontrar enDocumentación de PyTorch .
Se trata de pasar datos a PyTorch, así que imaginemos ahora una red neuronal simple que comenzará a clasificar nuestras imágenes.
¡Finalmente, una red neuronal!
Comenzaremos con la red de aprendizaje profundo más simple: una capa de entrada que funcionará con los tensores de entrada (nuestras imágenes); una capa de salida del tamaño del número de nuestras clases de salida (2); y una capa oculta en el medio. En el primer ejemplo, usaremos capas completamente vinculadas. En la Fig. 2.3 muestra una capa de entrada de tres nodos, una
capa oculta de tres nodos y una salida de dos nodos.
En este ejemplo, cada nodo en una capa afecta a un nodo en la siguiente capa, y cada conexión tiene un peso que determina la fuerza de la señal desde ese nodo a la siguiente capa. (Estos son los pesos que se actualizarán cuando entrenemos la red, generalmente a partir de una inicialización aleatoria). Cuando la entrada pasa a través de la red, nosotros (o PyTorch) podemos simplemente multiplicar matricialmente los pesos y sesgos de esa capa por la entrada. Antes de pasarlos a la siguiente función, este resultado ingresa a la función de activación, que es simplemente una forma de introducir la no linealidad en nuestro sistema.
Funciones de activación
La función de activación suena complicada, pero la función de activación más común que puede encontrar ahora es ReLU, o unidad lineal rectificada. ¡De nuevo inteligente! Pero esta es solo una función que implementa max (0, x), por lo que el resultado es 0 si la entrada es negativa, o solo la entrada (x) si x es positiva. ¡Es así de simple!
Otra función de activación con la que es más probable que te encuentres es la función logística multivariante (softmax), que es un poco más complicada en un sentido matemático. Básicamente, genera un conjunto de valores de 0 a 1, que suma 1 (¡probabilidades!), Y pondera los valores de tal manera que aumenta la diferencia, es decir, produce un resultado en un vector que será más grande que todos los demás. A menudo verá que se usa al final de una red de clasificación para asegurarse de que la red hará una predicción definitiva sobre qué clase cree que son los datos de entrada.
Ahora que tenemos todos estos componentes básicos, podemos comenzar a construir nuestra primera red neuronal.
Creación de redes neuronales
Construir una red neuronal en PyTorch es similar a programar en Python. Heredamos de una clase llamada torch.nn.Network y completamos los métodos __init__ y forward:
class SimpleNet(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(12288, 84)
self.fc2 = nn.Linear(84, 50)
self.fc3 = nn.Linear(50,2)
def forward(self):
x = x.view(-1, 12288)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.softmax(self.fc3(x))
return x
simplenet = SimpleNet()
De nuevo, esto no es difícil. Hacemos los ajustes necesarios en init (), en este caso llamamos al constructor de superclase y tres capas completamente conectadas (llamadas Lineal en PyTorch, se llaman Dense en Keras). El método forward () describe cómo se transmiten los datos a través de la red, tanto en el entrenamiento como en la predicción (inferencia). Primero, tenemos que transformar el tensor 3D (xey más información de color de 3 canales - rojo, verde, azul) en la imagen - ¡atención! - en un tensor unidimensional para que se pueda pasar a la primera capa lineal, y lo hacemos usando view (). Por lo tanto, aplicamos las capas y las funciones de activación en orden, devolviendo la salida de softmax para obtener la predicción de esta imagen.
Los números en las capas ocultas son arbitrarios, excepto por la salida de la última capa, que es 2, que coincide con nuestras dos clases: gato o pez. Requiere que los datos de las capas se reduzcan a medida que se reducen en la pila. Si la capa pasa, digamos, de 50 entradas a 100 salidas, entonces la red puede aprender simplemente pasando 50 enlaces a cincuenta de cien salidas y considerar su trabajo completado. Al reducir el tamaño de la salida en relación con la entrada, estamos obligando a esta parte de la red a aprender la representatividad de la entrada original con menos recursos, lo que presumiblemente significa que la red define algunas características distintivas de las imágenes: por ejemplo, aprendió a reconocer una aleta o una cola.
Tenemos un pronóstico y podemos compararlo con el etiquetado real de la imagen original para ver si era correcto. Pero necesita alguna forma que permita a PyTorch cuantificar no solo la exactitud o incorrección de una predicción, sino también cuán correcta o incorrecta es. La función de pérdida hace esto.
SOBRE EL AUTOR
Ian Poynter (Ian Pointer): ingeniero en ciencia de datos, especializado en soluciones para aprendizaje automático (incluidos métodos de enseñanza en profundidad) para varios clientes de Fortune 100. En la actualidad, Yang trabaja en Lucidworks, que se dedica al desarrollo de aplicaciones avanzadas y PNL.
»Más detalles sobre el libro se pueden encontrar en el sitio web de la editorial
» Tabla de contenido
» Extracto
para los habitantes un 25% de descuento en el cupón - PyTorch
Tras el pago de la versión impresa del libro, se envía un libro electrónico al correo electrónico.