Ich möchte mit GAN Schwarzweißfotos von Erinnerungen ausmalen

Motivation

Dies ist ein Thema, das häufig mit maschinellem Lernen zusammenhängt, aber ich möchte Schwarzweißfotos kolorieren. Ich habe den Code auch auf GitHub gestellt. [GitHub]

Als ich neulich auf die Reliquien meines Vaters zurückblickte, sah ich plötzlich ein altes Schwarzweißfoto. Ich habe es so gelassen, wie es war, als ich starb, aber jetzt denke ich, dass es gefärbt werden kann. Es ist eher ein persönliches Hobby-Blog als ein technisches Blog. Darüber hinaus gibt es Blogs und Bücher von Personen, auf die beim Studium von GAN Bezug genommen wird. Der Artikel dieser Person ist sehr lehrreich und dieser Artikel enthält auch einige Inhalte. Ich werde unten einen Link posten, also schauen Sie bitte.

[Referenz-URL] Shikoan's ML Blog Lernen Sie aus der Mosaikentfernung und dem neuesten Deep Learning

Blogstruktur

pix2 pix Übersicht

GAN Der Diskriminator ist in den Generator integriert, und beide werden parallel gelernt. Es ist ein Bild des Hinzufügens einer dynamischen Verlustfunktion unter Verwendung des vorhergesagten Werts von Diskriminator zu Generatorverlust. Es gibt verschiedene Arten von GAN, und die Unterteilung variiert je nach Definition. Es gibt jedoch die folgenden Unterteilungen.

Art Überblick
Conditional GAN Es gibt eine Beziehung zwischen dem Generator-Eingang und -Ausgang
Non-Conditional GAN Generatoreingang und -ausgang sind nicht miteinander verbunden

Diesmal ist pix2pix ein typisches bedingtes GAN (CGAN). DCGAN, das häufig in GAN-Tutorials verwendet wird, generiert eine Ausgabe aus Rauschdaten, sodass es zu einem nicht bedingten GAN wird. Da pix2pix CGAN ist, werden Eingabeinformationen sehr wichtig. In DCGAN wird beispielsweise mit Adversarial Loss, der in Bezug auf Discriminator angezeigt wird, weiter gelernt. Bei pix2pix wird jedoch zusätzlich zu Adversarial Loss auch der Unterschied zwischen gefälschtem Bild und realem Bild (z. B. L1-Verlust oder MSE) verwendet. Das Lernen schreitet voran. Auf diese Weise schreitet das Lernen schneller voran als bei anderen GANs, und die Ergebnisse sind stabiler. Im Gegensatz dazu ist bei einer Lernmethode, bei der nur der L1-Verlust verwendet wird, die Ausgabe insgesamt vage oder das Ganze mit durchschnittlichen Pixeln verfestigt, um den Verlust zu verringern. Es fühlt sich in der Regel schlampig an, und durch Hinzufügen von Adversarial Loss wird der L1-Verlust in der Regel gelernt, sodass ein realistischeres Bild ausgegeben werden kann, selbst wenn es ziemlich groß ist. Das Prinzip, dass es einen Kompromiss zwischen wahrgenommener Qualität und Verzerrung gibt, ist einer der sehr wichtigen Faktoren bei der Betrachtung von GAN.

Referenz: The Perception-Distortion Tradeoff (2017) [arXiv]

PatchGAN Das Folgende ist das Originalpapier von pix2pix, aber ich denke, es ist besser, hier auf Details zu PatchGAN zu verweisen.

Image-to-Image Translation with Conditional Adversarial Networks (2016) [arXiv]

Wenn in PatchGAN Discriminator die Richtigkeit eines Bildes beurteilt, wird es in mehrere Bereiche unterteilt und in jedem Bereich wird die Richtigkeit beurteilt.

france.jpg

スクリーンショット 2020-05-26 15.57.00.png

