45 votes

Que fait l'opération `conv2d_transpose()` de TensorFlow ?

La documentation relative à la conv2d_transpose() n'explique pas clairement ce qu'il fait :

La transposition de conv2d.

Cette opération est parfois appelée "déconvolution" après que Réseaux déconvolutionnels mais c'est en fait la transposition (gradient) de conv2d plutôt qu'une déconvolution réelle.

J'ai parcouru le document que le doc indique, mais cela n'a pas aidé.

Que fait cette opération et quels sont les exemples de raisons pour lesquelles vous voudriez l'utiliser ?

49voto

Steven Points 3487

C'est la meilleure explication que j'ai vue en ligne sur le fonctionnement de la convolution transposée. ici .

Je vais vous donner une brève description. Il applique une convolution avec un pas fractionnaire. En d'autres termes, elle espace les valeurs d'entrée (avec des zéros) pour appliquer le filtre sur une région qui est potentiellement plus petite que la taille du filtre.

Quant à la raison pour laquelle on voudrait l'utiliser. Elle peut être utilisée comme une sorte de suréchantillonnage avec des poids appris, par opposition à l'interpolation bilinéaire ou à une autre forme fixe de suréchantillonnage.

1 votes

Merci pour votre réponse, je ne sais pas pourquoi elle a été déclassée.

0 votes

Avez-vous d'autres questions à ce sujet ? Je peux aussi mettre à jour ma réponse ou répondre dans les commentaires.

3 votes

Donc, en théorie, vous pouvez obtenir un résultat similaire en utilisant le conv2d normal, mais d'abord en mettant en mosaïque l'image originale pour qu'elle soit plus grande au départ ? Et conv2d_transpose est juste un moyen plus facile de faire à peu près la même chose ?

29voto

Yixing Points 391

Voici un autre point de vue, du point de vue des "gradients", c'est-à-dire pourquoi la documentation de TensorFlow indique que conv2d_transpose() est "en fait la transposition ( gradient ) de conv2d plutôt qu'une déconvolution réelle". Pour plus de détails sur les calculs effectués dans le cadre du projet conv2d_transpose Je recommande vivement cet article à partir de la page 19.

Quatre fonctions connexes

Sur tf.nn il existe 4 fonctions étroitement liées et plutôt déroutantes pour la convolution 2d :

  • tf.nn.conv2d
  • tf.nn.conv2d_backprop_filter
  • tf.nn.conv2d_backprop_input
  • tf.nn.conv2d_transpose

Un résumé en une phrase : ce ne sont que des circonvolutions 2d . Leurs différences se situent au niveau de l'ordre des arguments d'entrée, de la rotation ou de la transposition de l'entrée, des pas (y compris la taille des pas fractionnaires), des paddings, etc. Avec tf.nn.conv2d en main, on peut mettre en œuvre les 3 autres opérations en transformant les entrées et en changeant les conv2d arguments.

Paramètres du problème

  • Calculs en avant et en arrière :

    forward

    out = conv2d(x, w)

    backward, given d_out

    => find d_x? => find d_w?

Dans le calcul en avant, nous calculons la convolution de l'image d'entrée x avec le filtre w et le résultat est out . Dans le calcul à rebours, supposons que l'on nous donne d_out qui est le gradient par rapport à la réalité. out . Notre objectif est de trouver d_x et d_w qui sont les gradients par rapport à la réalité. x et w respectivement.

Pour faciliter la discussion, nous supposons :

  • La taille de toutes les foulées doit être 1
  • Tous in_channels et out_channels sont 1
  • Utilisez VALID rembourrage
  • Taille du filtre en nombre impair, ce qui permet d'éviter un problème de forme asymétrique.

Réponse courte

Conceptuellement, avec les hypothèses ci-dessus, nous avons les relations suivantes :

out = conv2d(x, w, padding='VALID')
d_x = conv2d(d_out, rot180(w), padding='FULL')
d_w = conv2d(x, d_out, padding='VALID')

rot180 est une matrice 2d pivotée de 180 degrés (un retournement gauche-droite et un retournement haut-bas), FULL signifie "appliquer le filtre partout où il recouvre partiellement l'entrée" (voir docs sur le théo ). Note que ceci n'est valable qu'avec les hypothèses ci-dessus Cependant, on peut changer les arguments de conv2d pour le généraliser.

Les principaux points à retenir :

  • Le gradient d'entrée d_x est la convolution du gradient de sortie d_out et le poids w avec quelques modifications.
  • Le gradient de poids d_w est la convolution de l'entrée x et le gradient de sortie d_out avec quelques modifications.

Réponse longue

