python conv2d - Quelle est la différence entre le remplissage 'SAME' et 'VALID' dans tf.nn.max_pool de tensorflow?




layer maxpool (9)

Quelle est la différence entre le remplissage 'SAME' et 'VALID' dans tf.nn.max_pool de tensorflow ?

À mon avis, "VALIDE" signifie qu'il n'y aura pas de remplissage de zéro en dehors des bords lorsque nous faisons piscine max.

Selon un guide sur l'arithmétique par convolution pour l'apprentissage en profondeur , il est dit qu'il n'y aura pas de remplissage dans l'opérateur de la piscine, c'est-à-dire qu'il suffit d'utiliser 'VALID' de tensorflow . Mais qu'est-ce que "SAME" padding de max pool dans tensorflow ?


Answers

L'exemple TensorFlow Convolution donne un aperçu de la différence entre SAME et VALID :

  • Pour le remplissage SAME , la hauteur et la largeur de sortie sont calculées comme suit:

out_height = ceil (float (in_height) / float (foulées [1]))

out_width = ceil (float (in_width) / float (foulées [2]))

Et

  • 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 (foulées [1]))

out_width = ceil (float (in_width - largeur_multiplicateur + 1) / float (foulées [2]))


Je vais donner un exemple pour le rendre plus clair:

  • x : image d'entrée de forme [2, 3], 1 canal
  • valid_pad : pool max avec noyau 2x2, foulée 2 et remplissage VALID.
  • same_pad : max pool avec 2x2 noyau, stride 2 et SAME padding (c'est la manière classique d'aller)

Les formes de sortie sont:

  • valid_pad : ici, pas de remplissage donc la forme de sortie est [1, 1]
  • same_pad : ici, nous same_pad l'image à la forme [2, 4] (avec -inf et ensuite nous appliquons max pool), 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 rembourrage:

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

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

Dans cet exemple:

  • Largeur d'entrée = 13
  • Largeur du filtre = 6
  • Foulée = 5

Remarques:

  • "VALID" ne supprime que les colonnes les plus à droite (ou les lignes les plus basses).
  • "SAME" essaye de faire un pad égal gauche et droite, mais si le nombre de colonnes à ajouter est impair, il ajoutera la colonne supplémentaire à droite, comme c'est le cas dans cet exemple (la même logique s'applique verticalement: il peut y avoir une rangée supplémentaire de zéros en bas).

Explication rapide

VALID : n'appliquez pas de remplissage, c'est-à-dire que toutes les dimensions sont valides, de sorte que l'image d'entrée soit entièrement couverte par le filtre et la foulée que vous avez spécifiés.

SAME : Appliquez le remplissage à l'entrée (si nécessaire) afin que l'image d'entrée soit entièrement couverte par le filtre et la foulée que vous avez spécifiés. Pour la foulée 1, cela assurera que la taille de l'image de sortie est la même qu'en entrée.

