Aprendizaje profundo: Red totalmente convolucional (FCN)

Una red totalmente convolucional (FCN) utiliza una red neuronal convolucional para lograr la transformación de píxeles de imagen a categorías de píxeles [36]. Red neuronal convolucional con diferentes introducidos previamente, la convolución completa a través de una red transpuesta convolución ( convolución transpuesta) la altura de la capa y el tamaño de ancho de la imagen de entrada se transforma en el diagrama característico de la capa intermedia, de modo que el resultado de la predicción y la imagen de entrada Correspondencia uno a uno en la dimensión espacial (altura y ancho): Dada una posición en la dimensión espacial, la salida de la dimensión del canal es la predicción de categoría del píxel correspondiente a la posición.

Primero importamos los paquetes o módulos necesarios para el experimento y luego explicamos qué es una capa convolucional transpuesta.

En [1]:% matplotlib en línea 
        importar d2lzh como d2l 
        de mxnet importar gluon, imagen, init, nd 
        de mxnet.gluon importar datos como gdata, pérdida como brillo, model_zoo, nn 
        importar numpy como np 
        import sys

9.10.1 Capa convolucional transpuesta

Como su nombre lo indica, la capa convolucional transpuesta recibe el nombre de la operación de transposición de matriz. De hecho, la operación de convolución también se puede realizar mediante la multiplicación de matrices. En el siguiente ejemplo, definimos una entrada X con una altura y un ancho de 4, y un núcleo de convolución K con una altura y un ancho de 3. Imprima la salida de la operación de convolución bidimensional y el kernel de convolución. Como puede ver, la altura y el ancho de salida son 2 respectivamente.

En [2]: X = nd.arange (1, 17) .reshape ((1, 1, 4, 4)) 
        K = nd.arange (1, 10) .reshape ((1, 1, 3, 3) ) 
        conv = nn.Conv2D (canales = 1, kernel_size = 3) 
        conv.initialize (init.Constant (K)) 
        conv (X), K 

Out [2]: ( 
         [[[[348. 393.] 
            [528. 573.]]]] 
         <NDArray 1x1x2x2 @cpu (0)>, 
         [[[[1. 2. 3.] 
            [4. 5. 6.] 
            [7. 8. 9.]]]] 
         <NDArray 1x1x3x3 @ cpu (0)>)

A continuación, reescribimos el núcleo de convolución K en una matriz dispersa W que contiene una gran cantidad de elementos cero, es decir, la matriz de peso. La forma de la matriz de pesos es (4, 16), en la que los elementos distintos de cero provienen de los elementos del núcleo de convolución K. Concatenar la entrada X línea por línea para obtener un vector de longitud 16. Luego, haz una multiplicación matricial de W y vectorizado X para obtener un vector de longitud 4. Después de deformarlo, podemos obtener el mismo resultado que la operación de convolución anterior. Se puede ver que hemos implementado la operación de convolución usando la multiplicación de matrices en este ejemplo.

En [3]: W, k = nd.zeros ((4, 16)), nd.zeros (11) 
        k [: 3], k [4: 7], k [8:] = K [0, 0 , 0,:], K [0, 0, 1,:], K [0, 0, 2 
        ,:] W [0, 0:11], W [1, 1:12], W [2, 4 : 15], W [3, 5:16] = k, k, k, k 
        nd.dot (W, X.reformar (16)). Remodelar ((1, 1, 2, 2)), W 

Fuera [ 3]: ( 
         [[[[348. 393.] 
            [528. 573.]]]] 
         <NDArray 1x1x2x2 @cpu (0)>, 
         [[1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9. 0. 0. 0. 0. 0.] 
          [0. 1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9. 0. 0. 0. 0. ] 
          [0. 0. 0. 0. 1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9. 0.] 
          [0. 0. 0. 0. 0. 1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9.]] 
         <NDArray 4x16 @cpu (0)>)

Ahora describimos la operación de convolución desde la perspectiva de la multiplicación de matrices. Suponga que el vector de entrada es xy la matriz de peso es W.La realización de la función de cálculo directo de convolución puede considerarse como multiplicar la entrada de función por la matriz de peso y generar el vector.

. Sabemos que la propagación hacia atrás debe seguir la regla de la cadena. debido a 

, La realización de la función de retropropagación convolucional puede verse como multiplicar la entrada de la función por la matriz de peso transpuesta 

. La capa de convolución transpuesta simplemente intercambia la función de cálculo hacia adelante y la función de propagación hacia atrás de la capa de convolución: estas dos funciones pueden considerarse como una multiplicación del vector de entrada de la función por 

