Cosa fa tf.nn.conv2d in tensorflow?

Stavo guardando i documenti di tensorflow su tf.nn.conv2d qui . Ma non riesco a capire cosa fa o cosa sta cercando di ottenere. Dice sui documenti,

N. 1: appiattisce il filtro su una matrice 2-D con forma

[filter_height * filter_width * in_channels, output_channels] .

Ora che cosa fa? Questa moltiplicazione ad elemento o semplicemente la moltiplicazione di matrice? Inoltre, non ho capito gli altri due punti menzionati nei documenti. Li ho scritti qui sotto:

# 2: estrae patch di immagini dal tensore di input per formare un tensore virtuale di forma

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

3: per ogni patch, moltiplica a destra la matrice del filtro e il vettore del patch dell’immagine.

Sarebbe molto utile se qualcuno potesse dare un esempio, un pezzo di codice (estremamente utile) forse e spiegare cosa sta succedendo lì e perché l’operazione è così.

Ho provato a codificare una piccola parte e stampare la forma dell’operazione. Tuttavia, non riesco a capire.

Ho provato qualcosa di simile a questo:

 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) 

Comprendo frammenti di reti neuronali convoluzionali. Li ho studiati qui . Ma l’implementazione su tensorflow non è quello che mi aspettavo. Quindi sollevò la domanda.

EDIT : Quindi, ho implementato un codice molto più semplice. Ma non riesco a capire cosa sta succedendo. Voglio dire come i risultati sono così. Sarebbe estremamente utile se qualcuno potesse dirmi quale processo produce questo output.

 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) 

produzione

 input [[[[ 1.60314465] [-0.55022103]] [[ 0.00595062] [-0.69889867]]]] filter [[[[-0.59594476]]]] result [[[[-0.95538563] [ 0.32790133]] [[-0.00354624] [ 0.41650501]]]] 

La convoluzione 2D è calcasting in un modo simile per calcolare la convoluzione 1D : si scorre il kernel sull’input, si calcolano le moltiplicazioni elementali e si sumno. Ma invece del tuo kernel / input è un array, qui sono matrici.


Nell’esempio più semplice non c’è padding e stride = 1. Supponiamo che il tuo input e il tuo kernel siano: inserisci la descrizione dell'immagine qui

Al tuo kernel riceverai il seguente output: inserisci la descrizione dell'immagine qui , che viene calcolato nel modo seguente:

  • 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 funzione conv2d di TF calcola le convoluzioni in lotti e utilizza un formato leggermente diverso. Per un input è [batch, in_height, in_width, in_channels] per il kernel è [filter_height, filter_width, in_channels, out_channels] . Quindi dobbiamo fornire i dati nel formato corretto:

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

Successivamente la convoluzione è calcasting con:

 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) 

E sarà equivalente a quello che abbiamo calcolato a mano.


Per esempi con padding / passi, dai un’occhiata qui .

Ok penso che questo sia il modo più semplice per spiegare tutto.


Il tuo esempio è 1 immagine, dimensione 2×2, con 1 canale. Hai 1 filtro, con dimensioni 1×1 e 1 canale (la dimensione è altezza x larghezza x canali x numero di filtri).

Per questo semplice caso l’immagine risultante 2×2, 1 canale (dimensione 1x2x2x1, numero di immagini x altezza x larghezza xx canali) è il risultato della moltiplicazione del valore del filtro per ciascun pixel dell’immagine.


Ora proviamo più canali:

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

Qui l’immagine 3×3 e il filtro 1×1 hanno ciascuno 5 canali. L’immagine risultante sarà 3×3 con 1 canale (dimensione 1x3x3x1), in cui il valore di ciascun pixel è il prodotto punto attraverso i canali del filtro con il pixel corrispondente nell’immagine di input.


Ora con un filtro 3×3

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

Qui otteniamo un’immagine 1×1, con 1 canale (dimensione 1x1x1x1). Il valore è la sum dei prodotti punto 9, a 5 elementi. Ma si potrebbe semplicemente chiamare questo un prodotto punto 45 elementi.


Ora con un’immagine più 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') 

L’output è un’immagine 3×3 a 1 canale (dimensioni 1x3x3x1). Ognuno di questi valori è una sum di prodotti a punti da 9 e 5 elementi.

Ogni uscita viene realizzata centrando il filtro su uno dei 9 pixel centrali dell’immagine di input, in modo che nessuno dei filtri sporga. Le x s sotto rappresentano i centri filtro per ciascun pixel di uscita.

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

Ora con il riempimento “SAME”:

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

Questo dà un’immagine in uscita 5×5 (dimensione 1x5x5x1). Questo viene fatto centrando il filtro in ogni posizione dell’immagine.

Qualsiasi prodotto punto a 5 elementi in cui il filtro sporge oltre il bordo dell’immagine ottiene un valore pari a zero.

Quindi gli angoli sono solo somme di prodotti a punti a 4, 5 elementi.


Ora con più filtri.

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

Ciò fornisce comunque un’immagine in uscita 5×5, ma con 7 canali (dimensioni 1x5x5x7). Dove ogni canale è prodotto da uno dei filtri nel set.


Ora con passi da gigante 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') 

Ora il risultato ha ancora 7 canali, ma è solo 3×3 (dimensione 1x3x3x7).

Questo perché invece di centrare i filtri in ogni punto dell’immagine, i filtri sono centrati in ogni altro punto dell’immagine, facendo passi (passi) di larghezza 2. Le x sotto rappresentano il centro del filtro per ciascun pixel di uscita, sull’immagine di input.

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

E naturalmente la prima dimensione dell’input è il numero di immagini, quindi puoi applicarlo su un lotto di 10 immagini, ad esempio:

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

Questo esegue la stessa operazione, per ogni immagine in modo indipendente, dando come risultato una pila di 10 immagini (dimensioni 10x3x3x7)

Giusto per aggiungere alle altre risposte, dovresti pensare ai parametri in

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

come ‘5’ corrispondente al numero di canali in ciascun filtro. Ogni filtro è un cubo 3d con una profondità di 5. La profondità del filtro deve corrispondere alla profondità dell’immagine inserita. L’ultimo parametro, 7, dovrebbe essere considerato il numero di filtri nel batch. Basta dimenticare che questo è 4D, e invece immagina di avere un set o un lotto di 7 filtri. Quello che fai è creare 7 cubi filtro con dimensioni (3,3,5).

È molto più facile visualizzare nel dominio di Fourier poiché la convoluzione diventa una moltiplicazione puntuale. Per un’immagine di input delle dimensioni (100,100,3) è ansible riscrivere le dimensioni del filtro come

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

Per ottenere una delle 7 mappe delle caratteristiche di output, eseguiamo semplicemente la moltiplicazione puntiforms del cubo filtro con il cubo immagine, quindi sommiamo i risultati attraverso i canali / la dimensione della profondità (qui è 3), collassando in una 2d (100,100) mappa delle caratteristiche. Fai questo con ciascun cubo filtro e ottieni 7 mappe di funzioni 2D.

Ho provato a implementare conv2d (per i miei studi). Bene, ho scritto questo:

 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 

Spero di averlo fatto correttamente. Controllato su MNIST, ha avuto risultati molto ravvicinati (ma questa implementazione è più lenta). Spero che questo ti aiuta.

Oltre alle altre risposte, l’operazione conv2d funziona in c ++ (cpu) o cuda per le macchine gpu che richiede di appiattire e rimodellare i dati in un determinato modo e utilizzare la moltiplicazione della matrice gemmBLAS o cuBLAS (cuda).