[PYTHON] Ich wollte cGAN zu ACGAN weiterentwickeln

Einführung

Dieser Artikel ist eine Fortsetzung von "CGAN (bedingte GAN) generiert MNIST (KMNIST)". Dies ist ein Datensatz, wenn versucht wird, ACGAN basierend auf cGAN auszuführen.

Ich denke, es ist eine natürliche Idee in Bezug auf die Evolution von cGAN, aber wenn ich es implementiere, ist es ziemlich ...

Was ist ACGAN?

Da ich cGAN im vorherigen Artikel kurz vorgestellt habe, werde ich ACGAN kurz erläutern. ACGAN ist kurz gesagt ** "cGAN, bei dem Discriminator auch Klassifizierungsaufgaben ausführt" **. Diese Methode ermöglicht die Ausgabe von Bildern mit mehr Variationen.

Das Originalpapier ist [hier](Bedingte Bildsynthese mit Hilfsklassifikator-GANs)

A. Odena, C. Olah, J. Shlens. Conditional Image Synthesis With Auxiliary Classifier GANs. CVPR, 2016

In Bezug auf die Papiere von ACGAN haben einige Leute die Originalpapiere veröffentlicht, so dass dies hilfreich sein wird.
Referenzartikel
AC-GAN-Papierkommentar (Conditional Image Synthesis with Auxiliary Classifier GANs)

ACGAN-Modellstruktur

In cGAN wurden die Original- / Fälschungsbild- und Etiketteninformationen in Discriminator eingegeben und die Identifizierung von Original- oder Fälschungsinformationen ausgegeben. Andererseits ist in ACGAN die Eingabe von Discriminator nur ein Bild und nicht nur die Identifizierung von echt oder falsch, sondern auch das Klassenurteil, um zu erraten, welche Klasse der Ausgabe hinzugefügt wird. In einem Diagramm sieht es wie folgt aus. ACGAN.jpg Der "Klasse" -Teil in der Figur ist die Ausgabe der von Discriminator vorhergesagten Klassifizierung. Wie label hat es die Form eines Klassennummern-Dimensionsvektors.

Vorerst implementiert

ACGAN hat PyTorch-Implementierung auf GitHub. Lassen Sie uns als Referenz die Implementierung von cGAN ändern, die ich im vorherigen Artikel geschrieben habe.

Was ist zu tun

Ist fast alles. Dann sieht die Struktur von Discriminator so aus. ACGAN_discriminator.jpg Dies ist eine Zeichnung des Strukturdiagramms des cGAN-Diskriminators, das im vorherigen Artikel veröffentlicht wurde. Der rot dargestellte Teil ist jedoch die Änderung in ACGAN.

Eine Implementierung von Discriminator.

python



class Discriminator(nn.Module):
    def __init__(self, num_class):
        super(Discriminator, self).__init__()
        self.num_class = num_class
        
        self.conv = nn.Sequential(
            nn.Conv2d(1, 64, kernel_size=4, stride=2, padding=1), #Eingang ist 1 Kanal(Weil es schwarz und weiß ist),Anzahl der Filter 64,Filtergröße 4*4
            nn.LeakyReLU(0.2),
            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),
            nn.LeakyReLU(0.2),
            nn.BatchNorm2d(128),
        )
        
        self.fc = nn.Sequential(
            nn.Linear(128 * 7 * 7, 1024),
            nn.BatchNorm1d(1024),
            nn.LeakyReLU(0.2),
        )
        
        self.fc_TF = nn.Sequential(
            nn.Linear(1024, 1),
            nn.Sigmoid(),
        )
        
        self.fc_class = nn.Sequential(
            nn.Linear(1024, num_class),
            nn.LogSoftmax(dim=1),
        )
        
        self.init_weights()
        
    def init_weights(self):
        for module in self.modules():
            if isinstance(module, nn.Conv2d):
                module.weight.data.normal_(0, 0.02)
                module.bias.data.zero_()
            elif isinstance(module, nn.Linear):
                module.weight.data.normal_(0, 0.02)
                module.bias.data.zero_()
            elif isinstance(module, nn.BatchNorm1d):
                module.weight.data.normal_(1.0, 0.02)
                module.bias.data.zero_()
            elif isinstance(module, nn.BatchNorm2d):
                module.weight.data.normal_(1.0, 0.02)
                module.bias.data.zero_()
        
    def forward(self, img):
        x = self.conv(img)
        x = x.view(-1, 128 * 7 * 7)
        x = self.fc(x)
        x_TF = self.fc_TF(x)
        x_class = self.fc_class(x)
        return x_TF, x_class

