web-dev-qa-db-fra.com

Visualisation de la sortie de la couche convolutionnelle en tensorflow

J'essaie de visualiser la sortie d'une couche convolutionnelle en tensorflow en utilisant la fonction tf.image_summary. Je l'utilise déjà avec succès dans d'autres cas (par exemple, visualiser l'image d'entrée), mais j'ai quelques difficultés à remodeler correctement la sortie ici. J'ai la couche conv suivante:

img_size = 256
x_image = tf.reshape(x, [-1,img_size, img_size,1], "sketch_image")

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)

Ainsi, la sortie de h_conv1 Aurait la forme [-1, img_size, img_size, 32]. Il suffit d'utiliser tf.image_summary("first_conv", tf.reshape(h_conv1, [-1, img_size, img_size, 1])) Ne tient pas compte des 32 noyaux différents, donc je coupe essentiellement différentes cartes de fonctionnalités ici.

Comment puis-je les remodeler correctement? Ou existe-t-il une autre fonction d'aide que je pourrais utiliser pour inclure cette sortie dans le résumé?

34
panmari

Je ne connais pas de fonction d'assistance, mais si vous voulez voir tous les filtres, vous pouvez les regrouper en une seule image avec quelques utilisations fantaisistes de tf.transpose.

Donc, si vous avez un tenseur qui est images x ix x iy x channels

>>> V = tf.Variable()
>>> print V.get_shape()

TensorShape([Dimension(-1), Dimension(256), Dimension(256), Dimension(32)])

Donc dans cet exemple ix = 256, iy=256, channels=32

découpez d'abord 1 image et supprimez la dimension image

V = tf.slice(V,(0,0,0,0),(1,-1,-1,-1)) #V[0,...]
V = tf.reshape(V,(iy,ix,channels))

Ajoutez ensuite quelques pixels de remplissage nul autour de l'image

ix += 4
iy += 4
V = tf.image.resize_image_with_crop_or_pad(image, iy, ix)

Remodelez ensuite de sorte qu'au lieu de 32 canaux, vous ayez des canaux 4x8, appelons-les cy=4 Et cx=8.

V = tf.reshape(V,(iy,ix,cy,cx)) 

Maintenant, la partie délicate. tf semble renvoyer les résultats dans l'ordre C, la valeur par défaut de numpy.

L'ordre actuel, s'il est aplati, listerait tous les canaux du premier pixel (itération sur cx et cy), avant de lister les canaux du second pixel (incrémentation ix ). Traverser les lignes de pixels (ix) avant de passer à la ligne suivante (iy).

Nous voulons l'ordre qui disposerait les images dans une grille. Vous traversez donc une ligne d'une image (ix), avant de marcher le long de la ligne de canaux (cx), lorsque vous atteignez la fin de la ligne de canaux, vous passez à la ligne suivante dans l'image (iy) et lorsque vous manquez de lignes dans l'image, vous incrémentez la ligne suivante de canaux (cy). alors:

V = tf.transpose(V,(2,0,3,1)) #cy,iy,cx,ix

Personnellement, je préfère np.einsum Pour des transpositions fantaisistes, pour la lisibilité, mais ce n'est pas dans tfencore .

newtensor = np.einsum('yxYX->YyXx',oldtensor)

de toute façon, maintenant que les pixels sont dans le bon ordre, nous pouvons l'aplatir en toute sécurité dans un tenseur 2D:

# image_summary needs 4d input
V = tf.reshape(V,(1,cy*iy,cx*ix,1))

essayez tf.image_summary, vous devriez obtenir une grille de petites images.

Voici une image de ce que l'on obtient après avoir suivi toutes les étapes ici.

enter image description here

34
mdaoust

Dans le cas où quelqu'un voudrait "sauter" vers numpy et visualiser "là", voici un exemple pour afficher à la fois Weights et processing result. Toutes les transformations sont basées sur la réponse précédente de mdaoust.

# to visualize 1st conv layer Weights
vv1 = sess.run(W_conv1)

# to visualize 1st conv layer output
vv2 = sess.run(h_conv1,feed_dict = {img_ph:x, keep_prob: 1.0})
vv2 = vv2[0,:,:,:]   # in case of bunch out - slice first img