Y W .

No es difícil imaginar que la capa convolucional transpuesta pueda usarse para intercambiar la forma de la entrada y la salida de la capa convolucional. Continuemos describiendo la convolución con multiplicación de matrices. Deje que la matriz de peso tenga forma 

 Para el vector de entrada de longitud 16, el cálculo directo de convolución genera un vector de longitud 4. Si la longitud del vector de entrada es 4, la forma de la matriz de peso transpuesta es 

Luego, la capa convolucional transpuesta generará un vector de longitud 16. En el diseño de modelos, las capas convolucionales transpuestas se utilizan a menudo para transformar mapas de características más pequeños en mapas de características más grandes. En una red totalmente convolucional, cuando la entrada es un mapa de características con una altura y un ancho pequeños, la capa de convolución transpuesta se puede utilizar para ampliar la altura y el ancho al tamaño de la imagen de entrada.

Veamos un ejemplo. Construya una convolución de capa convolucional y establezca la forma de la entrada X en (1, 3, 64, 64). El número de canales de la salida de convolución Y aumenta a 10, pero la altura y el ancho se reducen a la mitad.

En [4]: ​​conv = nn.Conv2D (10, kernel_size = 4, padding = 1, 
        strides = 2) conv.initialize () 

        X = nd.random.uniform (shape = (1, 3, 64, 64)) 
        Y = conv (X) 
        Y.shape 

Out [4]: ​​(1, 10, 32, 32)

A continuación, construimos la capa convolucional transpuesta conv_trans creando una instancia de Conv2DTranspose. Aquí configuramos la forma, el relleno y el paso del núcleo de convolución conv_trans para que sean los mismos que los de conv, y establecemos el número de canales de salida en 3. Cuando la entrada es la salida Y de la capa de convolución conv, la salida de la capa de convolución transpuesta tiene la misma altura y ancho que la entrada de la capa de convolución: la capa de convolución transpuesta agranda la altura y el ancho del mapa de características en 2 veces.

En [5]: conv_trans = nn.Conv2DTranspose (3, kernel_size = 4, padding = 1, 
        strides = 2) conv_trans.initialize () 
        conv_trans (Y) .shape 

Out [5]: (1, 3, 64, 64)

En alguna literatura, también se conoce como convolución de transposición convolución de paso fraccional ( convolución de paso fraccionado) [12 ].

9.10.2 Modelo de construcción

Aquí damos el diseño más básico del modelo de red convolucional completo. Como se muestra en la Figura 9-11, la red convolucional completa primero usa la red neuronal convolucional para extraer características de la imagen y luego pasa 

 La capa de convolución transforma el número de canales en el número de categorías y, finalmente, transforma la altura y el ancho del mapa de características al tamaño de la imagen de entrada mediante la transposición de la capa de convolución. La salida del modelo tiene la misma altura y ancho que la imagen de entrada, y tiene una correspondencia uno a uno en la posición espacial: el canal de salida final contiene la predicción de categoría del píxel de posición espacial.

Figura 9-11 Red completamente convolucional

A continuación, usamos un modelo ResNet-18 entrenado previamente en el conjunto de datos de ImageNet para extraer características de la imagen y registrar la instancia de red como pretrained_net. Se puede ver que las dos últimas capas de las entidades variables del miembro del modelo son la capa de agrupación máxima global GlobalAvgPool2D y la capa de aplanamiento de muestra Flatten, y el módulo de salida contiene la capa completamente conectada para la salida. Las redes totalmente convolucionales no necesitan utilizar estas capas.

En [6]: pretrained_net = model_zoo.vision.resnet18_v2 (pretrained = True) 
        pretrained_net.features [-4:], pretrained_net.output 

Salida [6]: (HybridSequential ( 
           (0): BatchNorm (eje = 1, eps = 1e -05, impulso = 0.9, f ix_gamma = False, 
➥ use_global_stats = False, in_channels = 512) 
           (1): Activación (relu) 
           (2): GlobalAvgPool2D (size = (1, 1), stride = (1, 1) , padding = (0, 0), 
➥ ceil_mode = True) 
           (3): Aplanar 
         ), Denso (512 -> 1000, lineal))

A continuación, creamos una red de instancias de red totalmente convolucional. Copia todas las capas de las características de la variable miembro de la instancia pretrained_net, excepto las dos últimas capas y los parámetros del modelo obtenidos mediante el preentrenamiento.