Es scheint verschiedene Möglichkeiten zu geben, die Ausgabe der Klassifizierung hinzuzufügen. In der PyTorch-Implementierung des Links, den ich zuvor gepostet habe, wurde die Ebene "Linear" am Ende gegabelt, daher implementiere ich sie hier auf die gleiche Weise.

Entsprechend dieser Änderung sieht die Funktion pro Epoche so aus.

python



def train_func(D_model, G_model, batch_size, z_dim, num_class, TF_criterion, class_criterion,
               D_optimizer, G_optimizer, data_loader, device):
    #Trainingsmodus
    D_model.train()
    G_model.train()

    #Das echte Label ist 1
    y_real = torch.ones((batch_size, 1)).to(device)
    D_y_real = (torch.rand((batch_size, 1))/2 + 0.7).to(device) #Geräuschetikett zum Einfügen von D.

    #Gefälschtes Etikett ist 0
    y_fake = torch.zeros((batch_size, 1)).to(device)
    D_y_fake = (torch.rand((batch_size, 1)) * 0.3).to(device) #Geräuschetikett zum Einfügen von D.
    
    #Initialisierung des Verlustes
    D_running_TF_loss = 0
    G_running_TF_loss = 0
    D_running_class_loss = 0
    D_running_real_class_loss = 0
    D_running_fake_class_loss = 0
    G_running_class_loss = 0
    
    #Chargenweise Berechnung
    for batch_idx, (data, labels) in enumerate(data_loader):
        #Ignorieren, wenn weniger als die Stapelgröße
        if data.size()[0] != batch_size:
            break
        
        #Geräuschentwicklung
        z = torch.normal(mean = 0.5, std = 1, size = (batch_size, z_dim)) #Durchschnitt 0.Generieren Sie Zufallszahlen nach einer Normalverteilung von 5
        
        real_img, label, z = data.to(device), labels.to(device), z.to(device)
        
        #Diskriminator-Update
        D_optimizer.zero_grad()
        
        #Setzen Sie ein reales Bild in Discriminator und verbreiten Sie vorwärts ⇒ Verlustberechnung
        D_real_TF, D_real_class = D_model(real_img)
        D_real_TF_loss = TF_criterion(D_real_TF, D_y_real)
        CEE_label = torch.max(label, 1)[1].to(device)
        D_real_class_loss = class_criterion(D_real_class, CEE_label)
        
        #Fügen Sie das durch Einfügen von Rauschen in Generator in Discriminator erzeugte Bild ein und verbreiten Sie es vorwärts ⇒ Verlustberechnung
        fake_img = G_model(z, label)
        D_fake_TF, D_fake_class = D_model(fake_img.detach()) #fake_Stop Loss wird in Bildern berechnet, damit es nicht zurück zum Generator übertragen wird
        D_fake_TF_loss = TF_criterion(D_fake_TF, D_y_fake)
        D_fake_class_loss = class_criterion(D_fake_class, CEE_label)

        #Minimieren Sie die Summe von zwei Verlusten
        D_TF_loss = D_real_TF_loss + D_fake_TF_loss
        D_class_loss = D_real_class_loss + D_fake_class_loss
        
        D_TF_loss.backward(retain_graph=True)
        D_class_loss.backward()
        D_optimizer.step()
        
        D_running_TF_loss += D_TF_loss.item()
        D_running_class_loss += D_class_loss.item()
        D_running_real_class_loss += D_real_class_loss.item()
        D_running_fake_class_loss += D_fake_class_loss.item()


        #Generator-Update
        G_optimizer.zero_grad()
        
        #Das Bild, das durch Einfügen von Rauschen in den Generator erzeugt wird, wird in den Diskriminator eingefügt und vorwärts weitergegeben. ⇒ Der erkannte Teil wird zu Verlust
        fake_img_2 = G_model(z, label)
        D_fake_TF_2, D_fake_class_2 = D_model(fake_img_2)
        
        #G Verlust(max(log D)Optimiert mit)
        G_TF_loss = -TF_criterion(D_fake_TF_2, y_fake)
        G_class_loss = class_criterion(D_fake_class_2, CEE_label) #Aus Gs Sicht wäre es schön, wenn er D für real halten und ihm eine Klasse geben würde.
        
        G_TF_loss.backward(retain_graph=True)
        G_class_loss.backward()
        G_optimizer.step()
        G_running_TF_loss += G_TF_loss.item()
        G_running_class_loss -= G_class_loss.item()
        
    D_running_TF_loss /= len(data_loader)
    D_running_class_loss /= len(data_loader)
    D_running_real_class_loss /= len(data_loader)
    D_running_fake_class_loss /= len(data_loader)

    G_running_TF_loss /= len(data_loader)
    G_running_class_loss /= len(data_loader)
    
    return D_running_TF_loss, G_running_TF_loss, D_running_class_loss, G_running_class_loss, D_running_real_class_loss, D_running_fake_class_loss

