Was macht tf.nn.conv2d im Tensorflow?

135

Ich habe mir tf.nn.conv2d hier die Dokumente von Tensorflow angesehen . Aber ich kann nicht verstehen, was es tut oder was es zu erreichen versucht. Es steht auf den Dokumenten,

# 1: Glättet den Filter zu einer 2-D-Matrix mit Form

[filter_height * filter_width * in_channels, output_channels].

Was macht das nun? Ist das eine elementweise Multiplikation oder nur eine einfache Matrixmultiplikation? Ich konnte auch die beiden anderen in den Dokumenten erwähnten Punkte nicht verstehen. Ich habe sie unten geschrieben:

# 2: Extrahiert Bildfelder aus dem Eingangstensor, um einen virtuellen Formtensor zu bilden

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

# 3: Für jedes Patch multipliziert rechts die Filtermatrix und den Bild-Patch-Vektor.

Es wäre wirklich hilfreich, wenn jemand ein Beispiel geben könnte, vielleicht einen Code (äußerst hilfreich) und erklären könnte, was dort vor sich geht und warum die Operation so ist.

Ich habe versucht, einen kleinen Teil zu codieren und die Form der Operation auszudrucken. Trotzdem kann ich nicht verstehen.

Ich habe so etwas versucht:

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)

Ich verstehe Teile von Faltungs-Neuronalen Netzen. Ich habe sie hier studiert . Aber die Implementierung von Tensorflow entspricht nicht meinen Erwartungen. Also warf es die Frage auf.

EDIT : Also habe ich einen viel einfacheren Code implementiert. Aber ich kann nicht herausfinden, was los ist. Ich meine, wie die Ergebnisse so sind. Es wäre äußerst hilfreich, wenn mir jemand sagen könnte, welcher Prozess diese Ausgabe liefert.

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)

Ausgabe

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

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

  [[-0.00354624]
   [ 0.41650501]]]]
Shubhashis
quelle
Tatsächlich ist cudnn auf der GPU in standardmäßig aktiviert tf.nn.conv2d(), daher wird die betreffende Methode überhaupt nicht verwendet, wenn TF mit GPU-Unterstützung verwendet wird, es use_cudnn_on_gpu=Falsesei denn, dies ist explizit angegeben.
GKCN

Antworten:

59

Die 2D-Faltung wird auf ähnliche Weise berechnet wie die 1D-Faltung : Sie schieben Ihren Kernel über die Eingabe, berechnen die elementweisen Multiplikationen und fassen sie zusammen. Aber anstatt dass Ihr Kernel / Ihre Eingabe ein Array ist, sind sie hier Matrizen.


Im einfachsten Beispiel gibt es keine Polsterung und Schritt = 1. Nehmen wir an, Sie sind inputund kernelsind: Geben Sie hier die Bildbeschreibung ein

Wenn Sie Ihren Kernel verwenden, erhalten Sie die folgende Ausgabe : Geben Sie hier die Bildbeschreibung ein, die wie folgt berechnet wird:

  • 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

Die conv2d- Funktion von TF berechnet Faltungen in Stapeln und verwendet ein etwas anderes Format. Für eine Eingabe ist es [batch, in_height, in_width, in_channels]für den Kernel [filter_height, filter_width, in_channels, out_channels]. Wir müssen also die Daten im richtigen Format bereitstellen:

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

Danach wird die Faltung berechnet mit:

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)

Und entspricht dem, den wir von Hand berechnet haben.


Für Beispiele mit Polsterung / Fortschritte, finden hier sehen .

Salvador Dali
quelle
Schönes Beispiel, jedoch sind einige Links defekt.
Silgon
1
@silgon Dies liegt leider daran, dass SO beschlossen hat, die Dokumentationsfunktion, die sie zuerst erstellt und beworben haben, nicht zu unterstützen.
Salvador Dali
161

Ok, ich denke, das ist der einfachste Weg, alles zu erklären.


Ihr Beispiel ist 1 Bild, Größe 2x2, mit 1 Kanal. Sie haben 1 Filter mit der Größe 1x1 und 1 Kanal (Größe ist Höhe x Breite x Kanäle x Anzahl der Filter).

Für diesen einfachen Fall ist das resultierende 2x2, 1-Kanal-Bild (Größe 1x2x2x1, Anzahl der Bilder x Höhe x Breite xx Kanäle) das Ergebnis der Multiplikation des Filterwerts mit jedem Pixel des Bildes.


Probieren wir jetzt weitere Kanäle aus:

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

Hier haben das 3x3-Bild und der 1x1-Filter jeweils 5 Kanäle. Das resultierende Bild ist 3x3 mit 1 Kanal (Größe 1x3x3x1), wobei der Wert jedes Pixels das Punktprodukt über die Kanäle des Filters mit dem entsprechenden Pixel im Eingabebild ist.


Jetzt mit einem 3x3 Filter

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

Hier erhalten wir ein 1x1 Bild mit 1 Kanal (Größe 1x1x1x1). Der Wert ist die Summe der 9, 5-Element-Punktprodukte. Man könnte dies aber auch als ein Produkt mit 45 Elementen bezeichnen.


Jetzt mit einem größeren Bild

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

Die Ausgabe ist ein 3x3 1-Kanal-Bild (Größe 1x3x3x1). Jeder dieser Werte ist eine Summe von 9 5-Element-Punktprodukten.

Jede Ausgabe erfolgt durch Zentrieren des Filters auf einem der 9 mittleren Pixel des Eingabebildes, so dass keiner der Filter hervorsteht. Die folgenden xs repräsentieren die Filterzentren für jedes Ausgangspixel.

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

