web-dev-qa-db-fra.com

Que fait tf.nn.conv2d dans tensorflow?

Je regardais la documentation de tensorflow à propos de tf.nn.conv2dici . Mais je ne peux pas comprendre ce qu’il fait ou ce qu’il essaie de réaliser. Il est dit sur les docs, 

# 1: aplatit le filtre en une matrice 2D avec une forme 

[filter_height * filter_width * in_channels, output_channels].

Maintenant qu'est-ce que ça fait? S'agit-il d'une multiplication d'éléments ou simplement d'une multiplication matricielle? Je ne pouvais pas non plus comprendre les deux autres points mentionnés dans la documentation. Je les ai écris ci-dessous:

N ° 2: Extrait les patchs d'image du tenseur d'entrée pour former un tenseur virtuel de forme 

[batch, out_height, out_width, filter_height * filter_width * in_channels].

# 3: Pour chaque patch, multiplie à droite la matrice de filtre et le vecteur de patch d'image.

Ce serait vraiment utile si quelqu'un pouvait donner un exemple, un morceau de code (extrêmement utile) et peut-être expliquer ce qui se passe là-bas et pourquoi l'opération est comme ça.

J'ai essayé de coder une petite partie et d'imprimer la forme de l'opération. Pourtant, je ne peux pas comprendre. 

J'ai essayé quelque chose comme ça:

op = tf.shape(tf.nn.conv2d(tf.random_normal([1,10,10,10]), 
              tf.random_normal([2,10,10,10]), 
              strides=[1, 2, 2, 1], padding='SAME'))

with tf.Session() as sess:
    result = sess.run(op)
    print(result)

Je comprends des fragments de réseaux de neurones convolutifs. Je les ai étudiées ici . Mais l'implémentation sur tensorflow n'est pas ce que je pensais. Cela a donc posé la question.

EDIT: J'ai donc implémenté un code beaucoup plus simple. Mais je ne peux pas comprendre ce qui se passe. Je veux dire comment les résultats sont comme ça. Il serait extrêmement utile que quelqu'un me dise quel processus donne ce résultat.

