Quand la machine devient créative avec les GANs

machine learningkerasDeepLearningGenerativeNetsGANGenerative Adversarial NetworkAITensorFlowConvNetU-Net
Hind Chenini - 15/06/2021 à 11:20:520 commentaire

Images : Arthouse Gallery - Interior; thisartworkdoesnotexist.com


Comprendre les réseaux adversaires génératifs


Ce post vous propose une introduction aux réseaux adversaires génératifs ou GANs qui aborde les points suivants :


  •       La modélisation générative
  •       Le processus des GANs
  •       La différence entre les modèles génératifs et discriminatifs
  •       Les bonnes pratiques pour un meilleur GAN
  •       Cas d'application de colorisation avec un CGAN
  •       Les différentes applications basées sur les GANs


So let’s get started, shall we ?




Et si les machines pouvaient elles-aussi imaginer ?


L’une des facultés les plus formidable et puissantes que possède l’être humain est l’imagination. Cette faculté permet à l’esprit de se représenter des images, processus permettant d’explorer le monde mentalement, d'anticiper, d’appréhender et de simuler la résolution de problèmes par la pensée.


La modélisation générative est un domaine du machine learning, - et plus précisément du deep learning- qui vise à attribuer aux machines cette capacité d’imaginer et de synthétiser de nouvelles entités, de planifier et tester des hypothèses afin d’anticiper des actions et voir comment ces dernières peuvent affecter le "futur" sans supervision explicite. A côté de cela, la modélisation générative non-supervisée a d’autres motivations pratiques, comme par exemple la possibilité de faire de l’apprentissage supervisé ou de l’apprentissage par renforcement avec peu de données labélisées, à l’aide des représentations robustes construites par ces modèles génératifs non-supervisés. Tel est le cas des réseaux adverses, qui sont capables de prendre en entrée des vecteurs aléatoires et de produire des entités qui ressemblent au jeu de données.


Parmi les méthodes importantes de la modélisation générative on trouve les approches antagonistes ou adversaires et plus précisément les GANs.


Les GANs ou Generative Adversarial Networks étaient proposés en 2014 par Ian Goodfillow et ses co-chercheurs de l’université de Montréal dont Yoshua Benjio fait partie. Benjio étant l’un des pionniers de la recherche en apprentissage profond, lauréat prix Turing 2019 conjointement avec Yann LeCun et Geoffrey Hinton.


     Les GANs et les variations qui sont actuellement proposées est l’idée la plus intéressante des dix dernières années en machine learning. Yann Lecun 2016


Le GAN est un modèle d'apprentissage non-supervisé, cette notion de non supervision peut être confondante dans les GANs, car on fournit au système des solutions à ce qu’on souhaite obtenir comme résultat. Mais quand on regarde de près l'architecture des GANs, on voit bien qu’on ne leur donne pas la façon dont ce résultat est obtenu. Et c'est ça que le GAN va chercher à apprendre. De plus, comme on va le voir ci-dessous, c'est seulement le Discriminateur qui a accès aux données authentiques (connues). Tout simplement, l'apprentissage supervisé consisterait à comparer les distributions de probabilités vraies à celles des générées et à en réduire la différence via la rétropropagation à travers le réseau, alors que dans le cas des GANs, les distributions vraies ne sont pas directement comparées avec les images générées.



Quels sont les rôles du Générateur et du Discriminateur dans un system GAN ?


Le processus des GANs


Les GANs sont des modèles génératifs conçus selon un processus adversaire, où deux modèles sont confrontés l’un à l’autre:


  • Un modèle appelé Générateur -- capture la distribution des données et génère des données semblables aux instances d’apprentissage


  • Un autre appelé Discriminateur -- estime la probabilité de l’appartenance d’une instance au jeu de données d’apprentissage plutôt qu'à celui généré par le Générateur.


Dans une configuration GAN standard on trouve deux réseaux de neurones profonds, dressés selon une approche minimax de la théorie des jeux formalisée par la fonction suivante :


source

