Quelle est la différence entre les remplissages 'SAME' et 'VALID' dans tf.nn.max_pool
de tensorflow
?
À mon avis, 'VALID' signifie qu'il n'y aura pas de remplissage à zéro en dehors des bords lorsque nous faisons un pool maximum.
Selon Un guide d’arithmétique de convolution pour un apprentissage en profondeur , il indique qu’il n’y aura pas d’opérateur de remplissage dans le pool, c’est-à-dire que vous utiliserez simplement 'VALID' de tensorflow
. Mais quel est le remplissage 'SAME' du pool maximum dans tensorflow
?
Je vais donner un exemple pour le rendre plus clair:
x
: image d'entrée de forme [2, 3], 1 canalvalid_pad
: pool maximum avec noyau 2x2, stride 2 et remplissage VALID.same_pad
: pool max avec noyau 2x2, stride 2 et remplissage SAME (c'est le classic way to go)Les formes de sortie sont:
valid_pad
: ici, pas de remplissage, la forme de sortie est donc [1, 1]same_pad
: ici, nous plaçons l'image sur la forme [2, 4] (avec -inf
puis appliquons le pool maximum), donc la forme de sortie est [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 vous aimez l'art ascii:
"VALID"
= sans remplissage:
inputs: 1 2 3 4 5 6 7 8 9 10 11 (12 13)
|________________| dropped
|_________________|
"SAME"
= sans remplissage:
pad| |pad
inputs: 0 |1 2 3 4 5 6 7 8 9 10 11 12 13|0 0
|________________|
|_________________|
|________________|
Dans cet exemple:
Remarques:
"VALID"
ne supprime jamais que les colonnes les plus à droite (ou les lignes les plus basses)."SAME"
essaie de jouer uniformément à gauche et à droite, mais si le nombre de colonnes à ajouter est impair, la colonne supplémentaire sera ajoutée à droite, comme dans cet exemple rangée de zéros en bas).Quand stride
est 1 (plus typique avec convolution que pool), on peut penser à la distinction suivante:
"SAME"
: la taille de sortie est égale à identique en tant que taille d'entrée. Cela nécessite que la fenêtre de filtrage glisse en dehors de la carte d’entrée, d’où la nécessité d’appuyer. "VALID"
: la fenêtre de filtre reste à la position valide dans la mappe d'entrée, ainsi la taille de sortie est réduite de filter_size - 1
. Aucun remplissage ne se produit.Le TensorFlow Convolution example donne un aperçu de la différence entre SAME
et VALID
:
Pour le remplissage SAME
, la hauteur et la largeur en sortie sont calculées comme suit:
out_height = ceil(float(in_height) / float(strides[1]))
out_width = ceil(float(in_width) / float(strides[2]))
Et
Pour le remplissage VALID
, la hauteur et la largeur en sortie sont calculées comme suit:
out_height = ceil(float(in_height - filter_height + 1) / float(strides[1]))
out_width = ceil(float(in_width - filter_width + 1) / float(strides[2]))
Le rembourrage est une opération visant à augmenter la taille des données d'entrée. Dans le cas de données à une dimension, vous ajoutez simplement/préfixez le tableau avec une constante; dans la matrice à 2 dimensions, vous entourez la matrice avec ces constantes. Dans n-dim, vous entourez votre hypercube n-dim avec la constante. Dans la plupart des cas, cette constante est égale à zéro et est appelée zéro-remplissage.
Voici un exemple de remplissage à zéro avec p=1
appliqué au tenseur 2D:
Vous pouvez utiliser un remplissage arbitraire pour votre noyau, mais certaines des valeurs de remplissage sont utilisées plus fréquemment que d'autres:
k
k
, ce remplissage est égal à k - 1
.Pour utiliser un remplissage arbitraire dans TF, vous pouvez utiliser tf.pad()
Explication rapide
VALID
: N'appliquez aucun remplissage, c'est-à-dire que toutes les dimensions sont valid, de sorte que l'image d'entrée soit entièrement couverte par le filtre et la foulée spécifiées.
SAME
: appliquez un remplissage à l'entrée (si nécessaire) de sorte que l'image d'entrée soit entièrement couverte par le filtre et la foulée que vous avez spécifiées. Pour le pas 1, cela garantira que la taille de l'image de sortie est égale à identique comme entrée.
Remarques
NO_PADDING
à la place.AUTO_PADDING
à la place.SAME
(c.-à-d. Le mode de remplissage automatique), Tensorflow essaiera de répartir le remplissage de manière uniforme à gauche et à droite.VALID
(c’est-à-dire sans mode de remplissage), Tensorflow déposera les cellules droites et/ou inférieures si votre filtre et votre foulée ne couvrent pas complètement l’image d’entrée.Il existe trois choix de rembourrage: valide (pas de rembourrage), identique (ou à moitié), complet. Vous pouvez trouver des explications (dans Theano) ici: http://deeplearning.net/software/theano/tutorial/conv_arithmetic.html
Le remplissage valide n'implique aucun remplissage nul, il ne couvre donc que les entrées valides, à l'exclusion des zéros générés artificiellement. La longueur de la sortie est ((la longueur de l'entrée) - (k-1)) pour la taille du noyau k si le pas s = 1.
Le même remplissage fait en sorte que la taille des sorties soit identique à celle des entrées lorsque s = 1. Si s = 1, le nombre de zéros remplis est égal à (k-1).
Le remplissage complet signifie que le noyau fonctionne sur toutes les entrées. Ainsi, aux extrémités, le noyau peut rencontrer la seule entrée et les zéros restants. Le nombre de zéros remplis est 2(k-1) si s = 1. La longueur de la sortie est ((la longueur de l'entrée) + (k-1)) si s = 1.
Par conséquent, le nombre de paddings: (valide) <= (identique) <= (complet)
Je cite cette réponse de la documentation officielle de tensorflow https://www.tensorflow.org/api_guides/python/nn#Convolution Pour le remplissage 'SAME', la hauteur et la largeur de sortie sont calculées comme suit:
out_height = ceil(float(in_height) / float(strides[1]))
out_width = ceil(float(in_width) / float(strides[2]))
et le remplissage en haut et à gauche sont calculés comme suit:
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
Pour le remplissage 'VALID', la hauteur et la largeur de sortie sont calculées comme suit:
out_height = ceil(float(in_height - filter_height + 1) / float(strides[1]))
out_width = ceil(float(in_width - filter_width + 1) / float(strides[2]))
et les valeurs de remplissage sont toujours égales à zéro.
Sur la base de l'explication ici et suite à la réponse de Tristan, j'utilise généralement ces fonctions rapides pour les contrôles de cohérence.
# 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')
Rembourrage on/off. Détermine la taille effective de votre entrée .
VALID:
Pas de remplissage. Les opérations de convolution, etc., ne sont exécutées qu’aux emplacements "valides", c’est-à-dire qu’ils ne sont pas trop proches des limites de votre tenseur.
Avec un noyau de 3x3 et une image de 10x10, vous effectueriez une convolution sur la zone 8x8 à l'intérieur des frontières .
SAME:
Le rembourrage est fourni. Chaque fois que votre opération fait référence à un voisinage (quelle que soit sa taille), des valeurs zéro sont fournies lorsque ce voisinage dépasse du tenseur d'origine pour permettre à cette opération de fonctionner également sur les valeurs de bordure.
Avec un noyau de 3x3 et une image de 10x10, vous effectueriez une convolution sur la totalité de la zone 10x10.
VALIDE padding: c'est un padding nul. J'espère qu'il n'y a pas de confusion.
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)
SAME padding: C'est un peu difficile à comprendre en premier lieu car nous devons considérer 2 conditions séparément comme indiqué dans le document officiel .
Prenons l'entrée comme , sortie en tant que , rembourrage comme , foulée comme et la taille du noyau comme . (singal dimentina est considéré)
Cas 01: :
Cas 02: :
est calculée su que la valeur minimale pouvant être prise pour le remplissage. Depuis la valeur de est connue, valeur de peut être trouvé en utilisant ce formulaire .
Travaillons cet exemple:
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)
Ici la dimension de x est (3,4). Alors si la direction horizontale est prise (3):
Si la direction verticale est prise (4):
J'espère que cela vous aidera à comprendre comment fonctionne réellement LE M&ECIRC;ME remplissage dans TF.