[PYTHON] Implementieren Sie CVAE (Conditional Variational Autoencoder) im TensorFlow 2-System

TL;DR

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 Generierter 2D-Verteiler

Einführung

** CVAE (Conditional Variant Auto Encoder) ** ist ein (halb) überwachtes Generierungsmodell, das Daten generieren kann, die Etiketten entsprechen. Wie in "Vergleich von AutoEncoder, VAE, CVAE - Warum kann VAE fortlaufende Bilder erzeugen?" eingeführt. Dies kann einfach durch Hinzufügen des Prozesses zur Eingabe eines Etiketts in (VAE) erreicht werden.

Sie können Implementierungsbeispiele in den Serien Chainer, PyTorch und TensorFlow 1 im Internet finden, aber es scheint kein Beispiel in der Serie TensorFlow 2 zu geben. Daher werde ich einen Artikel schreiben, der auch als Studie über TensorFlow selbst dient. Bei der Implementierung haben wir das VAE-Beispiel gemäß der TensorFlow-Formel geändert, ohne es so weit wie möglich zu ändern. Wir hoffen, dass das Befolgen der Änderungen Ihnen hilft, den Inhalt zu verstehen.

Umgebung

Modellbeschreibung

Da leicht verständliche Erklärung bereits vorhanden ist, werde ich es hier beschreiben.

AutoEncoder (AE)

Ursprünglich als unbeaufsichtigtes Lernen mit ** Dimensionsreduktion ** vorgeschlagen. AE besteht aus zwei Modellen, Encoder und Decoder. Der Encoder komprimiert die Eingabe $ \ boldsymbol {x} $ auf $ \ boldsymbol {z} $, und der Decoder wechselt von $ \ boldsymbol {z} $ zu $ \ boldsymbol {x}. Versuchen Sie, $ zu reproduzieren. Das $ \ boldsymbol {z} $, das in der Mitte erscheint, wird als ** latente Variable ** bezeichnet und repräsentiert die Eigenschaften der Daten in einer kleinen Anzahl von Dimensionen.

AE kann zur Rauschentfernung und Erkennung von Anomalien angewendet werden, zusätzlich zur Wiederherstellung des Eingangs, wie er ist.

Variations-Autoencoder (VAE)

Durch Einbeziehung der Wahrscheinlichkeitsverteilung kann VAE auch zur ** Datengenerierung ** verwendet werden. Schätzen Sie die Parameter $ \ mu $ und $ \ sigma $ der multivariaten Gaußschen Verteilung mit Encoder und erstellen Sie $ \ boldsymbol {z} $ aus der erhaltenen Wahrscheinlichkeitsverteilung. Da $ \ boldsymbol {z} $ aus einer kontinuierlichen Wahrscheinlichkeitsverteilung erhalten werden kann, können Daten generiert werden, die im Datensatz nicht vorhanden sind.

Beim tatsächlichen Lernen wird $ \ boldsymbol {z} $ durch eine Näherungsmethode namens Reparametrization Trick berechnet, damit eine Fehlerrückübertragung durchgeführt werden kann. Zusätzlich wird die Regularisierung durchgeführt, indem die KL-Divergenz (Calback Libra-Divergenz) der erhaltenen Verteilung und die Standardnormalverteilung in die Zielfunktion einbezogen werden.

Durch VAE maximierte Zielfunktion. Der erste Term auf der rechten Seite ist der erwartete Wert der logarithmischen Wahrscheinlichkeit der vom Decoder erhaltenen Ausgabe, und der zweite Term ist der Regularisierungsterm.

\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})]

Bedingter Variations-Autoencoder (CVAE)

CVAE ermöglicht die ** Datengenerierung durch Angabe eines Labels ** durch Hinzufügen des Labels $ y $ zu jedem Encoder und Decoder als Eingabe. Da es Etiketteninformationen enthält, handelt es sich um überwachtes Lernen. Wenn Sie es jedoch entwickeln, kann es anscheinend auch als halbüberwachtes Lernen verwendet werden, für das nicht für alle Etiketten erforderlich sind.

Die Zielfunktion ändert sich von VAE wie folgt. Encoder / Decoder berücksichtigt jedoch nur $ y $, und es ist in Ordnung, die Zielfunktion von VAE in Bezug auf die Implementierung beizubehalten.

\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)]

Implementierung von CVAE

Lassen Sie uns nun CVAE implementieren. Laut Originalarbeit ist es möglich, mit Semi-Teacher zu lernen, indem VAE (M1-Modell) und CVAE (M2-Modell) kombiniert werden. In diesem Artikel ist dies jedoch nicht der Fall. Lernen Sie, indem Sie allen Daten Beschriftungen zuordnen.

Der gesamte Code wird im folgenden Repository veröffentlicht.

Offizielle VAE-Probe von TensorFlow

Für VAE ist ein Beispiel im offiziellen TensorFlow-Tutorial enthalten.

