What is a Variational Autoencoder (VAE) and How Does It Work? A Comprehensive Guide to Understanding VAEs

A Variational Autoencoder (VAE) is a type of model that helps us create new data. It uses ideas from autoencoders and variational inference. VAEs help us learn complex data patterns. They take input data, put it into a simpler form called latent space, and then recreate the data from that. This way, VAEs can make new data that looks like the original data. They are useful in many areas like making images, finding unusual data, and semi-supervised learning.

In this article, we will look at how Variational Autoencoders work. We will talk about their structure and what the encoder and decoder do. We will also cover latent variables and how they affect the VAE’s function. Plus, we will explain the loss function that helps train the VAE. We will share common uses of VAEs and good practices for using them. By the end of this guide, we will understand how VAEs work and why they are important in generative models.

  • What is a Variational Autoencoder VAE and How Does It Work?
  • Understanding the Structure of a Variational Autoencoder VAE
  • How Does the Encoder Work in a Variational Autoencoder VAE?
  • How Does the Decoder Function in a Variational Autoencoder VAE?
  • The Role of Latent Variables in a Variational Autoencoder VAE
  • Practical Examples of Variational Autoencoders VAE in Use
  • Understanding the Loss Function in a Variational Autoencoder VAE
  • Common Uses of Variational Autoencoders VAE
  • Good Practices for Using a Variational Autoencoder VAE
  • Questions We Often Ask

For more reading on similar topics, we can check these articles: What is Generative AI and How Does It Work?, What are the Key Differences Between Generative and Discriminative Models?, and Real-life Applications of Generative AI.

Understanding the Architecture of a Variational Autoencoder VAE

The architecture of a Variational Autoencoder (VAE) has two main parts. These parts are the encoder and the decoder. Both parts are neural networks. They work together to learn the data patterns and create new samples from those patterns.

Encoder

The encoder takes input data and changes it into a smaller space called latent space. It gives out the parameters of a probability distribution, which is usually Gaussian. This distribution represents the latent variables.

  • Input Layer: Takes the input data like images or text.
  • Hidden Layers: Has several dense or convolutional layers to find features.
  • Output Layer: Gives two vectors: mean (μ) and standard deviation (σ) for the latent space distribution.

Here is an example of an encoder in TensorFlow/Keras:

import tensorflow as tf
from tensorflow.keras import layers, models

def create_encoder(input_shape, latent_dim):
    inputs = layers.Input(shape=input_shape)
    x = layers.Flatten()(inputs)
    x = layers.Dense(128, activation='relu')(x)
    x = layers.Dense(64, activation='relu')(x)
    z_mean = layers.Dense(latent_dim)(x)
    z_log_var = layers.Dense(latent_dim)(x)
    return models.Model(inputs, [z_mean, z_log_var], name='encoder')

Decoder

The decoder takes the latent variables and rebuilds the input data. It maps the latent representation back to the original data.

  • Input Layer: Takes the latent space variables.
  • Hidden Layers: Has several dense or convolutional layers that learn to rebuild the input.
  • Output Layer: Gives the rebuilt output.

Here is an example of a decoder in TensorFlow/Keras:

def create_decoder(latent_dim, original_shape):
    latent_inputs = layers.Input(shape=(latent_dim,))
    x = layers.Dense(64, activation='relu')(latent_inputs)
    x = layers.Dense(128, activation='relu')(x)
    x = layers.Dense(np.prod(original_shape), activation='sigmoid')(x)
    outputs = layers.Reshape(original_shape)(x)
    return models.Model(latent_inputs, outputs, name='decoder')

Latent Space

The latent space is very important in the VAE architecture. It helps us learn smaller versions of the input data. Some key points are:

  • Dimensionality: The size of the latent space (latent_dim) is a hyperparameter. It can change how well the model works.
  • Regularization: The Kullback-Leibler divergence loss keeps the latent variables close to a standard Gaussian distribution.

Complete VAE Model

To make a complete VAE model, we combine the encoder and decoder. We also need to define the loss function that includes reconstruction loss and KL divergence.