Das Teilen des Bereichs bedeutet nicht, dass Sie das Bild tatsächlich teilen und es separat in den Diskriminator eintauchen. Theoretisch ist dies der Fall, aber in Bezug auf die Implementierung wird ein Bild in den Diskriminator eingefügt und seine Ausgabe in einen Tensor im zweiten Stock umgewandelt. Zu diesem Zeitpunkt wird der Wert jedes Pixels des Tensors basierend auf der Information des Patch-Bereichs des Eingabebildes abgeleitet, und als Ergebnis liegt der Wert jedes Pixels des Tensors zwischen dem tatsächlichen Wahr oder Falsch (1 oder 0). Patch GAN wird realisiert, indem der Verlust von genommen wird. Ich denke nicht, dass es einfach ist, es in Worten zu erklären, deshalb werde ich oben ein Beispiel mit der französischen Flagge geben.


fig, axes = plt.subplots(1,2)

#Bilder laden
img = cv2.imread('france.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (600, 300))   #Originalgröße-> (300, 600, 3)
axes[0].imshow(img)
axes[0].set_xticks([])
axes[0].set_yticks([])

img = ToTensor()(img)   # (300, 600, 3) -> (3, 300, 600)
img = img.view(1, 3, 300, 600)   # (3, 300, 600) -> (1, 3, 300, 600)
img = nn.AvgPool2d(100)(img)   # (1, 3, 300, 600) -> (1, 3, 3, 6)
img = nn.Conv2d(3, 1, kernel_size=1)(img)   # (1, 3, 3, 6) -> (1, 1, 3, 6)
img = np.squeeze(img.detach().numpy())

axes[1].imshow(img, cmap='gray')
axes[1].set_xticks([])
axes[1].set_yticks([])

[Ergebnis]

download.png

Oben wird das Eingabebild in eine Feature-Map mit der Größe (3, 6) eingefügt. Dies ist jedoch nichts anderes als das Komprimieren des gesamten Patch-Bereichs des Originalbilds auf (1, 1). In dem obigen Beispiel wird eine Echtheitsbeurteilung für "3 × 6 = 18" Pixel durchgeführt.

Unet pix2pix verwendet U-net als Generator. U-net, das in der Segmentierung identisch ist, verfügt zusätzlich zur Encoder-Decoder-Struktur über eine Skip-Verbindung, damit die Informationen der Eingabedaten nicht so weit wie möglich verloren gehen. Das Folgende ist die im Experiment verwendete Vorwärtsmethode des Generators, aber Sie können sehen, dass die ursprünglichen Informationen jedes Mal mit "torch.cat" verkettet werden.

    def forward(self, x):
        x1 = self.enc1(x)
        x2 = self.enc2(x1)
        x3 = self.enc3(x2)
        x4 = self.enc4(x3)
        out = self.dec1(x4)
        out = self.dec2(torch.cat([out, x3], dim=1))
        out = self.dec3(torch.cat([out, x2], dim=1))
        out = self.dec4(torch.cat([out, x1], dim=1))
        return out

Das folgende U-Net-Bild ist übrigens eine Bildrutsche zum Thema maschinelles Lernen, die von Google kostenlos veröffentlicht wurde und kürzlich auf Twitter erwähnt wurde. [ML Visuals] Es gibt viele schöne Bilder, also schauen Sie bitte.

ML Visuals by dair.ai.jpg

Über diesen Datensatz

Google PhotoScan Verwenden Sie MIT-Adobe FiveK-Datensatz als Trainingsdaten. Dies wird häufig in Bildverbesserungspapieren verwendet, und es handelt sich um eine Reihe von unverarbeiteten Bildern und nachbearbeiteten Bildern durch einen professionellen Redakteur. Dieses Mal werde ich dieses verarbeitete Bild verwenden. Bei den verarbeiteten Fotos gibt es viele Fotos mit helleren Farben als bei normalen Farbfotos. Daher dachte ich, dass sie auch für diese Aufgabe geeignet sind. Wenn die Datengröße klein ist, ist die Datenkapazität nicht groß und der Download ist bis zu einem gewissen Grad angemessen. Dies ist ein echtes Schwarzweißfoto, das jedoch mithilfe einer App namens "Google PhotoScan" auf dem iPhone in Daten konvertiert wurde. Diese App wird im Detail veröffentlicht, wenn Sie sie googeln. Sie ist jedoch hervorragend und wandelt sie in hübsche Daten um, ohne in einen Fotoladen zu gehen (und zwar sofort). Das Originalfoto war ziemlich alt und vergilbt, aber als ich es in ein Schwarzweißbild konvertierte, sah es nicht viel anders aus als ein normales Schwarzweißbild.

Bildgröße

Das Bild in Fivek und das Bild, das Sie tatsächlich einfärben möchten, sind nicht quadratisch, sondern rechteckig, und die Seitenverhältnisse sind unterschiedlich. Daher habe ich mich für eines der folgenden vier Muster entschieden.

  1. Ändern Sie die Größe in ein einheitliches Quadrat
  2. Reisisieren Sie das Quadrat, ohne das ursprüngliche Seitenverhältnis zu ändern, und füllen Sie den leeren Bereich mit Schwarz.
  3. Platzieren Sie Reisize in einem Quadrat, ohne das ursprüngliche Seitenverhältnis zu ändern, und fügen Sie die Informationen des Originalbilds in den leeren Bereich ein.
  4. Drehen Sie das Hochformat, konvertieren Sie es in Querformat und ändern Sie die Größe auf dieselbe rechteckige Größe.
  5. In ein Quadrat zuschneiden

Vorerst habe ich mich dieses Mal für Methode 3 entschieden. In Bezug auf 1 und 4 besteht der Grund für die Annahme darin, dass die Möglichkeit besteht, dass sich die Eigenschaften des Bildes erheblich ändern, und für 2 und 3 dachte ich, dass 3 mehr Informationen enthalten würde. Wenn Sie geradeaus fahren, ist es natürlich die Nummer 5, aber ich mag keine quadratischen Ernten bis zu den Produktionsfotos. Das Ausgabeergebnis wird bei der Nachbearbeitung auf das ursprüngliche Seitenverhältnis zugeschnitten.

スクリーンショット 2020-05-26 17.43.00.png

Fang an zu lernen

Teil 1

Teil 1 lernen

bce_loss.png l1_loss.png

Sie können sehen, wie der Diskriminator allmählich stärker wird, beginnend an der Stelle, an der er in den frühen Stadien zum Amoklauf neigt, und der Verlust des Generators steigt. Was den L1-Verlust betrifft, bin ich mir nicht sicher, ob er abnimmt. (Die Schwierigkeit der GAN-Bewertung besteht darin, dass "L1loss klein = nahe an der realen Farbe" ist.)

Teil 2

Teil 2 lernen In Lernen 1 war der Diskriminator tendenziell stärker, daher habe ich mich entschlossen, zu experimentieren, indem ich die folgenden Punkte geändert habe.

- D,Und Gs gegnerischer Verlust änderte sich von BCE zu Scharnierverlust
-Ändern Sie die Stapelnormalisierung von D in Instanznormalisierung
-Halbieren Sie die Häufigkeit der Gewichtsaktualisierung von D (die Hälfte von G).

Kreuzentropieverlust und Scharnierverlust

In Teil 2 besteht die Änderung von Teil 1 darin, die Verlustfunktion von der in Teil 1 verwendeten binären Kreuzentropie in Scharnierverlust zu ändern. Grob gesagt ist das Ziel, "einen schwachen Verlust zu machen, um zu verhindern, dass ein (D- oder G-) Netzwerk zu stark wird". Das Folgende ist die Kreuzentropie (D-Version) der Eisenplatte.

D:loss = -\sum_{i=1}^{n}\bigl(t_ilogy_i - (1-t_i) log(1-y_i) \bigl)

Kurz gesagt, wenn "Ziel = 1" ist, sollte die Ausgabe so hoch wie möglich sein (da die letzte Schicht Sigmoid ist, ist sie umso näher an 1, je größer sie ist). Wenn "Ziel = 0" ist und Sie trainieren, um die Leistung in negativer Richtung zu erhöhen, ist der Verlust geringer.

Im Gegenteil, für G werden wir trainieren, um die obige Gleichung zu maximieren. Darüber hinaus wird im Fall von G nur das von sich selbst erzeugte gefälschte Bild ausgewertet, so dass der linke Term der obigen Formel verschwindet und einfacher wird.

G:loss = \sum_{i=1}^{n}log\Bigl(1 - D\bigl(G(z_i))\bigl) \Bigl)(Maximieren)\\
= \sum_{i=1}^{n}log\Bigl(D\bigl(G(z_i)) -  1\bigl) \Bigl)(Minimieren)\\
=  -\sum_{i=1}^{n}log\Bigl(D\bigl(G(z_i))\bigl) \Bigl) (Man kann auch denken, dass dies minimiert ist)

