[PYTHON] Présentation et expérience du papier StyleGAN

introduction

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.

Introduction à la thèse

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. stylegan-teaser.png

Les caractéristiques de StyleGAN seront expliquées ci-dessous.

Structure du générateur

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

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. stylegan2-teaser-1024x256.png 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.

Réalise un traitement équivalent à AdaIN en une seule couche Conv

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

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.

Amélioration de la régularisation

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.

Amélioration de la structure du réseau, éliminant le besoin de croissance progressive

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

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.

Expérimentez avec un modèle entraîné

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)

Vérification de la préparation / du fonctionnement

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

É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] anime_face_v1_1_pt.png [portrait_v1] anime_portrait_v1_pt.png

expérience d'interpolation

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] face_v1_1_interpolation_2c.gif [portrait_v1] portrait_v1_interpolation_2c.gif

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.

fin

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.

Recommended Posts

Présentation et expérience du papier StyleGAN
introduction
Introduction à l'apprentissage profond ~ Expérience CNN ~