Zusätzlich zu den zuvor erwähnten Änderungen habe ich auch das hinzuzufügende Rauschen geändert. Beim letzten Mal war es eine Normalverteilung mit 30 Dimensionen, Durchschnitt 0,5 und Standardabweichung 0,2, aber diesmal ist es eine Normalverteilung mit 100 Dimensionen, Durchschnitt 0,5 und Standardabweichung 1.

Der Klassifizierungsverlust ist "torch.nn.NLLLoss ()". Dies stimmte auch mit der früheren Implementierung des Links überein.

Ergebnis

Erstens ist das Verlustdiagramm. In ACGAN gibt es zwei Arten von Verlusten: echte oder gefälschte Identifikationsverluste und Klassifizierungsverluste. Beide Verluste werden sowohl an den Generator als auch an den Diskriminator weitergegeben. Es wird auch separat in der Grafik dargestellt. simpleACGAN loss.png

"T / F_loss" ist der Verlust (durchgezogene Linie) für die echte / gefälschte Identifizierung, und "class_loss" ist der Verlust (gepunktete Linie) für die Klassifizierung.

Wenn man das betrachtet, sieht es so aus, als würde es funktionieren. Jedoch... result_ACGAN (1).gif Dies ist ein GIF, wenn für jede Epoche ein Bild von jedem Etikett generiert wird. Ich habe die Etiketteninformationen so eingegeben, dass die obere Zeile von links "Ah, I, U ..." und die untere rechte "..., N, ゝ" lautet. Es gibt fast keine Übereinstimmung zwischen dem Etikett und dem erzeugten Bild. Aber es sieht so aus, als würde "Text auf einem anderen Etikett" erzeugt und kein völlig bedeutungsloses Bild.

Ähnlich wie bei cGAN habe ich versucht, nach 100-tägigem Training mit Generator 5 "A" bis "ゝ" zu generieren. many_.png Ist es nicht nur "ke", das Label-chan zu entsprechen scheint? (Vielmehr bricht der Modus vollständig zusammen ...)

Dies ist übrigens das Ergebnis der cGAN-Generierung nach 100 Epochen Training unter den gleichen Bedingungen. epoch_00100.png Offensichtlich gibt cGAN Zeichen aus, die näher an der Beschriftung liegen.

Warum funktioniert es nicht ...?

Auf den ersten Blick denken sowohl Generator als auch Diskriminator ** bei ACGAN, dass Zeichen mit einer anderen Form die Zeichen auf diesem Etikett sind ** (Beispiel: Diskriminator und Generator sind beide "I"). Ist es nicht so (derjenige, der der Form ähnelt, wird als "A" -Label behandelt)? Ich dachte.

simpleACGAN_discriminatorloss.png Dies ist ein Diagramm, das den Verlust der Diskriminatorklassifizierung in den Verlust aus dem realen Bild und den Verlust aus dem gefälschten Bild (= vom Generator erstelltes Bild) unterteilt. sum_class_loss ist der Gesamtwert (= entspricht der rot gepunkteten Linie im vorherigen Diagramm). Wenn man sich diese Grafik ansieht, macht Discriminator einen Fehler bei der Beurteilung des realen Bildes (insbesondere in den frühen Lernphasen) und errät die Beurteilung des gefälschten Bildes. (In numerischen Begriffen ist "real_class_loss" am Anfang von "fake_class_loss" 20-mal höher und am Ende 5-mal höher.)

