Evrişimli Varyasyonlu Otomatik Kodlayıcı

TensorFlow.org'da görüntüleyin Google Colab'da çalıştırın Kaynağı GitHub'da görüntüleyin Not defterini indir

Bu not defteri, MNIST veri kümesi üzerinde bir Değişken Otomatik Kodlayıcının (VAE) ( 1 , 2 ) nasıl eğitileceğini gösterir. Bir VAE, yüksek boyutlu girdi verilerini alan ve daha küçük bir temsile sıkıştıran bir model olan otomatik kodlayıcının olasılıksal bir yorumudur. Girişi gizli bir vektöre eşleyen geleneksel bir otomatik kodlayıcının aksine, bir VAE, giriş verilerini bir Gauss'un ortalaması ve varyansı gibi bir olasılık dağılımının parametrelerine eşler. Bu yaklaşım, görüntü üretimi için yararlı olan sürekli, yapılandırılmış bir gizli alan üretir.

CVAE görüntü gizli alanı

Kurmak

pip install tensorflow-probability

# to generate gifs
pip install imageio
pip install git+https://github.com/tensorflow/docs
tutucu35 l10n-yer
from IPython import display

import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import PIL
import tensorflow as tf
import tensorflow_probability as tfp
import time

MNIST veri kümesini yükleyin

Her MNIST görüntüsü orijinal olarak her biri 0-255 arasında olan ve bir pikselin yoğunluğunu temsil eden 784 tamsayıdan oluşan bir vektördür. Modelimizde her pikseli bir Bernoulli dağılımıyla modelleyin ve veri kümesini statik olarak ikili hale getirin.

(train_images, _), (test_images, _) = tf.keras.datasets.mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step
yer tutucu38 l10n-yer
def preprocess_images(images):
  images = images.reshape((images.shape[0], 28, 28, 1)) / 255.
  return np.where(images > .5, 1.0, 0.0).astype('float32')

train_images = preprocess_images(train_images)
test_images = preprocess_images(test_images)
train_size = 60000
batch_size = 32
test_size = 10000

Verileri gruplamak ve karıştırmak için tf.data kullanın

train_dataset = (tf.data.Dataset.from_tensor_slices(train_images)
                 .shuffle(train_size).batch(batch_size))
test_dataset = (tf.data.Dataset.from_tensor_slices(test_images)
                .shuffle(test_size).batch(batch_size))

tf.keras.Sequential ile kodlayıcı ve kod çözücü ağlarını tanımlayın

Bu VAE örneğinde, kodlayıcı ve kod çözücü ağları için iki küçük ConvNet kullanın. Literatürde bu ağlara sırasıyla çıkarım/tanıma ve üretici modeller de denilmektedir. Uygulamayı basitleştirmek için tf.keras.Sequential kullanın. Aşağıdaki açıklamalarda l10n-yer \(x\) ve \(z\) sırasıyla gözlemi ve gizli değişkeni göstersin.

Enkoder ağı

Bu, girdi olarak bir gözlem alan ve gizli temsil \(z\)koşullu dağılımını belirtmek için bir dizi parametre çıktısı veren yaklaşık sonsal dağılımı \(q(z|x)\)tanımlar. Bu örnekte, dağılımı basitçe bir köşegen Gauss olarak modelleyin ve ağ, çarpanlara ayrılmış bir Gauss'un ortalama ve log-varyans parametrelerini verir. Sayısal kararlılık için doğrudan varyans yerine çıkış log-varyansı.

dekoder ağı

Bu, gizli bir \(z\) örneğini girdi olarak alan ve gözlemin koşullu dağılımı için parametreleri veren \(p(x|z)\)gözleminin koşullu dağılımını tanımlar. \(p(z)\) önceki gizli dağılımı bir Gauss birimi olarak modelleyin.

yeniden parametrelendirme hilesi

Eğitim sırasında kod çözücü için bir \(z\) örneği oluşturmak için, bir giriş gözlemi \(x\)verildiğinde kodlayıcı tarafından çıktı olarak verilen parametreler tarafından tanımlanan gizli dağılımdan örnek alabilirsiniz. Ancak, bu örnekleme işlemi bir darboğaz yaratır çünkü geri yayılım rastgele bir düğümden geçemez.

Bunu ele almak için bir yeniden parametrelendirme hilesi kullanın. Örneğimizde, kod çözücü parametrelerini ve başka bir \(\epsilon\) parametresini aşağıdaki gibi kullanarak \(z\) yaklaşık olarak hesaplarsınız:

\[z = \mu + \sigma \odot \epsilon\]

burada \(\mu\) tutucu13 ve \(\sigma\) sırasıyla bir Gauss dağılımının ortalamasını ve standart sapmasını temsil eder. Dekoder çıkışından elde edilebilirler. \(\epsilon\) , \(z\)stokastikliğini korumak için kullanılan rastgele bir gürültü olarak düşünülebilir. Standart bir normal dağılımdan \(\epsilon\) oluşturun.

