¿Qué hace tf.nn.conv2d hacer en tensorflow?


Estaba mirando los documentos de tensorflow sobre tf.nn.conv2d aquí . Pero no puedo entender lo que hace o lo que está tratando de lograr. Dice en los documentos,

#1: Aplana el filtro a una matriz 2-D con forma

[filter_height * filter_width * in_channels, output_channels].

Ahora, ¿qué hace eso? ¿Es una multiplicación por elementos o una simple multiplicación de matrices? Tampoco he podido entender los otros dos puntos mencionados en los documentos. Los he escrito a continuación:

# 2: Extrae parches de imagen del tensor de entrada para formar un tensor virtual de forma

[batch, out_height, out_width, filter_height * filter_width * in_channels].

# 3: Para cada parche, right-multiplica la matriz del filtro y el vector del parche de la imagen.

Sería muy útil si alguien pudiera dar un ejemplo, un trozo de código (extremadamente útil) tal vez y explicar lo que está pasando allí y por qué la operación es así.

He intentado codificar una pequeña porción e imprimir la forma de la operación. Aún así, yo no puedo entenderlo.

Probé algo como esto:

op = tf.shape(tf.nn.conv2d(tf.random_normal([1,10,10,10]), 
              tf.random_normal([2,10,10,10]), 
              strides=[1, 2, 2, 1], padding='SAME'))

with tf.Session() as sess:
    result = sess.run(op)
    print(result)

Entiendo partes y piezas de redes neuronales convolucionales. Los estudié aquí . Pero la implementación en tensorflow no es lo que esperaba. Así que planteó la pregunta.

EDITAR : Así que implementé un código mucho más simple. Pero no puedo entender qué está pasando. Me refiero a cómo los resultados son así. Sería extremadamente útil si alguien pudiera decirme qué proceso produce esto salida.

