[PYTHON] Im2col gründliches Verständnis

Zielperson

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.

Inhaltsverzeichnis

[Implementierung von Schritt und Polsterung](# Implementierung von Schritt und Polsterung) - [Implementierung des Schrittes](# Implementierung des Schrittes) - [Implementierung der Polsterung](# Implementierung der Polsterung) - [Berechnung der Ausgabedimension](# Berechnung der Ausgabedimension) - [Abgeschlossene Version `im2col`](# Abgeschlossene Version im2col) - [Experiment mit MNIST](Experiment mit #mnist) - [Schlussfolgerung](#Fazit)

Was ist im2col?

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.

Warum brauchst Du es

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. color_image.png 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. 4dim_image.png 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.

Was ist CNN?

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.

Filtern

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. filter_image.gif Das obige GIF zeigt ein Bild der Filterung.

Verhalten und erste Implementierung von im2col

Um die Implementierung von "im2col" zu verstehen, werden wir sein Verhalten mithilfe mathematischer Formeln, Bilder und Gifs gründlich analysieren.

Verhalten von 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. im2col_image.gif Ü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}

Frühe Implementierung von 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. cal_output_size.png 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

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_ex.png Die Berechnung für die Größe der Matrix ist wie oben. Im Folgenden sehen wir uns die Implementierung an, in der sie tatsächlich transformiert wird.

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. early_image.png 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.

Probleme mit frühem 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))
early_im2col_time.png 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.

Verbesserte Version 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.

Verbesserte Version `im2col` (initial ver)

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))
improved_early_im2col.png Wie Sie sehen, ist das Ergebnis 150-mal schneller! In diesem Fall dauert es nur 6 Sekunden, selbst wenn es einmal alle 60.000 Blatt verarbeitet wird, was es praktischer macht (als zuvor). Schauen wir uns nun an, was sich geändert hat und wie es sich geändert hat.

Änderung 1

Die erste Änderung ist der Speicherzuordnungsteil der Ausgabematrix.

improved_early_im2col.py


col = np.empty((F_h, F_w, O_h, O_w))

improved_col.png Der Speicher ist mit einer solchen 4-dimensionalen Datenstruktur gesichert.

Änderung 2

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. improved_im2col_numbering.png

Änderung 3

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. improved_im2col_reshape.png Du denkst es ist gut ~

Erweiterung auf mehrdimensionales Array

Ü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. color_image_and_filter.png In Anbetracht dessen werden wir verbessertes_early_im2col.py ändern.

Verfolge mit einer Formel

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.

Versuchen Sie zu implementieren

Die Implementierung ist fast die gleiche wie verbessert_early_im2col.py. Ich habe gerade oben Stapel- und Kanalabmessungen hinzugefügt. BC_cols.png

Batch-Channel-Unterstützung `im2col`

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_ex.png Die größte Änderung ist der Rückgabewert.

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!

Schritt und Polsterung

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.

schreiten

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.

Polsterung

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. pading_image.png

Implementierung von Schritt und Polsterung

Werfen wir einen Blick auf jede Implementierung.

Schrittweise Umsetzung

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 stride_image.gif 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. improved_stride_image.gif stride_col.png Immerhin ist es schwierig ... Es ist zu erstaunlich, darüber nachzudenken.

Padding-Implementierung

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

Berechnung der Ausgabedimension

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.

Auswirkung des Schrittes

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 $ O_h = \cfrac{4 - 2}{1} + 1 = 3 Und wenn $ I_h = 4, F_h = 2, \ textrm {stride} = 2 $ O_h = \cfrac{4 - 2}{2} + 1 = 2 Sie können sehen, dass es mit dem vorherigen Bild übereinstimmt.

Auswirkungen der Polsterung

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\}

Abgeschlossene Version 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

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.

Shaping usw.

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)
In diesem Teil

Ich mache die Verarbeitung so.

Vorbereitung

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.

Verarbeitungshauptteil und Rückgabewert

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.

Experimentieren Sie mit MNIST

Laden Sie die MNIST-Daten aus dem Keras-Datensatz herunter und experimentieren Sie.

mnist_test.py

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()
Ausgabeergebnis Originale Daten ![mnist_data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/0b3333f9-7c32-cef3-697b-8a24bdf8f5e3.png) Ergebnis der vertikalen Linienerkennung ![vertical_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/a7561d2c-0951-dd53-d718-1a3d61f02d69.png) Ergebnis der horizontalen Linienerkennung ![horizontal_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/db838924-ab55-d460-6026-467cfc6ef391.png) Ergebnis der Erkennung nach rechts unten ![right_down_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/aeddeae2-60cc-ffc4-312c-5c075bf760fc.png) Ergebnis der Erkennung nach rechts oben ![right_up_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/66964a57-9e17-6e4f-7c0f-7fca58832920.png)
Die ersten beiden Zeilen sind enthalten, da "Tensorflow" und "Keras" installiert sein müssen. Löschen Sie ggf. nur den Kommentar "#" und führen Sie ihn aus. Sobald Sie es ausgeführt haben, können Sie es erneut auskommentieren. Wie Sie dem Ausgabeergebnis entnehmen können, bleibt beim Anwenden jedes Filters nur die Ziellinie dunkel. Dies ist die Funktionserkennung.

abschließend

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.

Referenz

Deep Learning-Serie

Recommended Posts

Im2col gründliches Verständnis
col2im gründliches Verständnis
VQ-VAE verstehen
Verketten verstehen