Cette fonction concerne à la fois la probabilité du discriminateur D(x) de classifier l'entrée authentique x - qui peut être une image, un texte, de l'audio, de la vidéo, des traces GPS...etc.- comme telle, et la probabilité du discriminateur 1-D(G(z)) de classifier la donnée générée à partir du bruit z, comme synthétique. Donc, le but sera de maximiser l'espérance de classification sur l'ensemble d'entraînement par le discriminateur et de minimiser l'erreur entre les vraies données et les données générées par le générateur.



Images de pixabay et piqsels


Etapes d'entraînement des GANs :


  • Le Générateur reçoit comme entrée des vecteurs aléatoires


  • Le Générateur envoie des vecteurs qui capturent la distribution des données après entraînement


  • Le Discriminateur reçoit les vecteurs obtenus via le Générateur ainsi que les vrais vecteurs du jeu de données pour l'apprentissage


  • Le Discriminateur calcule les probabilités d’authenticité des vecteurs afin de pouvoir estimer l'erreur d'approximation des deux distributions


  • Par rétropropagation, le Discriminateur ajuste ses poids de neurones dans le but de maximiser le coût de classification


  • Le Générateur met à jour ses poids via le feedback du Discriminateur et tente à nouveau de tromper le Discriminateur

Le processus suit une double boucle de rétropropagation, où le Générateur est chargé de production de nouvelles données qui ressemblent plausiblement aux données d’origine pour l’amélioration du modèle par compétition avec le Discriminateur. Et le Discriminateur qui se charge de déterminer l’authenticité des données générées parmi les échantillons de référence. Cette compétition ou concurrence entraîne les deux modèles pour améliorer leurs techniques jusqu’à ce que les faux éléments soient indiscernables des éléments authentiques.



Le Générateur


Le rôle du Générateur est simple. Il est configuré d'une manière à générer de nouvelles instances dans le but de tromper le discriminateur en produisant des échantillons de plus en plus réalistes.

Alors tant que le Discriminateur apprend à identifier les données authentiques, le générateur crée de nouveaux échantillons, qui fait passer au Discriminateur. Il le fait dans l’espoir qu’ils seront eux aussi réputés authentiques, même s’ils sont faux. Son but est de générer des fausses instances sans se faire attraper, la moitié du temps.

L'information du gradient de perte du discriminateur est transmise au Générateur. Si le Discriminateur identifie la sortie du Générateur comme réelle, cela signifie que le Générateur a fait un bon travail donc il est récompensé. D'un autre côté, si le Discriminateur a reconnu qu'il a reçu un faux, cela signifie que le Générateur a échoué et donc il reçoit une rétroaction négative.



Le Discriminateur


Le Discriminateur est un réseau de neurones profond -ça peut être un simple CNN standard mais pas tout le temps, parce qu'il existe d'autres variations de GAN qui prennent des RNN (Recurrent Neural Network) pour discriminateur- qui évalue l’appartenance des nouvelles instances aux données d’apprentissage, afin d'identifier après les instances intrusives (générées) et reconnaître celles qui sont authentiques (issues du domaine). Plus précisément son but est d’identifier les données non authentiques venants du générateur. Si le discriminateur classe correctement les faux comme des faux et les vrais comme des vrais, il est récompensé avec un feedback positif sous la forme du gradient de perte. S'il échoue dans son travail, il reçoit un feedback négatif. Ce mécanisme lui permet d'apprendre et de s'améliorer.



Quelles sont les bonnes pratiques pour un meilleur GAN ?

note: la plupart de ces bonnes pratiques mentionnées ci-dessous sont utilisées et détaillées dans la suite de l'article


