How to Construct Autoencoders and VAEs for Image Compression: A Complete Guide

Autoencoders compress images by learning compact latent representations through encoder-decoder neural networks, while Variational Autoencoders (VAEs) add probabilistic sampling to create smooth, generative latent spaces suitable for robust compression.

This guide explains how to build autoencoders and VAEs for image compression using the curriculum from the microsoft/AI-For-Beginners repository. You will learn the architectural components, implementation differences between deterministic and probabilistic models, and practical code patterns for training these networks on image datasets.

Core Architecture of Image Compression Autoencoders

An autoencoder consists of three mandatory components that work together to compress and reconstruct images. When the latent vector dimensionality is significantly smaller than the input, the network functions as a learned compressor.

The Encoder Network

The encoder maps high-dimensional images to low-dimensional latent vectors using convolutional layers followed by dense projections. In the TensorFlow implementation from lessons/4-ComputerVision/09-Autoencoders/AutoencodersTF.ipynb, the encoder uses stacked Conv2D layers with MaxPooling2D to reduce spatial dimensions before flattening to a dense latent representation.

The Bottleneck (Latent Space)

The bottleneck holds the compressed information. In a vanilla autoencoder, this is a deterministic vector produced by a dense layer. In a Variational Autoencoder (VAE), the bottleneck represents a probability distribution with two dense heads outputting z_mean (μ) and z_log_var (log σ²), enabling probabilistic sampling.

The Decoder Network

The decoder reconstructs images from latent vectors using transposed convolutions or upsampling layers. As implemented in the PyTorch notebook AutoEncodersPyTorch.ipynb, the decoder typically uses ConvTranspose2d layers to upsample the latent representation back to the original image dimensions, ending with a sigmoid activation for pixel values.

Deterministic vs. Variational: Key Differences

Deterministic Autoencoder Structure

A deterministic autoencoder learns a direct mapping from input to latent space. The model minimizes reconstruction loss (MSE or binary cross-entropy) to force the decoder to reproduce the input from the compressed code. This architecture excels at compression but may produce discontinuous latent spaces unsuitable for generation.

VAE Probabilistic Framework

Variational Autoencoders treat the latent space probabilistically. The encoder outputs distribution parameters (mean and log-variance) rather than fixed values. The reparameterization trick samples z = μ + σ * ε where ε ∼ N(0, I), allowing gradients to flow through the stochastic node. The loss function combines reconstruction loss with KL-divergence, regularizing the latent distribution toward a standard normal.

TensorFlow/Keras Implementation

The AutoencodersTF.ipynb notebook provides a complete deterministic autoencoder implementation. Below is the core architecture:

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

# Encoder

inputs = layers.Input(shape=(28, 28, 1))
x = layers.Conv2D(32, 3, activation='relu', padding='same')(inputs)
x = layers.MaxPooling2D(2, padding='same')(x)
x = layers.Conv2D(64, 3, activation='relu', padding='same')(x)
x = layers.MaxPooling2D(2, padding='same')(x)
shape_before_flatten = tf.keras.backend.int_shape(x)
x = layers.Flatten()(x)
latent = layers.Dense(32, name='latent_vector')(x)   # 32-dim compression

# Decoder

x = layers.Dense(tf.math.reduce_prod(shape_before_flatten[1:]))(latent)
x = layers.Reshape(shape_before_flatten[1:])(x)
x = layers.Conv2DTranspose(64, 3, strides=2, activation='relu', padding='same')(x)
x = layers.Conv2DTranspose(32, 3, strides=2, activation='relu', padding='same')(x)
outputs = layers.Conv2D(1, 3, activation='sigmoid', padding='same')(x)

autoencoder = models.Model(inputs, outputs, name='autoencoder')
autoencoder.compile(optimizer='adam', loss='mse')

Training requires passing the input as both x and y (self-supervised learning):

autoencoder.fit(x_train, x_train,
                epochs=30,
                batch_size=128,
                validation_data=(x_val, x_val))

PyTorch VAE Implementation

For probabilistic compression, the AutoEncodersPyTorch.ipynb notebook demonstrates a VAE with the reparameterization trick:

import torch, torch.nn as nn, torch.nn.functional as F