En [7]: net = nn.HybridSequential () 
        para la capa en pretrained_net.features [: - 2]: 
            net.add (layer)

Dada una entrada con una altura y un ancho de 320 y 480 respectivamente, el cálculo hacia adelante de la red reduce la altura y el ancho de entrada a 1/32 del original, es decir, 10 y 15.

En [8]: X = nd.random.uniform (shape = (1, 3, 320, 480)) 
        net (X) .shape 

Out [8]: (1, 512, 10, 15)

A continuación, pasamos 

 La capa convolucional transforma el número de canales de salida en el número de categorías 21 en el conjunto de datos Pascal VOC2012. Finalmente, necesitamos aumentar la altura y el ancho del mapa de características en 32 veces para volver a cambiar a la altura y el ancho de la imagen de entrada. Recuerde el método de cálculo de la forma de salida de la capa convolucional descrito en la Sección 5.2. debido a 

 Y 

, Construimos una capa convolucional transpuesta con una zancada de 32, y establecemos la altura y el ancho del núcleo de convolución en 64 y el relleno en 16. No es difícil encontrar que si la zancada es s , el relleno es s / 2 (asumiendo que s / 2 es un número entero), y la altura y el ancho del núcleo de convolución son 2 s , el núcleo de convolución transpuesto agrandará la altura y el ancho de entrada en s veces respectivamente. .

En [9]: num_classes = 21 
        net.add (nn.Conv2D (num_classes, kernel_size = 1), 
                nn.Conv2DTranspose (num_classes, kernel_size = 64, padding = 16, 
                                   strides = 32))

9.10.3 Inicializar la capa convolucional transpuesta

Ya sabemos que la capa convolucional transpuesta puede ampliar el mapa de características. En el procesamiento de imágenes, a veces necesitamos ampliar la imagen, es decir , sobremuestrear ( upsample ). Existen muchos métodos para el muestreo ascendente y la interpolación bilineal se usa comúnmente . En pocas palabras, para obtener la imagen de salida en las coordenadas 

 Pixeles en la parte superior, primero mapee las coordenadas a las coordenadas de la imagen de entrada 

, Por ejemplo, mapeo basado en la relación entre el tamaño de la entrada y la salida. Mapeado

con

Generalmente números reales. Luego, encuentra las coordenadas en la imagen de entrada. 

Los 4 píxeles más cercanos. Finalmente, la imagen de salida está en las coordenadas 

 Los píxeles de la imagen de entrada se basan en estos 4 píxeles y sus 

Se calcula la distancia relativa. El muestreo ascendente de la interpolación bilineal se puede lograr mediante la capa de convolución transpuesta del núcleo de convolución construido por la siguiente función bilinear_kernel. Debido a las limitaciones de espacio, solo damos la implementación de la función bilinear_kernel y ya no discutimos el principio del algoritmo.

En [10]: def bilinear_kernel (in_channels, out_channels, kernel_size): 
             factor = (kernel_size + 1) // 2 
             if kernel_size% 2 == 1: 
                 center = factor - 1 
             else: 
                 center = factor - 0.5 
             og = np.ogrid [: kernel_size,: kernel_size] 
             f ilt = (1 - abs (og [0] - centro) / factor) * \ 
                    (1 - abs (og [1] - centro) / factor) 
             peso = np.zeros ((in_channels , out_channels, kernel_size, kernel_size), 
                              dtype = 'f loat32') 
             peso [rango (in_channels), rango (out_channels),:,:] = f 
             ilt return nd.array (peso)

Experimentemos con el muestreo superior de la interpolación bilineal utilizando capas convolucionales transpuestas. Construya una capa de convolución transpuesta que aumente 2 veces la altura y el ancho de entrada e inicialice su núcleo de convolución con la función bilinear_kernel.

En [11]: conv_trans = nn.Conv2DTranspose (3, kernel_size = 4, padding = 1, 
         strides = 2) conv_trans.initialize (init.Constant (bilinear_kernel (3, 3, 4)))

Lea la imagen X y registre el resultado del muestreo superior como Y. Para imprimir la imagen, necesitamos ajustar la posición de la dimensión del canal.

En [12]: img = image.imread ('../ img / catdog.jpg') 
         X = img.astype ('f loat32'). Transpose ((2, 0, 1)). Expand_dims (axis = 0 ) / 255 
         Y = conv_trans (X) 
         out_img = Y [0] .transpose ((1, 2, 0))