input = tf.Variable(tf.random_normal([1,2,2,1]))
filter = tf.Variable(tf.random_normal([1,1,1,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
init = tf.initialize_all_variables()
with tf.Session() as sess:
    sess.run(init)

    print("input")
    print(input.eval())
    print("filter")
    print(filter.eval())
    print("result")
    result = sess.run(op)
    print(result)

sortie

input
[[[[ 1.60314465]
   [-0.55022103]]

  [[ 0.00595062]
   [-0.69889867]]]]
filter
[[[[-0.59594476]]]]
result
[[[[-0.95538563]
   [ 0.32790133]]

  [[-0.00354624]
   [ 0.41650501]]]]
114
Shubhashis

La convolution 2D est calculée de la même manière que si vous calculiez convolution 1D : vous faites glisser votre noyau sur l’entrée, calculez les multiplications par élément et les additionnez. Mais au lieu que votre noyau/votre entrée soit un tableau, ce sont des matrices.


Dans l'exemple le plus élémentaire, il n'y a pas de remplissage et stride = 1. Supposons que vos input et kernel sont:  enter image description here

Lorsque vous utilisez votre noyau, vous recevrez le résultat suivant:  enter image description here , qui est calculé de la manière suivante:

  • 14 = 4 * 1 + 3 * 0 + 1 * 1 + 2 * 2 + 1 * 1 + 0 * 0 + 1 * 0 + 2 * 0 + 4 * 1
  • 6 = 3 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 0 * 1 + 1 * 0 + 2 * 0 + 4 * 0 + 1 * 1
  • 6 = 2 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 2 * 1 + 4 * 0 + 3 * 0 + 1 * 0 + 0 * 1
  • 12 = 1 * 1 + 0 * 0 + 1 * 1 + 2 * 2 + 4 * 1 + 1 * 0 + 1 * 0 + 0 * 0 + 2 * 1

La fonction conv2d de TF calcule les convolutions par lots et utilise un format légèrement différent. Pour une entrée, il s'agit de [batch, in_height, in_width, in_channels] pour le noyau, il s'agit de [filter_height, filter_width, in_channels, out_channels]. Nous devons donc fournir les données dans le format correct:

import tensorflow as tf
k = tf.constant([
    [1, 0, 1],
    [2, 1, 0],
    [0, 0, 1]
], dtype=tf.float32, name='k')
i = tf.constant([
    [4, 3, 1, 0],
    [2, 1, 0, 1],
    [1, 2, 4, 1],
    [3, 1, 0, 2]
], dtype=tf.float32, name='i')
kernel = tf.reshape(k, [3, 3, 1, 1], name='kernel')
image  = tf.reshape(i, [1, 4, 4, 1], name='image')

Ensuite, la convolution est calculée avec:

res = tf.squeeze(tf.nn.conv2d(image, kernel, [1, 1, 1, 1], "VALID"))
# VALID means no padding
with tf.Session() as sess:
   print sess.run(res)

Et sera équivalent à celui que nous avons calculé à la main. 


Pour des exemples avec padding/strides, jetez un œil ici .

42
Salvador Dali

Ok, je pense que c’est la manière la plus simple d’expliquer tout ça.


Votre exemple est 1 image, taille 2x2, avec 1 canal. Vous avez 1 filtre de taille 1x1 et 1 canal (la taille est hauteur x largeur x canaux x nombre de filtres). 

Pour ce cas simple, l'image de 2x2, 1 canal résultante (taille 1x2x2x1, nombre d'images x hauteur x largeur x x canaux) est le résultat de la multiplication de la valeur du filtre par chaque pixel de l'image.


Essayons maintenant plus de canaux:

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

Ici, l'image 3x3 et le filtre 1x1 ont chacun 5 canaux. L'image résultante sera 3x3 avec 1 canal (taille 1x3x3x1), où la valeur de chaque pixel est le produit scalaire sur les canaux du filtre avec le pixel correspondant dans l'image d'entrée.


Maintenant avec un filtre 3x3

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

Ici nous obtenons une image 1x1, avec 1 canal (taille 1x1x1x1). La valeur est la somme des 9 produits à 5 éléments. Mais vous pouvez simplement appeler cela un produit scalaire à 45 éléments.


Maintenant avec une image plus grande

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

La sortie est une image 3x3 1 canal (taille 1x3x3x1). Chacune de ces valeurs est une somme de 9 points produits à 5 éléments. 

Chaque sortie est réalisée en centrant le filtre sur l’un des 9 pixels centraux de l’image d’entrée, afin qu’aucun des filtres ne dépasse. Le xs ci-dessous représente les centres de filtrage pour chaque pixel de sortie.

.....
.xxx.
.xxx.
.xxx.
.....

Maintenant, avec "SAME" padding:

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

Cela donne une image de sortie 5x5 (taille 1x5x5x1). Ceci est fait en centrant le filtre à chaque position sur l'image. 

Tous les produits scalaires à 5 éléments dont le filtre dépasse le contour de l’image obtiennent une valeur de zéro. 

Ainsi, les angles ne sont que des sommes de produits scalaires à 4, 5 éléments.


Maintenant avec plusieurs filtres.

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

Cela donne quand même une image de sortie 5x5, mais avec 7 canaux (taille 1x5x5x7). Où chaque canal est produit par l'un des filtres de l'ensemble.


Maintenant à grands pas 2,2:

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

Maintenant, le résultat a toujours 7 canaux, mais seulement 3x3 (taille 1x3x3x7).

En effet, au lieu de centrer les filtres sur chaque point de l’image, les filtres sont centrés sur tous les autres points de l’image, en effectuant des pas (enjambées) de largeur 2. Le nom x représente le centre du filtre pour chaque pixel image d'entrée.

x.x.x
.....
x.x.x
.....
x.x.x

Et bien sûr, la première dimension de l’entrée est le nombre d’images afin que vous puissiez l’appliquer sur un lot de 10 images, par exemple:

input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

Ceci effectue la même opération, pour chaque image indépendamment, donnant une pile de 10 images comme résultat (taille 10x3x3x7) 

154
mdaoust

J'ai essayé d'implémenter conv2d (pour mes études). Eh bien, j'ai écrit ça:

def conv(ix, w):
   # filter shape: [filter_height, filter_width, in_channels, out_channels]
   # flatten filters
   filter_height = int(w.shape[0])
   filter_width = int(w.shape[1])
   in_channels = int(w.shape[2])
   out_channels = int(w.shape[3])
   ix_height = int(ix.shape[1])
   ix_width = int(ix.shape[2])
   ix_channels = int(ix.shape[3])
   filter_shape = [filter_height, filter_width, in_channels, out_channels]
   flat_w = tf.reshape(w, [filter_height * filter_width * in_channels, out_channels])
   patches = tf.extract_image_patches(
       ix,
       ksizes=[1, filter_height, filter_width, 1],
       strides=[1, 1, 1, 1],
       rates=[1, 1, 1, 1],
       padding='SAME'
   )
   patches_reshaped = tf.reshape(patches, [-1, ix_height, ix_width, filter_height * filter_width * ix_channels])
   feature_maps = []
   for i in range(out_channels):
       feature_map = tf.reduce_sum(tf.multiply(flat_w[:, i], patches_reshaped), axis=3, keep_dims=True)
       feature_maps.append(feature_map)
   features = tf.concat(feature_maps, axis=3)
   return features

J'espère l'avoir fait correctement. Vérifié sur MNIST, a eu des résultats très proches (mais cette mise en œuvre est plus lente). J'espère que ceci vous aide.

8
Artem Yaschenko

Pour ajouter aux autres réponses, vous devriez penser aux paramètres de 

filter = tf.Variable(tf.random_normal([3,3,5,7]))

«5» correspondant au nombre de canaux dans chaque filtre. Chaque filtre est un cube 3D d'une profondeur de 5. Votre profondeur de filtre doit correspondre à la profondeur de votre image d'entrée. Le dernier paramètre, 7, doit être considéré comme le nombre de filtres dans le lot. Oubliez cela, étant 4D, et imaginez plutôt que vous avez un ensemble ou un lot de 7 filtres. Ce que vous faites est de créer 7 filtres cubes avec des dimensions (3,3,5).

Il est beaucoup plus facile de visualiser dans le domaine de Fourier puisque la convolution devient une multiplication ponctuelle. Pour une image d'entrée de dimensions (100,100,3), vous pouvez réécrire les dimensions du filtre en tant que

filter = tf.Variable(tf.random_normal([100,100,3,7]))

Pour obtenir l'une des 7 cartes de caractéristiques en sortie, il suffit de procéder à la multiplication ponctuelle du cube de filtre avec le cube d'image, puis de faire la somme des résultats sur la dimension canaux/profondeur (ici 3) et de les réduire à 2d. (100,100) carte de fonctionnalité. Faites cela avec chaque cube de filtre et vous obtiendrez 7 cartes de caractéristiques 2D.

8
Val9265

En plus d’autres réponses, l’opération conv2d fonctionne en c ++ (cpu) ou cuda pour les machines gpu nécessitant d’aplatir et de remodeler les données d’une certaine manière et d’utiliser la multiplication matricielle gemmBLAS ou cuBLAS (cuda).

0
karaspd