Gizli değişken \(z\) şimdi, \(\mu\), \(\sigma\) ve \(\epsilon\)bir fonksiyonu tarafından üretilir; bu, modelin kodlayıcıdaki gradyanları sırasıyla \(\mu\) ve \(\sigma\) aracılığıyla geri yaymasını sağlarken, stokastikliği korurken \(\epsilon\).

Ağ mimarisi

Kodlayıcı ağı için, iki evrişimli katman ve ardından tam bağlantılı bir katman kullanın. Kod çözücü ağında, tam bağlı bir katman ve ardından üç evrişim devrik katman (bazı bağlamlarda evrişimsiz katmanlar olarak da bilinir) kullanarak bu mimariyi yansıtın. VAE'leri eğitirken toplu normalleştirmeyi kullanmaktan kaçınmanın yaygın bir uygulama olduğunu unutmayın, çünkü mini yığınların kullanılmasından kaynaklanan ek stokastiklik, örneklemeden elde edilen stokastikliğin üzerine kararsızlığı ağırlaştırabilir.

class CVAE(tf.keras.Model):
  """Convolutional variational autoencoder."""

  def __init__(self, latent_dim):
    super(CVAE, self).__init__()
    self.latent_dim = latent_dim
    self.encoder = tf.keras.Sequential(
        [
            tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
            tf.keras.layers.Conv2D(
                filters=32, kernel_size=3, strides=(2, 2), activation='relu'),
            tf.keras.layers.Conv2D(
                filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
            tf.keras.layers.Flatten(),
            # No activation
            tf.keras.layers.Dense(latent_dim + latent_dim),
        ]
    )

    self.decoder = tf.keras.Sequential(
        [
            tf.keras.layers.InputLayer(input_shape=(latent_dim,)),
            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(
                filters=64, kernel_size=3, strides=2, padding='same',
                activation='relu'),
            tf.keras.layers.Conv2DTranspose(
                filters=32, kernel_size=3, strides=2, padding='same',
                activation='relu'),
            # No activation
            tf.keras.layers.Conv2DTranspose(
                filters=1, kernel_size=3, strides=1, padding='same'),
        ]
    )

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

  def encode(self, x):
    mean, logvar = tf.split(self.encoder(x), num_or_size_splits=2, axis=1)
    return mean, logvar

  def reparameterize(self, mean, logvar):
    eps = tf.random.normal(shape=mean.shape)
    return eps * tf.exp(logvar * .5) + mean

  def decode(self, z, apply_sigmoid=False):
    logits = self.decoder(z)
    if apply_sigmoid:
      probs = tf.sigmoid(logits)
      return probs
    return logits

Kayıp fonksiyonunu ve optimize ediciyi tanımlayın

VAE'ler, marjinal log-olasılığı üzerinde kanıt alt sınırını (ELBO) maksimize ederek eğitim alır:

\[\log p(x) \ge \text{ELBO} = \mathbb{E}_{q(z|x)}\left[\log \frac{p(x, z)}{q(z|x)}\right].\]

Uygulamada, bu beklentinin tek örnek Monte Carlo tahminini optimize edin:

\[\log p(x| z) + \log p(z) - \log q(z|x),\]

burada \(z\) , \(q(z|x)\)örneklenir.

optimizer = tf.keras.optimizers.Adam(1e-4)


def log_normal_pdf(sample, mean, logvar, raxis=1):
  log2pi = tf.math.log(2. * np.pi)
  return tf.reduce_sum(
      -.5 * ((sample - mean) ** 2. * tf.exp(-logvar) + logvar + log2pi),
      axis=raxis)


def compute_loss(model, x):
  mean, logvar = model.encode(x)
  z = model.reparameterize(mean, logvar)
  x_logit = model.decode(z)
  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.)
  logqz_x = log_normal_pdf(z, mean, logvar)
  return -tf.reduce_mean(logpx_z + logpz - logqz_x)


@tf.function
def train_step(model, x, 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)
  gradients = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))

Eğitim

  • Veri kümesini yineleyerek başlayın
  • Her yineleme sırasında, yaklaşık posterior \(q(z|x)\)bir dizi ortalama ve log-varyans parametresini elde etmek için görüntüyü kodlayıcıya iletin29
  • daha sonra \(q(z|x)\)örneğe yeniden parametrelendirme hilesini uygulayın
  • Son olarak, üretici dağılımın \(p(x|z)\)tutucu31 logitlerini elde etmek için yeniden parametrelendirilmiş örnekleri kod çözücüye iletin.
  • Not: Keras tarafından yüklenen veri setini eğitim setinde 60k veri noktası ve test setinde 10k veri noktası ile kullandığınız için, test setinde elde ettiğimiz ELBO, Larochelle'nin MNIST dinamik ikilileştirmesini kullanan literatürde bildirilen sonuçlardan biraz daha yüksektir.

Görüntü oluşturma

  • Eğitimden sonra, bazı görüntüler oluşturmanın zamanı geldi
  • Gauss ön dağılımı \(p(z)\)biriminden bir dizi gizli vektör örnekleyerek başlayın.
  • Üreteç daha sonra \(z\) tutucu33 gizli örneğini gözlem logitlerine dönüştürerek bir l10n-yer \(p(x|z)\)dağılımı verir.
  • Burada, Bernoulli dağılımlarının olasılıklarını çizin
