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 |
** 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.
Puisque explication facile à comprendre existe déjà, je vais la décrire ici.
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.
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})]
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)]
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.
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.
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.
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.
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 |
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.
Restaurons les 32 images du début de MNIST.
Tout d'abord, donnez une ** image et l'étiquette correcte ** et exécutez-la sur le décodeur.
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.
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 **.
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.
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.
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é.
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.
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.
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.
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.
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.