Comprensión intuitiva de convoluciones 1D, 2D y 3D en redes neuronales convolucionales


Respuestas:


415

Quiero explicar con foto de C3D .

En pocas palabras, ¡la dirección convolucional y la forma de salida son importantes!

ingrese la descripción de la imagen aquí

↑↑↑↑↑ Convoluciones 1D - Básico ↑↑↑↑↑

  • solo 1 dirección (eje de tiempo) para calcular conv
  • entrada = [W], filtro = [k], salida = [W]
  • p. ej., entrada = [1,1,1,1,1], filtro = [0.25,0.5,0.25], salida = [1,1,1,1,1]
  • la forma de salida es una matriz 1D
  • ejemplo) suavizado de gráficos

Ejemplo de juguete de código tf.nn.conv1d

import tensorflow as tf
import numpy as np

sess = tf.Session()

ones_1d = np.ones(5)
weight_1d = np.ones(3)
strides_1d = 1

in_1d = tf.constant(ones_1d, dtype=tf.float32)
filter_1d = tf.constant(weight_1d, dtype=tf.float32)

in_width = int(in_1d.shape[0])
filter_width = int(filter_1d.shape[0])

input_1d   = tf.reshape(in_1d, [1, in_width, 1])
kernel_1d = tf.reshape(filter_1d, [filter_width, 1, 1])
output_1d = tf.squeeze(tf.nn.conv1d(input_1d, kernel_1d, strides_1d, padding='SAME'))
print sess.run(output_1d)

ingrese la descripción de la imagen aquí

↑↑↑↑↑ Convoluciones 2D - Básico ↑↑↑↑↑

  • 2 -dirección (x, y) para calcular conv
  • la forma de salida es Matriz 2D
  • entrada = [W, H], filtro = [k, k] salida = [W, H]
  • ejemplo) Sobel Egde Fllter

tf.nn.conv2d - Ejemplo de juguete

ones_2d = np.ones((5,5))
weight_2d = np.ones((3,3))
strides_2d = [1, 1, 1, 1]

in_2d = tf.constant(ones_2d, dtype=tf.float32)
filter_2d = tf.constant(weight_2d, dtype=tf.float32)

in_width = int(in_2d.shape[0])
in_height = int(in_2d.shape[1])

filter_width = int(filter_2d.shape[0])
filter_height = int(filter_2d.shape[1])

input_2d   = tf.reshape(in_2d, [1, in_height, in_width, 1])
kernel_2d = tf.reshape(filter_2d, [filter_height, filter_width, 1, 1])

output_2d = tf.squeeze(tf.nn.conv2d(input_2d, kernel_2d, strides=strides_2d, padding='SAME'))
print sess.run(output_2d)

ingrese la descripción de la imagen aquí

↑↑↑↑↑ Convoluciones 3D - Básico ↑↑↑↑↑

  • 3 -dirección (x, y, z) para calcular conv
  • la forma de salida es Volumen 3D
  • entrada = [W, H, L ], filtro = [k, k, d ] salida = [W, H, M]
  • d <¡L es importante! para hacer salida de volumen
  • ejemplo) C3D

tf.nn.conv3d - Ejemplo de juguete

ones_3d = np.ones((5,5,5))
weight_3d = np.ones((3,3,3))
strides_3d = [1, 1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_3d = tf.constant(weight_3d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])
in_depth = int(in_3d.shape[2])

filter_width = int(filter_3d.shape[0])
filter_height = int(filter_3d.shape[1])
filter_depth = int(filter_3d.shape[2])

input_3d   = tf.reshape(in_3d, [1, in_depth, in_height, in_width, 1])
kernel_3d = tf.reshape(filter_3d, [filter_depth, filter_height, filter_width, 1, 1])

output_3d = tf.squeeze(tf.nn.conv3d(input_3d, kernel_3d, strides=strides_3d, padding='SAME'))
print sess.run(output_3d)

ingrese la descripción de la imagen aquí

↑↑↑↑↑ Convoluciones 2D con entrada 3D - LeNet, VGG, ..., ↑↑↑↑↑

  • Aunque la entrada sea 3D ex) 224x224x3, 112x112x32
  • La forma de salida no es Volumen 3D , sino Matriz 2D
  • porque la profundidad del filtro = L debe coincidir con los canales de entrada = L
  • 2 -dirección (x, y) para calcular conv! no 3D
  • entrada = [W, H, L ], filtro = [k, k, L ] salida = [W, H]
  • la forma de salida es Matriz 2D
  • ¿Qué pasa si queremos entrenar N filtros (N es el número de filtros)
  • entonces la forma de salida es (2D apilada) 3D = matriz 2D x N.

