This is a minimal implementation of variational autoencoder (VAE) in pytorch.
I have tried to make this code as close to the underlaying math as possible, while retaining maximum flexibility. Because of this, it should be very easy to change the architecture of encoder/decoder, operate on other modalities of data like images instead of flattened vectors, modify the loss functions, etc.
A VAE is a generative model that can approximate complex distributions using only samples from that distribution. Using variational inference methods, we can leverage a dataset of samples to fit the parameters of a latent variable model. Latent variable models are very powerful as they can approximate many complex distributions. Therefore, they have become a popular choice for learning complex, high-dimensional data distributions, such as images, trajectories, etc.
In vae.py you can find a very minimal and clean implementation of a VAE. The code is written such that you can easily adapt it to your use cases without too much refactoring. It contains three main classes: Vanilla_Encoder, Vanilla_Decoder and, Vanilla_VAE.
The Vanilla_Encoder and Vanilla_Decoder classes are regular nn.Modules that implement the encoder and decoder of a VAE. Noticeably, they each output a torch.distributions.Distribution object, in line with the theory of variational autoencoders.
A VAE's loss function (known as - evidence lower bound or ELBO) is comprised of two main components: a reconstruction loss and a KL-divergence.
Given an input sample
Note 1: In many VAE tutorials, the reconstruction loss is written as an MSE loss between the original data point Vanilla_Decoder class so that it outputs the distribution that you like. The rest of the code does not need any changes.
The same is true for the approximate posterior Vanilla_Encoder class so that it computes and outputs the appropriate distribution.
Note 2: The prior distribution kl_loss method of Vanilla_VAE.
Note 3: Vanilla_VAE class provides two method kl_loss() and reconstruction_loss() to compute each part of the loss. In many cases, the KL divergence between kl_loss() you can choose between a monte-carlo estimate which is an unbiased estimator of KL or another estimator proposed by John Schulman which is biased, but has considerably lower variance. (see here for more information about these estimators)
By passing a batch of data to the forward method of a Vanilla_VAE object, you get a scalar loss and a dictionary containing additional information. Training the VAE is therefore very easy. You can loop over batches of data and update the VAE:
for data in data_loader:
loss, info = vae(data)
vae.optimizer.zero_grad()
loss.backward()
vae.optimizer.step()Given a batch of data, If you want to get their latent representation, you can use the following line of code:
latent_representation = vae.encoder(data).meanAnd if you want to get their reconstructions (e.g., for visualization), you can use
reconstructed = vae.decoder(latent).mean