[PYTHON] Implémenter le codeur automatique variationnel conditionnel (CVAE) dans le système TensorFlow 2

TL;DR --Un type d'auto-encodeur ** Conditional Variational Autoencoder (** CVAE **) a été implémenté en modifiant l'exemple TensorFlow. --J'ai joué avec les données MNIST d'apprentissage CVAE implémentées

github.com/kn1cht/tensorflow_v2_cvae_sample

sample-cvae-mnist.ipynb Google Colab GitHub
sample-cvae-mnist-manifold.ipynb Google Colab GitHub
manifold-cvae-2.png Manifold 2D généré

introduction

** Conditional Variant Auto Encoder (CVAE) ** est un modèle de génération (semi) supervisé qui peut générer des données correspondant à des étiquettes. Comme présenté dans "Comparaison d'AutoEncoder, VAE, CVAE - Pourquoi VAE peut-il générer des images continues?" Ceci peut être réalisé simplement en ajoutant le processus de saisie d'une étiquette dans (VAE).

Vous pouvez trouver des exemples d'implémentation dans les séries Chainer, PyTorch et TensorFlow 1 sur le net, mais il ne semble y avoir aucun exemple écrit dans la série TensorFlow 2. Par conséquent, j'écrirai un article qui servira également d'étude de TensorFlow lui-même. En l'implémentant, nous avons modifié l'exemple VAE selon la formule TensorFlow sans le changer autant que possible, nous espérons donc que suivre les modifications vous aidera à comprendre le contenu.

environnement

Description du modèle

Puisque explication facile à comprendre existe déjà, je vais la décrire ici.

Encodeur automatique (AE)

Initialement proposé comme apprentissage non supervisé avec ** réduction de dimension **. AE se compose de deux modèles, Encoder et Decoder. L'encodeur compresse l'entrée $ \ boldsymbol {x} $ en $ \ boldsymbol {z} $, et Decoder passe de $ \ boldsymbol {z} $ à $ \ boldsymbol {x}. Essayez de reproduire $. Le $ \ boldsymbol {z} $ qui apparaît au milieu est appelé une ** variable latente **, et peut être considéré comme représentant les caractéristiques des données dans un petit nombre de dimensions.

AE peut être appliqué à la suppression du bruit et à la détection d'anomalies en plus de restaurer l'entrée telle quelle.

Autoencodeur variationnel (VAE)

La VAE a permis de l'utiliser pour ** la génération de données ** en intégrant une distribution de probabilité. Estimez les paramètres $ \ mu $ et $ \ sigma $ de la distribution gaussienne multivariée avec Encoder, et créez $ \ boldsymbol {z} $ à partir de la distribution de probabilité obtenue. Puisque $ \ boldsymbol {z} $ peut être obtenu à partir d'une distribution de probabilité continue, il sera possible de générer des données qui n'existent pas dans l'ensemble de données.

Dans l'apprentissage réel, $ \ boldsymbol {z} $ est calculé par une méthode d'approximation appelée Reparametrization Trick afin que la propagation des erreurs puisse être effectuée. De plus, la régularisation est effectuée en incluant la divergence KL (divergence Calback Libra) de la distribution obtenue et la distribution normale standard dans la fonction objectif.

Fonction objective maximisée par VAE. Le premier terme sur le côté droit est la valeur attendue de la vraisemblance logarithmique de la sortie obtenue par Decoder, et le second terme est le terme de régularisation.

\mathcal{L}(\boldsymbol{x},\boldsymbol{z}) = \mathbb{E}_{q(\boldsymbol{z}|\boldsymbol{x})}[\log p(\boldsymbol{x}|\boldsymbol{z})] - D_{KL}[q(\boldsymbol{z}|\boldsymbol{x})||p(\boldsymbol{z})]

Autoencodeur variationnel conditionnel (CVAE)

CVAE permet ** la génération de données en spécifiant une étiquette ** en ajoutant l'étiquette $ y $ à chacun des encodeurs et décodeurs en entrée. Puisqu'il donne des informations sur l'étiquette, il s'agira d'un apprentissage supervisé, mais si vous le concevez, il semble qu'il puisse également être utilisé comme apprentissage semi-supervisé qui ne nécessite pas d'étiquettes pour tous.

La fonction objectif change de VAE comme suit. Cependant, Encoder / Decoder ne considère que $ y $, et il est normal de conserver la fonction objective de VAE en termes d'implémentation.