def create_vae(input_shape, latent_dim):
    encoder = create_encoder(input_shape, latent_dim)
    decoder = create_decoder(latent_dim, input_shape)
    
    inputs = layers.Input(shape=input_shape)
    z_mean, z_log_var = encoder(inputs)
    epsilon = tf.random.normal(tf.shape(z_mean))
    z = z_mean + tf.exp(0.5 * z_log_var) * epsilon
    outputs = decoder(z)
    
    vae = models.Model(inputs, outputs, name='vae')
    
    reconstruction_loss = tf.keras.losses.binary_crossentropy(inputs, outputs)
    reconstruction_loss *= np.prod(input_shape)
    kl_loss = -0.5 * tf.reduce_mean(z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1)
    vae.add_loss(tf.reduce_mean(reconstruction_loss + kl_loss))
    
    return vae

The VAE architecture captures the patterns of the input data well. It allows us to do generative tasks and data rebuilding.

How Does the Encoder Work in a Variational Autoencoder VAE?

The encoder in a Variational Autoencoder (VAE) is very important. It helps to change input data into a simpler form. This form is called a latent space representation. The encoder makes the input smaller while keeping the key features that the decoder needs to rebuild the data. Let’s look at how the encoder works.

  1. Architecture: The encoder has several layers of neural networks. These are often convolutional layers for image data or fully connected layers for other types of data. It changes the input ( x ) into two outputs:

    • Mean ( )
    • Logarithm of the variance ( (^2) )
  2. Latent Space Representation: The encoder gives us parameters for a Gaussian distribution in the latent space. We can write this as: [ z (, ^2) ] Here, ( z ) is the latent variable we get from the Gaussian distribution from the encoder’s outputs.

  3. Reparameterization Trick: To make backpropagation possible through the sampling process, we use a reparameterization trick. Instead of taking ( z ) directly, we write: [ z = + ] where ( (0, I) ) is noise from a standard normal distribution.

  4. Implementation Example: Here is a simple example of the encoder using TensorFlow/Keras:

    import tensorflow as tf
    from tensorflow.keras import layers, models
    
    class VAEEncoder(tf.keras.Model):
        def __init__(self, latent_dim):
            super(VAEEncoder, self).__init__()
            self.dense1 = layers.Dense(512, activation='relu')
            self.dense2 = layers.Dense(256, activation='relu')
            self.mean = layers.Dense(latent_dim)
            self.log_var = layers.Dense(latent_dim)
    
        def call(self, inputs):
            x = self.dense1(inputs)
            x = self.dense2(x)
            mean = self.mean(x)
            log_var = self.log_var(x)
            return mean, log_var
    
    # Usage
    latent_dim = 10
    encoder = VAEEncoder(latent_dim)
    input_data = tf.random.normal(shape=(1, 784))  # Example input
    mean, log_var = encoder(input_data)
  5. Loss Contribution: The encoder helps the loss function by adding the Kullback-Leibler divergence term. This term checks how much the learned distribution is different from the prior distribution, which is usually a standard normal. This keeps the encoded representations close to a Gaussian distribution. It helps with better sampling and rebuilding.

The encoder is key for a VAE to work well. It helps us learn useful latent representations. We can use these representations for different tasks, like generating data or reconstructing it.

How Does the Decoder Function in a Variational Autoencoder VAE?

In a Variational Autoencoder (VAE), we know that the decoder is very important. It helps to rebuild the input data from the latent space. The decoder is a type of neural network. It takes the sampled latent variables and makes a copy of the original data.

Key Functions of the Decoder:

  1. Input from Latent Space: The decoder takes a latent vector ( z ). This vector comes from the learned distribution ( q(z | x) ).

  2. Neural Network Architecture: The decoder usually has many layers. It often has dense layers. These layers have activation functions like ReLU or sigmoid. These functions add non-linearity.

  3. Output Layer: The last layer of the decoder gives the rebuilt data. The setup of this layer can change based on the data type:

    • For binary data, we often use a sigmoid activation function.
    • For continuous data, we might use a linear activation function.

Example Decoder Implementation in Python using TensorFlow/Keras:

Here is a simple way to make a decoder in a VAE using TensorFlow/Keras:

import tensorflow as tf
from tensorflow.keras import layers, Model