def vis_conv(v,ix,iy,ch,cy,cx, p = 0) :
    v = np.reshape(v,(iy,ix,ch))
    ix += 2
    iy += 2
    npad = ((1,1), (1,1), (0,0))
    v = np.pad(v, pad_width=npad, mode='constant', constant_values=p)
    v = np.reshape(v,(iy,ix,cy,cx)) 
    v = np.transpose(v,(2,0,3,1)) #cy,iy,cx,ix
    v = np.reshape(v,(cy*iy,cx*ix))
    return v

# W_conv1 - weights
ix = 5  # data size
iy = 5
ch = 32   
cy = 4   # grid from channels:  32 = 4x8
cx = 8
v  = vis_conv(vv1,ix,iy,ch,cy,cx)
plt.figure(figsize = (8,8))
plt.imshow(v,cmap="Greys_r",interpolation='nearest')

#  h_conv1 - processed image
ix = 30  # data size
iy = 30
v  = vis_conv(vv2,ix,iy,ch,cy,cx)
plt.figure(figsize = (8,8))
plt.imshow(v,cmap="Greys_r",interpolation='nearest')
2
rgr

vous pouvez essayer d'obtenir l'image d'activation de la couche de convolution de cette façon:

    h_conv1_features = tf.unpack(h_conv1, axis=3)
    h_conv1_imgs = tf.expand_dims(tf.concat(1, h_conv1_features_padded), -1)

cela obtient une bande verticale avec toutes les images concaténées verticalement.

si vous voulez les rembourrer (dans mon cas d'activations relu pour garnir avec une ligne blanche):

    h_conv1_features = tf.unpack(h_conv1, axis=3)
    h_conv1_max = tf.reduce_max(h_conv1)
    h_conv1_features_padded = map(lambda t: tf.pad(t-h_conv1_max, [[0,0],[0,1],[0,0]])+h_conv1_max, h_conv1_features)
    h_conv1_imgs = tf.expand_dims(tf.concat(1, h_conv1_features_padded), -1)
1
Anton Suslov

J'essaie personnellement de tuiler chaque filtre 2D en une seule image.

Pour ce faire -si je ne me trompe pas terriblement depuis que je suis assez nouveau sur DL- J'ai découvert qu'il pourrait être utile d'exploiter la fonction depth_to_space , car il faut un tenseur 4d

[batch, height, width, depth]

et produit une sortie de forme

[batch, height*block_size, width*block_size, depth/(block_size*block_size)]

Où block_size est le nombre de "tuiles" dans l'image de sortie. La seule limitation à cela est que la profondeur doit être le carré de block_size, qui est un entier, sinon il ne peut pas "remplir" correctement l'image résultante. Une solution possible pourrait être de rembourrer la profondeur du tenseur d'entrée jusqu'à une profondeur acceptée par la méthode, mais je n'ai pas essayé cela.

1
EdoardoG

Une autre façon, que je pense très simple, utilise le get_operation_by_name une fonction. J'ai eu du mal à visualiser les couches avec d'autres méthodes mais cela m'a aidé.

#first, find out the operations, many of those are micro-operations such as add etc.
graph = tf.get_default_graph()
graph.get_operations()

#choose relevant operations
op_name = '...' 
op = graph.get_operation_by_name(op_name)
out = sess.run([op.outputs[0]], feed_dict={x: img_batch, is_training: False}) 
#img_batch is a single image whose dimensions are (1,n,n,1). 

# out is the output of the layer, do whatever you want with the output
#in my case, I wanted to see the output of a convolution layer
out2 = np.array(out)
print(out2.shape)

# determine, row, col, and fig size etc.
for each_depth in range(out2.shape[4]):
    fig.add_subplot(rows, cols, each_depth+1)
    plt.imshow(out2[0,0,:,:,each_depth], cmap='gray')

Par exemple ci-dessous est l'entrée (chat coloré) et la sortie de la deuxième couche conv de mon modèle . below

Notez que je suis conscient que cette question est ancienne et qu'il existe des méthodes plus faciles avec Keras, mais pour les personnes qui utilisent un ancien modèle d'autres personnes (comme moi), cela peut être utile.

0
smttsp