Das Obige gibt zwei Optimierungsmuster an, aber es scheint, dass es beide Implementierungsmethoden gibt.

Auf der anderen Seite denke ich, dass diese Seite in Bezug auf Scharnierverlust leicht zu verstehen ist. [Referenz] Die Site sagt, dass es nicht oft für etwas anderes als SVM verwendet wird, aber es ist interessant, dass es derzeit in anderen Methoden verwendet wird. In der Kreuzentropie wird das Ziel als (0,1) ausgedrückt, im Scharnier als (-1,1).

t = ±1 \\
Loss = max(0, 1-t*y)

Wenn Sie für die Formel eine kleinere Ausgabe ausgeben, wenn "Ziel = -1", und umgekehrt eine größere Ausgabe ausgeben, wenn "Ziel = 1", ist der Verlust gering. Im Gegensatz zur Kreuzentropie können Sie jedoch auch sehen, dass der Verlust bis zu einem gewissen Grad auf 0 gesenkt wird. Im Fall der Kreuzentropie verschwindet der Verlust niemals, es sei denn, er wird vollständig durch (0,1) vorhergesagt, dies ist jedoch bei Scharnieren nicht der Fall. Dies ist der Grund, warum es als "schwacher Verlust" bezeichnet wird. Die Implementierung von PyTorch ist wie folgt. An den Scharnieren ist es schwierig, die Muster durch D und G zu teilen, daher denke ich, dass es besser ist, sie alle zusammen zu klassifizieren.