def build_decoder(latent_dim, original_shape):
    # Define the decoder model
    decoder_input = layers.Input(shape=(latent_dim,))
    x = layers.Dense(128, activation='relu')(decoder_input)
    x = layers.Dense(256, activation='relu')(x)
    x = layers.Dense(tf.reduce_prod(original_shape), activation='sigmoid')(x)
    
    # Reshape to original input shape
    decoder_output = layers.Reshape(original_shape)(x)
    
    decoder = Model(decoder_input, decoder_output, name="decoder")
    return decoder

# Example usage
latent_dim = 2  # Dimension of the latent space
original_shape = (28, 28, 1)  # For example, MNIST images
decoder_model = build_decoder(latent_dim, original_shape)
decoder_model.summary()

Important Components:

  • Sampling Layer: The decoder often has a sampling layer. This layer samples from the latent distribution when we train it.

  • Regularization: We can use techniques like dropout in the decoder. This helps to stop overfitting.

  • Loss Calculation: The loss function usually has a reconstruction loss. For example, we can use binary cross-entropy. This loss shows how well the decoder rebuilds the input data from the latent variables.

The decoder must rebuild the original input data from the latent space very well. This ability is key for the Variational Autoencoder to work properly. It helps the model learn good representations of the data. For more insights on generative models like VAEs, we can look at articles on generative AI.

The Role of Latent Variables in a Variational Autoencoder VAE

Latent variables are very important in Variational Autoencoders (VAEs). They help connect the data we see with the model that creates new data. These variables capture the key factors that cause differences in the input data. This helps VAEs to learn better representations.

Key Characteristics of Latent Variables in VAEs

  • Dimensionality Reduction: Latent variables make the data smaller by putting it into a simpler form. This helps with representing and creating data efficiently.
  • Continuous Representation: VAEs usually think of a continuous latent space. This space is often modeled as a multivariate Gaussian distribution. This allows smooth changes between different data points.
  • Variational Inference: With variational inference, VAEs try to estimate the posterior distribution of the latent variables based on the data we see. We use learned parameters to define a variational distribution.

Mathematical Formulation

The link between the observed data ( x ) and the latent variables ( z ) can be shown using Bayes’ theorem:

[ p(z|x) = ]

In VAEs, we want to make the Evidence Lower Bound (ELBO) as big as possible:

[ = {q(z|x)}[p(x|z)] - D{KL}(q(z|x) || p(z)) ]

Where: - ( q(z|x) ) is the variational distribution (encoder). - ( p(x|z) ) is the likelihood (decoder). - ( D_{KL} ) is the Kullback-Leibler divergence. It measures how different the variational distribution is from the prior.

Implementation Example

In a simple VAE using Python with TensorFlow/Keras, we can define the latent space like this:

import tensorflow as tf
from tensorflow.keras import layers, models

latent_dim = 2  # Size of the latent space

# Encoder
encoder_inputs = layers.Input(shape=(input_shape,))
h = layers.Dense(128, activation='relu')(encoder_inputs)
z_mean = layers.Dense(latent_dim)(h)
z_log_var = layers.Dense(latent_dim)(h)

# Sampling function
def sampling(args):
    z_mean, z_log_var = args
    epsilon = tf.random.normal(shape=tf.shape(z_mean))
    return z_mean + tf.exp(0.5 * z_log_var) * epsilon

z = layers.Lambda(sampling)([z_mean, z_log_var])

# Decoder
decoder_h = layers.Dense(128, activation='relu')
decoder_outputs = layers.Dense(input_shape, activation='sigmoid')(decoder_h(z))

# VAE Model
vae = models.Model(encoder_inputs, decoder_outputs)

In this code: - The encoder makes the input data smaller and creates z_mean and z_log_var. - The sampling function takes a sample from the latent space. - The decoder rebuilds the input from the latent variables.

Latent variables in VAEs help the model learn complex data patterns and still create new, similar data. This is very helpful in many areas, like image creation, finding unusual data, and semi-supervised learning. For more about the uses of generative models like VAEs, check out what are the real-life applications of generative AI.

Practical Examples of Variational Autoencoders VAE in Action

We can use Variational Autoencoders (VAEs) in many areas. They are good for generating new data, fixing images, and semi-supervised learning. Here are some examples that show what VAEs can do.

1. Image Generation

We can create new images by sampling from the latent space. Here is a simple example using TensorFlow/Keras:

import numpy as np
import tensorflow as tf
from tensorflow.keras import layers, models