\mathcal{L}(\boldsymbol{x},\boldsymbol{z},y) = \mathbb{E}_{q(\boldsymbol{z}|\boldsymbol{x},y)}[\log p(\boldsymbol{x}|\boldsymbol{z},y)] - D_{KL}[q(\boldsymbol{z}|\boldsymbol{x},y)||p(\boldsymbol{z}|y)]

Implémentation de CVAE

Maintenant, implémentons CVAE. D'après le papier original, il est possible d'étudier avec semi-enseignant en combinant VAE (modèle M1) et CVAE (modèle M2), mais dans cet article, ce n'est pas le cas. Apprenez en associant des étiquettes à toutes les données.

L'ensemble du code est publié dans le référentiel suivant.

Exemple VAE officiel de TensorFlow

Pour VAE, un exemple est inclus dans le didacticiel officiel TensorFlow.

** Il est publié sur Google Colaboratory **, vous pouvez donc simplement cliquer dessus et cela fonctionnera.

Il convient de noter que le codeur automatique variationnel convolutionnel est appelé ici CVAE et qu'il est ** différent du CVAE décrit dans cet article **. Le modèle de cet exemple est un VAE normal, simplement parce qu'il a une couche de pliage.

Nous modifierons la VAE qui apprend ce MNIST pour réaliser la VAE conditionnelle.

De VAE à CVAE

La partie qui définit le modèle est extraite de l'exemple de code ci-dessus vae.py, et le CVAE créé à partir de celui-ci J'ai mis le code dans le référentiel sous le nom cvae.py.

Puisqu'il est plus facile à comprendre en regardant la différence, je vais l'expliquer en postant les deux diffs. ..

CVAE.init()

--- vae.py
+++ cvae.py

-  def __init__(self, latent_dim):
+  def __init__(self, latent_dim, label_size):
     super(CVAE, self).__init__()
