कनवल्शनल वेरिएशनल ऑटोएन्कोडर

TensorFlow.org पर देखें Google Colab में चलाएं GitHub पर स्रोत देखें नोटबुक डाउनलोड करें

यह नोटबुक दर्शाती है कि MNIST डेटासेट पर एक वेरिएशनल ऑटोएन्कोडर (VAE) ( 1 , 2 ) को कैसे प्रशिक्षित किया जाए। VAE ऑटोएन्कोडर पर एक संभाव्य टेक है, एक मॉडल जो उच्च आयामी इनपुट डेटा लेता है और इसे एक छोटे प्रतिनिधित्व में संपीड़ित करता है। एक पारंपरिक ऑटोएन्कोडर के विपरीत, जो एक गुप्त वेक्टर पर इनपुट को मैप करता है, एक वीएई इनपुट डेटा को संभाव्यता वितरण के पैरामीटर में मैप करता है, जैसे गॉसियन का माध्य और भिन्नता। यह दृष्टिकोण एक सतत, संरचित गुप्त स्थान उत्पन्न करता है, जो छवि निर्माण के लिए उपयोगी है।

CVAE छवि अव्यक्त स्थान

सेट अप

pip install tensorflow-probability

# to generate gifs
pip install imageio
pip install git+https://github.com/tensorflow/docs
35 एल10एन-प्लेसहोल्डर
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 डेटासेट लोड करें

प्रत्येक एमएनआईएसटी छवि मूल रूप से 784 पूर्णांकों का एक वेक्टर है, जिनमें से प्रत्येक 0-255 के बीच है और एक पिक्सेल की तीव्रता का प्रतिनिधित्व करता है। हमारे मॉडल में बर्नौली वितरण के साथ प्रत्येक पिक्सेल को मॉडल करें, और डेटासेट को स्थिर रूप से बाइनरी करें।

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

डेटा को बैचने और फेरबदल करने के लिए tf.data का उपयोग करें

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 के साथ परिभाषित करें

इस VAE उदाहरण में, एन्कोडर और डिकोडर नेटवर्क के लिए दो छोटे ConvNets का उपयोग करें। साहित्य में, इन नेटवर्कों को क्रमशः अनुमान/मान्यता और जनक मॉडल के रूप में भी जाना जाता है। कार्यान्वयन को आसान बनाने के लिए tf.keras.Sequential का उपयोग करें। मान लीजिए \(x\) और \(z\) निम्नलिखित विवरण में क्रमशः प्रेक्षण और गुप्त चर को निरूपित करते हैं।

एनकोडर नेटवर्क

यह अनुमानित पश्च वितरण \(q(z|x)\)को परिभाषित करता है, जो इनपुट के रूप में एक अवलोकन लेता है और गुप्त प्रतिनिधित्व \(z\)के सशर्त वितरण को निर्दिष्ट करने के लिए पैरामीटर का एक सेट आउटपुट करता है। इस उदाहरण में, वितरण को केवल एक विकर्ण गाऊसी के रूप में मॉडल करें, और नेटवर्क एक कारक गाऊसी के माध्य और लॉग-विचरण मापदंडों को आउटपुट करता है। संख्यात्मक स्थिरता के लिए सीधे विचरण के बजाय आउटपुट लॉग-विचरण।

डिकोडर नेटवर्क

यह अवलोकन \(p(x|z)\)के सशर्त वितरण को परिभाषित करता है, जो इनपुट के रूप में एक गुप्त नमूना \(z\) लेता है और अवलोकन के सशर्त वितरण के लिए पैरामीटर आउटपुट करता है। एक इकाई गाऊसी के रूप में पूर्व \(p(z)\) के गुप्त वितरण को मॉडल करें।

रिपैरामीटराइजेशन ट्रिक

प्रशिक्षण के दौरान डिकोडर के लिए नमूना \(z\) उत्पन्न करने के लिए, आप इनपुट अवलोकन \(x\)दिए गए एन्कोडर द्वारा आउटपुट पैरामीटर द्वारा परिभाषित गुप्त वितरण से नमूना ले सकते हैं। हालाँकि, यह सैंपलिंग ऑपरेशन एक अड़चन पैदा करता है क्योंकि बैकप्रोपेगेशन एक यादृच्छिक नोड के माध्यम से प्रवाहित नहीं हो सकता है।

इसे संबोधित करने के लिए, एक पुनर्मूल्यांकन चाल का उपयोग करें। हमारे उदाहरण में, आप डिकोडर पैरामीटर और अन्य पैरामीटर \(\epsilon\) placeholder11 का उपयोग करके \(z\) का अनुमान लगाते हैं:

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

जहाँ \(\mu\) और \(\sigma\) क्रमशः गाऊसी वितरण के माध्य और मानक विचलन का प्रतिनिधित्व करते हैं। उन्हें डिकोडर आउटपुट से प्राप्त किया जा सकता है। \(\epsilon\) को एक यादृच्छिक शोर के रूप में माना जा सकता है जिसका उपयोग \(z\)की स्टोचैस्टिसिटी बनाए रखने के लिए किया जाता है। मानक सामान्य वितरण से \(\epsilon\) उत्पन्न करें।

अव्यक्त चर \(z\) अब \(\mu\), \(\sigma\) और \(\epsilon\)के एक फ़ंक्शन द्वारा उत्पन्न होता है, जो मॉडल को क्रमशः l10n \(\mu\) और \(\sigma\) के माध्यम से एन्कोडर में ग्रेडिएंट को बैकप्रोपेगेट करने में सक्षम बनाता है, जबकि स्टोचैस्टिसिटी बनाए रखता है \(\epsilon\)।

नेटवर्क आर्किटेक्चर