conv2d - LeNet, VGG, ... para 1 filtro

in_channels = 32 # 3 for RGB, 32, 64, 128, ... 
ones_3d = np.ones((5,5,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae with in_channels
weight_3d = np.ones((3,3,in_channels)) 
strides_2d = [1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_3d = tf.constant(weight_3d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])

filter_width = int(filter_3d.shape[0])
filter_height = int(filter_3d.shape[1])

input_3d   = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_3d = tf.reshape(filter_3d, [filter_height, filter_width, in_channels, 1])

output_2d = tf.squeeze(tf.nn.conv2d(input_3d, kernel_3d, strides=strides_2d, padding='SAME'))
print sess.run(output_2d)

conv2d - LeNet, VGG, ... para filtros N

in_channels = 32 # 3 for RGB, 32, 64, 128, ... 
out_channels = 64 # 128, 256, ...
ones_3d = np.ones((5,5,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae x number of filters = 4D
weight_4d = np.ones((3,3,in_channels, out_channels))
strides_2d = [1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_4d = tf.constant(weight_4d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])

filter_width = int(filter_4d.shape[0])
filter_height = int(filter_4d.shape[1])

input_3d   = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_4d = tf.reshape(filter_4d, [filter_height, filter_width, in_channels, out_channels])

#output stacked shape is 3D = 2D x N matrix
output_3d = tf.nn.conv2d(input_3d, kernel_4d, strides=strides_2d, padding='SAME')
print sess.run(output_3d)

ingrese la descripción de la imagen aquí ↑↑↑↑↑ Bonificación 1x1 conv en CNN - GoogLeNet, ..., ↑↑↑↑↑

  • 1x1 conv es confuso cuando cree que esto es un filtro de imagen 2D como sobel
  • para 1x1 conv en CNN, la entrada es la forma 3D como en la imagen de arriba.
  • calcula el filtrado en profundidad
  • entrada = [W, H, L], filtro = [1,1, L] salida = [W, H]
  • La forma apilada de salida es 3D = matriz 2D x N.

tf.nn.conv2d - caso especial conv. 1x1

in_channels = 32 # 3 for RGB, 32, 64, 128, ... 
out_channels = 64 # 128, 256, ...
ones_3d = np.ones((1,1,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae x number of filters = 4D
weight_4d = np.ones((3,3,in_channels, out_channels))
strides_2d = [1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_4d = tf.constant(weight_4d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])

filter_width = int(filter_4d.shape[0])
filter_height = int(filter_4d.shape[1])

input_3d   = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_4d = tf.reshape(filter_4d, [filter_height, filter_width, in_channels, out_channels])

#output stacked shape is 3D = 2D x N matrix
output_3d = tf.nn.conv2d(input_3d, kernel_4d, strides=strides_2d, padding='SAME')
print sess.run(output_3d)

Animación (conv. 2D con entradas 3D)

ingrese la descripción de la imagen aquí - Enlace original: LINK
- Autor: Martin Görner
- Twitter: @martin_gorner
- Google +: plus.google.com/+MartinGorne

Convoluciones 1D adicionales con entrada 2D

ingrese la descripción de la imagen aquí ↑↑↑↑↑ Convoluciones 1D con entrada 1D ↑↑↑↑↑

ingrese la descripción de la imagen aquí ↑↑↑↑↑ Convoluciones 1D con entrada 2D ↑↑↑↑↑

  • Aunque la entrada es 2D ex) 20x14
  • la forma de salida no es 2D , sino Matriz 1D
  • porque la altura del filtro = L debe coincidir con la altura de entrada = L
  • 1 -dirección (x) para calcular conv! no 2D
  • entrada = [W, L ], filtro = [k, L ] salida = [W]
  • la forma de salida es Matriz 1D
  • ¿Qué pasa si queremos entrenar N filtros (N es el número de filtros)
  • entonces la forma de salida es (apilada 1D) 2D = 1D x N matriz.

Bono C3D

in_channels = 32 # 3, 32, 64, 128, ... 
out_channels = 64 # 3, 32, 64, 128, ... 
ones_4d = np.ones((5,5,5,in_channels))
weight_5d = np.ones((3,3,3,in_channels,out_channels))
strides_3d = [1, 1, 1, 1, 1]

in_4d = tf.constant(ones_4d, dtype=tf.float32)
filter_5d = tf.constant(weight_5d, dtype=tf.float32)

in_width = int(in_4d.shape[0])
in_height = int(in_4d.shape[1])
in_depth = int(in_4d.shape[2])

filter_width = int(filter_5d.shape[0])
filter_height = int(filter_5d.shape[1])
filter_depth = int(filter_5d.shape[2])

input_4d   = tf.reshape(in_4d, [1, in_depth, in_height, in_width, in_channels])
kernel_5d = tf.reshape(filter_5d, [filter_depth, filter_height, filter_width, in_channels, out_channels])

output_4d = tf.nn.conv3d(input_4d, kernel_5d, strides=strides_3d, padding='SAME')
print sess.run(output_4d)

sess.close()

Entrada y salida en Tensorflow

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Resumen

ingrese la descripción de la imagen aquí


18
Teniendo en cuenta su trabajo y claridad en las explicaciones, los votos a favor de 8 son demasiado menos.
user3282777

3
La conv 2d con entrada 3d es un buen toque. Sugeriría una edición para incluir 1d conv con entrada 2d (por ejemplo, una matriz multicanal) y comparar la diferencia con una conv 2d con entrada 2d.
SumNeuron

2
¡Increíble respuesta!
Ben

¿Por qué la dirección conv en 2d ↲. He visto fuentes que afirman que la dirección es para fila 1, luego para fila 1+stride. La convolución en sí es invariante al desplazamiento, entonces, ¿por qué es importante la dirección de la convolución?
Minh Triet

Gracias por su pregunta. ¡Si! la convolución en sí es invariante al desplazamiento. así que para el cálculo, la dirección de las convulsiones no importa. (Puede calcular las convulsiones 2d con dos grandes multiplicaciones de matrices. El marco caffe ya lo hizo), pero para entender es mejor explicar con la dirección de conv. porque 2d conv con entrada 3d es confuso sin dirección. ^^
runhani

6

Siguiendo la respuesta de @runhani, estoy agregando algunos detalles más para que la explicación sea un poco más clara e intentaré explicar esto un poco más (y, por supuesto, con ejemplos de TF1 y TF2).

Uno de los principales bits adicionales que incluyo son:

  • Énfasis en las aplicaciones
  • Uso de tf.Variable
  • Explicación más clara de entradas / núcleos / salidas Convolución 1D / 2D / 3D
  • Los efectos de la zancada / acolchado

Convolución 1D

Así es como puede hacer una convolución 1D usando TF 1 y TF 2.

Y para ser específico, mis datos tienen las siguientes formas,

  • Vector 1D - [batch size, width, in channels](p 1, 5, 1. Ej. )
  • Kernel - [width, in channels, out channels](p 5, 1, 4. Ej. )
  • Salida - [batch size, width, out_channels](p 1, 5, 4. Ej. )

Ejemplo de TF1

import tensorflow as tf
import numpy as np

inp = tf.placeholder(shape=[None, 5, 1], dtype=tf.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5, 1, 4]), dtype=tf.float32)
out = tf.nn.conv1d(inp, kernel, stride=1, padding='SAME')

with tf.Session() as sess:
  tf.global_variables_initializer().run()
  print(sess.run(out, feed_dict={inp: np.array([[[0],[1],[2],[3],[4]],[[5],[4],[3],[2],[1]]])}))

Ejemplo TF2

import tensorflow as tf
import numpy as np

inp = np.array([[[0],[1],[2],[3],[4]],[[5],[4],[3],[2],[1]]]).astype(np.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5, 1, 4]), dtype=tf.float32)
out = tf.nn.conv1d(inp, kernel, stride=1, padding='SAME')
print(out)

Es mucho menos trabajo con TF2 ya que TF2 no lo necesita Sessiony, variable_initializerpor ejemplo.

¿Cómo se vería esto en la vida real?

Entonces, entendamos qué está haciendo esto usando un ejemplo de suavizado de señal. A la izquierda tienes el original y a la derecha tienes la salida de un Convolution 1D que tiene 3 canales de salida.

ingrese la descripción de la imagen aquí

¿Qué significan varios canales?

Los canales múltiples son básicamente representaciones de características múltiples de una entrada. En este ejemplo, tiene tres representaciones obtenidas por tres filtros diferentes. El primer canal es el filtro de suavizado de igual ponderación. El segundo es un filtro que pondera el medio del filtro más que los límites. El filtro final hace lo opuesto al segundo. Para que pueda ver cómo estos diferentes filtros producen diferentes efectos.

Aplicaciones de aprendizaje profundo de convolución 1D

La convolución 1D se ha utilizado con éxito para la tarea de clasificación de frases .

Convolución 2D

Desactivado a convolución 2D. Si eres una persona de aprendizaje profundo, las posibilidades de que no te hayas encontrado con la convolución 2D son ... bueno, aproximadamente cero. Se utiliza en CNN para clasificación de imágenes, detección de objetos, etc., así como en problemas de PNL que involucran imágenes (por ejemplo, generación de subtítulos de imágenes).

Probemos un ejemplo, obtuve un kernel de convolución con los siguientes filtros aquí,

  • Kernel de detección de bordes (ventana 3x3)
  • Kernel de desenfoque (ventana 3x3)
  • Afilar kernel (ventana 3x3)

Y para ser específico, mis datos tienen las siguientes formas,

  • Imagen (blanco y negro) - [batch_size, height, width, 1](p 1, 340, 371, 1. Ej. )
  • Kernel (también conocido como filtros) - [height, width, in channels, out channels](p 3, 3, 1, 3. Ej. )
  • Salida (también conocidos como mapas de características) - [batch_size, height, width, out_channels](p 1, 340, 371, 3. Ej. )

Ejemplo TF1,

import tensorflow as tf
import numpy as np
from PIL import Image

im = np.array(Image.open(<some image>).convert('L'))#/255.0

kernel_init = np.array(
    [
     [[[-1, 1.0/9, 0]],[[-1, 1.0/9, -1]],[[-1, 1.0/9, 0]]],
     [[[-1, 1.0/9, -1]],[[8, 1.0/9,5]],[[-1, 1.0/9,-1]]],
     [[[-1, 1.0/9,0]],[[-1, 1.0/9,-1]],[[-1, 1.0/9, 0]]]
     ])

inp = tf.placeholder(shape=[None, image_height, image_width, 1], dtype=tf.float32)
kernel = tf.Variable(kernel_init, dtype=tf.float32)
out = tf.nn.conv2d(inp, kernel, strides=[1,1,1,1], padding='SAME')

with tf.Session() as sess:
  tf.global_variables_initializer().run()
  res = sess.run(out, feed_dict={inp: np.expand_dims(np.expand_dims(im,0),-1)})

Ejemplo TF2

import tensorflow as tf
import numpy as np
from PIL import Image

im = np.array(Image.open(<some image>).convert('L'))#/255.0
x = np.expand_dims(np.expand_dims(im,0),-1)

kernel_init = np.array(
    [
     [[[-1, 1.0/9, 0]],[[-1, 1.0/9, -1]],[[-1, 1.0/9, 0]]],
     [[[-1, 1.0/9, -1]],[[8, 1.0/9,5]],[[-1, 1.0/9,-1]]],
     [[[-1, 1.0/9,0]],[[-1, 1.0/9,-1]],[[-1, 1.0/9, 0]]]
     ])

kernel = tf.Variable(kernel_init, dtype=tf.float32)

out = tf.nn.conv2d(x, kernel, strides=[1,1,1,1], padding='SAME')

¿Cómo se vería esto en la vida real?

Aquí puede ver el resultado producido por el código anterior. La primera imagen es la original y, en el sentido de las agujas del reloj, tiene salidas del primer filtro, segundo filtro y tercer filtro. ingrese la descripción de la imagen aquí

¿Qué significan varios canales?

En el contexto de la convolución 2D, es mucho más fácil comprender lo que significan estos canales múltiples. Digamos que está haciendo reconocimiento facial. Puede pensar en (esta es una simplificación muy poco realista pero transmite el punto) cada filtro representa un ojo, boca, nariz, etc. De modo que cada mapa de características sería una representación binaria de si esa característica está en la imagen que proporcionó . No creo que deba enfatizar que para un modelo de reconocimiento facial esas son características muy valiosas. Más información en este artículo .

Esta es una ilustración de lo que estoy tratando de articular.

ingrese la descripción de la imagen aquí

Aplicaciones de aprendizaje profundo de convolución 2D

La convolución 2D es muy frecuente en el ámbito del aprendizaje profundo.

Las CNN (redes neuronales de convolución) utilizan la operación de convolución 2D para casi todas las tareas de visión por computadora (por ejemplo, clasificación de imágenes, detección de objetos, clasificación de video).

Convolución 3D

Ahora se vuelve cada vez más difícil ilustrar lo que sucede a medida que aumenta el número de dimensiones. Pero con una buena comprensión de cómo funciona la convolución 1D y 2D, es muy sencillo generalizar esa comprensión a la convolución 3D. Así que aquí va.

Y para ser específico, mis datos tienen las siguientes formas,

  • Datos 3D (LIDAR) - [batch size, height, width, depth, in channels](p 1, 200, 200, 200, 1. Ej. )
  • Kernel - [height, width, depth, in channels, out channels](p 5, 5, 5, 1, 3. Ej. )
  • Salida - [batch size, width, height, width, depth, out_channels](p 1, 200, 200, 2000, 3. Ej. )

Ejemplo TF1

import tensorflow as tf
import numpy as np

tf.reset_default_graph()

inp = tf.placeholder(shape=[None, 200, 200, 200, 1], dtype=tf.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5,5,5,1,3]), dtype=tf.float32)
out = tf.nn.conv3d(inp, kernel, strides=[1,1,1,1,1], padding='SAME')