Maintenant, donnons un exemple concret de code qui montre comment utiliser les 4 fonctions ci-dessus pour calculer d_x et d_w donné d_out . Cela montre comment conv2d , conv2d_backprop_filter , conv2d_backprop_input et conv2d_transpose sont liés les uns aux autres. Veuillez trouver les scripts complets ici .

Informatique d_x de 4 façons différentes :

# Method 1: TF's autodiff
d_x = tf.gradients(f, x)[0]

# Method 2: manually using conv2d
d_x_manual = tf.nn.conv2d(input=tf_pad_to_full_conv2d(d_out, w_size),
                          filter=tf_rot180(w),
                          strides=strides,
                          padding='VALID')

# Method 3: conv2d_backprop_input
d_x_backprop_input = tf.nn.conv2d_backprop_input(input_sizes=x_shape,
                                                 filter=w,
                                                 out_backprop=d_out,
                                                 strides=strides,
                                                 padding='VALID')

# Method 4: conv2d_transpose
d_x_transpose = tf.nn.conv2d_transpose(value=d_out,
                                       filter=w,
                                       output_shape=x_shape,
                                       strides=strides,
                                       padding='VALID')

Informatique d_w de 3 manières différentes :

# Method 1: TF's autodiff
d_w = tf.gradients(f, w)[0]

# Method 2: manually using conv2d
d_w_manual = tf_NHWC_to_HWIO(tf.nn.conv2d(input=x,
                                          filter=tf_NHWC_to_HWIO(d_out),
                                          strides=strides,
                                          padding='VALID'))

# Method 3: conv2d_backprop_filter
d_w_backprop_filter = tf.nn.conv2d_backprop_filter(input=x,
                                                   filter_sizes=w_shape,
                                                   out_backprop=d_out,
                                                   strides=strides,
                                                   padding='VALID')

Veuillez consulter le scripts complets pour la mise en œuvre de tf_rot180 , tf_pad_to_full_conv2d , tf_NHWC_to_HWIO . Dans les scripts, nous vérifions que les valeurs de sortie finales des différentes méthodes sont les mêmes ; une implémentation numpy est également disponible.

0 votes

Qu'est-ce que "f" dans l'exemple de la longue distance ? Je crois que vous avez quelques fautes de frappe : tf_rot180, etc. Très instructif !

2 votes

@RobertLugg f est une fonction différentiable arbitraire (typiquement une valeur scalaire ici) calculée sur x . tf_rot180 est une fonction personnalisée utilisée à des fins d'illustration, veuillez consulter les scripts complets fournis dans la réponse pour plus de détails.

1 votes

Pour effectuer le passage en avant, nous multiplions chaque valeur de l'entrée avec le filtre et nous obtenons une entrée mise à l'échelle par le filtre, donc nous échantillonnons vers le haut. Pendant le passage en arrière, nous faisons l'inverse, nous convolvons sur les deltas de la couche suivante, n'est-ce pas ?

14voto

simo23 Points 196

Conv2d_transpose() transpose simplement les poids et les retourne de 180 degrés. Ensuite, elle applique la méthode standard conv2d(). "Transpose" signifie pratiquement qu'elle change l'ordre des "colonnes" dans le tenseur des poids. Veuillez consulter l'exemple ci-dessous.

Voici un exemple qui utilise des convolutions avec stride=1 et padding='SAME'. C'est un cas simple mais le même raisonnement pourrait être appliqué aux autres cas.

Disons que nous avons :

  • Entrée : Image MNIST de 28x28x1, forme = [28,28,1].
  • Couche convolutive : 32 filtres de 7x7, forme des poids = [7, 7, 1, 32], nom = W_conv1

Si nous effectuons une convolution de l'entrée, les activations des auront la forme suivante : [1,28,28,32].

 activations = sess.run(h_conv1,feed_dict={x:np.reshape(image,[1,784])})

Où :

 W_conv1 = weight_variable([7, 7, 1, 32])
 b_conv1 = bias_variable([32])
 h_conv1 = conv2d(x, W_conv1, strides=[1, 1, 1, 1], padding='SAME') + b_conv1

Pour obtenir la "déconvolution" ou la "convolution transposée", nous pouvons utiliser conv2d_transpose() sur les activations de la convolution de cette manière :

  deconv = conv2d_transpose(activations,W_conv1, output_shape=[1,28,28,1],padding='SAME')

OU en utilisant conv2d() nous devons transposer et inverser les poids :

  transposed_weights = tf.transpose(W_conv1, perm=[0, 1, 3, 2])

