Parmi les GAN qui évoluent rapidement ces dernières années, j'ai voulu étudier StyleGAN, qui est l'un des plus connus, j'ai donc choisi ce thème.
Dans la première moitié, en guise d'introduction à l'article, je résumerai ce que j'ai appris sur la structure et les caractéristiques de StyleGAN. Dans la seconde moitié, j'ai essayé la génération d'images en utilisant le StyleGAN réellement formé, donc j'écrirai le résultat.
StyleGAN (v1) StyleGAN a été annoncé en 2018. (Lien de l'article) Ce qui suit est une citation d'une image d'exemple générée par StyleGAN, mais je pense qu'elle produit une image de haute qualité qui ne se distingue pas d'une photo réelle.
Les caractéristiques de StyleGAN seront expliquées ci-dessous.
Je dirais que la particularité de StyleGAN réside principalement dans la structure du générateur. Dans la figure ci-dessous dans l'article, le côté gauche est la structure du générateur GAN conventionnel (ici PGGAN), et le côté droit est la structure du générateur Style GAN.
Dans le GAN conventionnel, la variable latente (z latente) est générée de manière aléatoire et entrée depuis la première couche du générateur. D'autre part, dans le cas de StyleGAN, la première entrée du générateur est une valeur fixe, et z latent est d'abord converti via le réseau Mapping, puis entré en utilisant AdaIN à divers endroits au milieu du générateur. De plus, un bruit généré aléatoirement est ajouté à chaque couche du générateur.
Style Mixing Les informations de style saisies dans chaque couche du générateur ne doivent pas nécessairement être identiques et plusieurs informations de style peuvent être combinées. Par exemple, en supposant qu'il existe des variables latentes z1 et z2 qui génèrent certaines images A et B, en entrant le style dérivé de z1 jusqu'à une certaine couche du générateur puis le style dérivé de z2, les caractéristiques de A et B Vous pouvez générer une image qui ressemble à un mélange de. (Mélange de styles)
À ce stade, si vous passez de z1 à z2 dans la première étape, les principales caractéristiques de B (orientation et forme du visage) seront reflétées, mais si vous basculez dans la deuxième étape, seules les caractéristiques détaillées (couleur des cheveux, etc.) seront reflétées. Je sais déjà.
Progressive Growing C'est la méthode d'apprentissage proposée par PGGAN (Progressive-Growing GAN). * Le style GAN est basé sur PGGAN. Il a été rapporté qu'une image haute résolution telle que 1024x1024 peut être générée de manière stable en augmentant la résolution de l'image générée en ajoutant des couches de générateur et de discriminateur pas à pas pendant l'apprentissage.
Cependant, la croissance progressive semble présenter certains inconvénients. Dans StyleGAN2 ci-dessous, cette zone est également prise en compte.
StyleGAN2 StyleGAN2 a été annoncé en 2019 comme une version améliorée de StyleGAN. (Lien de l'article) Voici un exemple d'image générée par StyleGAN2. Il est difficile de voir la différence de qualité avec StyleGAN, mais il a été rapporté que le motif de gouttelettes d'eau caractéristique qui se produit dans StyleGAN a été éliminé et que les scores tels que FID, qui est un indice de qualité d'image, se sont considérablement améliorés. Je vais.
Les principaux points à améliorer sont les suivants.
Chaque élément est décrit ci-dessous.
StyleGAN a amélioré cela car il a été constaté que le processus de normalisation AdaIN provoquait des artefacts ressemblant à des gouttelettes d'eau. Ci-dessous, la structure du générateur est citée de l'article.
Le côté gauche (a) et (b) est le StyleGAN d'origine, et le côté droit (d) est le résultat du passage à un formulaire qui n'utilise pas AdaIN. La même opération que la normalisation par AdaIN est réalisée par une opération appelée Démodulation de poids (divisant le poids de la couche Conv par l'écart type).
Le fait est que la démodulation du poids est effectuée sur la base de l'hypothèse de distribution sans utiliser les statistiques des données d'entrée réelles, et il est rapporté que cela a résolu le problème de la configuration des gouttelettes d'eau.
Nous avons ajouté cela au terme de régularisation parce que la longueur du chemin perceptif, qui indique si l'espace latent est perceptuellement lisse, est importante pour améliorer la qualité de l'image générée. (Régularisation de la longueur du chemin) La compréhension de cette zone est ambiguë, mais cela signifie-t-il que des images perceptuellement similaires devraient être générées (apprises à le faire) pour z qui sont proches en distance dans l'espace latent? ..
On rapporte également que pour le terme de perte principal, la mise à jour du terme de régularisation n'a pas eu d'incidence négative sur le score même si elle était moins fréquente. (La réduction de la fréquence de mise à jour du terme de régularisation s'appelle "Régularisation paresseuse") Cela réduit les coûts de calcul et l'utilisation de la mémoire, et contribue également à raccourcir le temps d'apprentissage.
Progressive Growing a l'avantage de pouvoir apprendre de manière stable la génération d'images haute résolution, Il y a un problème que les parties locales telles que les yeux et les dents ne suivent pas le mouvement global (orientation du visage). La figure suivante est un exemple. Vous pouvez voir que l'alignement des dents ne bouge pas même si la direction du visage change. Par rapport au GAN il y a quelques années, je pense que c'est incroyable que nous discutions de tels détails.
Le problème ci-dessus est attribué au fait que la croissance progressive facilite la génération de fonctionnalités fréquentes en augmentant progressivement la résolution, et la structure du réseau a été revue pour que l'apprentissage puisse réussir sans l'utiliser. À la suite de l'expérience, il a été montré qu'il est efficace d'introduire la structure de saut à la fois au générateur et au discriminateur, et il a réussi à générer une image de haute qualité sans croissance progressive. (→ Résolvez le problème que les dents et les yeux ne suivent pas la direction du visage)
... C'est tout pour étudier, et à partir de maintenant, essayons réellement la génération d'images par StyleGAN.
Pour mener l'expérience de génération d'images, j'ai utilisé l'implémentation StyleGAN publiée ci-dessous. (GitHub) stylegans-pytorch
Le StyleGAN officiel est TensorFlow, mais ce qui précède est reproduit et implémenté dans PyTorch. Il a été très utile d'avoir une explication détaillée de la préparation de l'environnement à la procédure de conversion des poids appris. Il est compatible avec StyleGAN1 et 2, mais cette fois je l'ai essayé avec StyleGAN1. (Parce qu'il semblait y avoir de nombreux types de poids qui pourraient être utilisés)
L'environnement est le suivant. C'est un environnement créé en mettant Ubuntu et CUDA etc. dans un PC de jeu. OS : Ubuntu 18.04.4 LTS GPU : GeForce RTX 2060 SUPER x1
La préparation s'est achevée sans problème selon la procédure de READ ME. L'image réellement générée à l'aide du poids converti est la suivante.
Étant donné que la même image que l'original a été générée, il a été confirmé que la conversion de poids et le processus de génération par générateur se sont déroulés correctement.
J'ai également essayé cela car il était compatible avec un modèle entraîné pour la génération de caractères 2D. [face_v1_1] [portrait_v1]
Jusqu'à présent, j'ai pu essayer la génération d'images avec StyleGAN, mais j'ai décidé d'essayer l'interpolation de la variable latente z, qui est souvent vue dans les vidéos GAN.
En référence à l'original waifu / run_pt_stylegan.py, j'ai écrit un processus pour effectuer une interpolation z sur le modèle entraîné du personnage d'anime et enregistrer le résultat sous forme de gif.
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
#Générer aléatoirement des latents comme point de départ
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) #Interpolation linéaire
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 #Nombre d'images à aligner(Verticale,côté)
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')
Dans make_latents_seq (), une interpolation linéaire est effectuée à partir de 3 points de z latent pour générer z sous forme de séquence.
Les résultats sont les suivants. [face_v1_1] [portrait_v1]
Comme prévu, j'ai eu une vidéo dans laquelle le visage du personnage change continuellement. Cela dépend des données au moment de l'apprentissage, mais je pense que c'est intéressant car différents styles de dessin sont mélangés. (D'une certaine manière, il y a des visages familiers ...) La zone autour de la tête et sous les épaules semble changer assez aléatoirement, mais comme pour la partie du visage, vous pouvez voir que c'est un vrai visage à tout moment d'interpolation. Cela signifie-t-il que les espaces latents sont connectés d'une manière perceptuellement lisse?
De plus, si vous ne vous souciez pas de la taille du fichier du gif, vous pouvez augmenter l'interpolation_step pour rendre l'animation plus fluide.
Pour StyleGAN, nous avons présenté l'article et mené une expérience à l'aide d'un modèle entraîné. Je vous serais reconnaissant de bien vouloir signaler toute erreur d’interprétation du document. Cela peut prendre beaucoup de temps sur mon ordinateur personnel, mais j'aimerais un jour essayer d'apprendre par moi-même.