Remarques

  • Ceci s'applique aux couches coov ainsi qu'aux couches max pool de la même manière
  • Le terme "valide" est un peu un abus de langage parce que les choses ne deviennent pas "invalides" si vous laissez tomber une partie de l'image. Parfois, vous pourriez même vouloir cela. Cela devrait probablement être appelé "NO_PADDING" à la place.
  • Le terme «même» est également inapproprié car il n'a de sens que pour une foulée de 1 lorsque la dimension de sortie est la même que la dimension d'entrée. Pour la foulée de 2, les dimensions de sortie seront de moitié, par exemple. Cela devrait probablement être appelé "AUTO_PADDING" à la place.
  • Dans SAME (c'est-à-dire le mode auto-pad), Tensorflow essayera de répartir le rembourrage uniformément sur la gauche et la droite.
  • En mode VALID (c'est-à-dire sans mode de remplissage), Tensorflow laisse tomber les cellules droites et / ou inférieures si votre filtre et votre foulée ne couvrent pas complètement l'image d'entrée.

Basé sur l'explication here et le suivi de la réponse de Tristan, j'utilise habituellement ces fonctions rapides pour les contrôles de santé mentale.

# 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')

Lorsque la stride est 1 (plus typique avec la convolution que la mise en commun), on peut penser à la distinction suivante:

  • "SAME" : la taille de sortie est la même que la taille d'entrée. Cela nécessite que la fenêtre de filtre glisse à l'extérieur de la carte d'entrée, d'où la nécessité de tamponner.
  • "VALID" : La fenêtre de filtre reste à une position valide dans la carte d'entrée, donc la taille de sortie rétrécit de filter_size - 1 . Aucun remplissage n'a lieu.

Le remplissage est une opération visant à augmenter la taille des données d'entrée. Dans le cas de données en 1 dimension, vous ajoutez / ajoutez juste le tableau avec une constante, en 2-dim 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 zéro et elle est appelée zéro-remplissage.

Voici un exemple de remplissage nul avec p=1 appliqué au tenseur 2-d:

Vous pouvez utiliser un remplissage arbitraire pour votre noyau mais certaines des valeurs de remplissage sont utilisées plus fréquemment que d'autres:

  • Rembourrage VALIDE . Le cas le plus simple, signifie pas de rembourrage du tout. Laissez simplement vos données identiques.
  • SAME rembourrage parfois appelé rembourrage HALF . Il est appelé SAME parce que pour une convolution avec une foulée = 1, (ou pour le regroupement), il devrait produire une sortie de la même taille que l'entrée. Il s'appelle HALF car pour un noyau de taille k
  • Le remplissage complet est le remplissage maximum qui n'aboutit pas à une convolution sur des éléments simplement rembourrés. Pour un noyau de taille k , ce padding est égal à k - 1 .

Pour utiliser un remplissage arbitraire dans TF, vous pouvez utiliser tf.pad()


Je cite cette réponse à partir des documents officiels 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 rembourrage 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 nulles.


Peut-être qu'un exemple de code aidera: Notez la différence entre les signatures d'appel de foo , class_foo et static_foo :

class A(object):
    def foo(self,x):
        print "executing foo(%s,%s)"%(self,x)

    @classmethod
    def class_foo(cls,x):
        print "executing class_foo(%s,%s)"%(cls,x)

    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)"%x    

a=A()

Ci-dessous, la manière habituelle pour une instance d'objet d'appeler une méthode. L'instance d'objet, a , est implicitement passée en tant que premier argument.

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>,1)

Avec classmethods , la classe de l'instance d'objet est implicitement passée en tant que premier argument au lieu de self .

a.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

Vous pouvez également appeler class_foo utilisant la classe. En fait, si vous définissez quelque chose comme étant une méthode de classe, c'est probablement parce que vous avez l'intention de l'appeler depuis la classe plutôt que depuis une instance de classe. A.foo(1) aurait généré une erreur TypeError, mais A.class_foo(1) fonctionne parfaitement:

A.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

Une utilisation que les utilisateurs ont trouvée pour les méthodes de classe consiste à créer des constructeurs alternatifs héritables .

Avec staticmethods , ni self (l'instance de l'objet) ni cls (la classe) ne sont implicitement passés en tant que premier argument. Ils se comportent comme des fonctions simples, sauf que vous pouvez les appeler à partir d'une instance ou de la classe:

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

Staticmethods sont utilisés pour grouper des fonctions qui ont une connexion logique avec une classe à la classe.

foo n'est qu'une fonction, mais lorsque vous appelez a.foo vous n'obtenez pas seulement la fonction, vous obtenez une version "partiellement appliquée" de la fonction, l'instance de l'objet étant liée comme premier argument de la fonction. foo attend 2 arguments, alors que a.foo n'attend qu'un argument.

a est lié à foo . C'est ce que l'on entend par le terme "lié" ci-dessous:

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

Avec a.class_foo , a n'est pas lié à class_foo , mais la classe A est liée à class_foo .

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

Ici, avec une méthode static, même s’il s’agit d’une méthode, a.static_foo ne fait que a.static_foo une bonne fonction ole sans argument lié. static_foo attend 1 argument et a.static_foo attend également 1 argument.

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

Et bien sûr, la même chose se produit lorsque vous appelez static_foo avec la classe A

print(A.static_foo)
# <function static_foo at 0xb7d479cc>




python tensorflow deep-learning