CNTK - Red neuronal convolucional
En este capítulo, estudiemos cómo construir una red neuronal convolucional (CNN) en CNTK.
Introducción
Las redes neuronales convolucionales (CNN) también están formadas por neuronas, que tienen pesos y sesgos que se pueden aprender. Por eso, de esta manera, son como redes neuronales ordinarias (NN).
Si recordamos el funcionamiento de los NN ordinarios, cada neurona recibe una o más entradas, toma una suma ponderada y pasa por una función de activación para producir la salida final. Aquí, surge la pregunta de que si las CNN y las NN ordinarias tienen tantas similitudes, ¿qué hace que estas dos redes sean diferentes entre sí?
¿Qué los hace diferentes es el tratamiento de los datos de entrada y los tipos de capas? La estructura de los datos de entrada se ignora en NN ordinario y todos los datos se convierten en una matriz 1-D antes de introducirlos en la red.
Pero, la arquitectura de red neuronal convolucional puede considerar la estructura 2D de las imágenes, procesarlas y permitirle extraer las propiedades que son específicas de las imágenes. Además, las CNN tienen la ventaja de tener una o más capas convolucionales y capas de agrupación, que son los componentes principales de las CNN.
Estas capas van seguidas de una o más capas completamente conectadas como en los NN multicapa estándar. Entonces, podemos pensar en CNN como un caso especial de redes completamente conectadas.
Arquitectura de red neuronal convolucional (CNN)
La arquitectura de CNN es básicamente una lista de capas que transforma el volumen tridimensional, es decir, el ancho, alto y profundidad de la imagen en un volumen de salida tridimensional. Un punto importante a tener en cuenta aquí es que, cada neurona en la capa actual está conectada a un pequeño parche de la salida de la capa anterior, que es como superponer un filtro N * N en la imagen de entrada.
Utiliza filtros M, que son básicamente extractores de características que extraen características como bordes, esquinas, etc. Las siguientes son las capas [INPUT-CONV-RELU-POOL-FC] que se utilizan para construir redes neuronales convolucionales (CNN) -
INPUT- Como su nombre lo indica, esta capa contiene los valores de píxeles sin procesar. Los valores de píxeles sin procesar significan los datos de la imagen tal como están. Por ejemplo, INPUT [64 × 64 × 3] es una imagen RGB de 3 canales de ancho 64, alto 64 y profundidad 3.
CONV- Esta capa es uno de los componentes básicos de las CNN, ya que la mayor parte del cálculo se realiza en esta capa. Ejemplo: si usamos 6 filtros en la ENTRADA [64 × 64 × 3] mencionada anteriormente, esto puede resultar en el volumen [64 × 64 × 6].
RELU−También llamada capa de unidad lineal rectificada, que aplica una función de activación a la salida de la capa anterior. De otra manera, RELU agregaría una no linealidad a la red.
POOL- Esta capa, es decir, la capa de agrupación, es otro componente básico de las CNN. La tarea principal de esta capa es el muestreo descendente, lo que significa que opera de forma independiente en cada porción de la entrada y la redimensiona espacialmente.
FC- Se llama capa totalmente conectada o más específicamente capa de salida. Se utiliza para calcular la puntuación de la clase de salida y la salida resultante es el volumen del tamaño 1 * 1 * L donde L es el número correspondiente a la puntuación de la clase.
El siguiente diagrama representa la arquitectura típica de las CNN
Creando la estructura de CNN
Hemos visto la arquitectura y los conceptos básicos de CNN, ahora vamos a construir una red convolucional usando CNTK. Aquí, primero veremos cómo armar la estructura de la CNN y luego veremos cómo entrenar los parámetros de la misma.
Por fin veremos cómo podemos mejorar la red neuronal cambiando su estructura con varias configuraciones de capas diferentes. Vamos a utilizar el conjunto de datos de imágenes MNIST.
Entonces, primero creemos una estructura de CNN. Generalmente, cuando creamos una CNN para reconocer patrones en imágenes, hacemos lo siguiente:
Usamos una combinación de capas de convolución y agrupación.
Una o más capas ocultas al final de la red.
Finalmente, terminamos la red con una capa softmax para fines de clasificación.
Con la ayuda de los siguientes pasos, podemos construir la estructura de la red.
Step 1- Primero, necesitamos importar las capas requeridas para CNN.
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
Step 2- A continuación, necesitamos importar las funciones de activación para CNN.
from cntk.ops import log_softmax, relu
Step 3- Después de eso, para inicializar las capas convolucionales más tarde, necesitamos importar el glorot_uniform_initializer como sigue
from cntk.initializer import glorot_uniform
Step 4- A continuación, para crear variables de entrada, importe el input_variablefunción. E importardefault_option función, para facilitar un poco la configuración de NN.
from cntk import input_variable, default_options
Step 5- Ahora para almacenar las imágenes de entrada, cree una nueva input_variable. Contendrá tres canales, a saber, rojo, verde y azul. Tendría el tamaño de 28 por 28 píxeles.
features = input_variable((3,28,28))
Step 6−A continuación, necesitamos crear otro input_variable para almacenar las etiquetas para predecir.
labels = input_variable(10)
Step 7- Ahora, necesitamos crear el default_optionpara la NN. Y necesitamos usar elglorot_uniform como función de inicialización.
with default_options(initialization=glorot_uniform, activation=relu):
Step 8- A continuación, para establecer la estructura de la NN, necesitamos crear una nueva Sequential conjunto de capas.
Step 9- Ahora necesitamos agregar un Convolutional2D capa con una filter_shape de 5 y un strides ajuste de 1, dentro de Sequentialconjunto de capas. Además, habilite el relleno, de modo que la imagen se rellene para conservar las dimensiones originales.
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
Step 10- Ahora es el momento de agregar un MaxPooling capa con filter_shape de 2, y un strides ajuste de 2 para comprimir la imagen a la mitad.
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Step 11- Ahora, como hicimos en el paso 9, necesitamos agregar otro Convolutional2D capa con una filter_shape de 5 y un stridesajuste de 1, utilice 16 filtros. Además, habilite el relleno, para que se mantenga el tamaño de la imagen producida por la capa de agrupación anterior.
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
Step 12- Ahora, como hicimos en el paso 10, agregue otro MaxPooling capa con una filter_shape de 3 y un strides ajuste de 3 para reducir la imagen a un tercio.
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Step 13- Por último, agregue una capa Densa con diez neuronas para las 10 clases posibles que la red puede predecir. Para convertir la red en un modelo de clasificación, utilice unlog_siftmax función de activación.
Dense(10, activation=log_softmax)
])
Ejemplo completo para crear una estructura CNN
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
from cntk.ops import log_softmax, relu
from cntk.initializer import glorot_uniform
from cntk import input_variable, default_options
features = input_variable((3,28,28))
labels = input_variable(10)
with default_options(initialization=glorot_uniform, activation=relu):
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Dense(10, activation=log_softmax)
])
z = model(features)
Entrenando CNN con imágenes
Como hemos creado la estructura de la red, es hora de entrenar la red. Pero antes de comenzar el entrenamiento de nuestra red, necesitamos configurar fuentes de minibatch, porque entrenar un NN que trabaja con imágenes requiere más memoria que la mayoría de las computadoras.
Ya hemos creado fuentes de minibatch en secciones anteriores. A continuación se muestra el código Python para configurar dos fuentes de minibatch:
Como tenemos el create_datasource función, ahora podemos crear dos fuentes de datos separadas (entrenamiento y prueba uno) para entrenar el modelo.
train_datasource = create_datasource('mnist_train')
test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
Ahora, ya que hemos preparado las imágenes, podemos empezar a entrenar a nuestra NN. Como hicimos en secciones anteriores, podemos usar el método de entrenamiento en la función de pérdida para iniciar el entrenamiento. A continuación se muestra el código para esto:
from cntk import Function
from cntk.losses import cross_entropy_with_softmax
from cntk.metrics import classification_error
from cntk.learners import sgd
@Function
def criterion_factory(output, targets):
loss = cross_entropy_with_softmax(output, targets)
metric = classification_error(output, targets)
return loss, metric
loss = criterion_factory(z, labels)
learner = sgd(z.parameters, lr=0.2)
Con la ayuda del código anterior, hemos configurado la pérdida y el aprendizaje para la NN. El siguiente código entrenará y validará el NN−
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
features: train_datasource.streams.features,
labels: train_datasource.streams.labels
}
loss.train(train_datasource,
max_epochs=10,
minibatch_size=64,
epoch_size=60000,
parameter_learners=[learner],
model_inputs_to_streams=input_map,
callbacks=[progress_writer, test_config])
Ejemplo de implementación completo
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
from cntk.ops import log_softmax, relu
from cntk.initializer import glorot_uniform
from cntk import input_variable, default_options
features = input_variable((3,28,28))
labels = input_variable(10)
with default_options(initialization=glorot_uniform, activation=relu):
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Dense(10, activation=log_softmax)
])
z = model(features)
import os
from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
import cntk.io.transforms as xforms
def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
mapping_file = os.path.join(folder, 'mapping.bin')
image_transforms = []
if train:
image_transforms += [
xforms.crop(crop_type='randomside', side_ratio=0.8),
xforms.scale(width=28, height=28, channels=3, interpolations='linear')
]
stream_definitions = StreamDefs(
features=StreamDef(field='image', transforms=image_transforms),
labels=StreamDef(field='label', shape=10)
)
deserializer = ImageDeserializer(mapping_file, stream_definitions)
return MinibatchSource(deserializer, max_sweeps=max_sweeps)
train_datasource = create_datasource('mnist_train')
test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
from cntk import Function
from cntk.losses import cross_entropy_with_softmax
from cntk.metrics import classification_error
from cntk.learners import sgd
@Function
def criterion_factory(output, targets):
loss = cross_entropy_with_softmax(output, targets)
metric = classification_error(output, targets)
return loss, metric
loss = criterion_factory(z, labels)
learner = sgd(z.parameters, lr=0.2)
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
features: train_datasource.streams.features,
labels: train_datasource.streams.labels
}
loss.train(train_datasource,
max_epochs=10,
minibatch_size=64,
epoch_size=60000,
parameter_learners=[learner],
model_inputs_to_streams=input_map,
callbacks=[progress_writer, test_config])
Salida
-------------------------------------------------------------------
average since average since examples
loss last metric last
------------------------------------------------------
Learning rate per minibatch: 0.2
142 142 0.922 0.922 64
1.35e+06 1.51e+07 0.896 0.883 192
[………]
Transformaciones de imagen
Como hemos visto, es difícil entrenar a las NN utilizadas para el reconocimiento de imágenes y también requieren muchos datos para entrenar. Un problema más es que tienden a sobreajustarse en las imágenes utilizadas durante el entrenamiento. Veamos con un ejemplo, cuando tenemos fotos de rostros en posición vertical, nuestro modelo tendrá dificultades para reconocer rostros que estén girados en otra dirección.
Para superar este problema, podemos utilizar el aumento de imágenes y CNTK admite transformaciones específicas al crear fuentes de minibatch para imágenes. Podemos usar varias transformaciones de la siguiente manera:
Podemos recortar aleatoriamente las imágenes utilizadas para el entrenamiento con solo unas pocas líneas de código.
También podemos usar una escala y un color.
Veamos, con la ayuda del siguiente código de Python, cómo podemos cambiar la lista de transformaciones al incluir una transformación de recorte dentro de la función utilizada para crear la fuente del minibatch anteriormente.
import os
from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
import cntk.io.transforms as xforms
def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
mapping_file = os.path.join(folder, 'mapping.bin')
image_transforms = []
if train:
image_transforms += [
xforms.crop(crop_type='randomside', side_ratio=0.8),
xforms.scale(width=28, height=28, channels=3, interpolations='linear')
]
stream_definitions = StreamDefs(
features=StreamDef(field='image', transforms=image_transforms),
labels=StreamDef(field='label', shape=10)
)
deserializer = ImageDeserializer(mapping_file, stream_definitions)
return MinibatchSource(deserializer, max_sweeps=max_sweeps)
Con la ayuda del código anterior, podemos mejorar la función para incluir un conjunto de transformaciones de imagen, de modo que, cuando estemos entrenando, podamos recortar aleatoriamente la imagen, de modo que obtengamos más variaciones de la imagen.