# Define the VAE model
latent_dim = 2

# Encoder
inputs = layers.Input(shape=(28, 28, 1))
x = layers.Flatten()(inputs)
x = layers.Dense(128, activation='relu')(x)
z_mean = layers.Dense(latent_dim)(x)
z_log_var = layers.Dense(latent_dim)(x)

# Sampling function
def sampling(args):
    z_mean, z_log_var = args
    epsilon = tf.keras.backend.random_normal(shape=tf.shape(z_mean))
    return z_mean + tf.exp(0.5 * z_log_var) * epsilon

z = layers.Lambda(sampling)([z_mean, z_log_var])

# Decoder
encoder = models.Model(inputs, [z_mean, z_log_var, z], name="encoder")

decoder_input = layers.Input(shape=(latent_dim,))
x = layers.Dense(128, activation='relu')(decoder_input)
x = layers.Dense(28 * 28 * 1, activation='sigmoid')(x)
outputs = layers.Reshape((28, 28, 1))(x)

decoder = models.Model(decoder_input, outputs, name="decoder")

# VAE Model
outputs = decoder(encoder(inputs)[2])
vae = models.Model(inputs, outputs, name="vae")

# Loss function
reconstruction_loss = tf.keras.losses.binary_crossentropy(inputs, outputs)
reconstruction_loss *= 28 * 28
kl_loss = -0.5 * tf.reduce_sum(1 + z_log_var - tf.square(z_mean) - tf.exp(z_log_var), axis=-1)
vae_loss = tf.reduce_mean(reconstruction_loss + kl_loss)
vae.add_loss(vae_loss)
vae.compile(optimizer='adam')

2. Anomaly Detection

We can use VAEs for finding anomalies. We train the model on normal data. If new data has high reconstruction errors, we can consider them anomalies.

# Assuming 'model' is your trained VAE model and 'data' is the test dataset
reconstructed_data = model.predict(data)
reconstruction_errors = np.mean(np.square(data - reconstructed_data), axis=(1, 2))
threshold = np.percentile(reconstruction_errors, 95)  # Set threshold for anomalies

anomalies = data[reconstruction_errors > threshold]

3. Semi-Supervised Learning

VAEs can work with labeled data to help with classification tasks. By using the latent space, VAEs can make classifiers better.

# Example using VAE latent space for classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

# Split data into labeled and unlabeled
X_labeled, X_unlabeled, y_labeled, _ = train_test_split(X, y, test_size=0.8)

# Encode the labeled data
z_labeled = encoder.predict(X_labeled)

# Train a classifier on the latent space
classifier = LogisticRegression()
classifier.fit(z_labeled, y_labeled)

4. Text Generation

We can also use VAEs to generate text. We can encode sentences into a latent space and then decode them back.

# Assume a pre-trained VAE for text is available
# Sampling new text from latent space
latent_samples = np.random.normal(size=(10, latent_dim))  # Generate 10 new samples
generated_texts = decoder.predict(latent_samples)

5. Data Imputation

We can use VAEs to fill in missing data. They can help us reconstruct the full dataset from what we observe.

# Assuming 'data_with_nan' has missing values
filled_data = model.predict(data_with_nan)

These examples show how we can use Variational Autoencoders (VAEs) in different fields. They are useful for generating data, finding anomalies, and more. For more information about generative AI, you can check out What are the real-life applications of generative AI.

Understanding the Loss Function in a Variational Autoencoder VAE

The loss function in a Variational Autoencoder (VAE) is very important. It helps the model learn by balancing how well it reconstructs data and how it organizes the latent space. The total loss has two parts: the reconstruction loss and the Kullback-Leibler (KL) divergence.

Reconstruction Loss

The reconstruction loss shows how good the decoder is at rebuilding the input data from the latent representation. We usually calculate it using the Negative Log Likelihood (NLL) for a Gaussian distribution.

For continuous data, we can define the reconstruction loss like this:

reconstruction_loss = -NLL(x | x_hat) = -∑(x * log(x_hat) + (1 - x) * log(1 - x_hat))

KL Divergence

The KL divergence shows the difference between the learned latent variable distribution ( q(z|x) ) and the prior distribution ( p(z) ). This prior is usually a standard normal distribution ( N(0, I) ). This part helps the latent space to follow a certain distribution.