input = tf.Variable(tf.random_normal([1,2,2,1]))
filter = tf.Variable(tf.random_normal([1,1,1,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
init = tf.initialize_all_variables()
with tf.Session() as sess:
    sess.run(init)

    print("input")
    print(input.eval())
    print("filter")
    print(filter.eval())
    print("result")
    result = sess.run(op)
    print(result)

Salida

input
[[[[ 1.60314465]
   [-0.55022103]]

  [[ 0.00595062]
   [-0.69889867]]]]
filter
[[[[-0.59594476]]]]
result
[[[[-0.95538563]
   [ 0.32790133]]

  [[-0.00354624]
   [ 0.41650501]]]]
Author: user2314737, 2016-01-05

5 answers

La convolución 2D se calcula de una manera similar uno calcularía La convolución 1D: deslice su núcleo sobre la entrada, calcule las multiplicaciones en cuanto a elementos y las sume. Pero en lugar de que su kernel/input sea una matriz, aquí son matrices.


En el ejemplo más básico no hay relleno y stride=1. Supongamos que su input y kernel son: introduzca la descripción de la imagen aquí

Cuando su núcleo recibirá la siguiente salida: introduzca la descripción de la imagen aquí, que se calcula de la siguiente manera:

  • 14 = 4 * 1 + 3 * 0 + 1 * 1 + 2 * 2 + 1 * 1 + 0 * 0 + 1 * 0 + 2 * 0 + 4 * 1
  • 6 = 3 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 0 * 1 + 1 * 0 + 2 * 0 + 4 * 0 + 1 * 1
  • 6 = 2 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 2 * 1 + 4 * 0 + 3 * 0 + 1 * 0 + 0 * 1
  • 12 = 1 * 1 + 0 * 0 + 1 * 1 + 2 * 2 + 4 * 1 + 1 * 0 + 1 * 0 + 0 * 0 + 2 * 1

La función conv2d de TF's calcula las convoluciones en lotes y utiliza un formato diferente. Para una entrada es [batch, in_height, in_width, in_channels] para el núcleo es [filter_height, filter_width, in_channels, out_channels]. Así que necesitamos proporcionar los datos en el formato correcto:

import tensorflow as tf
k = tf.constant([
    [1, 0, 1],
    [2, 1, 0],
    [0, 0, 1]
], dtype=tf.float32, name='k')
i = tf.constant([
    [4, 3, 1, 0],
    [2, 1, 0, 1],
    [1, 2, 4, 1],
    [3, 1, 0, 2]
], dtype=tf.float32, name='i')
kernel = tf.reshape(k, [3, 3, 1, 1], name='kernel')
image  = tf.reshape(i, [1, 4, 4, 1], name='image')

Después la convolución se calcula con:

res = tf.squeeze(tf.nn.conv2d(image, kernel, [1, 1, 1, 1], "VALID"))
# VALID means no padding
with tf.Session() as sess:
   print sess.run(res)

Y será equivalente a la que calculamos a mano.


Para ejemplos con padding/strides, echa un vistazo aquí.

 34
Author: Salvador Dali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-13 18:28:32

Ok Creo que esta es la forma más sencilla de explicarlo todo.


Su ejemplo es 1 imagen, tamaño 2x2, con 1 canal. Tiene 1 filtro, con tamaño 1x1, y 1 canal (el tamaño es altura x ancho x canales x número de filtros).

Para este simple caso, la imagen resultante de 2x2, 1 canal (tamaño 1x2x2x1, número de imágenes x altura x ancho x canales x) es el resultado de multiplicar el valor del filtro por cada píxel de la imagen.


Ahora probemos más canales:

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

Aquí la imagen 3x3 y el filtro 1x1 tienen cada uno 5 canales. La imagen resultante será 3x3 con 1 canal (tamaño 1x3x3x1), donde el valor de cada píxel es el producto escalar a través de los canales del filtro con el píxel correspondiente en la imagen de entrada.


Ahora con un filtro 3x3

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

Aquí obtenemos una imagen 1x1, con 1 canal (tamaño 1x1x1x1). El valor es la suma de los productos de punto de 9, 5 elementos. Pero podrías llamar a esto un elemento 45 producto escalar.


Ahora con una imagen más grande

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

La salida es una imagen de 1 canal de 3x3 (tamaño 1x3x3x1). Cada uno de estos valores es una suma de 9, 5-element dot products.

Cada salida se realiza centrando el filtro en uno de los 9 píxeles centrales de la imagen de entrada, de modo que ninguno de los filtros sobresale. Los x s siguientes representan los centros de filtro para cada píxel de salida.

.....
.xxx.
.xxx.
.xxx.
.....

Ahora con "SAME" padding:

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

Esto da un 5x5 imagen de salida (tamaño 1x5x5x1). Esto se hace centrando el filtro en cada posición de la imagen.

Cualquiera de los productos dot de 5 elementos donde el filtro sobresale más allá del borde de la imagen obtiene un valor de cero.

Así que las esquinas son solo sumas de 4, productos de 5 elementos.


Ahora con múltiples filtros.

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

Esto todavía da una imagen de salida de 5x5, pero con 7 canales (tamaño 1x5x5x7). Donde cada canal es producido por uno de los filtros en el establecer.


Ahora con zancadas 2,2: {[12]]}

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

Ahora el resultado todavía tiene 7 canales, pero es solo 3x3 (tamaño 1x3x3x7).

Esto se debe a que en lugar de centrar los filtros en cada punto de la imagen, los filtros se centran en cada otro punto de la imagen, dando pasos (pasos) de ancho 2. Los x siguientes representan el centro del filtro para cada píxel de salida, en la imagen de entrada.

x.x.x
.....
x.x.x
.....
x.x.x

Y por supuesto la primera dimensión de la entrada es el número de imágenes para poder aplicarlo sobre un lote de 10 imágenes, por ejemplo:

input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

Esto realiza la misma operación, para cada imagen de forma independiente, dando como resultado una pila de 10 imágenes (tamaño 10x3x3x7)

 148
Author: mdaoust,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-07 23:55:56

Solo para agregar a las otras respuestas, debe pensar en los parámetros en

filter = tf.Variable(tf.random_normal([3,3,5,7]))

Como ' 5 ' correspondiente al número de canales en cada filtro. Cada filtro es un cubo 3d, con una profundidad de 5. La profundidad del filtro debe corresponder a la profundidad de la imagen de entrada. El último parámetro, 7, debe ser considerado como el número de filtros en el lote. Simplemente olvídate de que esto es 4D, y en su lugar imagina que tienes un conjunto o un lote de 7 filtros. Lo que haces es crear 7 cubos de filtro con dimensiones (3,3,5).

Es mucho más fácil de visualizar en el dominio de Fourier ya que la convolución se convierte en multiplicación puntual. Para una imagen de entrada de dimensiones (100,100,3) puede reescribir las dimensiones del filtro como

filter = tf.Variable(tf.random_normal([100,100,3,7]))

Para obtener uno de los 7 mapas de características de salida, simplemente realizamos la multiplicación puntual del cubo de filtro con el cubo de imagen, luego sumamos los resultados a través de la dimensión canales/profundidad (aquí es 3), colapsando a una característica 2d (100,100) asignar. Haga esto con cada cubo de filtro, y obtendrá 7 mapas de características 2D.

 8
Author: Val9265,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-14 05:10:21

Traté de implementar conv2d (para mi estudio). Bueno, yo escribí que:

def conv(ix, w):
   # filter shape: [filter_height, filter_width, in_channels, out_channels]
   # flatten filters
   filter_height = int(w.shape[0])
   filter_width = int(w.shape[1])
   in_channels = int(w.shape[2])
   out_channels = int(w.shape[3])
   ix_height = int(ix.shape[1])
   ix_width = int(ix.shape[2])
   ix_channels = int(ix.shape[3])
   filter_shape = [filter_height, filter_width, in_channels, out_channels]
   flat_w = tf.reshape(w, [filter_height * filter_width * in_channels, out_channels])
   patches = tf.extract_image_patches(
       ix,
       ksizes=[1, filter_height, filter_width, 1],
       strides=[1, 1, 1, 1],
       rates=[1, 1, 1, 1],
       padding='SAME'
   )
   patches_reshaped = tf.reshape(patches, [-1, ix_height, ix_width, filter_height * filter_width * ix_channels])
   feature_maps = []
   for i in range(out_channels):
       feature_map = tf.reduce_sum(tf.multiply(flat_w[:, i], patches_reshaped), axis=3, keep_dims=True)
       feature_maps.append(feature_map)
   features = tf.concat(feature_maps, axis=3)
   return features

Espero haberlo hecho correctamente. Comprobado en MNIST, tuvo resultados muy cercanos (pero esta implementación es más lenta). Espero que esto te ayude.

 8
Author: Artem Yaschenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-27 18:32:08

Además de otras respuestas, la operación conv2d está operando en c++ (cpu) o cuda para máquinas gpu que requieren aplanar y remodelar datos de cierta manera y usar la multiplicación de matrices gemmBLAS o cuBLAS(cuda).

 0
Author: karaspd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-05 22:02:59