# ones:Patch mit allen Werten 1
# zeros:Patch mit allen Werten 0

# Gloss(BCE)
loss = torch.nn.BCEWithLogitsLoss()(d_out_fake, ones)

# Gloss(Hinge)
loss = -1 * torch.mean(d_out_fake)

# Dloss(BCE)
loss_real = torch.nn.BCEWithLogitsLoss()(d_out_real, ones)
loss_fake = torch.nn.BCEWithLogitsLoss()(d_out_fake, zeros)
loss = loss_reak + loss_fake

# Dloss(Hinge)
loss_real = -1 * torch.mean(torch.min(d_out_real-1, zeros))
loss_fake = -1 * torch.mean(torch.min(-d_out_fake-1, zeros))
loss = loss_reak + loss_fake

Instance Normalization Die Instanznormalisierung ist eine Ableitung der Stapelnormalisierung. Der Inhalt einschließlich der Chargennormalisierung ist in den folgenden Artikeln gut organisiert.

[GIF] Erklärung von CNN für Anfänger zur Batch-Normalisierung und ihre Freunde

Die Chargennormalisierung führt eine Standardisierungsverarbeitung zwischen denselben Datenkanälen durch, die in einem Mini-Batch enthalten sind. Die Instanznormalisierung führt jedoch nicht den gesamten Mini-Batch durch, sondern nur die Daten. Der Punkt ist Chargennormalisierung mit Chargengröße 1. Zum Beispiel wird es auch in pix2pix HD verwendet, einem Derivat von pix2pix, aber sein Zweck ist es, es schwierig zu machen, das Lernen durch Unterdrücken des Anstiegs des Gradienten zu konvergieren. Der Hauptzweck besteht darin, D und G auszugleichen, indem dies auf D angewendet wird.

Unten sind die Ergebnisse. Sie können sehen, dass die Konvergenz von Discriminator deutlich langsamer ist als zuvor. Ich habe auch das Gefühl, dass die Abnahme des L1-Verlusts größer ist als zuvor. adv_loss.png l1_loss.png

Teil 3

Lernen # 3 hat die folgenden Punkte von Lernen 1 geändert.