Jetzt mit "SAME" -Polsterung:

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

Dies ergibt ein 5x5-Ausgabebild (Größe 1x5x5x1). Dies erfolgt durch Zentrieren des Filters an jeder Position im Bild.

Jedes der 5-Element-Punktprodukte, bei denen der Filter über den Bildrand hinausragt, erhält den Wert Null.

Die Ecken sind also nur Summen von 4, 5-Element-Punktprodukten.


Jetzt mit mehreren Filtern.

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

Dies ergibt immer noch ein 5x5-Ausgabebild, jedoch mit 7 Kanälen (Größe 1x5x5x7). Wobei jeder Kanal von einem der Filter im Set erzeugt wird.


Jetzt mit Schritten 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')

Jetzt hat das Ergebnis noch 7 Kanäle, ist aber nur 3x3 (Größe 1x3x3x7).

Dies liegt daran, dass die Filter nicht an jedem Punkt des Bildes zentriert werden, sondern an jedem anderen Punkt des Bildes zentriert werden, wobei Schritte (Schritte) der Breite 2 ausgeführt werden. Die xfolgenden Werte geben die Filtermitte für jedes Ausgabepixel an das Eingabebild.

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

Und natürlich ist die erste Dimension der Eingabe die Anzahl der Bilder, sodass Sie sie auf einen Stapel von 10 Bildern anwenden können, zum Beispiel:

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

Dies führt die gleiche Operation für jedes Bild unabhängig aus und ergibt einen Stapel von 10 Bildern als Ergebnis (Größe 10x3x3x7).

mdaoust
quelle
@ ZijunLost Nein, die Dokumente geben an, dass das erste und letzte Element 1 sein muss.Must have strides[0] = strides[3] = 1. For the most common case of the same horizontal and vertices strides, strides = [1, stride, stride, 1].
JohnAllen
Ist das eine Toeplitz-Matrix- basierte Implementierung der Faltung?
GKCN
Dazu: "Dies ergibt immer noch ein 5x5-Ausgabebild, aber mit 7 Kanälen (Größe 1x5x5x7). Wo jeder Kanal von einem der Filter im Set erzeugt wird." Habe ich immer noch Schwierigkeiten zu verstehen, woher die 7 Kanäle stammen? Was meinst du mit "Filter im Set"? Vielen Dank.
Derek
@mdaoust Hallo, in Bezug auf Ihr zweites Beispiel, bei dem the 3x3 image and the 1x1 filter each have 5 channelsdas Ergebnis vom manuell berechneten Punktprodukt abweicht.
Tgn Yang
1
@derek Ich habe die gleiche Frage, stimmt der "output_channel" mit der "Anzahl der Filter" überein ??? Wenn ja, warum heißen sie in den Tensorflow-Dokumenten "output_channel"?
Wei
11

Um die anderen Antworten zu ergänzen, sollten Sie an die Parameter in denken

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

als '5' entsprechend der Anzahl der Kanäle in jedem Filter. Jeder Filter ist ein 3D-Würfel mit einer Tiefe von 5. Ihre Filtertiefe muss der Tiefe Ihres Eingabebildes entsprechen. Der letzte Parameter, 7, sollte als Anzahl der Filter im Stapel betrachtet werden. Vergessen Sie einfach, dass dies 4D ist, und stellen Sie sich stattdessen vor, Sie haben einen Satz oder einen Stapel von 7 Filtern. Sie erstellen 7 Filterwürfel mit den Abmessungen (3,3,5).

Es ist viel einfacher, im Fourier-Bereich zu visualisieren, da die Faltung zur punktweisen Multiplikation wird. Für ein Eingabebild mit Abmessungen (100, 100, 3) können Sie die Filterabmessungen wie folgt umschreiben

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

Um eine der 7 Ausgabe-Feature-Maps zu erhalten, führen wir einfach die punktweise Multiplikation des Filterwürfels mit dem Bildwürfel durch. Dann summieren wir die Ergebnisse über die Kanal- / Tiefen-Dimension (hier 3) und reduzieren sie auf 2d (100.100) Feature-Map. Wenn Sie dies mit jedem Filterwürfel tun, erhalten Sie 7 2D-Feature-Maps.

Val9265
quelle
8

Ich habe versucht, conv2d (für mein Studium) zu implementieren. Nun, das habe ich geschrieben:

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

Hoffe ich habe es richtig gemacht. Auf MNIST überprüft, hatte sehr enge Ergebnisse (aber diese Implementierung ist langsamer). Ich hoffe das hilft dir.

Artem Yaschenko
quelle
0

Zusätzlich zu anderen Antworten wird die conv2d-Operation in c ++ (cpu) oder cuda für GPU-Maschinen ausgeführt, bei denen Daten auf bestimmte Weise reduziert und umgeformt werden müssen und die Matrixmultiplikation gemmBLAS oder cuBLAS (cuda) verwendet werden muss.

karaspd
quelle
Im Speicher wird die Faltung also tatsächlich als Matrixmultiplikation durchgeführt, was erklärt, warum größere Bilder nicht notwendigerweise eine größere Rechenzeit benötigen, sondern eher einen OOM-Fehler (nicht genügend Speicher) aufweisen. Können Sie mir erklären, warum die 3D-Faltung im Vergleich zur 2D-Faltung speicherineffizienter / effizienter ist? Zum Beispiel 3D-Konv. Auf [B, H, W, D, C] im Vergleich zu 2D-Konv. Auf [B * C, H, W, D]. Sicherlich kosten sie rechnerisch gleich?
SomePhysicsStudent