Il est souvent recommandé pour obtenir des meilleurs résultats lors de la construction des GANs à convolution de :


  • Commencer par implémenter un DCGAN


  • Utiliser une activation LeakyRelu au lieu de Relu pour les couches à convolution que ça soit pour le Générateur ou le Discriminateur


  • Une activation tanh au lieu de sigmoid pour la dernière couche du Générateur avec la normalisation des entrées pour avoir des valeurs dans l'intervalle [-1,1]


  • Utilisation de stride > 1, et Batch normalisation des couches cachées du Générateur et du Discriminateur


  • Initialisation des poids de chaque couche à partir d'une distribution gaussienne avec une déviation standard égale à 0.02


  • Remplacer les couches de déconvolution par des couches convolutives transposées


  • Ajouter des Dropout pour l'entraînement des couches profondes du Générateur. Cela permet de contrôler le sur-apprentissage


  • Tester différentes fonctions de perte à part la binary-crossentropy. Par exemple, Wasserstein ou RootMeanSquaredError


  • Ajouter du bruit aux données d'entrée du discriminateur que ça soit les instances réelles ou synthétiques. Cela permet au discriminateur de converger lentement et finalement évoluer pour abaisser sa probabilité d'erreur tout en évitant un sur-apprentissage avant la convergence du générateur. Parmi les méthodes proposées : One-sided label smoothing -- Au lieu d'utiliser 0 et 1 pour labeliser les entrées du discriminateur comme vraies ou fausses, cette technique proposée par Salimans et al. consiste à attribuer une valeur souple "smoothed" 0.9 aux labels positifs et de garder 0 pour les labels négatifs. Et, Instance noise -- proposée par Casper Kaae Sønderby consiste à ajouter du bruit -gaussien ou autre- directement aux données d'entrée du Discriminateur


  • Feature matching -- Adresse la non stabilité des GANs en spécifiant un nouvel objectif pour le générateur, afin d'éviter le surapprentissage du discriminateur. Dans ce cas, le loss du Générateur devient feature_matching_loss qui consiste à minimiser la différence absolue entre les caractéristiques dans les données réelles et celles attendues dans les données générées. Cette technique est plus recommandée pour les implémentations des GANs semi-supervisés que pour un GAN standard. Salimans et al.


  • Minibatch discrimination -- Apprentissage du discriminateur par mini-batch c'est surtout utile quand le Générateur produit toujours la même sortie. Cela va permettre au Discriminateur de développer des caractéristiques sur une combinaison de différentes instances de données plutôt que sur des instances individuelles isolées. Comme ça, à chaque fois que les éléments d'un batch sont très proches les uns des autres, le Discriminateur comprendra qu'il s'agit des faux. Ce qui va forcer le Générateur à générer plus rapidement des échantillons plus importants en améliorant leur diversité. Salimans et al.


  • Top-k GANs qui consiste à mettre à jour le Générateur seulement à partir des top-k des meilleures prédictions faites par le Discriminateur. Idée proposée par Samarth Sinha et al. et présentée pendant le neurIPS 2020


  • En plus du loss antagoniste ajouter un autre loss comme le loss perceptuel ou le loss par pixel


  • Comme parfois les deux modèles n'apprennent pas à la même vitesse, on peut équilibrer l'apprentissage du Générateur et du Discriminateur : en ajustant les itérations de l'un par rapport à l'autre, en fonction des changements du loss. Soumith et al.



Implémentation de CGAN avec un U-Net CNN


ai-meme généré par un DCNN


La colorisation des croquis, est un domaine de recherche avec une demande importante du marché. C'est très différent de la colorisation des photos qui se base principalement sur les informations de texture. La colorisation à partir des traits de contour est plus exigeante, puisque les croquis peuvent ne pas avoir de texture. Et encore, tout ce qui est couleur, texture, gradient, doit être généré à partir des traits abstraits du croquis.


Dans cette partie on va voir comment on peut implémenter une architecture CGAN (Conditional GAN) pour faire de la colorisation des croquis. Pour notre cas on va tenter de colorier des croquis de chats.


Dans cette implémentation le modèle a pour objectif de transformer un sketch en noir et blanc en une image en couleurs. Contrairement à la configuration initiale du CGAN qui génère l'image conditionnée à partir du bruit.


Ce modèle est principalement basé sur l'article de Qiwen Fu & al. démontrant une méthode de colorisation d'images grâce à une combinaison des réseaux de neurones convolutifs ou CNN et les CGANs


Avant de commencer voici l'architecture générale de notre modèle de colorisation:






Commençons par importer les librairies requises :


from tensorflow.keras.optimizers import Adam
from tensorflow.keras.initializers import RandomNormal
from tensorflow.keras.layers import Activation, Input, LeakyReLU
from tensorflow.keras.layers import Conv2D, Conv2DTranspose
from tensorflow.keras.layers import Concatenate, BatchNormalization, Dropout 
from tensorflow.keras.models import Model
import numpy as np
from sklearn.model_selection import train_test_split
from PIL import Image
import skimage
import os
import datetime
import random
import cv2


