¿Cuál es la diferencia entre el relleno ‘SAME’ y ‘VALID’ en tf.nn.max_pool de tensorflow?

¿Cuál es la diferencia entre el relleno ‘SAME’ y ‘VALID’ en tf.nn.max_pool de tensorflow ?

En mi opinión, “VÁLIDO” significa que no habrá relleno cero fuera de los bordes cuando hagamos el máximo.

De acuerdo con la Guía de aritmética de convolución para el aprendizaje profundo , dice que no habrá relleno en el operador de la piscina, es decir, simplemente use ‘VÁLIDO’ de tensorflow . Pero, ¿cuál es el relleno ‘MISMO’ de la agrupación máxima en tensorflow ?

Daré un ejemplo para que quede más claro:

  • x : imagen de entrada de la forma [2, 3], 1 canal
  • valid_pad : grupo máximo con kernel 2×2, zancada 2 y relleno VÁLIDO.
  • same_pad : max pool con 2×2 kernel, stride 2 y SAME padding (esta es la forma clásica de hacerlo)

Las formas de salida son:

  • valid_pad : aquí, sin relleno, la forma de salida es [1, 1]
  • same_pad : aquí, same_pad la imagen a la forma [2, 4] (con -inf y luego aplicamos max pool), por lo que la forma de salida es [1, 2]

 x = tf.constant([[1., 2., 3.], [4., 5., 6.]]) x = tf.reshape(x, [1, 2, 3, 1]) # give a shape accepted by tf.nn.max_pool valid_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='VALID') same_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME') valid_pad.get_shape() == [1, 1, 1, 1] # valid_pad is [5.] same_pad.get_shape() == [1, 1, 2, 1] # same_pad is [5., 6.] 

Si te gusta el arte ascii:

  • "VALID" = sin relleno:

      inputs: 1 2 3 4 5 6 7 8 9 10 11 (12 13) |________________| dropped |_________________| 
  • "SAME" = con cero relleno:

      pad| |pad inputs: 0 |1 2 3 4 5 6 7 8 9 10 11 12 13|0 0 |________________| |_________________| |________________| 

En este ejemplo:

  • Ancho de entrada = 13
  • Ancho del filtro = 6
  • Stride = 5

Notas:

  • "VALID" solo deja caer las columnas más a la derecha (o las filas más bajas).
  • "SAME" intenta rellenar de manera uniforme hacia la izquierda y hacia la derecha, pero si la cantidad de columnas que se agregarán es impar, agregará la columna adicional a la derecha, como es el caso en este ejemplo (la misma lógica se aplica verticalmente: puede haber una fila extra de ceros en la parte inferior).

Cuando el stride es 1 (más típico con convolución que con agrupamiento), podemos pensar en la siguiente distinción:

  • "SAME" : el tamaño de salida es el mismo que el tamaño de entrada. Esto requiere que la ventana del filtro se deslice fuera del mapa de entrada, de ahí la necesidad de rellenar.
  • "VALID" : la ventana del filtro permanece en una posición válida dentro del mapa de entrada, por lo que el tamaño de salida se reduce por filter_size - 1 . No se produce ningún relleno.

El ejemplo de TensorFlow Convolution ofrece una descripción general de la diferencia entre SAME y VALID :

  • Para el relleno SAME , la altura y el ancho de salida se calculan como:

     out_height = ceil(float(in_height) / float(strides[1])) out_width = ceil(float(in_width) / float(strides[2])) 

Y

  • Para el relleno VALID , la altura y el ancho de salida se calculan como:

     out_height = ceil(float(in_height - filter_height + 1) / float(strides[1])) out_width = ceil(float(in_width - filter_width + 1) / float(strides[2])) 

El relleno es una operación para boost el tamaño de los datos de entrada. En el caso de datos unidimensionales, simplemente agregue / anteponga la matriz con una constante, en 2-dim rodeará la matriz con estas constantes. En n-dim rodeas tu hipercubo n-dim con la constante. En la mayoría de los casos, esta constante es cero y se denomina relleno cero.

Aquí hay un ejemplo de relleno cero con p=1 aplicado al tensor 2-d: introduzca la descripción de la imagen aquí