-Grundsätzlich folgen Sie dem Lernen 1
-Richten Sie die Lernrate an der Originalarbeit aus.(1e-4 -> 2e-4)
-Die Anpassung der Lernrate mit dem Scheduler wurde entfernt
-Die Bildgröße wurde von 320 auf 256 geändert (gemäß dem Papier).
-Die Anzahl der PatchGAN-Bereiche wurde von 10x10 auf 4x4 geändert
-Unschärfe in Augmentation im Zug entfernt

Das Ergebnis war jedoch fast das gleiche wie 1.

Teil 4

Wenn wir uns die bisherigen Ergebnisse ansehen, können wir die Tendenz erkennen, dass "natürliche Landschaften relativ gefärbt werden können, aber die Menschen überhaupt nicht arbeiten". Dies ist für den ursprünglichen Zweck bedeutungslos, daher habe ich beschlossen, die Daten erneut selbst zu erfassen. Wie man Daten sammelt, habe ich im vorherigen Qiita-Artikel geschrieben, aber BingImageSearch verwendet. [Qiita-Artikel-Link] Obwohl es auf Teil 2 basiert, wurde es außerdem leicht manipuliert. Da sich die Anzahl der Daten verdreifacht hat (mehr als 13.000), haben wir 200 Epochen auf 100 Epochen reduziert.

#Änderungen vom Lernen 1
-Trainingsdaten hinzufügen (Fivek)-> fivek+Menschen Bild)
-200 Epochennummern-> 97
- D,Der gegnerische Verlust wurde von BCE in Scharnierverlust geändert(Mit Teil 2)
-Ändern Sie die Stapelnormalisierung von D in Instanznormalisierung(Mit Teil 2)
-Halbieren Sie die Häufigkeit der Gewichtsaktualisierung von D.(Mit Teil 2)
-Die Anpassung der Lernrate mit dem Scheduler wurde entfernt(Mit Teil 3)
-Lernrate 1e-4->2e-Geändert zu 4 (mit Teil 3)

#Der Grund, warum die Anzahl der Pixel in der Ausgabe etwas unbefriedigend war, ist möglicherweise nicht wirklich gut
-Bildgröße ändern(320->352) (<-new)
-Zusammen mit den oben genannten die Anzahl der PatchGAN-Bereiche(10,10)->(11,11)ändern (<-new)

Unten sind die Ergebnisse. Im Vergleich zu Teil 2 scheint die große vertikale Bewegung in der Endphase die Hauptursache für das Entfernen des Schedulers zu sein. adv_loss.png l1_loss.png

Zusammenfassung. Und das Ergebnis des wichtigen Fotos

Durch verschiedene Versuche und Irrtümer können Landschaftsbilder mit einer beträchtlichen Geschwindigkeit ohne Unbehagen gefärbt werden, während Porträtbilder und auffällige Kontrastbilder (z. B. Menschen, Kleidung, Blumen, künstliche Objekte usw.) nicht so gefärbt sind. Ich fand eine Tendenz, nicht fortzufahren.

(Links: falsches Bild Rechts: echtes Bild) 000062.png 000133.png 000200.png 000219.png 000314.png 000324.png 000331.png 000372.png 000552.png 000553.png 000684.png 000758.png 000771.png 000909.png 001081.png 001179.png 001242.png 001494.png 001554.png 002079.png 002330.png 002377.png 002480.png Im Gegenteil, ich fühle, dass sich das mysteriös anfühlt. Es ist interessant anzusehen. Außerdem schien mein Foto für die eigentliche Produktion gut gefärbt zu sein (lacht). .. ..

(Schließlich betrachtet) Obwohl es mein Testbild ist (Foto des Vaters), ist das Ergebnis ungleichmäßiger als die Lern- und Verifizierungsdaten. Wahrscheinlich denke ich, dass es wahrscheinlich überhaupt ein Problem mit der Auflösung gibt. Das obige Bild ist immer noch gut, und selbst wenn andere Schwarzweißbilder in Daten konvertiert werden, kann der Umriss beim Vergrößern gesehen werden, aber die Auflösung ist in kleinen Bereichen ungleichmäßig und das Ergebnis ist enttäuschend. Wenn ich es also ernsthaft tun möchte, werde ich die Superauflösungsaufgabe parallel erledigen. Alternativ kann es erforderlich sein, Maßnahmen wie das Verwischen der Lerndaten zu ergreifen. Ich werde es nächstes Mal tun (geplant).