Se puede ver que la capa convolucional transpuesta amplía la altura y el ancho de la imagen en 2 veces. Vale la pena mencionar que, a excepción de las diferentes escalas de coordenadas, la imagen ampliada por interpolación bilineal no se ve diferente de la imagen original impresa en la Sección 9.3.

En [13]: d2l.set_f igsize () 
         print ('forma de imagen de entrada:', img.shape) 
         d2l.plt.imshow (img.asnumpy ()); 
         print ('forma de imagen de salida:', out_img.shape) 
         d2l.plt.imshow (out_img.asnumpy ()); 

forma de la imagen de entrada: (561, 728, 3) 
forma de la imagen de salida: (1122, 1456, 3)

En una red completamente convolucional, inicializamos la capa convolucional transpuesta como muestreo ascendente de interpolación bilineal. Para la capa convolucional 1 × 1, utilizamos la inicialización aleatoria de Xavier.

En [14]: net [-1] .initialize (init.Constant (bilinear_kernel (num_classes, num_classes, 
                                                        64))) 
         net [-2] .initialize (init = init.Xavier ())

9.10.4 Leer conjunto de datos

Usamos el método descrito en la sección 9.9 para leer el conjunto de datos. La forma de la imagen de salida recortada aleatoriamente se especifica aquí como 320 × 480: tanto la altura como la anchura pueden ser divisibles por 32.

En [15]: crop_size, batch_size, colormap2label = (320, 480), 32, nd.zeros (256 ** 3) 
         for i, cm in enumerate (d2l.VOC_COLORMAP): 
             colormap2label [(cm [0] * 256 + cm [1]) * 256 + cm [2]] = i 
         voc_dir = d2l.download_voc_pascal (data_dir = '.. / data') 

         num_workers = 0 if sys.platform.startswith ('win32') else 4 
         train_iter = gdata. DataLoader ( 
             d2l.VOCSegDataset (True, crop_size, voc_dir, colormap2label), batch_size, 
             shuff le = True, last_batch = 'discard', núm_trabajadores = núm_trabajadores) 
         test_iter = gdata.DataLoader ( 
             d2l.VOCSegDataset_dimensionado, color, vocultivo , tamaño del lote,
             last_batch = 'descartar', num_workers = num_workers) 

leer 1114 ejemplos 
leer 1078 ejemplos

9.10.5 Modelo de formación

Ahora puede comenzar a entrenar el modelo. La función de pérdida y el cálculo de precisión aquí no son esencialmente diferentes de los de la clasificación de imágenes. Debido a que usamos el canal de la capa de convolución transpuesta para predecir la categoría del píxel, la opción axis = 1 (dimensión del canal) se especifica en SoftmaxCrossEntropyLoss. Además, el modelo calcula la tasa de precisión en función de si la categoría prevista de cada píxel es correcta.

En [16]: ctx = d2l.try_all_gpus () 
         loss = gloss.SoftmaxCrossEntropyLoss (eje = 1) 
         net.collect_params (). Reset_ctx (ctx) 
         trainer = gluon.Trainer (net.collect_params (), 'sgd', {' learning_rate ': 0.1, 
                                                            ' wd ': 1e-3}) 
         d2l.train (train_iter, test_iter, net, loss, trainer, ctx, num_epochs = 5) 

entrenamiento en [gpu (0), gpu (1), gpu (2 ), gpu (3)] 
época 1, pérdida 1.3306, acc de tren 0.726, acc de prueba 0.811, tiempo 17.5 seg 
época 2, pérdida 0.6524, acc de tren 0.811, prueba acc 0.820, tiempo 16.6 seg 
época 3, pérdida 0.5364, acc de tren 0.838 , prueba acc 0.812, tiempo 16.3 seg 
epoch 4, pérdida 0.4650, tren acc 0.856, prueba acc 0.842, tiempo 16.5 seg
época 5, pérdida 0.4017, tren acc 0.872, prueba acc 0.851, tiempo 16.3 seg

9.10.6 Predecir categorías de píxeles

Al realizar predicciones, necesitamos estandarizar la imagen de entrada en cada canal y convertirla al formato de entrada de cuatro dimensiones requerido por la red neuronal convolucional.

En [17]: def predice (img): 
             X = test_iter._dataset.normalize_image (img) 
             X = X.transpose ((2, 0, 1)). Expand_dims (axis = 0) 
             pred = nd.argmax (net ( X.as_in_context (ctx [0])), axis = 1) 
             return pred.reshape ((pred.shape [1], pred.shape [2]))

