Unter den sich schnell entwickelnden GANs der letzten Jahre wollte ich StyleGAN studieren, eines der bekanntesten, also habe ich dieses Thema gewählt.
In der ersten Hälfte werde ich als Einführung in das Papier zusammenfassen, was ich über die Struktur und Eigenschaften von StyleGAN gelernt habe. In der zweiten Hälfte habe ich versucht, Bilder mit dem tatsächlich trainierten StyleGAN zu generieren, also werde ich das Ergebnis schreiben.
StyleGAN (v1) StyleGAN wurde 2018 angekündigt. (Artikellink) Das Folgende ist ein Zitat aus einem von StyleGAN generierten Beispielbild, aber ich denke, es erzeugt ein qualitativ hochwertiges Bild, das von einem echten Foto nicht zu unterscheiden ist.
Die Funktionen von StyleGAN werden nachfolgend erläutert.
Ich würde sagen, dass die Funktion von StyleGAN hauptsächlich in der Struktur des Generators liegt. In der folgenden Abbildung im Papier ist die linke Seite die Struktur des herkömmlichen GAN-Generators (hier PGGAN) und die rechte Seite die Struktur des Style-GAN-Generators.
In der herkömmlichen GAN wird die latente Variable (latentes z) zufällig erzeugt und von der ersten Schicht des Generators eingegeben. Andererseits ist im Fall von StyleGAN der erste Eingang des Generators ein fester Wert, latentes z wird zuerst über das Mapping-Netzwerk konvertiert und dann mit AdaIN an verschiedenen Stellen in der Mitte des Generators eingegeben. Zusätzlich wird jeder Schicht des Generators zufällig erzeugtes Rauschen hinzugefügt.
Style Mixing Die in jeder Generatorebene eingegebenen Stilinformationen müssen nicht identisch sein, und mehrere Stilinformationen können kombiniert werden. Wenn es beispielsweise latente Variablen z1 und z2 gibt, die bestimmte Bilder A und B erzeugen, werden durch Eingabe des von z1 abgeleiteten Stils bis zu einer bestimmten Schicht des Generators und dann des von z2 abgeleiteten Stils die Eigenschaften von A und B eingegeben Sie können ein Bild erzeugen, das wie eine Mischung aus aussieht. (Stilmischung)
Wenn Sie zu diesem Zeitpunkt in der ersten Stufe von z1 zu z2 wechseln, werden die Hauptmerkmale von B (Gesichtsausrichtung und -form) wiedergegeben. Wenn Sie jedoch in der zweiten Stufe wechseln, werden nur detaillierte Merkmale (Haarfarbe usw.) wiedergegeben. Ich weiß schon.
Progressive Growing Dies ist die von PGGAN (Progressive-Growing GAN) vorgeschlagene Lernmethode. * Style GAN basiert auf PGGAN. Es wurde berichtet, dass hochauflösende Bilder wie 1024 x 1024 stabil erzeugt werden können, indem die Auflösung des erzeugten Bildes erhöht wird, indem während des Lernens allmählich Schichten von Generator und Diskriminator hinzugefügt werden.
Progressives Wachsen scheint jedoch einige Nachteile zu haben. In StyleGAN2 unten wird dieser Bereich ebenfalls berücksichtigt.
StyleGAN2 StyleGAN2 wurde 2019 als verbesserte Version von StyleGAN angekündigt. (Artikel Link) Das folgende Beispiel zeigt ein von StyleGAN2 generiertes Bild. Es ist schwierig, den Qualitätsunterschied zu StyleGAN zu erkennen, es wurde jedoch berichtet, dass das in StyleGAN auftretende charakteristische Wassertropfenmuster beseitigt wurde und sich die Werte wie FID, ein Index für die Bildqualität, erheblich verbessert haben. Ich werde.
Die wichtigsten Verbesserungspunkte sind folgende. --AdaIN-äquivalente Verarbeitung wird in einer einzelnen Conv-Schicht realisiert (Gewichtsdemodulation)
Jeder Artikel wird unten beschrieben.
StyleGAN hat dies verbessert, da festgestellt wurde, dass der AdaIN-Normalisierungsprozess Wassertropfen-ähnliche Artefakte verursachte. Im Folgenden wird die Struktur des Generators aus dem Papier zitiert.
Die linke Seite (a) und (b) sind das ursprüngliche StyleGAN, und die rechte Seite (d) ist das Ergebnis des Wechsels zu einem Formular, das AdaIN nicht verwendet. Die gleiche Operation wie die Normalisierung durch AdaIN wird durch eine Operation namens Gewichtsdemodulation realisiert (Teilen des Gewichts der Conv-Schicht durch die Standardabweichung).
Der Punkt ist, dass die Gewichtsdemodulation basierend auf der Annahme der Verteilung durchgeführt wird, ohne die Statistik der tatsächlichen Eingabedaten zu verwenden, und es wird berichtet, dass dies das Problem des Wassertropfenmusters gelöst hat.
Wir haben dies dem Regularisierungsterm hinzugefügt, da die Wahrnehmungspfadlänge, die angibt, ob der latente Raum wahrnehmungsmäßig glatt ist, für die Verbesserung der Qualität des erzeugten Bildes wichtig ist. (Pfadlängenregulierung) Das Verständnis dieses Bereichs ist nicht eindeutig, aber bedeutet dies, dass wahrnehmungsmäßig ähnliche Bilder für z erzeugt werden sollten (gelernt, dies zu tun), die sich im latenten Raum in der Nähe befinden? ..
Es wird auch berichtet, dass die Aktualisierung des Regularisierungszeitraums für den Hauptverlustzeitraum den Score nicht nachteilig beeinflusste, selbst wenn er weniger häufig war. (Das Reduzieren der Aktualisierungshäufigkeit des Regularisierungsterms wird als "Lazy Regularization" bezeichnet.) Dies reduziert die Berechnungskosten und die Speichernutzung und trägt auch zur Verkürzung der Lernzeit bei.
Progressives Wachsen hat den Vorteil, dass es die hochauflösende Bilderzeugung stabil lernen kann. Es besteht das Problem, dass lokale Teile wie Augen und Zähne nicht der Gesamtbewegung folgen (Gesichtsausrichtung). Die folgende Abbildung ist ein Beispiel. Sie können sehen, dass sich die Ausrichtung der Zähne nicht bewegt, selbst wenn sich die Richtung des Gesichts ändert. Im Vergleich zu GAN vor einigen Jahren finde ich es erstaunlich, dass wir solche Details diskutieren.
Das obige Problem wird auf die Tatsache zurückgeführt, dass progressives Wachstum es einfacher macht, häufige Features durch schrittweise Erhöhung der Auflösung zu generieren, und die Netzwerkstruktur wurde überprüft, damit das Lernen ohne Verwendung erfolgreich sein kann. Als Ergebnis des Experiments wurde gezeigt, dass es effektiv ist, die Sprungstruktur sowohl in den Generator als auch in den Diskriminator einzuführen, und es gelang, ein Bild von hoher Qualität ohne progressives Wachsen zu erzeugen. (→ Lösen Sie das Problem, dass Zähne und Augen nicht der Gesichtsrichtung folgen.)
... Das ist alles zum Lernen, und von nun an versuchen wir es tatsächlich mit der Bilderzeugung von StyleGAN.
Bei der Durchführung des Bilderzeugungsexperiments habe ich die unten veröffentlichte StyleGAN-Implementierung verwendet. (GitHub) stylegans-pytorch
Das offizielle StyleGAN ist TensorFlow, aber das Obige wird in PyTorch reproduziert und implementiert. Es war sehr hilfreich, eine detaillierte Erklärung von der Umgebungsvorbereitung bis zum Verfahren zum Umrechnen der gelernten Gewichte zu haben. Es ist sowohl mit StyleGAN1 als auch mit StyleGAN1 kompatibel, aber dieses Mal habe ich es mit StyleGAN1 versucht. (Weil es viele Arten von Gewichten zu geben schien, die verwendet werden konnten)
Die Umgebung ist wie folgt. Es ist eine Umgebung, die durch das Einfügen von Ubuntu und CUDA usw. in einen Spiel-PC erstellt wurde. OS : Ubuntu 18.04.4 LTS GPU : GeForce RTX 2060 SUPER x1
Die Vorbereitung wurde ohne Probleme nach dem Verfahren von READ ME abgeschlossen. Das mit dem umgerechneten Gewicht tatsächlich erzeugte Bild ist wie folgt.
Da das gleiche Bild wie das Original erzeugt wurde, wurde bestätigt, dass die Gewichtsumwandlung und der Erzeugungsprozess durch den Generator korrekt durchgeführt wurden.
Ich habe dies auch versucht, weil es mit einem trainierten Modell für die Erzeugung von 2D-Zeichen kompatibel war. [face_v1_1] [portrait_v1]
Bis zu diesem Zeitpunkt konnte ich die Bilderzeugung mit StyleGAN ausprobieren, entschied mich jedoch für die Interpolation der latenten Variablen z, die häufig in GAN-Videos zu sehen ist.
Unter Bezugnahme auf die ursprüngliche Datei waifu / run_pt_stylegan.py habe ich einen Prozess geschrieben, um eine z-Interpolation für das trainierte Modell des Anime-Charakters durchzuführen und das Ergebnis als GIF zu speichern.
anime_face_interpolation.py
import argparse
from pathlib import Path
import pickle
import numpy as np
import cv2
import torch
from tqdm import tqdm
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--model', type=str, default='face_v1_1',
choices=['face_v1_1','face_v1_2','portrait_v1','portrait_v2'])
parser.add_argument('--weight_dir', type=str, default='../../data')
args = parser.parse_args()
return args
def prepare_generator(args):
from run_pt_stylegan import ops_dict, setting
if 'v1' in args.model:
from stylegan1 import Generator, name_trans_dict
else:
from stylegan2 import Generator, name_trans_dict
generator = Generator()
cfg = setting[args.model]
with (Path(args.weight_dir)/cfg['src_weight']).open('rb') as f:
src_dict = pickle.load(f)
new_dict = {k : ops_dict[v[0]](src_dict[v[1]]) \
for k,v in name_trans_dict.items() if v[1] in src_dict}
generator.load_state_dict(new_dict)
return generator
def make_latents_seq():
n_latent_point = 3
interpolation_step = 13
n_image = 3
latent_dim = 512
#Generieren Sie zufällig Latenten als Ausgangspunkt
points = np.random.randn(n_latent_point, n_image, latent_dim)
results = []
for i in range(n_latent_point):
s = points[i]
e = points[i+1] if i+1 < n_latent_point else points[0]
latents_ = np.linspace(s, e, interpolation_step, endpoint=False) #Lineare Interpolation
results.append(latents_)
return np.concatenate(results)
def generate_image(generator, latents, device):
img_size = 320
latents = torch.from_numpy(latents.astype(np.float32))
with torch.no_grad():
N, _ = latents.shape
generator.to(device)
images = np.empty((N, img_size, img_size, 3), dtype=np.uint8)
for i in range(N):
z = latents[i].unsqueeze(0).to(device)
img = generator(z)
normalized = (img.clamp(-1, 1) + 1) / 2 * 255
np_img = normalized.permute(0, 2, 3, 1).squeeze().cpu().numpy().astype(np.uint8)
images[i] = cv2.resize(np_img, (img_size, img_size),
interpolation=cv2.INTER_CUBIC)
def make_table(imgs):
num_H, num_W = 1, 3 #Anzahl der auszurichtenden Bilder(Vertikal,Seite)
H = W = img_size
num_total = num_H * num_W
canvas = np.zeros((H*num_H, W*num_W, 3), dtype=np.uint8)
for i, p in enumerate(imgs[:num_total]):
h, w = i//num_W, i%num_W
canvas[H*h:H*-~h, W*w:W*-~w, :] = p[:, :, ::-1]
return canvas
return make_table(images)
def save_gif(images, save_path, fps=10):
from moviepy.editor import ImageSequenceClip
images = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in images]
clip = ImageSequenceClip(images, fps=fps)
clip.write_gif(save_path)
if __name__ == '__main__':
args = parse_args()
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
generator = prepare_generator(args).to(device)
latents_seq = make_latents_seq()
print('generate images ...')
frames = []
for latents in tqdm(latents_seq):
img = generate_image(generator, latents, device)
frames.append(img)
save_gif(frames, f'{args.model}_interpolation.gif')
In make_latents_seq () wird eine lineare Interpolation ausgehend von 3 Punkten latenten z durchgeführt, um z als Sequenz zu erzeugen.
Die Ergebnisse sind wie folgt. [face_v1_1] [portrait_v1]
Wie erwartet habe ich ein Video bekommen, in dem sich das Gesicht des Charakters ständig ändert. Es hängt von den Daten zum Zeitpunkt des Lernens ab, aber ich denke, es ist interessant, weil verschiedene Zeichenstile gemischt sind. (Irgendwie gibt es einige bekannte Gesichter ...) Der Bereich um den Kopf und unter den Schultern scheint sich ziemlich zufällig zu ändern, aber was den Gesichtsteil betrifft, können Sie sehen, dass es sich zu jedem Zeitpunkt der Interpolation um ein richtiges Gesicht handelt. Bedeutet dies, dass die latenten Räume wahrnehmungsmäßig glatt miteinander verbunden sind?
Wenn Sie sich nicht für die Dateigröße des GIF interessieren, können Sie den interpolation_step erhöhen, um die Animation flüssiger zu gestalten.
Für StyleGAN haben wir das Papier vorgestellt und ein Experiment mit einem trainierten Modell durchgeführt. Ich würde mich freuen, wenn Sie auf Fehler bei der Interpretation des Papiers hinweisen könnten. Auf meinem Heimcomputer kann es lange dauern, aber ich möchte eines Tages versuchen, selbst zu lernen.