Recommended Posts

Ich möchte mit GAN Schwarzweißfotos von Erinnerungen ausmalen
Ich möchte ○○ mit Pandas machen
Ich möchte mit Python debuggen
Ich möchte den Anfang des nächsten Monats mit Python ausgeben
Ich möchte die Position meines Gesichts mit OpenCV überprüfen!
Ich möchte Objekte mit OpenCV erkennen
Ich möchte einen Blog mit Jupyter Notebook schreiben
Ich möchte eine Pip-Installation mit PythonAnywhere durchführen
Ich möchte Protokolle mit Python analysieren
Ich möchte mit aws mit Python spielen
Ich möchte meine Gefühle mit den Texten von Mr. Children ausdrücken
Ich möchte einen Teil der Excel-Zeichenfolge mit Python einfärben
Ich möchte das automatische Löschen des tmp-Bereichs in RHEL7 stoppen
Ich möchte MATLAB feval mit Python verwenden
Ich habe versucht, GAN (mnist) mit Keras zu bewegen
Ich möchte datetime.datetime.now () auch mit pytest verspotten!
Ich möchte mehrere Bilder mit matplotlib anzeigen.
Ich möchte 100 Datenwissenschaften mit Colaboratory schlagen
Ich möchte ein Spiel mit Python machen
Ich möchte OREMO mit setParam sein!
Ich möchte Temporäres Verzeichnis mit Python2 verwenden
Ich möchte die Daten von League of Legends ③ erhalten
Ich möchte die Daten von League of Legends ② erhalten
Ich möchte -inf nicht mit np.log verwenden
#Unresolved Ich möchte Gobject-Introspection mit Python3 kompilieren
Ich möchte ip vrf mit SONiC verwenden
Ich möchte APG4b mit Python lösen (Kapitel 2)
Ich möchte mit Djangos Migrate von vorne beginnen
Ich möchte das Erscheinungsbild von zabbix anpassen
Ich möchte League of Legends-Daten erhalten ①
Ich möchte mit Python in eine Datei schreiben
Ich möchte nur verschiedene Zeilen der Textdatei mit diff anzeigen
Ich möchte Google Mail mit Python senden, kann dies jedoch aufgrund eines Fehlers nicht
Ich möchte ein Bild mit Lollipop in WebP konvertieren
Ich möchte mit Jubatus (1) eine nicht autorisierte Anmeldung bei Facebook erkennen.
Ich habe versucht, Funktionen mit SIFT von OpenCV zu extrahieren
Ich möchte mit einem Knopf am Kolben übergehen
Ich möchte das Ausführungsergebnis von strace erfassen
Ich möchte die Optimierung mit Python und CPlex behandeln
Ich möchte mit verstärkendem Lernen einen Berg besteigen
Ich möchte mit Python-Datenklasse nach hinten erben
Ich möchte die Grundlagen von Bokeh vollständig verstehen
Ich möchte mit einem Roboter in Python arbeiten.
Ich möchte eine Zeichenkette mit Hiragana teilen
Ich möchte ein Paket von Php Redis installieren
Ich möchte Lambda mit Python auf Mac AWS!
Ich möchte manuell eine Legende mit matplotlib erstellen
[TensorFlow] Ich möchte Fenster mit Ragged Tensor verarbeiten
Ich möchte einen Quantencomputer mit Python betreiben
Ich möchte eine lokale Variable mit Lambda binden
Ich möchte die Sicherheit der SSH-Verbindung erhöhen
Ich möchte die Standortinformationen von GTFS Realtime auf Jupyter zeichnen! (Mit Ballon)
Ich habe versucht, die Entropie des Bildes mit Python zu finden
Ich möchte Daten mit Python analysieren können (Teil 3)
Ich möchte Pythons ungelöste Importwarnung mit vsCode entfernen
Ich möchte R-Funktionen einfach mit ipython notebook verwenden
Ich habe versucht, mit TensorFlow den Durchschnitt mehrerer Spalten zu ermitteln
Ich möchte Daten mit Python analysieren können (Teil 1)
Ich möchte einen Blog-Editor mit dem Administrator von Django erstellen