We can define the KL divergence like this:

KL_divergence = D_KL(q(z|x) || p(z)) = -0.5 * ∑(1 + log(σ²) - μ² - σ²)

Total Loss Function

The total loss ( L ) for the VAE is the sum of the reconstruction loss and the KL divergence:

total_loss = reconstruction_loss + KL_divergence

Implementation Example

Here is a simple way to implement the loss function in a Keras model:

import keras.backend as K

def vae_loss(x, x_hat, z_mean, z_log_var):
    reconstruction_loss = K.binary_crossentropy(x, x_hat) * original_dim
    kl_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
    return K.mean(reconstruction_loss + kl_loss)

Optimization

In training, we optimize both parts of the loss function at the same time. The VAE learns to create data that looks like the input. It also makes sure the latent space is well organized. This helps with smooth transitions and creating new samples.

By knowing the loss function in a VAE, we can better adjust our models for improved performance. This helps us get good results in generating data. VAEs are powerful tools in generative modeling. For more insights into generative models, we can check out What are the key differences between generative and discriminative models?.

Common Applications of Variational Autoencoders VAE

Variational Autoencoders (VAEs) are strong models that can create new data. They can learn complex patterns, which help in many areas. Here are some ways we can use VAEs:

  1. Image Generation: VAEs can make new images like those in a training set. For example, they can create new faces, landscapes, or objects by taking samples from a special space.

    # Example: Generating images using a trained VAE model
    import numpy as np
    import matplotlib.pyplot as plt
    
    # Assuming `vae` is a trained VAE model
    z_sample = np.random.normal(size=(10, latent_dim))  # 10 random samples from latent space
    generated_images = vae.decoder.predict(z_sample)
    
    for i in range(10):
        plt.imshow(generated_images[i].reshape(28, 28), cmap='gray')
        plt.axis('off')
        plt.show()
  2. Data Imputation: We can use VAEs to fill in missing data. They learn from complete data points. This helps when we have data with missing parts.

  3. Anomaly Detection: VAEs learn what normal data looks like. They can find unusual data points. If a point has low chance in the learned data, it may be an anomaly.

  4. Semi-Supervised Learning: VAEs can help when we have both labeled and unlabeled data. They learn from unlabeled data while using labeled data for guidance.

  5. Recommendation Systems: VAEs can understand user preferences and item features in recommendation systems. We can get personalized suggestions by sampling from the learned space.

  6. Text Generation: We can use VAEs for language tasks too. They can create new text or finish sentences by learning how words and phrases are used.

  7. Molecular Generation: In drug discovery, VAEs can help make new molecular structures. They learn from existing chemical compounds, which helps in designing new drugs.

  8. Style Transfer: VAEs can work in style transfer. They learn to separate content and style. This allows us to create images with certain artistic styles.

  9. 3D Object Generation: VAEs can also generate 3D shapes from their learned representations. This can help in virtual reality and gaming.

  10. Music Generation: VAEs can learn about musical notes and rhythms. This helps in making new music compositions.

These examples show how useful Variational Autoencoders are in many areas. They can learn complex data patterns and create valuable outputs. For more insights into generative models, you can check what are the key differences between generative and discriminative models.

Best Practices for Implementing a Variational Autoencoder VAE

When we implement a Variational Autoencoder (VAE), we want to follow some best practices. These can help us with performance, stability, and understanding. Here are some key points to consider:

  1. Data Preprocessing:
    • We should normalize our input data. This means making sure it has a zero mean and unit variance. This helps the VAE learn better.
    • We can also use techniques like PCA to reduce noise in big datasets before we start training.
  2. Model Architecture:
    • It is good to use deeper networks for both the encoder and decoder. This helps in capturing complex patterns. But we need to be careful of overfitting. We can use dropout layers if needed.
    • We can add residual connections. This helps with the gradient flow, especially in deeper networks.
  3. Latent Space Configuration:
    • We can try different sizes for the latent space. A smaller space may lose some information. A bigger space might cause overfitting.
    • We can use latent space interpolation to visualize and understand what the model has learned.
  4. Loss Function Tuning:
    • It is important to check the balance between reconstruction loss and KL divergence. We can adjust weights if one term is too strong.
    • We can use dynamic weights to balance these two parts during training.
  5. Training Strategies:
    • We can start with a low learning rate. Then we can gradually increase it. We can use learning rate schedules or optimizers like Adam or RMSprop.
    • Early stopping is helpful. We can stop training when the validation loss stops improving to avoid overfitting.
  6. Regularization Techniques:
    • We can apply dropout or batch normalization. This helps in stabilizing training and improving general results.
    • If we have a recurrent VAE model, we can use variational dropout.
  7. Hyperparameter Optimization:
    • We should try different activation functions like ReLU, Leaky ReLU, or ELU. This helps us find the best performance.
    • We can tune hyperparameters like batch size, latent dimension, number of epochs, and learning rate through cross-validation.
  8. Evaluation and Visualization:
    • We can use metrics like Fréchet Inception Distance (FID) or Inception Score (IS) to check the quality of generated samples.
    • Visualizing the latent space is useful. It helps us see how well the VAE has structured the data.