** Es wird in Google Colaboratory veröffentlicht **, Sie können also einfach darauf klicken und es wird funktionieren.

Es ist zu beachten, dass der Convolutional Variational Autoencoder hier als CVAE bezeichnet wird und sich ** von dem in diesem Artikel beschriebenen CVAE ** unterscheidet. Das Modell in diesem Beispiel ist eine normale VAE, nur weil es eine Faltschicht hat.

Wir werden die VAE, die diesen MNIST lernt, modifizieren, um die bedingte VAE zu realisieren.

Von VAE zu CVAE

Der Teil, der das Modell definiert, wird aus dem obigen Beispielcode vae.py extrahiert und der CVAE daraus erstellt Ich habe den Code als [cvae.py] in das Repository gestellt (https://github.com/kn1cht/tensorflow_v2_cvae_sample/blob/master/cvae.py).

Da es einfacher ist, den Unterschied zu verstehen, werde ich ihn beim Posten [beider Unterschiede] erläutern (https://github.com/kn1cht/tensorflow_v2_cvae_sample/blob/master/diff-vae-cvae.diff). ..

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(

Erstens ist der Definitionsteil des Modells. Der Etikettentyp $ y $ (10 Typen für MNIST) wird auf "label_size" gesetzt, und die Eingabegröße jedes Encoders / Decoders wird um "label_size" erhöht. Sie können das konvertierte Etikett jetzt mit Ihrer Eingabe zu einer One-Hot-Darstellung kombinieren.

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 () ist der Prozess des Empfangens latenter Variablen und Bezeichnungen und des Generierens von Daten.

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

Es ist ein Prozess, bei dem der Encoder die Eingabe liest. Konvertieren Sie zunächst die Bezeichnung "y" in die One-Hot-Darstellung "y_onehot". Abgesehen davon machen Sie einen Tensor "k", dessen Form "Bildgröße (28 x 28 für MNIST) x 1" ist und alle Elemente 1 sind. Wenn "k * y_onehot" berechnet wird, wird es von der Broadcast-Funktion zu "28 x 28 x label_size" und kann mit "x" kombiniert werden.

(Dieser Teil basiert auf der Implementierung von Ysasaki6023. Es scheint zu funktionieren, auch wenn Sie eine Verbindung herstellen und 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

In ähnlicher Weise übergeben Sie "z" und "y_onehot" an Decoder. Der Grund, warum y None ist oder nicht, besteht darin, die Datengenerierung ohne Übergabe eines Etiketts zu versuchen. Da ich jedoch nicht ohne Etiketten studiert habe, habe ich nur Bilder erhalten, die ich nicht verstanden habe ...

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.)

Dies ist die Verarbeitung der Zielfunktion während des Lernens. Wie oben erwähnt, ist die Implementierung der Zielfunktion dieselbe wie bei VAE, daher wird dem Argument nur "y" hinzugefügt.

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))

Es ist ein Prozess, der das Lernen zu einem Schritt macht.

Vorbereitung des Eingabedatensatzes

Die TensorFlow-Dataset-Funktion (tf.data.Dataset) erstellt eine MNIST ** Bild- und Beschriftungspaar-Eingabe **. Ich bin ein Anfänger von TensorFlow. Als ich das offizielle Beispiel sah, war ich besorgt über "Wenn ich train_dataset so mische, wird die Korrespondenz zwischen Bildern und Etiketten unterbrochen ...?" Natürlich gibt es Funktionen, die diese Anforderungen erfüllen, und diese werden in diesem Lernprogramm ausführlich erläutert.

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)

Konvertieren Sie zunächst die Bilder und Beschriftungen in ** Datensätze ohne ** Mischen.

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)

Sobald jeder Datensatz erstellt wurde, kann er mit tf.data.Dataset.zip () zu einem ** (Bild, Label) Paar ** kombiniert werden. Selbst wenn Sie von hier aus mischen oder Mini-Batch verwenden, wird die Korrespondenz zwischen den beiden nicht unterbrochen. Wenn es kursiv geschrieben ist, wird ein Tape von "(Bildtensor, Etikettentensor)" angezeigt, sodass Sie sie zusammen oder einzeln verwenden können.

Spielen Sie mit CVAE

Nachdem CVAE fertig ist, habe ich MNIST-Daten trainiert und damit gespielt. Der Code in diesem Abschnitt ist im Repository und im Google Colaboratory verfügbar. Wenn Sie ein Google Colab sind, können Sie es tatsächlich ausführen.

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

Hyperparameter

In der offiziellen TensorFlow-Stichprobe wurde die Dimension der latenten Variablen (latent_dim) auf 2 gesetzt, um die gesamte latente Variable als zweidimensionales Bild (2D-Mannigfaltigkeit) darzustellen. Vergleich von AutoEncoder, VAE, CVAE-Warum kann VAE kontinuierliche Bilder erzeugen? Je größer die Anzahl der Dimensionen ist, desto klarer ist das Ergebnis. Machen Sie mit Ausnahme des letzten Verteilers die latente Variable 64 Dimensionen **.