Puede usar un relleno arbitrario para su kernel, pero algunos de los valores de relleno se usan con más frecuencia que otros:

  • Acolchado VALIDO . El caso más fácil, significa que no hay relleno en absoluto. Solo deja tus datos como estaba.
  • El mismo relleno a veces se llama medio relleno . Se llama SAME porque para una convolución con un paso = 1, (o para agrupación) debe producir una salida del mismo tamaño que la entrada. Se llama HALF porque para un kernel de tamaño k introduzca la descripción de la imagen aquí
  • El relleno COMPLETO es el relleno máximo que no resulta en una convolución sobre los elementos acolchados. Para un núcleo de tamaño k , este relleno es igual a k - 1 .

Para usar un relleno arbitrario en TF, puedes usar tf.pad()

Explicación rápida

VALID : no aplique ningún relleno, es decir, suponga que todas las dimensiones son válidas para que la imagen de entrada se cubra completamente con el filtro y el paso que especificó.

SAME : aplique el relleno a la entrada (si es necesario) para que la imagen de entrada quede totalmente cubierta por el filtro y la zancada que especificó. Para el paso 1, esto asegurará que el tamaño de la imagen de salida sea el mismo que el de la entrada.

Notas

  • Esto se aplica tanto a las capas conv como a las capas de agrupación máx.
  • El término “válido” es un poco inapropiado porque las cosas no se vuelven “inválidas” si sueltas parte de la imagen. En algún momento puede que incluso quieras eso. Esto probablemente debería haberse llamado NO_PADDING en NO_PADDING lugar.
  • El término “igual” también es un nombre inapropiado porque solo tiene sentido para el paso de 1 cuando la dimensión de salida es la misma que la dimensión de entrada. Para un paso de 2, las dimensiones de salida serán la mitad, por ejemplo. Esto probablemente debería haberse llamado AUTO_PADDING en AUTO_PADDING lugar.
  • En SAME (es decir, en el modo auto-pad), Tensorflow intentará repartir el relleno de manera uniforme tanto a la izquierda como a la derecha.
  • En VALID (es decir, sin modo de relleno), Tensorflow caerá hacia la derecha o hacia abajo si el filtro y la zancada no cubren completamente la imagen de entrada.

Hay tres opciones de relleno: válido (sin relleno), igual (o medio), completo. Puede encontrar explicaciones (en Theano) aquí: http://deeplearning.net/software/theano/tutorial/conv_arithmetic.html

  • Válido o sin relleno:

El relleno válido no implica relleno cero, por lo que cubre solo la entrada válida, sin incluir los ceros generados artificialmente. La longitud de la salida es ((la longitud de la entrada) – (k-1)) para el tamaño del núcleo k si la zancada s = 1.

  • Mismo o medio relleno:

El mismo relleno hace que el tamaño de las salidas sea el mismo que el de las entradas cuando s = 1. Si s = 1, el número de ceros rellenados es (k-1).

  • Relleno completo:

El relleno completo significa que el kernel se ejecuta en todas las entradas, por lo que en los extremos, el kernel puede cumplir con la única entrada y con los ceros. El número de ceros rellenados es 2 (k-1) si s = 1. La longitud de salida es ((la longitud de entrada) + (k-1)) si s = 1.

Por lo tanto, el número de rellenos: (válido) <= (igual) <= (completo)

Estoy citando esta respuesta de los documentos oficiales de tensorflow https://www.tensorflow.org/api_guides/python/nn#Convolution Para el relleno ‘SAME’, la altura y el ancho de salida se calculan como:

 out_height = ceil(float(in_height) / float(strides[1])) out_width = ceil(float(in_width) / float(strides[2])) 

y el relleno en la parte superior e izquierda se computan como:

 pad_along_height = max((out_height - 1) * strides[1] + filter_height - in_height, 0) pad_along_width = max((out_width - 1) * strides[2] + filter_width - in_width, 0) pad_top = pad_along_height // 2 pad_bottom = pad_along_height - pad_top pad_left = pad_along_width // 2 pad_right = pad_along_width - pad_left 

Para el relleno ‘VÁLIDO’, la altura y el ancho de salida se calculan como:

 out_height = ceil(float(in_height - filter_height + 1) / float(strides[1])) out_width = ceil(float(in_width - filter_width + 1) / float(strides[2])) 

y los valores de relleno son siempre cero.