Ensuite on passe à la définition des modèles ;


Le Générateur


Le Générateur proposé dans l'article est un CNN d'architecture U-Net. Cette architecture a été élaborée pour la segmentation d'images biomédicales. Les architectures U-Nets sont connues pour être efficaces pour des tâches où la donnée d'entrée a la même taille que celle de la sortie avec un espace de résolution important. Cela les rend très utiles pour la création des masques de segmentation et pour le traitement et la génération d'images telle que la transformation en images de haute résolution ou la colorisation.


Le modèle reçoit en entrée l'image du croquis dans l'espace latent et renvoi une image RGB de même taille. Cela est réalisé par le biais des couches de convolution à stride = 2 et commençant par un filtre = 64 qui fournissent des activations suffisantes remodelées en de nombreuses copies d'une version basse résolution de l'image de sortie, pour ensuite suréchantillonner cela avec des couches de transposition convolutive jusqu'à obtention de l'image souhaitée qui aura la même taille que celle d'entrée.


L'application de la structure U-net permet un meilleur flux d'informations à travers le réseau par l'ajout des skip_connections via la concaténation directe des channels des couches miroirs. Cela permet de conserver des composantes de l'entrée source plus facilement et ainsi, produire des images bien définies.


Le modèle est implémenté suivant les bonnes pratiques mentionnées ci-dessus, comme l'activation LeakyRelu et le stride = 2 avec un kernel_size qui est un multiple du stride choisi, et une fonction hyperbolique pour l'activation de la dernière couche tanh.