with tf.Session() as sess:
  tf.global_variables_initializer().run()
  res = sess.run(out, feed_dict={inp: np.random.normal(size=(1,200,200,200,1))})

Ejemplo TF2

import tensorflow as tf
import numpy as np

x = np.random.normal(size=(1,200,200,200,1))
kernel = tf.Variable(tf.initializers.glorot_uniform()([5,5,5,1,3]), dtype=tf.float32)
out = tf.nn.conv3d(x, kernel, strides=[1,1,1,1,1], padding='SAME') 

Aplicaciones de aprendizaje profundo de convolución 3D

La convolución 3D se ha utilizado al desarrollar aplicaciones de aprendizaje automático que involucran datos LIDAR (Detección y rango de luz) que son de naturaleza tridimensional.

¿Qué ... más jerga ?: Stride and padding

Muy bien, estás casi allí. Así que aguanta. Veamos qué es la zancada y el acolchado. Son bastante intuitivos si se piensa en ellos.

Si cruzas un pasillo, llegarás más rápido en menos pasos. Pero también significa que observó un entorno menor que si caminara por la habitación. ¡Refuercemos ahora nuestra comprensión con una bonita imagen también! Entendamos esto a través de la convolución 2D.

Comprender la zancada

Zancada de convolución

Cuando usa, tf.nn.conv2dpor ejemplo, debe configurarlo como un vector de 4 elementos. No hay razón para sentirse intimidado por esto. Solo contiene los pasos en el siguiente orden.

  • Convolución 2D - [batch stride, height stride, width stride, channel stride]. Aquí, paso por lotes y paso de canal que acaba de establecer en uno (he estado implementando modelos de aprendizaje profundo durante 5 años y nunca tuve que configurarlos en nada excepto uno). Así que eso te deja solo con 2 zancadas para establecer.

  • Convolución 3D - [batch stride, height stride, width stride, depth stride, channel stride]. Aquí solo te preocupas por los pasos de altura / ancho / profundidad.