Basado en la explicación aquí y en el seguimiento de la respuesta de Tristan, generalmente uso estas funciones rápidas para las comprobaciones de seguridad.

 # a function to help us stay clean def getPaddings(pad_along_height,pad_along_width): # if even.. easy.. if pad_along_height%2 == 0: pad_top = pad_along_height / 2 pad_bottom = pad_top # if odd else: pad_top = np.floor( pad_along_height / 2 ) pad_bottom = np.floor( pad_along_height / 2 ) +1 # check if width padding is odd or even # if even.. easy.. if pad_along_width%2 == 0: pad_left = pad_along_width / 2 pad_right= pad_left # if odd else: pad_left = np.floor( pad_along_width / 2 ) pad_right = np.floor( pad_along_width / 2 ) +1 # return pad_top,pad_bottom,pad_left,pad_right # strides [image index, y, x, depth] # padding 'SAME' or 'VALID' # bottom and right sides always get the one additional padded pixel (if padding is odd) def getOutputDim (inputWidth,inputHeight,filterWidth,filterHeight,strides,padding): if padding == 'SAME': out_height = np.ceil(float(inputHeight) / float(strides[1])) out_width = np.ceil(float(inputWidth) / float(strides[2])) # pad_along_height = ((out_height - 1) * strides[1] + filterHeight - inputHeight) pad_along_width = ((out_width - 1) * strides[2] + filterWidth - inputWidth) # # now get padding pad_top,pad_bottom,pad_left,pad_right = getPaddings(pad_along_height,pad_along_width) # print 'output height', out_height print 'output width' , out_width print 'total pad along height' , pad_along_height print 'total pad along width' , pad_along_width print 'pad at top' , pad_top print 'pad at bottom' ,pad_bottom print 'pad at left' , pad_left print 'pad at right' ,pad_right elif padding == 'VALID': out_height = np.ceil(float(inputHeight - filterHeight + 1) / float(strides[1])) out_width = np.ceil(float(inputWidth - filterWidth + 1) / float(strides[2])) # print 'output height', out_height print 'output width' , out_width print 'no padding' # use like so getOutputDim (80,80,4,4,[1,1,1,1],'SAME') 

Relleno de encendido / apagado. Determina el tamaño efectivo de su entrada.

VALID: Sin relleno. Las operaciones de convolución, etc., solo se realizan en ubicaciones que son “válidas”, es decir, no muy cerca de los límites de su tensor.
Con un núcleo de 3×3 y una imagen de 10×10, estarías realizando una convolución en el área 8×8 dentro de los bordes.

SAME: Se proporciona relleno. Cuando su operación hace referencia a un vecindario (sin importar cuán grande sea), se proporcionan valores cero cuando ese vecindario se extiende fuera del tensor original para permitir que la operación funcione también en valores de borde.
Con un núcleo de 3×3 y una imagen de 10×10, estarías realizando una convolución en el área completa de 10×10.

Relleno VÁLIDO : esto es con relleno cero. Espero que no haya confusión.

 x = tf.constant([[1., 2., 3.], [4., 5., 6.],[ 7., 8., 9.], [ 7., 8., 9.]]) x = tf.reshape(x, [1, 4, 3, 1]) valid_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='VALID') print (valid_pad.get_shape()) # output-->(1, 2, 1, 1) 

MISMO relleno: esto es un poco difícil de entender en primer lugar porque tenemos que considerar 2 condiciones por separado como se menciona en los documentos oficiales .

Tomemos entrada como , salida como , relleno como caminar como y el tamaño del núcleo como . (se considera dimentina singal)

Caso 01: :

Caso 02: :

Se calcula que el valor mínimo que se puede tomar para el relleno. Dado que el valor de es conocido, valor de Se puede encontrar utilizando este formulario. .

Vamos a trabajar en este ejemplo:

 x = tf.constant([[1., 2., 3.], [4., 5., 6.],[ 7., 8., 9.], [ 7., 8., 9.]]) x = tf.reshape(x, [1, 4, 3, 1]) same_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME') print (same_pad.get_shape()) # --> output (1, 2, 2, 1) 

Aquí la dimensión de x es (3,4). Luego, si se toma la dirección horizontal (3):

Si se toma la dirección vertical (4):

Espero que esto ayude a entender cómo funciona realmente el mismo relleno en TF.

Formula general

Aquí, W y H son el ancho y el alto de la entrada, F son las dimensiones del filtro, P es el tamaño del relleno (es decir, el número de filas o columnas que se deben rellenar)

Para el mismo relleno:

El mismo relleno

Para el relleno VÁLIDO:

Acolchado VALIDO