-    self.latent_dim = latent_dim
+    (self.latent_dim, self.label_size) = (latent_dim, label_size)
     self.encoder = tf.keras.Sequential(
         [
-            tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
+            tf.keras.layers.InputLayer(input_shape=(28, 28, label_size + 1)),
             tf.keras.layers.Conv2D(
                 filters=32, kernel_size=3, strides=(2, 2), activation='relu'),
             tf.keras.layers.Conv2D(
@@ -30,7 +31,7 @@ class CVAE(tf.keras.Model):

     self.decoder = tf.keras.Sequential(
         [
-            tf.keras.layers.InputLayer(input_shape=(latent_dim,)),
+            tf.keras.layers.InputLayer(input_shape=(latent_dim + label_size,)),
             tf.keras.layers.Dense(units=7*7*32, activation=tf.nn.relu),
             tf.keras.layers.Reshape(target_shape=(7, 7, 32)),
             tf.keras.layers.Conv2DTranspose(

Le premier est la partie définition du modèle. Le type d'étiquette $ y $ (10 types pour MNIST) est défini sur label_size, et la taille d'entrée de chaque encodeur / décodeur est augmentée de label_size. Vous pouvez maintenant combiner l'étiquette convertie en une représentation One-hot avec votre entrée.

CVAE.sample()

   @tf.function
-  def sample(self, eps=None):
+  def sample(self, eps=None, y=None):
     if eps is None:
       eps = tf.random.normal(shape=(100, self.latent_dim))
-    return self.decode(eps, apply_sigmoid=True)
+    return self.decode(eps, y, apply_sigmoid=True)

sample () est le processus de réception de variables et d'étiquettes latentes et de génération de données.

CVAE.encode()

-  def encode(self, x):
-    mean, logvar = tf.split(self.encoder(x), num_or_size_splits=2, axis=1)
+  def encode(self, x, y):
+    n_sample = x.shape[0]
+    image_size = x.shape[1:3]
+
+    y_onehot = tf.reshape(tf.one_hot(y, self.label_size), [n_sample, 1, 1, self.label_size]) # 1 x 1 x label_size
+    k = tf.ones([n_sample, *image_size, 1]) # {image_size} x 1
+    h = tf.concat([x, k * y_onehot], 3) # {image_size} x (1 + label_size)
+
+    mean, logvar = tf.split(self.encoder(h), num_or_size_splits=2, axis=1)
     return mean, logvar

C'est un processus pour que Encoder lise l'entrée. Commencez par convertir le libellé y en la représentation One-hot y_onehot. En dehors de cela, créez un tenseur «k» dont la forme est «taille d'image (28 x 28 pour MNIST) x 1» et tous les éléments sont 1. Lorsque «k * y_onehot» est calculé, il devient «28 x 28 x label_size» par la fonction de diffusion, et il peut être combiné avec «x».

(Cette partie est basée sur l'implémentation de Ysasaki6023. Cela semble fonctionner même si vous vous connectez et y)

CVAE.decode()

-  def decode(self, z, apply_sigmoid=False):
-    logits = self.decoder(z)
+  def decode(self, z, y=None, apply_sigmoid=False):
+    n_sample = z.shape[0]
+    if not y is None:
+      y_onehot = tf.reshape(tf.one_hot(y, self.label_size), [n_sample, self.label_size]) # label_size
+      h = tf.concat([z, y_onehot], 1) # latent_dim + label_size
+    else:
+      h = tf.concat([z, tf.zeros([n_sample, self.label_size])], 1)  # latent_dim + label_size
+    logits = self.decoder(h)
     if apply_sigmoid:
       probs = tf.sigmoid(logits)
       return probs

De même, transmettez «z» et «y_onehot» au décodeur. La raison pour laquelle «y» est None ou non est de permettre d'essayer la génération de données sans passer d'étiquette. Cependant, comme je n'ai pas étudié sans étiquettes, je me suis retrouvé avec seulement des images que je ne comprenais pas ...

compute_loss()

-def compute_loss(model, x):
-  mean, logvar = model.encode(x)
+def compute_loss(model, xy):
+  (x, y) = xy # x: image, y: label
+  mean, logvar = model.encode(x, y)
   z = model.reparameterize(mean, logvar)
-  x_logit = model.decode(z)
+  x_logit = model.decode(z, y)
   cross_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
   logpx_z = -tf.reduce_sum(cross_ent, axis=[1, 2, 3])
   logpz = log_normal_pdf(z, 0., 0.)

Il s'agit du traitement de la fonction objectif lors de l'apprentissage. Comme mentionné ci-dessus, l'implémentation de la fonction objectif est la même que VAE, donc seul «y» est ajouté à l'argument.

train_step()

 @tf.function
-def train_step(model, x, optimizer):
+def train_step(model, xy, optimizer):
   """Executes one training step and returns the loss.

   This function computes the loss and gradients, and uses the latter to
   update the model's parameters.
   """
   with tf.GradientTape() as tape:
-    loss = compute_loss(model, x)
+    loss = compute_loss(model, xy)
   gradients = tape.gradient(loss, model.trainable_variables)
   optimizer.apply_gradients(zip(gradients, model.trainable_variables))

C'est un processus qui transforme l'apprentissage en une étape.

Préparation du jeu de données d'entrée

La fonction d'ensemble de données TensorFlow (tf.data.Dataset) crée une image MNIST ** et une entrée appariée d'étiquettes **. Je suis un débutant de TensorFlow, donc quand j'ai vu l'échantillon officiel, je m'inquiétais du "Si je mélange train_dataset comme ça, la correspondance entre les images et les étiquettes serait rompue ...?" Bien sûr, il existe des fonctions qui répondent à de tels besoins, et elles sont expliquées attentivement dans ce tutoriel.

train_dataset_x = tf.data.Dataset.from_tensor_slices(x_train)
test_dataset_x = tf.data.Dataset.from_tensor_slices(x_test)
print(train_dataset_x, test_dataset_x)

train_dataset_y= tf.data.Dataset.from_tensor_slices(y_train)
test_dataset_y = tf.data.Dataset.from_tensor_slices(y_test)
print(train_dataset_y, test_dataset_y)

Commencez par convertir les images et les étiquettes en ** ensembles de données sans ** mélange.

train_dataset_xy = tf.data.Dataset.zip((train_dataset_x, train_dataset_y))
train_dataset_xy = train_dataset_xy.shuffle(train_size).batch(batch_size)
test_dataset_xy = tf.data.Dataset.zip((test_dataset_x, test_dataset_y))
test_dataset_xy = test_dataset_xy.shuffle(train_size).batch(batch_size)
print(train_dataset_xy, test_dataset_xy)

Une fois que chaque ensemble de données est créé, il peut être combiné avec tf.data.Dataset.zip () en une paire ** (image, étiquette) **. Même si vous mélangez ou faites un mini-lot à partir d'ici, la correspondance entre les deux ne sera pas rompue. Une fois itéré, un taple de (tenseur d'image, tenseur d'étiquette) apparaîtra, vous pouvez donc les utiliser ensemble ou les utiliser individuellement.

Jouez avec CVAE

Maintenant que CVAE est terminé, j'ai formé les données MNIST et joué avec. Le code de cette section est disponible dans le référentiel et dans le Google Colaboratory. Si vous êtes un Google Colab, vous pouvez réellement l'exécuter.

sample-cvae-mnist.ipynb Google Colab GitHub
sample-cvae-mnist-manifold.ipynb Google Colab GitHub

Hyper paramètres

Dans l'échantillon officiel de TensorFlow, la dimension de la variable latente (latent_dim) a été fixée à 2 afin de représenter la variable latente entière sous la forme d'une image bidimensionnelle (variété 2D). Cependant, Comparaison d'AutoEncoder, VAE, CVAE-Pourquoi VAE peut-il générer des images continues? , plus le nombre de dimensions est grand, plus le résultat est clair. Sauf pour la dernière variété, faites la variable latente 64 dimensions **.

L'ensemble des données MNIST (60000 et 10000, respectivement) a été utilisé pour la formation et les tests, et toutes les données de formation ont été entraînées avec des étiquettes correctes. Le nombre d'époques est de 100 (50 pour le collecteur) et la taille du mini-lot est de 32.

Restauration d'image

Restaurons les 32 images du début de MNIST.

mnist32.png

Tout d'abord, donnez une ** image et l'étiquette correcte ** et exécutez-la sur le décodeur.

mnist32-cvae.png

Les détails ont été flous, mais les nombres naturels ont été restaurés.

Ensuite, attribuez aux 32 feuilles une étiquette ** "8" **. Puisque les données de «8» ne sont pas incluses dans l'entrée des 32 feuilles traitées ici, les données qui n'existent pas dans le jeu de données seront générées.

mnist32-cvae-8.png

Certains d'entre eux se sont effondrés, mais il semble qu'environ la moitié d'entre eux soient des personnages qui peuvent être tolérés comme 8. J'ai pu confirmer que ** CVAE peut générer les données de l'étiquette spécifiée **.

Changement continu de l'écriture manuscrite

Une caractéristique de la série VAE est qu'elle peut générer des ** données continues **. Avec VAE, par exemple, en passant continuellement de la variable latente «0» à la variable latente «1», vous pouvez créer une image qui passe progressivement à un autre nombre.

cont-vae.png

Dans CVAE, on dit que les informations d'étiquette sont supprimées de la variable latente et ** il s'agit d'exprimer la différence d'écriture manuscrite **. Par conséquent, fixez l'étiquette et modifiez la variable latente en continu pour créer une image dans laquelle le trait change continuellement. L'entrée a été sélectionnée parmi les 32 premières feuilles de MNIST comme dans la section précédente.

cont-cvae-4.png

C'est une image continue générée avec des variables latentes allant de "ligne épaisse 0" à "ligne fine 0" et une étiquette de "4". En descendant, les lignes sont devenues plus fines et le rapport hauteur / largeur des lettres a changé.

cont-cvae-6.png

Il s'agit d'une image continue générée avec des variables latentes de «1 incliné vers la droite» à «3 incliné vers la gauche» et une étiquette de «6». Vous pouvez voir que la pente des nombres change progressivement.

2D Manifold Une image générée à partir de tout l'espace de variables latentes bidimensionnelles et disposées verticalement et horizontalement est appelée ** Manifold 2D **. Avec VAE, l'image ressemble à ceci.

manifold-vae.png

Générons un manifold 2D CVAE avec quelques étiquettes. Notez que l'orientation de l'image n'a pas de sens car l'orientation dans l'espace latent change à chaque apprentissage.

manifold-cvae-4.png

C'est le résultat lorsque "4" est spécifié. Vous pouvez voir que la variable latente contient des informations sur le rapport hauteur / largeur et l'inclinaison du caractère.

manifold-cvae-2.png

Le résultat de "2" était personnellement intéressant. ** S'il faut écrire en arrondissant la partie inférieure du nombre ** est divisé. Il a été confirmé que CVAE supprime les informations d'étiquette de l'espace latent et conserve les informations d'écriture manuscrite.

en conclusion

Dans cet article, nous avons modifié l'exemple VAE de la série TensorFlow 2 pour implémenter CVAE. J'ai également essayé la restauration d'image et le changement continu avec CVAE qui a appris MNIST.

Puisque je suis un débutant de TensorFlow, il était très utile de pouvoir l'implémenter tout en faisant référence à l'exemple de code qui fonctionne de manière fiable. Je sens que je comprends comment l'écrire, alors j'essaierai d'apprendre diverses données à l'avenir.

Article de référence

Recommended Posts

Implémenter le codeur automatique variationnel conditionnel (CVAE) dans le système TensorFlow 2
Implémenter LSTM AutoEncoder avec Keras
J'ai essayé d'implémenter Autoencoder avec TensorFlow
Comment exécuter CNN en notation système 1 avec Tensorflow 2