Para visualizar la categoría predicha de cada píxel, asignamos la categoría predicha a su color de etiqueta en el conjunto de datos.

En [18]: def label2image (pred): 
             colormap = nd.array (d2l.VOC_COLORMAP, ctx = ctx [0], dtype = 'uint8') 
             X = pred.astype ('int32') 
             return colormap [X ,: ]

Las imágenes del conjunto de datos de prueba varían en tamaño y forma. Dado que el modelo utiliza una capa de convolución transpuesta con un paso de 32, cuando la altura o el ancho de la imagen de entrada no puede ser divisible por 32, la altura o el ancho de salida de la capa de convolución transpuesta se desviará del tamaño de la imagen de entrada. Para resolver este problema, podemos interceptar múltiples áreas rectangulares con alto y ancho que son múltiplos enteros de 32 en la imagen y realizar cálculos hacia adelante en los píxeles en estas áreas, respectivamente. La unión de estas regiones debe cubrir completamente la imagen de entrada. Cuando un píxel está cubierto por múltiples regiones, el valor promedio de la salida de la capa convolucional transpuesta en el cálculo directo de diferentes regiones puede usarse como la entrada de la operación softmax para predecir la categoría.

En aras de la simplicidad, solo leemos unas pocas imágenes de prueba más grandes y comenzamos desde la esquina superior izquierda de la imagen para interceptar un área de 320 × 480: solo esta área se usa para la predicción. Para la imagen de entrada, imprimimos primero el área interceptada, luego imprimimos el resultado de la predicción y finalmente imprimimos la categoría etiquetada (ver también la ilustración en color 20).

En [19]: test_images, test_labels = d2l.read_voc_images (is_train = False) 
         n, imgs = 4, [] 
         para i en el rango (n): 
             crop_rect = (0, 0, 480, 320) 
             X = image.f ixed_crop (test_images [i], * crop_rect) 
             pred = label2image (predecir (X)) 
             imgs + = [X, pred, image.f ixed_crop (test_labels [i], * crop_rect)] 
         d2l.show_images (imgs [:: 3] + imágenes [1 :: 3] + imágenes [2 :: 3], 3, n);

resumen

La operación de convolución se puede realizar mediante la multiplicación de matrices.

La red convolucional completa primero utiliza una red neuronal convolucional para extraer características de la imagen, luego transforma la cantidad de canales en la cantidad de categorías a través de una capa convolucional 1 × 1 y finalmente transforma la altura y el ancho del mapa de características en la imagen de entrada a través de la capa convolucional transpuesta Tamaño para generar la categoría de cada píxel.

En una red completamente convolucional, la capa convolucional transpuesta se puede inicializar como muestreo ascendente de interpolación bilineal.

Este artículo es un extracto de "Aprendizaje profundo práctico"

Este libro tiene como objetivo ofrecer una experiencia de aprendizaje interactivo sobre el aprendizaje profundo a los lectores. El libro no solo explica los principios de los algoritmos de aprendizaje profundo, sino que también demuestra su implementación y funcionamiento. A diferencia de los libros tradicionales, cada sección de este libro es un cuaderno de Jupyter que se puede descargar y ejecutar. Combina texto, fórmulas, imágenes, códigos y resultados de ejecución. Además, los lectores también pueden visitar y participar en la discusión de los contenidos del libro. 

El contenido del libro se divide en 3 partes: la primera parte presenta los antecedentes del aprendizaje profundo, proporciona conocimientos previos e incluye los conceptos y técnicas básicos del aprendizaje profundo; la segunda parte describe los componentes importantes de la computación del aprendizaje profundo y también explica cómo se ha realizado el aprendizaje profundo en los últimos años. Redes neuronales convolucionales y redes neuronales recurrentes que han tenido éxito en muchos campos; la tercera parte evalúa los algoritmos de optimización, examina los factores importantes que afectan el rendimiento informático del aprendizaje profundo y enumera las aplicaciones importantes del aprendizaje profundo en la visión por computadora y el procesamiento del lenguaje natural. . 

Este libro también cubre los métodos y prácticas de aprendizaje profundo, principalmente para estudiantes universitarios, técnicos e investigadores. La lectura de este libro requiere que los lectores comprendan la programación básica de Python o los conceptos básicos del álgebra lineal, la diferenciación y la probabilidad que se describen en el apéndice.

 

Supongo que te gusta

Origin blog.csdn.net/epubit17/article/details/107793122
Recomendado
Clasificación