Mit anderen Worten, ** das vom Generator mit der Bezeichnung "A" erstellte Bild wird vom Diskriminator als "A" behandelt, selbst wenn die tatsächliche Form von "A" ** sehr unterschiedlich ist **. Ich kann mir vorstellen, dass.

Idealerweise sollte der Verlust der Klassifizierung sowohl für echte als auch für gefälschte Bilder ungefähr gleich sein.

Vergleichen Sie mit dem, was zu funktionieren scheint

Wie im Original-ACGAN-Dokument erwähnt, scheint sich ** bei zu vielen Klassen die Qualität des Ausgabebilds im selben Netzwerk zu verschlechtern **. In der Originalarbeit ist ImageNet (1000 Klassen) zum Experimentieren in 10 Klassen x 100 Fälle unterteilt.

Deshalb habe ich mich entschlossen, dies einmal in 5 Klassen zu versuchen. Lassen Sie uns die Netzwerkstruktur gleich machen und versuchen, 5 Zeichen von "A" bis "O" zu generieren. ACGAN_5class.png Das Verlustdiagramm sieht ähnlich aus. Es scheint, dass noch Platz für den T / F_loss ist, um zu fallen. result_tmp.gif Auch hier gibt es einige Unebenheiten, aber die zweite Hälfte ist ziemlich schön. Als nächstes generieren wir nach 100 Epochentraining jeweils 5 Bilder. many_ (1).png Es scheint, dass der Modus nicht zusammenbricht.

Dann ist es der Verlust der Klassifizierung des Diskriminators. ACGAN_Discriminator_5class.png Auf numerischer Basis gab es in der frühen Phase einen Unterschied von ungefähr 10, aber es ist fast der gleiche Wert in der letzten Phase, aber es ist schwierig, diese Grafik zu sehen, so dass ich sie erst nach 3 Epochen anzeigen werde. ACGAN_Discriminator_5class_3epoch~.png Wenn Sie sich das ansehen, können Sie sehen, dass real_class_loss und fake_class_loss ziemlich nahe beieinander liegen.

Verlust pro Iter

Gibt es überhaupt einen 10- bis 20-fachen Unterschied zwischen der echten Klassifikation und der falschen Klassifikation aus der 1. Epoche in den frühen Lernphasen? ?? Ich dachte, also habe ich versucht, den Verlust für jeden Iter (für jeden Mini-Batch) anzuzeigen. ACGAN_discriminator_loss_per_iter.png Es ist wahr, dass sich der Verlustwert zunächst nicht zwischen "real_class_loss" und "fake_class_loss" ändert, aber Sie können sehen, dass "fake_class_loss" stark abfällt.

ACGAN_discriminate_loss.png ACGAN_discriminating_acc.png

Ich habe in den ersten Epochen versucht, nur das reale Bild zu trainieren, aber es machte nicht viel Sinn, deshalb habe ich beschlossen, nur die Klassifizierungsaufgabe vorab zu lernen. Holen Sie sich nur den Diskriminator und lösen Sie nur die Klassifizierungsaufgabe. Das Ergebnis der Klassifizierungsaufgabe konvergiert ziemlich schnell, daher mache ich nur 20 Epochen. Infolgedessen ist es subtil, aber vorerst werde ich diesen Diskriminator nach 20 Epochen Training verwenden. Ergebnisse bei der Anwendung von Pre-Learning

pretrianed_ACGAN.png Richtig / Falsch-Verlust ist fast das gleiche wie ohne Vorlernen. Klassifizierungsverlust Es ist von Anfang an ziemlich klein geworden.

Betrachten wir nun den Klassifizierungsverlust, der sich aus dem realen Bild und dem gefälschten Bild ergibt. preteained_ACGAN_real_fake.png Ich habe versucht, bis zu 300 Epochen zu lernen. Im Vergleich zu nicht vorgelernten ist der aus dem realen Bild abgeleitete Verlustwert ebenfalls erheblich niedriger. Es ist ungefähr viermal so viel wie der Verlust, der aus dem gefälschten Bild resultiert, aber es ist immer noch nicht der gleiche Wert.