Wir haben die gesamten MNIST-Daten (60000 bzw. 10000) für Training und Test verwendet und alle Trainingsdaten mit korrekten Etiketten trainiert. Die Anzahl der Epochen beträgt 100 (50 für Verteiler) und die Mini-Batch-Größe beträgt 32.

Bildwiederherstellung

Lassen Sie uns die 32 Bilder vom Anfang von MNIST wiederherstellen.

mnist32.png

Geben Sie zunächst ein ** Bild und das richtige Etikett ** ein und führen Sie es auf dem Decoder aus.

mnist32-cvae.png

Die Details wurden verwischt, aber die natürlichen Zahlen wurden wiederhergestellt.

Geben Sie als nächstes allen 32 Blättern ein ** "8" -Label **. Da die Daten von "8" nicht in der Eingabe von 32 hier behandelten Blättern enthalten sind, werden Daten generiert, die nicht im Datensatz vorhanden sind.

mnist32-cvae-8.png

Einige von ihnen sind zusammengebrochen, aber es scheint, dass etwa die Hälfte von ihnen Zeichen sind, die als 8 toleriert werden können. Ich konnte bestätigen, dass ** CVAE die Daten des angegebenen Etiketts generieren kann **.

Kontinuierliche Änderung der Handschrift

Ein Merkmal der VAE-Serie ist, dass sie ** kontinuierliche Daten ** erzeugen kann. Mit VAE können Sie beispielsweise durch kontinuierliches Wechseln von der latenten Variablen "0" zur latenten Variablen "1" ein Bild erstellen, das sich allmählich zu einer anderen Zahl ändert.

cont-vae.png

In CVAE wird gesagt, dass die Etiketteninformationen aus der latenten Variablen entfernt werden und ** es kommt, um den Unterschied in der Handschrift auszudrücken **. Korrigieren Sie daher die Beschriftung und ändern Sie die latente Variable kontinuierlich, um ein Bild zu erstellen, in dem sich der Strich kontinuierlich ändert. Die Eingabe wurde wie im vorherigen Abschnitt aus den ersten 32 Blättern von MNIST ausgewählt.

cont-cvae-4.png

Es ist ein kontinuierliches Bild, das mit latenten Variablen von "dicke Linie 0" bis "dünne Linie 0" und einer Bezeichnung von "4" erzeugt wird. Als ich hinunterging, wurden die Linien dünner und das Seitenverhältnis der Buchstaben änderte sich.

cont-cvae-6.png

Dies ist ein kontinuierliches Bild, das mit latenten Variablen von "1 nach rechts geneigt" bis "3 nach links geneigt" und einer Bezeichnung von "6" erzeugt wird. Sie können sehen, dass sich die Steigung der Zahlen allmählich ändert.

2D Manifold Ein Bild, das aus dem gesamten Raum zweidimensionaler latenter Variablen erzeugt und vertikal und horizontal angeordnet wird, wird als ** 2D-Verteiler ** bezeichnet. Mit VAE sieht das Bild so aus.

manifold-vae.png

Lassen Sie uns einen CVAE 2D-Verteiler mit einigen Beschriftungen erstellen. Beachten Sie, dass die Ausrichtung des Bildes bedeutungslos ist, da sich die Ausrichtung im latenten Raum mit jedem Lernen ändert.

manifold-cvae-4.png

Dies ist das Ergebnis, wenn "4" angegeben ist. Sie können sehen, dass die latente Variable Informationen zum Seitenverhältnis und zur Neigung des Zeichens enthält.

manifold-cvae-2.png

Das Ergebnis von "2" war persönlich interessant. ** Ob durch Runden des unteren Teils der Zahl geschrieben werden soll ** wird geteilt. Es wurde bestätigt, dass CVAE die Etiketteninformationen aus dem latenten Raum entfernt und die Handschriftinformationen beibehält.

abschließend

In diesem Artikel haben wir das VAE-Beispiel der TensorFlow 2-Serie geändert, um CVAE zu implementieren. Ich habe auch versucht, Bilder wiederherzustellen und kontinuierlich mit CVAE zu ändern, das MNIST gelernt hat.

Da ich ein Anfänger von TensorFlow bin, war es sehr hilfreich, es implementieren zu können, während ich auf den Beispielcode verweise, der zuverlässig funktioniert. Ich habe das Gefühl, dass ich verstehe, wie man es schreibt, deshalb werde ich versuchen, in Zukunft verschiedene Daten zu lernen.

Referenzartikel

Recommended Posts

Implementieren Sie CVAE (Conditional Variational Autoencoder) im TensorFlow 2-System
Implementieren Sie LSTM AutoEncoder mit Keras
Ich habe versucht, Autoencoder mit TensorFlow zu implementieren
So führen Sie CNN in 1 Systemnotation mit Tensorflow 2 aus