epochs = 10
# set the dimensionality of the latent space to a plane for visualization later
latent_dim = 2
num_examples_to_generate = 16

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement.
random_vector_for_generation = tf.random.normal(
    shape=[num_examples_to_generate, latent_dim])
model = CVAE(latent_dim)
def generate_and_save_images(model, epoch, test_sample):
  mean, logvar = model.encode(test_sample)
  z = model.reparameterize(mean, logvar)
  predictions = model.sample(z)
  fig = plt.figure(figsize=(4, 4))

  for i in range(predictions.shape[0]):
    plt.subplot(4, 4, i + 1)
    plt.imshow(predictions[i, :, :, 0], cmap='gray')
    plt.axis('off')

  # tight_layout minimizes the overlap between 2 sub-plots
  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()
# Pick a sample of the test set for generating output images
assert batch_size >= num_examples_to_generate
for test_batch in test_dataset.take(1):
  test_sample = test_batch[0:num_examples_to_generate, :, :, :]
generate_and_save_images(model, 0, test_sample)

for epoch in range(1, epochs + 1):
  start_time = time.time()
  for train_x in train_dataset:
    train_step(model, train_x, optimizer)
  end_time = time.time()

  loss = tf.keras.metrics.Mean()
  for test_x in test_dataset:
    loss(compute_loss(model, test_x))
  elbo = -loss.result()
  display.clear_output(wait=False)
  print('Epoch: {}, Test set ELBO: {}, time elapse for current epoch: {}'
        .format(epoch, elbo, end_time - start_time))
  generate_and_save_images(model, epoch, test_sample)
Epoch: 10, Test set ELBO: -156.4964141845703, time elapse for current epoch: 4.854437351226807

png

Son eğitim döneminden oluşturulan bir görüntüyü göster

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
plt.imshow(display_image(epoch))
plt.axis('off')  # Display images
-yer tutucu50 l10n-yer
(-0.5, 287.5, 287.5, -0.5)

png

Kaydedilen tüm görüntülerin animasyonlu bir GIF'ini görüntüleyin

anim_file = 'cvae.gif'

with imageio.get_writer(anim_file, mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  for filename in filenames:
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)
tutucu52 l10n-yer
import tensorflow_docs.vis.embed as embed
embed.embed_file(anim_file)

gif

Gizli alandan bir 2B basamak manifoldu görüntüleyin

Aşağıdaki kodu çalıştırmak, her bir rakamın 2B gizli uzayda diğerine dönüşmesiyle farklı rakam sınıflarının sürekli bir dağılımını gösterecektir. Gizli alan için standart bir normal dağılım oluşturmak üzere TensorFlow Olasılığını kullanın.

def plot_latent_images(model, n, digit_size=28):
  """Plots n x n digit images decoded from the latent space."""

  norm = tfp.distributions.Normal(0, 1)
  grid_x = norm.quantile(np.linspace(0.05, 0.95, n))
  grid_y = norm.quantile(np.linspace(0.05, 0.95, n))
  image_width = digit_size*n
  image_height = image_width
  image = np.zeros((image_height, image_width))

  for i, yi in enumerate(grid_x):
    for j, xi in enumerate(grid_y):
      z = np.array([[xi, yi]])
      x_decoded = model.sample(z)
      digit = tf.reshape(x_decoded[0], (digit_size, digit_size))
      image[i * digit_size: (i + 1) * digit_size,
            j * digit_size: (j + 1) * digit_size] = digit.numpy()

  plt.figure(figsize=(10, 10))
  plt.imshow(image, cmap='Greys_r')
  plt.axis('Off')
  plt.show()
tutucu54 l10n-yer
plot_latent_images(model, 20)

png

Sonraki adımlar

Bu öğretici, TensorFlow kullanarak bir evrişimsel değişken otomatik kodlayıcının nasıl uygulanacağını göstermiştir.

Bir sonraki adım olarak, ağ boyutunu artırarak model çıktısını iyileştirmeyi deneyebilirsiniz. Örneğin, Conv2D ve Conv2DTranspose katmanlarının her biri için filter parametrelerini 512'ye ayarlamayı deneyebilirsiniz. Son 2B gizli görüntü grafiğini oluşturmak için latent_dim 2'de tutmanız gerektiğini unutmayın. Ayrıca, eğitim süresi artacaktır. ağ boyutu arttıkça.

CIFAR-10 gibi farklı bir veri kümesi kullanarak bir VAE uygulamayı da deneyebilirsiniz.

VAE'ler birkaç farklı stilde ve değişen karmaşıklıkta uygulanabilir. Ek uygulamaları aşağıdaki kaynaklarda bulabilirsiniz:

VAE'lerin ayrıntıları hakkında daha fazla bilgi edinmek isterseniz, lütfen Varyasyonlu Otomatik Kodlayıcılara Giriş bölümüne bakın.