Für diejenigen, die mehr über die Funktion "im2col" erfahren möchten, die bei der Bilderkennung mit CNN angezeigt wird Wir werden von der ersten Implementierung bis zur verbesserten Version, der Batch-Channel-kompatiblen Version und der Schritt-Padding-kompatiblen Version unter Verwendung von Gifs und Bildern ausführlich erläutern.
im2col
](Was ist # im2col)im2col
](Verhalten und Erstimplementierung von # im2col)im2col
](Bedienung von # im2col)im2col
](Erstimplementierung von # im2col)im2col
(initial ver)](# verbesserte Version im2col initial ver)im2col
ist eine Funktion, die bei der Bilderkennung verwendet wird. Die Operation besteht darin, ein mehrdimensionales Array reversibel in ein zweidimensionales Array umzuwandeln.
Der größte Vorteil davon ist, dass Sie die Vorteile von Numpy für schnelle Matrixoperationen maximieren können.
Es ist keine Übertreibung zu sagen, dass sich die heutige Bilderkennung ohne sie (wahrscheinlich) nicht entwickelt hätte.
Ich denke, das Bild hat ursprünglich eine zweidimensionale Datenstruktur, oder? Es sieht zweidimensional aus, aber wenn wir tatsächlich maschinell lernen, verwenden wir häufig Bilder, die in RGB zerlegt sind (dies wird als ** Kanal ** bezeichnet). Mit anderen Worten hat das Farbbild eine dreidimensionale Datenstruktur. Obwohl Schwarzweißbilder einen Kanal haben, werden mehrere Bilder in einer Ausbreitung gestreamt (dies wird als ** Stapel ** bezeichnet), sodass sie eine dreidimensionale Datenstruktur aufweisen. In der Praxis ist es ineffizient, nur Schwarzweißbilder in 3D zu implementieren, sodass Schwarzweißbilder eine 4D-Datenstruktur aufweisen, indem sie mit Farbbildern mit einem Kanal ausgerichtet werden. Wenn Sie eine Doppelschleife verwenden, können Sie die Bilder einzeln verarbeiten. Dadurch wird jedoch der Vorteil von numpy aufgehoben (numpy hat die Eigenschaft, langsam zu sein, wenn es in einer for-Schleife usw. gedreht wird). Daher benötigen wir eine Funktion namens "im2col", die die Vorteile von numpy maximieren kann, indem 4-dimensionale Daten in 2D erstellt werden.
CNN ist eine Abkürzung für Convolutional Neural Network, die für Daten verwendet wird, die in enger Beziehung zu einem bestimmten Koordinatenpunkt und den Koordinatenpunkten um ihn herum stehen. Ein einfaches Beispiel ist ein Bild oder Video. Vor dem Aufkommen von CNN wurden beim Lernen einer Datenstruktur wie eines Bildes unter Verwendung eines neuronalen Netzwerks die zweidimensionalen Daten geglättet und als eindimensionale Daten behandelt, wobei die wichtige Korrelation der zweidimensionalen Daten ignoriert wurde. Ich tat. CNN verursachte einen Durchbruch bei der Bilderkennung, indem Merkmale extrahiert wurden, während die zweidimensionale Datenstruktur von Bildern beibehalten wurde. Diese Technologie ist von der Verarbeitung inspiriert, die bei der Übertragung von Informationen von der Netzhaut zum Sehnerv durchgeführt wird, sodass eine Verarbeitung durchgeführt werden kann, die der menschlichen Erkennung näher kommt.
Der Inhalt der CNN-Verarbeitung besteht hauptsächlich aus einer Verarbeitung, die als Filterung (Faltungsschicht) und Pooling (Pooling-Schicht) bezeichnet wird.
Beim Filtern werden Merkmale wie vertikale Linien aus Bilddaten erkannt.
Dies ähnelt dem, was menschliche Netzhautzellen tun (einige menschliche Netzhautzellen reagieren auf bestimmte Muster und senden elektrische Signale aus, um Informationen an den Sehnerv zu übermitteln).
Pooling ist ein Prozess, um charakteristischere aus den durch Filterung extrahierten Merkmalsmengen zu extrahieren.
Dies ist ähnlich wie beim menschlichen Sehnerv (die Anzahl der Nervenzellen nimmt ab, wenn Informationen vom Sehnerv zum Gehirn übertragen werden → die Informationen werden komprimiert).
Unter dem Gesichtspunkt der Reduzierung des Datenvolumens ist dies ein sehr guter Prozess, der Speicher sparen und die Berechnung reduzieren kann, während gute Funktionen erhalten bleiben.
Im2col
und col2im
, die in einem anderen Artikel vorgestellt werden, sind nützlich für die Implementierung von Pooling, aber dieses Mal werden wir der Filterung besondere Aufmerksamkeit widmen.
Das obige GIF zeigt ein Bild der Filterung.
im2col
Um die Implementierung von "im2col" zu verstehen, werden wir sein Verhalten mithilfe mathematischer Formeln, Bilder und Gifs gründlich analysieren.
im2col
Das vorherige GIF ist mathematisch
a = 1W + 2X + 5Y + 6Z \\
b = 2W + 3X + 6Y + 7Z \\
c = 3W + 4X + 7Y + 8Z \\
d = 5W + 6X + 9Y + 10Z \\
e = 6W + 7X + 10Y + 11Z \\
f = 7W + 8X + 11Y + 12Z \\
g = 9W + 10X + 13Y + 14Z \\
h = 10W + 11X + 14Y + 15Z \\
i = 11W + 12X + 15Y + 16Z
Es sieht aus wie. im2col
transformiert die Bilddaten gut, um dies mit der Matrixproduktion zu erreichen.
Überprüfen Sie auch die Formel.
\begin{align}
\left(
\begin{array}{c}
a \\
b \\
c \\
d \\
e \\
f \\
g \\
h \\
i
\end{array}
\right)^{\top}
&=
\left(
\begin{array}{cccc}
W & X & Y & Z
\end{array}
\right)
\left(
\begin{array}{ccccccccc}
1 & 2 & 3 & 5 & 6 & 7 & 9 & 10 & 11 \\
2 & 3 & 4 & 6 & 7 & 8 & 10 & 11 & 12 \\
5 & 6 & 7 & 9 & 10 & 11 & 13 & 14 & 15 \\
6 & 7 & 8 & 10 & 11 & 12 & 14 & 15 & 16
\end{array}
\right) \\
&=
\left(
\begin{array}{c}
1W + 2X + 5Y + 6Z \\
2W + 3X + 6Y + 7Z \\
3W + 4X + 7Y + 8Z \\
5W + 6X + 9Y + 10Z \\
6W + 7X + 10Y + 11Z \\
7W + 8X + 11Y + 12Z \\
8W + 9X + 12Y + 13Z \\
10W + 11X + 14Y + 15Z \\
11W + 12X + 15Y + 16Z
\end{array}
\right)^{\top}
\end{align}
im2col
Lassen Sie uns dies also zuerst ehrlich umsetzen.
Durch Filtern der $ 4 \ times 4 $ -Matrix in die $ 2 \ times 2 $ -Matrix wird die $ 3 \ times 3 $ -Matrix ausgegeben. Verallgemeinern wir das.
Filtern Sie die Matrix $ I_h \ times I_w $ mit $ F_h \ times F_w $.
Zu diesem Zeitpunkt entspricht der Index oben links im Filter, wenn der letzte Filter angewendet wird, der Größe der Ausgabematrix. Dies liegt daran, dass die Anzahl der Filter und die Größe der Ausgabematrix übereinstimmen.
Aus dem Bild kann die Größe der Ausgabematrix als $ (I_h - F_h + 1) \ mal (I_w --F_w + 1) = O_h \ mal O_w $ berechnet werden.
Mit anderen Worten, $ O_h O_w $ -Elemente sind erforderlich, sodass die Anzahl der Spalten in im2col
$ O_h O_w $ beträgt.
Da andererseits die Anzahl der Zeilen proportional zur Größe des Filters ist, wird sie zu $ F_hF_w $. Wenn Sie also die Eingabematrix von $ I_h \ mal I_w $ mit $ F_h \ mal F_w $ filtern, ist die Ausgangsmatrix von im2col
$ F_h F_w \ times O_h O_w $.
Das Obige kann wie folgt in das Programm aufgenommen werden.
early_im2col.py
import time
import numpy as np
def im2col(image, F_h, F_w):
I_h, I_w = image.shape
O_h = I_h - F_h + 1
O_w = I_w - F_w + 1
col = np.empty((F_h*F_w, O_h*O_w))
for h in range(O_h):
for w in range(O_w):
col[:, w + h*O_w] = image[h : h+F_h, w : w+F_w].reshape(-1)
return col
x = np.arange(1, 17).reshape(4, 4)
f = np.arange(-4, 0).reshape(2, 2)
print(im2col(x, 2, 2))
print(im2col(f, 2, 2).T)
print(im2col(f, 2, 2).T @ im2col(x, 2, 2))
early_im2col.py
for h in range(O_h):
for w in range(O_w):
col[:, w + h*O_w] = image[h : h+F_h, w : w+F_w].reshape(-1)
Der Schreibort in die Ausgabematrix, der jedem "h, w" entspricht, ist wie folgt.
Dies ist der durch col [:, w + h * O_w]
angegebene Schreibort. Der entsprechende Teil der Eingabematrix "Bild [h: h + F_h, w: w + F_w]" wird mit ".reshape (-1)" geglättet und hier ersetzt.
Es ist immer noch einfach.
im2col
Jetzt hat früh_im2col.py einen schwerwiegenden Nachteil.
Der Nachteil ist, dass, wie bereits erwähnt, numpy langsam ist, wenn auf Schleifenverarbeitung wie "for" zugegriffen wird.
Im Allgemeinen ist das in früh_im2dol.py als Beispiel für eine Operation gezeigte Eingabearray x
viel größer (z. B. ** sehr kleiner Datensatz ** [MNIST](http: //yann.lecun). Das handschriftliche Zahlenbild von .com / exdb / mnist /) ist eine $ 28 \ times 28 $ Matrix).
Lassen Sie uns die Verarbeitungszeit messen.
early_im2col.py
y = np.zeros((28, 28))
start = time.time()
for i in range(1000):
im2col(y, 2, 2)
end = time.time()
print("time: {}".format(end - start))
Es dauert 1,5 Sekunden, um eine Matrix von höchstens 28 $ mal 28 $ 1000 mal zu verarbeiten.
Da es sich bei der MNIST-Datenbank um eine Datenbank mit 60.000 handgeschriebenen Zahlen handelt, dauert es ** 900 Sekunden **, um alle Bilder durch einfache Berechnung einmal zu filtern.
Dies ist nicht praktikabel, da beim realen maschinellen Lernen mehrere Filter häufig angewendet werden.
im2col
(initial ver)Wenn Sie das Problem überprüfen, werden Sie feststellen, dass das Problem darin besteht, dass die for
-Schleife häufig auf das numpy-Array zugreift. Dies bedeutet, dass Sie die Anzahl der Zugriffe reduzieren können.
In früh_im2col.py wird auf das numpy-Array image
$ O_h O_w $ mal zugegriffen, und die Eingabematrix $ 28 \ times 28 $ wird nach $ 2 \ times 2 $ gefiltert, und die Anzahl der Zugriffe beträgt tatsächlich $ 27 \ times. 27 = 729 $ mal.
Übrigens sind Filter im Allgemeinen viel kleiner als Ausgabematrizen, was verwendet werden kann, um die Anzahl der Zugriffe auf ein Numpy-Array mit äquivalenter Verarbeitung drastisch zu reduzieren.
Das ist die verbesserte Version im2col
(initial ver).
Ich mache etwas kniffliges.
improved_early_im2col.py
import time
import numpy as np
def im2col(image, F_h, F_w):
I_h, I_w = image.shape
O_h = I_h - F_h + 1
O_w = I_w - F_w + 1
col = np.empty((F_h, F_w, O_h, O_w))
for h in range(F_h):
for w in range(F_w):
col[h, w, :, :] = image[h : h+O_h, w : w+O_w]
return col.reshape(F_h*F_w, O_h*O_w)
x = np.arange(1, 17).reshape(4, 4)
f = np.arange(-4, 0).reshape(2, 2)
print(im2col(x, 2, 2))
print(im2col(f, 2, 2).T)
print(im2col(f, 2, 2).T @ im2col(x, 2, 2))
y = np.zeros((28, 28))
start = time.time()
for i in range(1000):
im2col(y, 2, 2)
end = time.time()
print("time: {}".format(end - start))
Die erste Änderung ist der Speicherzuordnungsteil der Ausgabematrix.
improved_early_im2col.py
col = np.empty((F_h, F_w, O_h, O_w))
Der Speicher ist mit einer solchen 4-dimensionalen Datenstruktur gesichert.
Die nächste Änderung besteht darin, dass die Anzahl der Schleifen von $ O_h O_w $ in $ F_h F_w $ geändert wurde, um die Anzahl der Zugriffe zu verringern.
improved_early_im2col.py
for h in range(F_h):
for w in range(F_w):
col[h, w, :, :] = image[h : h+O_h, w : w+O_w]
Dies reduziert die Anzahl der Numpy-Array-Zugriffe pro MNIST-Bild von 729 auf satte 4! Außerdem sind der Zugriffsort auf das Ausgabearray und der Zugriffspunkt auf das Eingabearray in jeder Schleife wie folgt. Bei einem solchen Zugriff wird das folgende Ausgabearray erstellt.
Zum Schluss formen Sie es zum Zeitpunkt der Ausgabe in die gewünschte Form.
improved_early_im2col.py
return col.reshape(F_h*F_w, O_h*O_w)
In Bezug auf die Operation von numpy werden die eindimensionalen Daten von $ (F_h F_w O_h O_w,) $, die $ (F_h, F_w, O_h, O_w) $ geglättet sind, in die zweidimensionalen Daten von $ (F_h F_w, O_h O_w) $ transformiert. Ich fühle mich wie ich es tue. Genauer gesagt, es fühlt sich an, als würde man jedes der zweidimensionalen Daten in der Figur in eine Dimension glätten und darunter stapeln. Du denkst es ist gut ~
Übrigens hat die Zielmatrix dieser Funktion, wie in [Was ist im2col
](Was ist # im2col) erwähnt, ursprünglich eine 4-dimensionale Datenstruktur.
Der Filter hat auch eine 4-dimensionale Datenstruktur, in der zusätzlich zur Sicherung der Anzahl der Kanäle der Eingangsmatrix $ M $ des Satzes vorbereitet wird.
In Anbetracht dessen werden wir verbessertes_early_im2col.py ändern.
Lassen Sie uns zunächst überlegen, welche Form mathematisch transformiert werden muss.
Die Struktur des Farbbildes ist $ (B, C, I_h, I_w) $, wenn die Anzahl der Kanäle $ C $ und die Stapelgröße $ B $ beträgt.
Andererseits hat der Filter eine Struktur von $ (M, C, F_h, F_w) $.
Wenn in verbesserter_early_im2col.py die Matrix $ (I_h, I_w) $ nach $ (F_h, F_w) $ gefiltert wird, lautet die Ausgabematrix $ (F_h F_w, O_h O_w) $ und $ (1, F_h F_w) $. Du machtest.
Unter der Annahme von $ B = 1 $ und $ M = 1 $ müssen die Zeilen und Spalten der durch im2col
transformierten Eingabedaten und die Form des Filters übereinstimmen, damit die Filterung als Matrixprodukt berechnet werden kann. Weil sie $ (C F_h F_w, O_h O_w) $ und $ (1, C F_h F_w) $ sein müssen.
Da es sich im Allgemeinen um $ B \ ne M $ handelt, sollten diese mit denen kombiniert werden, die nichts mit $ C F_h F_w $ zu tun haben.
Wenn man diese Tatsachen kombiniert, ist die Form des Arrays, das von im2col
ausgegeben werden soll, $ (C F_h F_w, B O_h O_w) $ und $ (M, C F_h F_w) $.
Das Ergebnis der Filterberechnung ist übrigens $ (M, C F_h F_w) \ times (C F_h F_w, B O_h O_w) = (M, B O_h O_w) $, was "umgeformt" wird und die Dimensionen ersetzt werden. (B, M, O_h, O_w): = (B, C ', I_h', I_w ') $ wird als Eingabe in die nächste Schicht weitergegeben.
Die Implementierung ist fast die gleiche wie verbessert_early_im2col.py. Ich habe gerade oben Stapel- und Kanalabmessungen hinzugefügt.
BC_support_im2col.py
import time
import numpy as np
def im2col(images, F_h, F_w):
B, C, I_h, I_w = images.shape
O_h = I_h - F_h + 1
O_w = I_w - F_w + 1
cols = np.empty((B, C, F_h, F_w, O_h, O_w))
for h in range(F_h):
for w in range(F_w):
cols[:, :, h, w, :, :] = images[:, :, h : h+O_h, w : w+O_w]
return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)
x = np.arange(1, 3*3*4*4+1).reshape(3, 3, 4, 4)
f = np.arange(-3*3*2*2, 0).reshape(3, 3, 2, 2)
print(im2col(x, 2, 2))
print(im2col(f, 2, 2).T)
print(np.dot(im2col(f, 2, 2).T, im2col(x, 2, 2)))
y = np.zeros((100, 3, 28, 28))
start = time.time()
for i in range(10):
im2col(y, 2, 2)
end = time.time()
print("time: {}".format(end - start))
BC_support_im2col.py
return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)
Hier wird die Reihenfolge der Dimensionen mit der Transponierungsfunktion von numpy geändert. Jeder von ihnen entspricht wie folgt, und die korrekte Ausgabe wird zurückgegeben, indem die Reihenfolge geändert und dann "umgeformt" wird.
\begin{array}{ccccccc}
(&0, &1, &2, &3, &4, &5) \\
(&B, &C, &F_h, &F_w, &O_h, &O_w)
\end{array}
\xrightarrow[\textrm{transpose}]{Tauschen}
\begin{array}{ccccccc}
(&1, &2, &3, &0, &4, &5) \\
(&C, &F_h, &F_w, &B, &O_h, &O_w)
\end{array}
\xrightarrow[\textrm{reshape}]{Verformung}
(C F_h F_w, B O_h O_w)
Dies vervollständigt im2col
, das auch Batch-Kanäle unterstützt!
Nun, ich denke nicht, dass dies das Ende ist. Das Letzte, was ich vorstellen möchte, ist ein Prozess namens ** Schritt ** und ** Auffüllen **. Beides sind wesentliche Elemente für eine effizientere und effektivere Implementierung von CNN.
In der bisherigen Implementierung wurden die Filter selbstverständlich um ein Quadrat verschoben, oder? Dieser Betrag der Abweichung wird als ** Schritt ** bezeichnet, es gibt jedoch keine Regel, dass dies jeweils ein Quadrat sein muss. In den meisten Fällen ist der Schritt nicht 1, da das tatsächliche Bild die Informationen mit nur einer Pixelverschiebung weniger wahrscheinlich erheblich ändert.
Im Gegensatz zu Schritten wurde ** Auffüllen ** in früheren Implementierungen nie erwähnt. Seine Hauptaufgabe besteht darin, ** die Größe des Ausgabebildes durch Filtern ** und ** unverändert zu lassen, um alle Informationen in Richtung der Bildränder ** zu erhalten. Insbesondere wird der Bereich, in dem sich der Filter bewegt, erweitert, indem der Umfang des Eingabebildes mit $ 0 $ gefüllt wird.
Werfen wir einen Blick auf jede Implementierung.
Die schnelle Implementierung ist nicht so schwierig. Sie können nur die Schrittbewegungsbreite so weit von 1 ändern. bis jetzt
BC_support_im2col.py
cols[:, :, h, w, :, :] = images[:, :, h : h+O_h, w : w+O_w]
Ich habe das immer gemacht
im2col.py
cols[:, :, h, w, :, :] = images[:, :, h : h + stride*O_h : stride, w : w + stride*O_w : stride]
Ändern Sie wie folgt. Die Bewegung der ursprünglichen Version ist wie folgt In der Formel
a = 1W + 2X + 5Y + 6Z \\
b = 3W + 4X + 7Y + 8Z \\
c = 9W + 10X + 13Y + 14Z \\
d = 11W + 12X + 15Y + 16Z \\
\Leftrightarrow \left(
\begin{array}{c}
a \\
b \\
c \\
d
\end{array}
\right)^{\top}
=
\left(
\begin{array}{cccc}
W & X & Y & Z
\end{array}
\right)
\left(
\begin{array}{cccc}
1 & 3 & 9 & 11 \\
2 & 4 & 10 & 12 \\
5 & 7 & 13 & 15 \\
6 & 8 & 14 & 16
\end{array}
\right)
Es sieht so aus und die verbesserte Version sieht so aus. Immerhin ist es schwierig ... Es ist zu erstaunlich, darüber nachzudenken.
Andererseits ist die Implementierung der Auffüllverarbeitung sehr einfach.
Verwenden der pad
Funktion in numpy
im2col.py
images = np.pad(images, [(0, 0), (0, 0), (pad, pad), (pad, pad)], "constant")
Wenn ja, ist es OK. Die Bedienung der "Pad" -Funktion ist ziemlich kompliziert (ich werde sie später vorstellen), daher werde ich das oben Gesagte vorerst erklären. Das erste Argument von "pad" ist das Zielarray. Das sollte in Ordnung sein. Das Problem ist das zweite Argument.
im2col.py
[(0, 0), (0, 0), (pad, pad), (pad, pad)]
Wenn Sie dies in die Pad-Funktion eingeben,
Es gibt einige dritte Argumente, die angegeben werden können, aber dieses Mal möchte ich sie mit 0 auffüllen, also gebe ich "Konstante" an. Weitere Informationen finden Sie in der offiziellen Dokumentation (https://numpy.org/devdocs/reference/generated/numpy.pad.html).
Nun, selbst wenn ich die obigen Änderungen vornehme und sie ausführe, erscheint immer noch ein Fehler und es funktioniert nicht. Ja. Der Grund ist, wie zu erwarten, dass sich die Ausgabedimensionen mit der Implementierung von Schritt und Abstand ändern. Lassen Sie uns darüber nachdenken, wie sich dies ändern wird.
Durch Erhöhen der Schrittweite wird die Anzahl der Filter in umgekehrtem Verhältnis verringert. Sie können sehen, dass die Häufigkeit halbiert wird, je nachdem, ob der Filter alle 1 Zelle oder alle 2 Zellen angewendet wird. In einer mathematischen Formel ausgedrückt
O_h = \cfrac{I_h - F_h}{\textrm{stride}} + 1\\
O_w = \cfrac{I_w - F_w}{\textrm{stride}} + 1
Es wird so sein.
Wenn $ I_h = 4, F_h = 2, \ textrm {stride} = 1 $
Die Wirkung der Polsterung ist sehr einfach. Da die Größe jedes Eingabebildes auf und ab $ + \ textrm {pad} \ _ {ud} $ ist, links und rechts $ + \ textrm {pad} \ _ {lr} $
I_h \leftarrow I_h + 2\textrm{pad}_{ud} \\
I_w \leftarrow I_w + 2\textrm{pad}_{lr}
Kann durch ersetzt werden, das heißt
O_h = \cfrac{I_h - F_h + 2\textrm{pad}_{ud}}{\textrm{stride}} + 1 \\
O_w = \cfrac{I_w - F_w + 2\textrm{pad}_{lr}}{\textrm{stride}} + 1
Es wird sein. Wenn Sie dagegen die Größe des Ausgabebilds an die Größe des Eingabebilds anpassen möchten, ist $ O_h = I_h $ und $ O_w = I_w $.
\textrm{pad}_{ud} = \cfrac{1}{2}\left\{(I_h - 1) \textrm{stride} - I_h + F_h\right\} \\
\textrm{pad}_{lr} = \cfrac{1}{2}\left\{(I_w - 1) \textrm{stride} - I_w + F_w\right\}
Es kann wie folgt berechnet werden. Übrigens, lassen Sie uns den Freiheitsgrad im Schritt erhöhen.
O_h = \cfrac{I_h - F_h + 2\textrm{pad}_{ud}}{\textrm{stride}_{ud}} + 1 \\
O_w = \cfrac{I_w - F_w + 2\textrm{pad}_{lr}}{\textrm{stride}_{lr}} + 1 \\
\textrm{pad}_{ud} = \cfrac{1}{2}\left\{(I_h - 1) \textrm{stride}_{ud} - I_h + F_h\right\} \\
\textrm{pad}_{lr} = \cfrac{1}{2}\left\{(I_w - 1) \textrm{stride}_{lr} - I_w + F_w\right\}
im2col
Das "im2col" mit erhöhter Freiheit durch Hinzufügen von Schritt und Polsterung ist wie folgt. Ich werde auch einige Anpassungen vornehmen.
im2col.py
import numpy as np
def im2col(images, filters, stride=1, pad=0, get_out_size=True):
if images.ndim == 2:
images = images.reshape(1, 1, *images.shape)
elif images.ndim == 3:
B, I_h, I_w = images.shape
images = images.reshape(B, 1, I_h, I_w)
if filters.ndim == 2:
filters = filters.reshape(1, 1, *filters.shape)
elif images.ndim == 3:
M, F_h, F_w = filters.shape
filters = filters.reshape(M, 1, F_h, F_w)
B, C, I_h, I_w = images.shape
_, _, F_h, F_w = filters.shape
if isinstance(stride, tuple):
stride_ud, stride_lr = stride
else:
stride_ud = stride
stride_lr = stride
if isinstance(pad, tuple):
pad_ud, pad_lr = pad
elif isinstance(pad, int):
pad_ud = pad
pad_lr = pad
elif pad == "same":
pad_ud = 0.5*((I_h - 1)*stride_ud - I_h + F_h)
pad_lr = 0.5*((I_w - 1)*stride_lr - I_w + F_w)
pad_zero = (0, 0)
O_h = int(np.ceil((I_h - F_h + 2*pad_ud)/stride_ud) + 1)
O_w = int(np.ceil((I_w - F_w + 2*pad_lr)/stride_lr) + 1)
pad_ud = int(np.ceil(pad_ud))
pad_lr = int(np.ceil(pad_lr))
pad_ud = (pad_ud, pad_ud)
pad_lr = (pad_lr, pad_lr)
images = np.pad(images, [pad_zero, pad_zero, pad_ud, pad_lr], \
"constant")
cols = np.empty((B, C, F_h, F_w, O_h, O_w))
for h in range(F_h):
h_lim = h + stride_ud*O_h
for w in range(F_w):
w_lim = w + stride_lr*O_w
cols[:, :, h, w, :, :] \
= images[:, :, h:h_lim:stride_ud, w:w_lim:stride_lr]
if get_out_size:
return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w), (O_h, O_w)
else:
return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)
Ich werde es kurz erklären.
im2col.py
def im2col(images, filters, stride=1, pad=0, get_out_size=True):
if images.ndim == 2:
images = images.reshape(1, 1, *images.shape)
elif images.ndim == 3:
B, I_h, I_w = images.shape
images = images.reshape(B, 1, I_h, I_w)
if filters.ndim == 2:
filters = filters.reshape(1, 1, *filters.shape)
elif images.ndim == 3:
M, F_h, F_w = filters.shape
filters = filters.reshape(M, 1, F_h, F_w)
B, C, I_h, I_w = images.shape
_, _, F_h, F_w = filters.shape
if isinstance(stride, tuple):
stride_ud, stride_lr = stride
else:
stride_ud = stride
stride_lr = stride
if isinstance(pad, tuple):
pad_ud, pad_lr = pad
elif isinstance(pad, int):
pad_ud = pad
pad_lr = pad
elif pad == "same":
pad_ud = 0.5*((I_h - 1)*stride_ud - I_h + F_h)
pad_lr = 0.5*((I_w - 1)*stride_lr - I_w + F_w)
pad_zero = (0, 0)
_, _, ...
Teil) und erhalten Sie die Größe eines Filters.
--Wenn "Schritt" "Tupel" ist, wird davon ausgegangen, dass die obere und untere sowie die linke und rechte Schrittbreite einzeln angegeben werden, andernfalls wird der gleiche Wert verwendet.pad`` tuple
ist, wird davon ausgegangen, dass die obere und untere sowie die linke und rechte Polsterbreite einzeln angegeben werden, andernfalls wird der gleiche Wert verwendet.
--Wenn pad ==" same "
angegeben ist, wird die Füllbreite, die die Größe des Eingabebildes beibehält, mit ** float
** berechnet (für die spätere Berechnung der Ausgabegröße).Ich mache die Verarbeitung so.
im2col.py
O_h = int(np.ceil((I_h - F_h + 2*pad_ud)/stride_ud) + 1)
O_w = int(np.ceil((I_w - F_w + 2*pad_lr)/stride_lr) + 1)
pad_ud = int(np.ceil(pad_ud))
pad_lr = int(np.ceil(pad_lr))
pad_ud = (pad_ud, pad_ud)
pad_lr = (pad_lr, pad_lr)
images = np.pad(images, [pad_zero, pad_zero, pad_ud, pad_lr], \
"constant")
cols = np.empty((B, C, F_h, F_w, O_h, O_w))
Hier
--Berechnen Sie die Größe des Ausgabebildes
Ich mache.
im2col.py
for h in range(F_h):
h_lim = h + stride_ud*O_h
for w in range(F_w):
w_lim = w + stride_lr*O_w
cols[:, :, h, w, :, :] \
= images[:, :, h:h_lim:stride_ud, w:w_lim:stride_lr]
if get_out_size:
return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w), (O_h, O_w)
else:
return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)
Schließlich über den Verarbeitungskörper und den Rückgabewert.
cols
Laden Sie die MNIST-Daten aus dem Keras-Datensatz herunter und experimentieren Sie.
mnist_test.py
#%pip install tensorflow
#%pip install keras
from keras.datasets import mnist
import matplotlib.pyplot as plt
#Geben Sie die Anzahl der zu erfassenden Blätter an
B = 3
#Datensatzerfassung
(x_train, _), (_, _) = mnist.load_data()
x_train = x_train[:B]
#Versuchen Sie anzuzeigen
fig, ax = plt.subplots(1, B)
for i, x in enumerate(x_train):
ax[i].imshow(x, cmap="gray")
fig.tight_layout()
plt.savefig("mnist_data.png ")
plt.show()
#Versuchen Sie, vertikale Linien zu erkennen
M = 1
C = 1
F_h = 7
F_w = 7
_, I_h, I_w = x_train.shape
f = np.zeros((F_h, F_w))
f[:, int(F_w/2)] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig2, ax2 = plt.subplots(1, B)
for i, x in enumerate(y):
ax2[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig2.tight_layout()
plt.savefig("vertical_filtering.png ")
plt.show()
#Versuchen Sie, horizontale Linien zu erkennen
f = np.zeros((F_h, F_w))
f[int(F_h / 2), :] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig3, ax3 = plt.subplots(1, B)
for i, x in enumerate(y):
ax3[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig3.tight_layout()
plt.savefig("horizontal_filtering.png ")
plt.show()
#Versuchen Sie, einen Gefälle zu erkennen
f = np.zeros((F_h, F_w))
for i in range(F_h):
f[i, i] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig4, ax4 = plt.subplots(1, B)
for i, x in enumerate(y):
ax4[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig4.tight_layout()
plt.savefig("right_down_filtering.png ")
plt.show()
#Versuchen Sie, einen Anstieg nach rechts zu erkennen
f = np.zeros((F_h, F_w))
for i in range(F_h):
f[F_h - i - 1, i] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig4, ax4 = plt.subplots(1, B)
for i, x in enumerate(y):
ax4[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig4.tight_layout()
plt.savefig("right_up_filtering.png ")
plt.show()
Dies ist das Ende der Erklärung zu im2col
.
Wenn Sie Fehler oder intelligentere Schreibstile haben, würde ich mich freuen, wenn Sie mich in den Kommentaren informieren könnten.