Entendiendo el relleno

Ahora, observa que no importa cuán pequeño sea su paso (es decir, 1), hay una reducción de dimensión inevitable que ocurre durante la convolución (por ejemplo, el ancho es 3 después de convolucionar una imagen de 4 unidades de ancho). Esto no es deseable, especialmente cuando se construyen redes neuronales de convolución profunda. Aquí es donde el acolchado viene al rescate. Hay dos tipos de relleno más utilizados.

  • SAME y VALID

A continuación puede ver la diferencia.

ingrese la descripción de la imagen aquí

Palabra final : si tiene mucha curiosidad, es posible que se esté preguntando. Acabamos de lanzar una bomba sobre la reducción de dimensión completamente automática y ahora hablamos de tener diferentes pasos. Pero lo mejor de la zancada es que controlas cuándo y cómo se reducen las dimensiones.


2
  1. CNN 1D, 2D o 3D se refiere a la dirección de convolución, en lugar de la dimensión de entrada o filtro.

  2. Para la entrada de 1 canal, CNN2D es igual a CNN1D es la longitud del núcleo = la longitud de la entrada. (1 dirección conv.)


2

En resumen, en 1D CNN, el kernel se mueve en una dirección. Los datos de entrada y salida de 1D CNN son bidimensionales. Se utiliza principalmente en datos de series temporales.

En CNN 2D, el kernel se mueve en 2 direcciones. Los datos de entrada y salida de CNN 2D son tridimensionales. Se utiliza principalmente en datos de imagen.

En 3D CNN, el kernel se mueve en 3 direcciones. Los datos de entrada y salida de 3D CNN son de 4 dimensiones. Se utiliza principalmente en datos de imágenes 3D (resonancia magnética, tomografías computarizadas).

Puede encontrar más detalles aquí: https://medium.com/@xzz201920/conv1d-conv2d-and-conv3d-8a59182c4d6

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.