Werfen wir einen Blick auf das Bild, das ACGAN nach diesem 300-Epochen-Training erstellt hat. pretrained_ACGAN_300epoch.png Hmm. .. Es ist kein Effekt zu sehen. Die Anzahl der erfolgreichen Zeichen nimmt nicht zu, und es kommt zu einem Zusammenbruch des Modus.

Impressionen

Es gibt mehrere Kuzuji-Datensätze mit einer großen Anzahl von Daten pro Zeichen, 6000 und nur etwa 300 bis 400. Ich denke, je größer die Anzahl der Daten pro Klasse ist, desto besser. Daher dachte ich, dass es funktionieren könnte, wenn die Anzahl der Daten größer als CIFAR-10 ist, aber es war nicht gut.

Ist der Abstand zwischen den Zeichen auf jeder Beschriftung im latenten Raum nicht eng (= Zeichen mit unterschiedlichen Beschriftungen sind im latenten Raum ziemlich eng)? Ich denke. Im Experiment des Originalpapiers habe ich für jeweils 10 Klassen mit CIFAR-10 und ImageNet experimentiert, aber im Fall von Junk-Zeichen gab es nur etwas mehr als die Hälfte der Zeichen, die in 10 Klassen arbeiteten, und es funktionierte nur in 5 Klassen.

Auf jeden Fall scheint es ziemlich schwierig zu sein, die 49er-Klasse mit ACGAN zu zielen und auszugeben, also werde ich aufgeben ...

Recommended Posts

Ich wollte cGAN zu ACGAN weiterentwickeln
Hash-Kette wollte ich vermeiden (2)
Hash-Kette wollte ich vermeiden (1)
Ich wollte ABC160 mit Python lösen
Ich wollte ABC159 mit Python lösen
Ich wollte ABC172 mit Python lösen
Ich wollte unbedingt mit Selen kopieren
DQN mit TensorFlow implementiert (ich wollte ...)
i-Town Page Scraping: Ich wollte den Platz von Wise-Kun einnehmen
Ich wollte mit der Bezier-Kurve spielen
Ich wollte Python 3.4.3 mit Homebrew + pyenv installieren
Ich wollte nur Pythons Pickle-Modul verstehen
Ich wollte auch Typhinweise mit numpy überprüfen
Ich wollte die Python-Bibliothek von MATLAB verwenden
Ich habe versucht zu debuggen.
[Fehler] Ich wollte Sätze mit Flairs TextRegressor generieren
Eine Geschichte über den Wunsch, die Django-Administrationsseite ein wenig zu ändern
Ich wollte den Panasonic Programming Contest 2020 mit Python lösen
Ich wollte bestimmte Erweiterungen beim Erstellen der Sphinx-Dokumentation überspringen
Ich wollte mich um die Ausführungszeit und die Speichernutzung kümmern
Ich habe das Toho-Projekt mit Deep Learning aufgenommen ... ich wollte.
Ich wollte ein Array mit der Subs-Methode von Sympy berechnen
Ich wollte mit boto3 mehrere objekte in s3 löschen
Ich habe versucht, PredNet zu lernen
Ich habe versucht, SVM zu organisieren.
Ich habe mit Raspberry Pi gesprochen
Ich habe versucht, PCANet zu implementieren
Einführung in die nichtlineare Optimierung (I)
Ich habe LightFM auf Movielens angewendet
Ich möchte SUDOKU lösen
Ich habe versucht, Linux wieder einzuführen
Ich habe versucht, Pylint vorzustellen
Ich habe versucht, SparseMatrix zusammenzufassen
jupyter ich habe es berührt
Ich habe versucht, StarGAN (1) zu implementieren.
Ich wollte es so machen, als würde ich einen Testfall für AtCoder ausführen.
Ich wollte eine intelligente Präsentation mit Jupyter Notebook + nb present erstellen
Ich wollte mein Gesichtsfoto in einen Yuyu-Stil umwandeln.
Ich wollte die Klassifizierung von CIFAR-10 mit dem Chainer-Trainer in Frage stellen
Ich wollte so etwas wie Elixirs Pipe in Python machen
Ich wollte das ABC164 A ~ D-Problem mit Python lösen
Was ich getan habe, als ich Python schneller machen wollte - Numba Edition -
[Django] Ich wollte testen, wenn ich eine große Datei poste [TDD]