class VAE(nn.Module):
    def __init__(self, latent_dim=20):
        super().__init__()
        # Encoder

        self.enc_conv1 = nn.Conv2d(1, 32, 3, stride=2, padding=1)   # 28→14

        self.enc_conv2 = nn.Conv2d(32, 64, 3, stride=2, padding=1)  # 14→7

        self.fc_mu   = nn.Linear(64*7*7, latent_dim)
        self.fc_logvar = nn.Linear(64*7*7, latent_dim)

        # Decoder

        self.fc_dec  = nn.Linear(latent_dim, 64*7*7)
        self.dec_conv1 = nn.ConvTranspose2d(64, 32, 3, stride=2, padding=1, output_padding=1)
        self.dec_conv2 = nn.ConvTranspose2d(32, 1, 3, stride=2, padding=1, output_padding=1)

    def encode(self, x):
        x = F.relu(self.enc_conv1(x))
        x = F.relu(self.enc_conv2(x))
        x = x.view(x.size(0), -1)
        return self.fc_mu(x), self.fc_logvar(x)

    def reparameterize(self, mu, logvar):
        std = torch.exp(0.5*logvar)
        eps = torch.randn_like(std)
        return mu + eps*std

    def decode(self, z):
        x = F.relu(self.fc_dec(z))
        x = x.view(-1, 64, 7, 7)
        x = F.relu(self.dec_conv1(x))
        x = torch.sigmoid(self.dec_conv2(x))
        return x

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparameterize(mu, logvar)
        return self.decode(z), mu, logvar

The VAE requires a custom loss function combining reconstruction and KL-divergence:

def vae_loss(recon_x, x, mu, logvar):
    recon_loss = F.mse_loss(recon_x, x, reduction='sum')
    kl = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
    return recon_loss + kl

Compression Workflow

To use these models as practical image compressors:

  1. Encode: Pass the image through the encoder to obtain the latent vector (32 floats ≈ 128 bytes).
  2. Store: Save the latent representation (e.g., as .npy files or quantized integers) instead of the full pixel array (784 bytes for 28×28 grayscale).
  3. Decode: Feed the stored latent vector through the decoder to reconstruct the image when needed.

This achieves order-of-magnitude compression ratios while maintaining perceptual quality through end-to-end optimization.

Summary

  • Autoencoders compress images by learning encoder-decoder mappings that reduce dimensionality through convolutional bottlenecks.
  • Deterministic autoencoders use fixed latent vectors and minimize reconstruction loss (MSE/binary cross-entropy).
  • Variational Autoencoders introduce probabilistic latent spaces with μ and log-variance outputs, enabling smooth interpolation via the reparameterization trick.
  • The microsoft/AI-For-Beginners repository provides complete implementations in both TensorFlow (AutoencodersTF.ipynb) and PyTorch (AutoEncodersPyTorch.ipynb).
  • Practical compression involves encoding images to compact latent vectors, storing the low-dimensional representations, and decoding on demand.

Frequently Asked Questions

What is the difference between an autoencoder and a VAE for image compression?

A standard autoencoder learns a deterministic mapping to a fixed latent vector, optimizing purely for reconstruction accuracy. A VAE learns a probability distribution over the latent space, adding a KL-divergence regularization term that forces the distribution toward a standard normal. This creates a smoother, more continuous latent space that is robust to perturbations and suitable for generative tasks, though it may sacrifice some reconstruction fidelity compared to deterministic models.

How does the reparameterization trick work in VAEs?

The reparameterization trick enables gradient descent through the stochastic sampling process. Instead of sampling directly from the latent distribution (which would block gradients), the model samples ε from a standard normal distribution and computes z = μ + σ * ε. This moves the randomness to an input variable while keeping the path from parameters to z differentiable, allowing backpropagation to update the encoder weights.

What loss functions are used to train autoencoders for compression?

Deterministic autoencoders typically use Mean Squared Error (MSE) or Binary Cross-Entropy between the input and reconstructed output. VAEs use a composite loss function: the reconstruction loss (MSE) plus the KL-divergence between the learned latent distribution and a prior (usually standard normal). The KL term acts as a regularizer, preventing overfitting and ensuring the latent space maintains desirable geometric properties.

How much compression can autoencoders achieve compared to traditional codecs?

Autoencoders can achieve significant compression ratios depending on the latent dimensionality. For example, compressing a 28×28 grayscale image (784 bytes) to a 32-dimensional latent vector (128 bytes) yields a 6:1 compression ratio. However, learned codecs differ from traditional methods like JPEG because they optimize for reconstruction fidelity on specific datasets rather than general-purpose entropy coding, often producing better perceptual quality at low bit-rates for in-distribution images but potentially poorer generalization to out-of-distribution data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →