web-dev-qa-db-fra.com

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

La documentation de l'opération conv2d_transpose() n'explique pas clairement ce qu'elle fait:

La transposition de conv2d.

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

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

Que fait cette opération et quels sont les exemples de pourquoi vous voudriez l’utiliser?

31
MiniQuark

C’est la meilleure explication que j’ai vue en ligne sur la façon dont la transposition par convolution fonctionne est ici .

Je vais donner ma propre courte description. Il applique la convolution avec une foulée fractionnelle. En d'autres termes, espacer les valeurs d'entrée (avec des zéros) pour appliquer le filtre sur une région potentiellement plus petite que la taille du filtre. 

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

28
Steven

Voici un autre point de vue de la perspective "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 le calcul effectué dans conv2d_transpose, je recommanderais vivement cet article , à partir de la page 19.

Quatre fonctions connexes

Dans 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

Résumé en une phrase: elles ne sont que des 2èmes convolutions. Leurs différences sont dans l'ordre des arguments d'entrée, la rotation ou la transposition d'entrée, les foulées (y compris la taille de foulée fractionnelle), les bourrages, etc. Avec tf.nn.conv2d, vous pouvez implémenter les 3 autres opérations en transformant les entrées et en modifiant les arguments conv2d.

Problème Paramètres

  • 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 en arrière, supposons que nous recevons d_out, qui est le gradient w.r.t. out. Notre objectif est de trouver d_x et d_w, qui sont le dégradé w.r.t. x et w respectivement.

Pour faciliter la discussion, supposons:

  • Toute la taille de la foulée doit être 1
  • Tous in_channels et out_channels sont 1
  • Utiliser le remplissage VALID
  • Filtre de nombre impair, cela évite certains problèmes 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 à 180 degrés (une inversion gauche-droite et une inversion vers le haut), FULL signifie "appliquer un filtre chaque fois qu'il chevauche partiellement l'entrée" (voir theeano docs ). Note que ceci n'est valide qu'avec les hypothèses précédentes, cependant, on peut changer les arguments de conv2d pour le généraliser.

Les points à retenir:

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

Longue réponse

Maintenant, donnons un exemple de code de travail réel montrant comment utiliser les 4 fonctions ci-dessus pour calculer d_x et d_w étant donné d_out. Cela montre comment conv2d, conv2d_backprop_filter, conv2d_backprop_input, et conv2d_transpose sont liés l'un à l'autre. Veuillez trouver les scripts complets ici

Computing d_x de 4 manières 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')

Computing 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 les scripts complets pour l’implémentation de tf_rot180, tf_pad_to_full_conv2d, tf_NHWC_to_HWIO. Dans les scripts, nous vérifions que les valeurs de sortie finales de différentes méthodes sont les mêmes; une implémentation numpy est également disponible. 

21
Yixing

conv2d_transpose () transpose simplement les poids et les retourne à 180 degrés. Ensuite, il applique la norme conv2d (). "Transpose" signifie pratiquement qu'il change l'ordre des "colonnes" dans le tenseur de poids. Veuillez vérifier 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, poids de forme = [7, 7, 1, 32], nom = W_conv1

Si nous effectuons une convolution de l'entrée, les activations du auront alors une forme: [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", vous pouvez utiliser conv2d_transpose () sur les activations de convolution de la manière suivante:

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

OU en utilisant conv2d (), nous devons transposer et retourner 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]. Alors de [7, 7, 1, 32] nous obtiendrons un tenseur de forme = [7,7,32,1]. Ensuite, nous retournons 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]))

Ensuite, nous pouvons calculer la convolution avec conv2d () comme suit: 

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

Et nous obtiendrons le même résultat qu'avant. De plus, le même résultat peut être obtenu 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 affichés ici:

Test de conv2d (), conv2d_tranposed () et conv2d_backprop_input ()

Nous pouvons voir que les résultats sont les mêmes. Pour mieux le voir, veuillez vérifier mon code sur:

https://github.com/simo23/conv2d_transpose

Ici, je réplique la sortie de la fonction conv2d_transpose () en utilisant la norme conv2d ().

11
simo23

Une application pour conv2d_transpose est la conversion ascendante. Voici un exemple qui explique son fonctionnement

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)
0
pentadecagon