Ici, nous changeons l'ordre des "colonnes" de [0,1,2,3] à [0,1,3,2]. Ainsi, à partir de [7, 7, 1, 32], nous obtiendrons un tenseur de forme=[7,7,32,1]. Ensuite, nous inversons les poids :

  for i in range(n_filters):
      # Flip the weights by 180 degrees
      transposed_and_flipped_weights[:,:,i,0] =  sess.run(tf.reverse(transposed_weights[:,:,i,0], axis=[0, 1]))

Nous pouvons alors calculer la convolution avec conv2d() comme :

  strides = [1,1,1,1]
  deconv = conv2d(activations,transposed_and_flipped_weights,strides=strides,padding='SAME')

Et nous obtiendrons le même résultat que précédemment. On peut également obtenir le même résultat avec conv2d_backprop_input() en utilisant :

   deconv = conv2d_backprop_input([1,28,28,1],W_conv1,activations, strides=strides, padding='SAME')

Les résultats sont présentés ici :

Test des fonctions conv2d(), conv2d_tranposed() et conv2d_backprop_input()

Nous pouvons constater que les résultats sont les mêmes. Pour le voir d'une meilleure façon, veuillez consulter mon code à l'adresse suivante :

https://github.com/simo23/conv2d_transpose

Ici, je reproduis la sortie de la fonction conv2d_transpose() en utilisant la fonction standard conv2d().

0 votes

Merci pour votre explication ! Pourriez-vous m'aider en expliquant les détails de l'implémentation de cette couche ? La passe avant consiste simplement à mettre à l'échelle les valeurs de l'entrée par les poids des filtres. Selon le pas, la mise à l'échelle se chevauchera. Pendant le passage en arrière, c'est la même chose qu'une convolution régulière sur les deltas, non ?

5voto

pentadecagon Points 2688

Une application de conv2d_transpose est l'upscaling, voici un exemple qui explique comment cela fonctionne :

a = np.array([[0, 0, 1.5],
              [0, 1, 0],
              [0, 0, 0]]).reshape(1,3,3,1)

filt = np.array([[1, 2],
                 [3, 4.0]]).reshape(2,2,1,1)

b = tf.nn.conv2d_transpose(a,
                           filt,
                           output_shape=[1,6,6,1],
                           strides=[1,2,2,1],
                           padding='SAME')

print(tf.squeeze(b))

tf.Tensor(
[[0.  0.  0.  0.  1.5 3. ]
 [0.  0.  0.  0.  4.5 6. ]
 [0.  0.  1.  2.  0.  0. ]
 [0.  0.  3.  4.  0.  0. ]
 [0.  0.  0.  0.  0.  0. ]
 [0.  0.  0.  0.  0.  0. ]], shape=(6, 6), dtype=float64)

2voto

irudyak Points 39

Voici une explication simple de ce qui se passe dans un cas particulier qui est utilisé dans U-Net - c'est l'un des principaux cas d'utilisation de la convolution transposée.

Nous sommes intéressés par la couche suivante :

Conv2DTranspose(64, (2, 2), strides=(2, 2))

Que fait cette couche exactement ? Peut-on reproduire ses travaux ?

Voici le réponse :

  • Tout d'abord, le rembourrage par défaut dans ce cas est valide. Cela signifie que nous n'avons pas de remplissage.
  • La taille de la sortie sera 2 fois plus grande : si l'entrée est (m, n), la sortie sera (2m, 2n). Pourquoi cela ? Voir le point suivant.
  • Prenez le premier élément de l'entrée et multipliez-le par les poids de filtre de forme (2,2). Mettez-le dans la sortie. Prenez l'élément suivant, multipliez-le et mettez-le dans la sortie à côté du premier résultat sans le superposer. Pourquoi cela ? Nous avons des foulées (2, 2).

Voici un exemple d'entrée et de sortie (voir détails) ici et ici ) :

In [15]: X.reshape(n, m)
Out[15]:
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
In [16]: y_resh
Out[16]:
array([[ 0.,  0.,  1.,  1.,  2.,  2.,  3.,  3.,  4.,  4.],
       [ 0.,  0.,  1.,  1.,  2.,  2.,  3.,  3.,  4.,  4.],
       [ 5.,  5.,  6.,  6.,  7.,  7.,  8.,  8.,  9.,  9.],
       [ 5.,  5.,  6.,  6.,  7.,  7.,  8.,  8.,  9.,  9.],
       [10., 10., 11., 11., 12., 12., 13., 13., 14., 14.],
       [10., 10., 11., 11., 12., 12., 13., 13., 14., 14.]], dtype=float32)

Cette diapositive de la cs231n de Stanford est utile pour notre question :

enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X