एन्कोडर नेटवर्क के लिए, दो कन्वेन्शनल लेयर्स का उपयोग करें और उसके बाद पूरी तरह से कनेक्टेड लेयर का उपयोग करें। डिकोडर नेटवर्क में, पूरी तरह से जुड़ी हुई परत का उपयोग करके इस आर्किटेक्चर को मिरर करें, इसके बाद तीन कनवल्शन ट्रांसपोज़ लेयर्स (कुछ संदर्भों में उर्फ ​​​​डीकोनवोल्यूशनल लेयर्स) का उपयोग करें। ध्यान दें, वीएई को प्रशिक्षण देते समय बैच सामान्यीकरण का उपयोग करने से बचना आम बात है, क्योंकि मिनी-बैच का उपयोग करने के कारण अतिरिक्त स्टोचैस्टिसिटी नमूनाकरण से स्टोचैस्टिसिटी के शीर्ष पर अस्थिरता को बढ़ा सकती है।

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

हानि फ़ंक्शन और अनुकूलक को परिभाषित करें

वीएई सीमांत लॉग-संभावना पर साक्ष्य को कम करने वाले (ईएलबीओ) को अधिकतम करके प्रशिक्षित करते हैं:

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

व्यवहार में, इस अपेक्षा के एकल नमूना मोंटे कार्लो अनुमान का अनुकूलन करें:

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

जहां \(z\) \(q(z|x)\)नमूना लिया गया है।

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

प्रशिक्षण

  • डेटासेट पर पुनरावृति करके प्रारंभ करें
  • प्रत्येक पुनरावृत्ति के दौरान, अनुमानित पश्च \(q(z|x)\)के माध्य और लॉग-विचरण मापदंडों का एक सेट प्राप्त करने के लिए छवि को एन्कोडर को पास करें
  • फिर \(q(z|x)\). से नमूने के लिए रिपैरामीटराइजेशन ट्रिक लागू करें
  • अंत में, जनरेटिव डिस्ट्रीब्यूशन l10n- \(p(x|z)\)31 के लॉग प्राप्त करने के लिए डिकोडर को रिपैरामीटराइज्ड सैंपल पास करें
  • नोट: चूंकि आप केरस द्वारा लोड किए गए डेटासेट का उपयोग प्रशिक्षण सेट में 60k डेटापॉइंट्स और परीक्षण सेट में 10k डेटापॉइंट्स के साथ करते हैं, इसलिए परीक्षण सेट पर हमारा परिणामी ELBO साहित्य में रिपोर्ट किए गए परिणामों की तुलना में थोड़ा अधिक है जो लैरोशेल के MNIST के डायनेमिक बायनेराइज़ेशन का उपयोग करता है।

चित्र बनाना

  • प्रशिक्षण के बाद, कुछ चित्र बनाने का समय आ गया है
  • इकाई गाऊसी पूर्व वितरण \(p(z)\). से अव्यक्त वैक्टर के एक सेट का नमूना लेकर प्रारंभ करें
  • इसके बाद जनरेटर अव्यक्त नमूने \(z\) को प्रेक्षण के लॉग में बदल देगा, जिससे वितरण \(p(x|z)\)हो जाएगा।
  • यहाँ, बर्नौली बंटन की प्रायिकताएँ आलेखित करें
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

पीएनजी

पिछले प्रशिक्षण युग से उत्पन्न छवि प्रदर्शित करें

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
(-0.5, 287.5, 287.5, -0.5)

पीएनजी

सभी सहेजी गई छवियों का एनिमेटेड GIF प्रदर्शित करें

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)
import tensorflow_docs.vis.embed as embed
embed.embed_file(anim_file)

जीआईएफ

अव्यक्त स्थान से अंकों का एक 2D कई गुना प्रदर्शित करें

नीचे दिए गए कोड को चलाने से विभिन्न अंकों के वर्गों का निरंतर वितरण दिखाई देगा, जिसमें प्रत्येक अंक 2D गुप्त स्थान में दूसरे में रूपांतरित हो जाएगा। गुप्त स्थान के लिए मानक सामान्य वितरण उत्पन्न करने के लिए TensorFlow प्रायिकता का उपयोग करें।

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()
plot_latent_images(model, 20)

पीएनजी

अगले कदम

इस ट्यूटोरियल ने प्रदर्शित किया है कि TensorFlow का उपयोग करके एक कन्वेन्शनल वेरिएबल ऑटोएन्कोडर को कैसे कार्यान्वित किया जाए।

अगले चरण के रूप में, आप नेटवर्क आकार को बढ़ाकर मॉडल आउटपुट को बेहतर बनाने का प्रयास कर सकते हैं। उदाहरण के लिए, आप प्रत्येक Conv2D और Conv2DTranspose लेयर के लिए 512 पर filter पैरामीटर सेट करने का प्रयास कर सकते हैं। ध्यान दें कि अंतिम 2D अव्यक्त छवि प्लॉट उत्पन्न करने के लिए, आपको latent_dim को 2 पर रखना होगा। साथ ही, प्रशिक्षण समय में वृद्धि होगी जैसे-जैसे नेटवर्क का आकार बढ़ता है।

आप CIFAR-10 जैसे भिन्न डेटासेट का उपयोग करके VAE को लागू करने का भी प्रयास कर सकते हैं।

VAE को कई अलग-अलग शैलियों और अलग-अलग जटिलता में लागू किया जा सकता है। आप निम्नलिखित स्रोतों में अतिरिक्त कार्यान्वयन पा सकते हैं:

यदि आप वीएई के विवरण के बारे में अधिक जानना चाहते हैं, तो कृपया विविधतापूर्ण ऑटोएन्कोडर का परिचय देखें।