Example Code Snippet

Here is a simple way to implement a VAE in TensorFlow/Keras:

import tensorflow as tf
from tensorflow.keras import layers, models

# Encoder
def build_encoder(latent_dim):
    inputs = layers.Input(shape=(original_dim,))
    h = layers.Dense(128, activation='relu')(inputs)
    z_mean = layers.Dense(latent_dim)(h)
    z_log_var = layers.Dense(latent_dim)(h)
    return models.Model(inputs, [z_mean, z_log_var])

# Decoder
def build_decoder(latent_dim):
    latent_inputs = layers.Input(shape=(latent_dim,))
    h = layers.Dense(128, activation='relu')(latent_inputs)
    outputs = layers.Dense(original_dim, activation='sigmoid')(h)
    return models.Model(latent_inputs, outputs)

# VAE Model
def build_vae(encoder, decoder):
    z_mean, z_log_var = encoder(inputs)
    z = reparameterize(z_mean, z_log_var)  # Implement reparameterization trick
    reconstructed = decoder(z)
    vae = models.Model(inputs, reconstructed)
    return vae

# Compile and Train
vae.compile(optimizer='adam', loss='binary_crossentropy')
vae.fit(x_train, x_train, epochs=50, batch_size=128)

By following these best practices for a Variational Autoencoder (VAE), we can make the model better at generating high-quality results. For more information on generative models, we can check out this guide on generative AI.

Frequently Asked Questions

1. What are Variational Autoencoders (VAEs) used for?

We use Variational Autoencoders (VAEs) for generative tasks. They help us create new data that looks like our training data. VAEs work well in areas like image generation, semi-supervised learning, and finding unusual patterns. They learn complex data distributions. So, they improve our work in artificial intelligence. This makes VAEs important in fields like computer vision and natural language processing.

2. How do Variational Autoencoders differ from traditional Autoencoders?

VAEs and traditional Autoencoders are different mainly in how they handle latent space. VAEs use a probabilistic approach. They learn distributions for latent variables instead of fixed ones. This allows us to generate new samples by taking from the learned distribution. On the other hand, traditional Autoencoders focus on just reconstructing input data. They do not create new data. Because of this, VAEs are better for generative tasks.

3. What is the loss function used in Variational Autoencoders?

The loss function in VAEs has two parts: reconstruction loss and Kullback-Leibler (KL) divergence. The reconstruction loss tells us how well the VAE can rebuild the input data. The KL divergence measures the difference between the learned latent variable distribution and a prior distribution, which is often Gaussian. This mix helps the model create accurate outputs while keeping a good structure in latent space. This way, we can generate data effectively.

4. Can Variational Autoencoders be used for anomaly detection?

Yes, VAEs are good for anomaly detection. When we train them on normal data, they learn to recreate usual patterns. When we check new data that does not fit the learned patterns, the VAE has trouble reconstructing it. This shows us possible anomalies. We can use the reconstruction error to find outliers. This makes VAEs a helpful tool in many areas, like fraud detection and monitoring industries.

5. What are some practical examples of Variational Autoencoders in action?

Variational Autoencoders have many real-life uses. For example, in image generation, VAEs can make realistic images based on what they learned from a dataset. They also help in natural language processing by generating text or changing sentences. In medical imaging, VAEs improve image quality or find problems. This shows how important VAEs are in generative AI. For more details, you can check real-life applications of generative AI.