neural network - network - ¿Qué hace tf.nn.conv2d en tensorflow?
tensorflow text classification (5)
Además de otras respuestas, la operación conv2d está operando en c ++ (cpu) o cuda para máquinas gpu que requiere aplanar y remodelar datos de cierta manera y usar la multiplicación de matrices gemmBLAS o cuBLAS (cuda).
Estaba mirando los documentos de tensorflow sobre
tf.nn.conv2d
here
.
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 2D con forma
[filter_height * filter_width * in_channels, output_channels]
.
¿Y ahora qué hace eso? ¿Es esa multiplicación por elementos o simplemente multiplicación de matriz simple? Tampoco pude 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 de forma virtual
[batch, out_height, out_width, filter_height * filter_width * in_channels]
.# 3: Para cada parche, multiplica a la derecha la matriz del filtro y el vector del parche de imagen.
Sería realmente útil si alguien pudiera dar un ejemplo, un fragmento de código (extremadamente útil) tal vez y explicar lo que está sucediendo 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í, no puedo entender.
Intenté 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 de redes neuronales convolucionales. Los estudié here . Pero la implementación en tensorflow no es lo que esperaba. Entonces planteó la pregunta.
EDITAR : Entonces, implementé un código mucho más simple. Pero no puedo entender qué está pasando. Me refiero a cómo son los resultados así. Sería extremadamente útil si alguien pudiera decirme qué proceso produce este resultado.
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]]]]
La convolución 2D se calcula de forma similar a la que se calcularía la convolución 1D : desliza el núcleo sobre la entrada, calcula las multiplicaciones por elementos y las suma. Pero en lugar de que su núcleo / entrada sea una matriz, aquí están las matrices.
En el ejemplo más básico no hay relleno y zancada = 1.
Supongamos que su
input
y
kernel
son:
Cuando use su núcleo, recibirá el siguiente resultado: , 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
here
de TF calcula las convoluciones en lotes y utiliza un formato ligeramente diferente.
Para una entrada es
[batch, in_height, in_width, in_channels]
para el kernel es
[filter_height, filter_width, in_channels, out_channels]
.
Por lo tanto, debemos 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'')
Luego, 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 al que calculamos a mano.
Para ejemplos con relleno / zancadas, eche un vistazo aquí .
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 alto x ancho x canales x número de filtros).
Para este caso simple, la imagen resultante de 2x2 y 1 canal (tamaño 1x2x2x1, número de imágenes x alto x ancho xx canales) es el resultado de multiplicar el valor del filtro por cada píxel de la imagen.
Ahora intentemos 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 5 canales cada uno. 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 9 productos de punto de 5 elementos. Pero podría llamarlo un producto de punto de 45 elementos.
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 3x3 de 1 canal (tamaño 1x3x3x1). Cada uno de estos valores es una suma de 9 productos de puntos de 5 elementos.
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 sobresalga.
Las
x
s a continuación representan los centros de filtro para cada píxel de salida.
.....
.xxx.
.xxx.
.xxx.
.....
Ahora con el relleno "MISMO":
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 proporciona una imagen de salida de 5x5 (tamaño 1x5x5x1). Esto se hace centrando el filtro en cada posición de la imagen.
Cualquiera de los productos de puntos de 5 elementos donde el filtro sobresale más allá del borde de la imagen obtiene un valor de cero.
Por lo tanto, las esquinas son solo sumas de 4 productos de punto 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 conjunto.
Ahora con zancadas 2,2:
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 solo es 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 cualquier otro punto de la imagen, tomando pasos (zancadas) de ancho 2. Las
x
debajo 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 la cantidad de imágenes para que pueda aplicarla en 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)
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 de su filtro debe corresponder con la profundidad de su imagen de entrada. El último parámetro, 7, debe considerarse como el número de filtros en el lote. Solo olvídate de que esto es 4D y, en cambio, imagina que tienes un conjunto o un lote de 7 filtros. Lo que debe hacer es crear 7 cubos de filtro con dimensiones (3,3,5).
Es mucho más fácil 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 del filtro con el cubo de la imagen, luego sumamos los resultados a través de los canales / dimensión de profundidad (aquí es 3), colapsando a 2d (100,100) mapa de características. Haga esto con cada cubo de filtro, y obtendrá 7 mapas de características 2D.
Traté de implementar conv2d (para mis estudios). Bueno, escribí eso:
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. Marcado en MNIST, tuvo resultados muy cercanos (pero esta implementación es más lenta). Espero que esto te ayude.