def build_generator(self):

        concat = Concatenate(axis = -1) 
        init = RandomNormal(stddev = 0.02)
        model_input = Input(shape = (self.img_width, self.img_height, 1))
        conv1 = Conv2D(filters = 64, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(model_input)
        conv2 = BatchNormalization()(Conv2D(filters = 128, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)     (conv1)))
        conv3 = BatchNormalization()(Conv2D(filters = 256, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv2)))
        conv4 = BatchNormalization()(Conv2D(filters = 512, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv3)))
        conv5 = BatchNormalization()(Conv2D(filters = 512, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv4)))
        deonv6 = BatchNormalization()(Conv2DTranspose(filters = 512, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv5)))
        deconv6 = Activation('relu')(Dropout(0.5)(deconv6))
        deconv7 = Activation('relu')(Dropout(0.5)(BatchNormalization()(Conv2DTranspose(filters = 1024, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(concat([deconv6, conv4])))))
        deconv8 = Activation('relu')(BatchNormalization()(Conv2DTranspose(filters = 512, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(concat([deconv7, conv3]))))
        deconv9 = Activation('relu')(BatchNormalization()(Conv2DTranspose(filters = 256, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(concat([deconv8, conv2]))))
        deconv10 = Activation('relu')(BatchNormalization()(Conv2DTranspose(filters = 128, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(concat([deconv9, conv1]))))
        deconv11 = Activation('relu')(BatchNormalization()(Conv2DTranspose(filters = 64, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(deconv10)))
        deconv12 = Conv2D(filters = 3, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(deconv11)      
        
        x = Activation('tanh')(deconv12)
        
        generator = Model(model_input, x)
        return generator


Encore une fois le Générateur est entraîné via le loss du Discriminateur, ce qui justifie le manque du generator.compile().


Le Discriminateur


Dans ce cas le discriminateur reçoit une combinaison de l'image source (sketch) et l'image cible (colorée), et doit déterminer si la cible est une transformation plausible de l’image source. Et renvoyer une prédiction binaire quant à savoir si l'image cible est fausse ou vraie. La structure de ce modèle CNN va être composée de six couches convolutive avec une activation LeakyRelu et une normalisation par lots des couches cachées, suivis d'une couche d'activation sigmoïde pour le calcul de la probabilité, et l'optimisateur Adam du gradient stochastique avec un taux d'apprentissage de 0.0002 et un Momentum de 0.5 comme recommandé dans l'article.


La fonction de perte utilisée dans cette implémentation est l'entropie croisée binaire (BCE loss).

Avec l’ajout du Dropout appliqué sur les dernières couches cachées du Discriminateur, les images résultantes sont moins bruyantes.


def build_discriminator(self):

        init = RandomNormal(stddev = 0.02)
        sketch = Input(shape = (self.img_width, self.img_height, 1))
        in_color = Input(shape = (self.img_width, self.img_height, 3))
        model_input = Concatenate()([sketch, in_color])
        conv1 = Conv2D(filters = 64, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(model_input)
        conv2 = BatchNormalization()(Conv2D(filters = 128, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv1)))
        conv3 = BatchNormalization()(Conv2D(filters = 256, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv2)))
        conv4 = BatchNormalization()(Conv2D(filters = 512, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv3)))
        conv5 = BatchNormalization()(Dropout(0.5)(Conv2D(filters = 512, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv4))))
        conv6 = BatchNormalization()(Dropout(0.5)(Conv2D(filters = 512, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv5))))
        conv7 = Conv2D(filters = 1, kernel_size = 4, strides = 2, padding = 'same',kernel_initializer = init)(LeakyReLU(0.1)(conv6))
        x = Activation('sigmoid')(conv7)

        # l'entrée du modele est conditionnée par des labels (vrai,faux)
        discriminator = Model([sketch,in_color], x)
        opt = Adam(lr = 0.0002, beta_1 = 0.5)
        discriminator.compile(loss = 'binary_crossentropy', optimizer = opt, loss_weights = [0.5])
        return discriminator


Le CGAN_model


Ensuite un modèle CGAN est construit pour combiner le Générateur et le Discriminateur ensemble. Le Discriminateur est entraîné séparément, donc dans cette étape, la mise à jour de celui-ci est bloquée pendant l'apprentissage du Générateur, afin de s'assurer que l'ajustement se fait uniquement au niveau des poids du Générateur.


Ce modèle CGAN prend en entrée l'image qui correspond au croquis, utilise le Générateur pour générer l'image en couleur correspondante, puis la passe au Discriminateur pour déterminer la validité de sa proximité au domaine de vérité.


L'entraînement du Générateur se fait via le loss du Discriminateur, ce qui encourage le Générateur à générer des images plausibles dans le domaine cible. Il est aussi mis à jour via la fonction de perte du cgan.model mesurée par la distance L1 ou MAE (l'erreur moyenne absolue) entre l’image générée et l’image cible. Ce loss, appelé loss de contenu, encourage le Générateur à créer des transformations plausibles des images sources.


La formule de la fonction de perte du cgan.model est représentée comme suite: loss= gan_loss + λ*L1 où λ=100, cette valeur a été fixée par les auteurs de l'article, pour inciter le Générateur à générer des images plus proches de la vérité-terrain.


def __init__(self, img_width, img_height, n_channels):
        self.img_width = img_width
        self.img_height = img_height
        self.n_channels = n_channels
        self.img_shape = (self.img_width, self.img_height, self.n_channels)
      
        optimizer = Adam(lr = 0.0002, beta_1 = 0.5 )

        # construire et compiler le discriminateur
        self.discriminator = self.build_discriminator()
        self.generator = self.build_generator()

        # bloquer la mise à jour du discriminateur pendant l'apprentissage du générateur
        self.discriminator.trainable = False
        
        sketch = Input(shape = (img_width,img_height, 1))
        
        generated_img = self.generator(sketch)

        # determiner la validité de proximité de l'image générée à la vérité sur le terrain
        validity = self.discriminator([sketch,generated_img])


        self.cgan_model = Model(inputs = sketch, outputs = [validity,generated_img])
        self.cgan_model.compile(loss = ['binary_crossentropy','mae'], loss_weights = [1, 100],
                                optimizer = optimizer,
                                metrics = ['accuracy'])



L'entraînement du réseau antagoniste par lot


L'entraînement du modèle pendant une epoch se fait par lot de batch_size. On commence par permuter la liste des indexes des éléments de la base d'apprentissage pour éviter d'avoir des lots identiques -contenant les mêmes éléments- à chaque epoch. Puis, on sélectionne un lot d'échantillons aléatoires pour l'apprentissage. d'avoir tout le temps des lots de mêmes éléments. Pour chaque lot le Générateur produit les images colorées à partir des croquis sélectionnés. Ensuite le Discriminateur reçoit dans un premier temps le lot des croquis ainsi que leur équivalent en couleur. Puis dans un deuxième temps le même lot de croquis avec leur équivalent d'images colorées par le Générateur. Après cela, on passe le lot au CGAN pour mettre à jour les poids du Générateur.


Notez ici que les labels sont inversés pour l'entraînement du Générateur. Lors de l'entraînement du Générateur via le CGAN, les labels attendus sont des 1 (Vrais). Au départ, le Générateur produit des images pas très réalistes donc le Discriminateur les classe comme des 0 (Faux), ce qui provoque la rétropropagation pour ajuster les poids des couches du Générateur. Le Discriminateur reste inchangé car il est verrouillé dans cette étape.


Vers la fin d'une epoch on évalue les progrès d'apprentissage du modèle. Pour cela on sélectionne 5 éléments des données, on utilise le Générateur pour le tester, et on affiche les images produites ainsi que les images cibles dans une figure qu'on va enregistrer dans un fichier pour examiner et évaluer le résultat subjectivement.


def train(self, x_train, y_train, epoch, batch_size) :

        output_true = np.ones((batch_size, 1, 1, 1))#labels pour les vraies données
        #tester one-sided label smoothing 
        #output_true[:batch_size]=0.9
        output_fake = np.zeros((batch_size, 1, 1, 1))#labels pour les données générées
      
        idx = [i for i in range(x_train.shape[0])]
        
        d_losses = []
        cgan_losses = []

        #permuter les indexes pour recupérer des lots random
        random.shuffle(idx)
        
        #pour chaque lot de batch_size éléments 
        for i in range(0, len(idx), batch_size):  

            idx_bat = idx[i : i + batch_size] 

            #  --------------------- entraîner le Discriminateur ---------------------
            
            #sélectionner le lot d'images d'apprentissage
            sketches, in_colors = x_train[idx_bat], y_train[idx_bat]

            # Générer un lot d'images par le générateur à partir des croquis sélectionnés
            gen_imgs = self.generator.predict(sketches)

            #ajouter du bruit gaussien aux données (instance noise) vous pouvez tester avec et sans pour voir la différence
            #in_colors = skimage.util.random_noise(in_colors , mode="gaussian")
            #gen_imgs = skimage.util.random_noise(gen_imgs , mode="gaussian")

            #entraîner le discriminateur avec les données authentiques
            d_loss_real = self.discriminator.train_on_batch([sketches,in_colors], output_true[:len(idx_bat)])
            #introduire les images générées au discriminateur
            d_loss_fake = self.discriminator.train_on_batch([sketches,gen_imgs], output_fake[:len(idx_bat)])
            #calcul du loss
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
            d_losses.append(d_loss)
            

            #  --------------------- entraîner le Générateur ---------------------
           
            cgan_loss = self.cgan_model.train_on_batch(sketches, [output_true[:len(idx_bat)],in_colors])
            cgan_losses.append(cgan_loss)
            
         
        #récuperer 5 éléments pour la visualisation du progrès
        idxp = np.random.randint(0, x_train.shape[0], size=5)
        sketchesp, in_colorsp = x_train[idxp], y_train[idxp]
        self.sample_images(epoch,sketches,in_colorsp)
        
        print("\n Epoch:", epoch)
        print("d_Loss:",np.mean(d_losses))
        print("cgan_loss:",np.mean(cgan_losses))
        

        return d_losses,cgan_losses



La préparation des données


Les données d'apprentissage sont récupérées depuis le web puis on applique un modèle d'extraction des sketches pour construire le set de croquis.


Ces données sont ensuite normalisées suivant les bonnes pratiques sur les GANs; à savoir la projection des valeurs de [0,255] dans l'intervalle [-1,1], afin de les rendre compatibles avec la sortie de la fonction tanh du Générateur.


#définir une dimension unique pour avoir des images de la même taille
RESHAPE = (128,128)
#charger les images
X = np.array([np.array(Image.open(sketch_dir+f).convert('L').resize(RESHAPE)).reshape((RESHAPE[0],RESHAPE[1],1)) for f in os.listdir(sketch_dir)])
y = np.array([np.array(Image.open(color_dir+f).convert('RGB').resize(RESHAPE)) for f in os.listdir(color_dir)])
#passer de [0,255] à [-1,1]
X = ( X.astype ( 'float32' ) - 128 ) / 128
y = ( y.astype ( 'float32' ) - 128 ) / 128


La construction du modèle CGAN


Maintenant, on est prêts à construire le modèle. On définit le nombre d'epoch, et on appelle pour chaque epoch la fonction train avec un batch_size de 32. Après l'entraînement du modèle on teste le Générateur sur les données de test pour suivre le progrès d'apprentissage. Aussi on sauvegarde les poids des deux modèles avant de passer à l'epoch suivante.


Vous pouvez choisir le nombre d'epoch qui vous convient, cela dépendra de la quantité d'images et de leur répartition. Pour cette implémentation 350 epochs étaient suffisantes.


#diviser les données en set d apprentissage et set de test
X, X_test, y, y_test = train_test_split(X, y, test_size = 0.1,random_state = 2)

#recuperer les dimensions de l'espace latent pour construire le model
img_w, img_h, num_channels = X[0].shape

cgan = CGAN(img_w, img_h, num_channels)
        
epochs = 350

for epoch in range(epochs): 
        
        
        d_losses,cgan_losses = cgan.train(X, y, epoch = epoch, batch_size = 32)
        
        cgan.sample_images(epoch,X_test,y_test)
                                                                                                                  
        cgan_mean_loss = np.mean(cgan_losses)

        #sauvegarder les modèles
        save_all_weights(cgan.discriminator, cgan.generator,epoch, cgan_mean_loss )
        
       


Un ensemble d'images générées est représenté ci-dessous:



Images originales de pixabay et publicdomainevectors



Il est remarquable que les images produites sont davantage basées sur une palette à ton orangé, cela est peut-être dû à la surreprésentation de la couleur orange dans la base d'apprentissage. Aussi on remarque que les images générées ne ressemblent pas forcément aux images originales, ce qui est en fait souhaité, car l'objectif n'est pas de reproduire la même image mais de pouvoir cibler les zones à coloriser.


Parfois quand le contour n'est pas bien défini le rendu sort comme une image peinte.


Dans le cas où vous tentez de coloriser avec une palette de couleurs bien spécifique, d'autres travaux existent qui traitent ce sujet, notamment en utilisant les GANs avec un système d'attention comme dans cet exemple. Ou celui-là, un GAN avec balisage textuel contenant les critères souhaités.


Avec l'augmentation de la dimension des images de 128x128 à 512x512, on remarque des images plus nettes, surtout au niveau des caractéristiques fines du croquis. Après, le choix des couleurs n'était pas basé sur un ensemble très homogène mais peut-être que le modèle a besoin d'encore plus d'entraînement.


Afin d'avoir des résultats de bonne qualité il faut s'assurer que les données soient bien homogènes, bien préparées et normalisées, et qu'au moment de la génération des synthétiques, les données d'entrée du Générateur soient conformes aux mêmes normes.


Quelles sont les applications révolutionnaires des GANs ?


Comme on a pu voir jusqu'à maintenant les Gans peuvent être utilisés pour résoudre des problèmes de transformation d'image, de données non suffisantes ou small data ou même le problème de données non équilibrées. On peut par exemple construire un GAN qui va générer les données nécessaires pour augmenter les échantillons de classes minoritaires, ils peuvent aussi être utiles dans le domaine commercial pour la personnalisation des produits ou la création de nouveaux produits.


On peut citer d'autres exemples de cas d'utilisations :


  • SRGAN (Super high-Resolution GAN), une variante de GAN qui permet d'avoir des images de haute résolution. Cette technique a été utilisée dans un cas cité par IBM où un client avait besoin d'une solution pour avoir des images de haute résolution des organismes vivants sans risquer de les endommager. Dans un autre cas, les chercheurs chez Pixar ont testé l'utilisation du même modèle dans le but d'augmenter la résolution des contenus avec une qualité consistante, ils estiment une réduction des coûts de la ferme de rendu de 50% à 75%. Ils espèrent que cette méthode sera utilisée dans les prochaines productions. Pour plus de détails voici leur article.


L'utilisation des GANs par Pixar


  • Création de nouvelles instances : portraits animés, Pokémons, Manga avec histoire et personnages, design des chambres, articles de mode, ouvrages d'art, photos de visages uniques synthétiques mais photoréalistes, …etc.


                                                                           

                           Portrait de Edmond Belamy créé par un GAN                           Manga créé à l'aide d'un StyleGAN

                          vendu chez Christie's aux enchères à 432,500$               reflétant les œuvres légendaires d'Osamu Tezuka



  • Transformation d’images ou styleGAN -- image → style peinture, paysage hiver → paysage été, doodles ou sketching ↔ photographe, changement d’âge à partir des visages, image en noir et blanc → image colorées, par exemple la transformation d'une image nocturne de route en une image de jour peut être utile pour l'amélioration de la navigation des voitures autonomes, qui ont besoin de s'adapter quelles que soient les circonstances; nuit, jour, pluie ou neige. Les GANs sont aussi utilisés pour la filtration des images qui peut être aussi utile pour ce même exemple dans le cas de pluie ou neige.


Manipulation d'images avec texte via cycleGAN


  • Prédiction et/ou imagination de scénarios quand appliqué à des données séquentielles. Exemple.


  • Missing data : générer des données synthétiques mais réalistes, pour compléter les données d'apprentissage insuffisantes pour cause de temps de sauvegarde ou de confidentialité ou même de rareté, afin de pouvoir améliorer la robustesse des modèles de classification pré-entraînés. Par exemple dans le domaine de la recherche médicale pour la lutte contre les maladies, les données ne sont pas suffisantes et pour combler ce manque les GANs sont sollicités. Autre cas d'usage.


  • Génération de données réalistes pour prévenir les coûts et contraintes de collecte de données et les préoccupations concernant la sécurité et la confidentialité des données. Comme dans le cas de la recherche médicale, les GANs peuvent fournir des données réalistes sans avoir à divulguer les données privées des patients.


  • Remplacement des caméras par du code pour générer des vidéos de formation ou de pub avec des têtes parlantes synthétiques et photoréalistes et qui lisent des scripts personnalisés dans plusieurs langues comme des locuteurs natifs. Comme dans le cas de Synthesia. Les avatars de Synthesia sont basés sur des vrais visages. L'intérêt ici est d'adapter les traits et les expressions du visage ainsi que la voix de la personne à des scripts particuliers. La personne concernée perçoit des redevances en fonction de la quantité d'images réalisées avec son visage et selon des règles d'éthique imposées par l'entreprise. Ils utilisent les GANs pour la plupart de leurs rendus, mais leur pipeline de production comprend aussi d'autres algorithmes de deep learning, de vision par ordinateur et les effets visuels, rapporte The Batch.


AI Video Avatar


  • Création musicale comme dans cet exemple d'apprentissage d'un GAN à jouer de la guitare, pour faire une reprise vidéo à la guitare à partir d'un clip audio.


  • Visualisation du son un projet appelé NeuralSynesthesia. Il s'agit de la création d'expériences visuelles à partir du son, en commençant par analyser l'audio pour détecter les percussions et les éléments harmoniques, puis alimenter le GAN par ces éléments afin de créer une expérience visuelle unique. Le résultat généré sera ensuite combiné avec le son pour composer une vidéo. Pour profiter de l'expérience et ressentir la musique visuellement, c'est par ici.


Extrait de "When AI generated paintings dance to music..."



  • Génération de textes à partir d'images - comme dans cette proposition - ou à partir d'un petit texte de contexte.



Exemples de génération de poème à partir d'image par I2P-GAN



  • Pour plus d'exemples d'applications et de références voici une liste plus riche par là.



Commentaires :

Aucun commentaires pour le moment


Laissez un commentaire :

Réalisé par
Expaceo