EasyVAE

VAE Decoder

Auto-Encoding Variational Bayes (VAE)

Easy

Problem

Implement a Bernoulli variational-autoencoder decoder. Project each latent vector into the data width, add the supplied bias, and apply a sigmoid so every reconstructed feature lies between zero and one.

\widehat{X} = \sigma(ZW_{\mathrm{dec}} + b_{\mathrm{dec}})

\sigma(a) = \frac{1}{1+e^{-a}}

Here, Z \in \mathbb{R}^{B \times L} contains latent samples, W_{\mathrm{dec}} \in \mathbb{R}^{L \times D} is the decoder weight matrix, and b_{\mathrm{dec}} \in \mathbb{R}^{D} is the decoder bias. Return \widehat{X} as a float64 NumPy array with shape (B,D).

Theory

The decoder is the generative half of a Variational Autoencoder. Given a latent code z sampled from the latent space, the decoder maps it back to the data space to produce a reconstruction \hat{x}. In Kingma and Welling (2014), this is the learned conditional distribution $$, the component that gives VAEs their generative power.


What It Is

The VAE decoder takes a low-dimensional latent vector z and transforms it into a reconstruction \hat{x} that lives in the original data space. If the input data has dimensionality D (for example, a flattened 28x28 MNIST image has $ = 784$) and the latent space has dimensionality L (typically much smaller, such as L = 2 or L = 10), the decoder is a function f: \mathbb{R}^L \to \mathbb{R}^D that expands the compressed representation back to full size.

In probabilistic terms, the decoder parameterizes the conditional distribution p_\theta(x|z). Given a specific latent code z, it outputs the parameters of a distribution over data x. For continuous data with a Gaussian output assumption, the decoder outputs the mean \mu_{x|z} of a Gaussian, and the reconstruction loss becomes the mean squared error between the decoder output and the true data point.

This problem implements the simplest possible decoder: a single linear transformation with no activation function. The paper uses deeper, nonlinear networks, but the linear version isolates the core concept of mapping from latent space to data space without the complexity of hidden layers.


Key Equations

The Reconstruction

The decoder computes the reconstruction as a matrix multiplication between the latent vector and a weight matrix:

\hat{x} = z \cdot W

where z \in \mathbb{R}^{B \times L} is the batch of latent vectors, W \in \mathbb{R}^{L \times D} is the decoder weight matrix, and \hat{x} \in \mathbb{R}^{B \times D} is the batch of reconstructed outputs.

The Generative Model p_\theta(x|z)

In the probabilistic framework of Kingma and Welling (2014), the decoder defines a conditional likelihood. For continuous data with a Gaussian assumption:

p_\theta(x|z) = \mathcal{N}(x; \mu_\theta(z), \sigma^2 I)

where \mu_\theta(z) = z \cdot W is the decoder's output and \sigma^2 is a fixed variance. The negative log-likelihood of this Gaussian reduces to the mean squared error:

-\log p_\theta(x|z) \propto \|x - \hat{x}\|^2 = \|x - zW\|^2

Minimizing MSE is equivalent to maximizing the log-likelihood under the Gaussian output assumption. This is why MSE loss is the standard reconstruction loss for VAEs with continuous data.


The Generative Model

The decoder is where the "generative" in "generative model" comes from. The encoder compresses data into latent codes, but the decoder is the component that can produce new data. Once trained, the decoder can take any point z in the latent space and produce a plausible data point \hat{x}.

The generative process works in two stages. First, sample a latent code from the prior distribution z \sim p(z) = \mathcal{N}(0, I). Second, pass this sampled z through the decoder to get \hat{x} = f_\theta(z). The quality of generation depends entirely on how well the decoder has learned to map from the latent space to the data space.

During training, the decoder receives z values produced by the encoder via the reparameterization trick: z = \mu + \epsilon \odot \sigma where \epsilon \sim \mathcal{N}(0, I). The KL divergence term in the ELBO loss regularizes the encoder's output distribution to stay close to \mathcal{N}(0, I), ensuring that the latent space is structured in a way that the decoder can generalize to unseen z values at generation time.


Why Linear

This problem uses a single linear transformation \hat{x} = zW with no activation function and no hidden layers. This is a deliberate simplification that isolates the decoder's essential role: spatial expansion from latent dimension to data dimension.

A linear decoder can only learn linear relationships between the latent space and the data space. If the true data distribution requires nonlinear mappings (as virtually all real-world data does), a linear decoder will produce blurry, low-quality reconstructions. It captures principal directions of variation, similar to PCA, but cannot model curved manifolds or sharp transitions.

The original paper uses multi-layer neural networks with nonlinear activations for both the encoder and decoder. In the MNIST experiments, Kingma and Welling use a 500-unit hidden layer with tanh activation followed by a sigmoid output layer. The sigmoid constrains outputs to [0, 1], matching pixel intensity range. The linear-only version here removes these layers to focus on the core latent-to-data mapping.


Decoder as Inverse of Encoder

The encoder compresses data from \mathbb{R}^D down to \mathbb{R}^L. The decoder expands from \mathbb{R}^L back to \mathbb{R}^D. These are conceptual inverses, but not mathematical inverses.

If the encoder applies z = xW_{\text{enc}} and the decoder applies \hat{x} = zW_{\text{dec}}, the full reconstruction is \hat{x} = xW_{\text{enc}}W_{\text{dec}}. For \hat{x} = x to hold, we would need W_{\text{enc}}W_{\text{dec}} = I_D. But the product W_{\text{enc}}W_{\text{dec}} \in \mathbb{R}^{D \times D} has rank at most L < D, so perfect reconstruction is impossible when L < D. The decoder finds the best approximation, not an exact inverse.

The encoder and decoder have separate, independently learned weight matrices. Nothing in the training procedure forces W_{\text{dec}} = W_{\text{enc}}^T. Some autoencoder variants (called tied-weight autoencoders) enforce this relationship, but the standard VAE does not. Each matrix is free to learn whatever mapping minimizes the combined reconstruction and KL loss.

The asymmetry extends to the probabilistic interpretation. The encoder approximates the posterior q_\phi(z|x), while the decoder defines the likelihood p_\theta(x|z). The encoder learns to infer latent codes that explain observed data; the decoder learns to generate data that matches observed examples.


The Latent Space

The latent space is the low-dimensional continuous space that the decoder reads from. Its properties directly determine the quality and usefulness of the decoder's output.


Paper Context

Kingma and Welling (2014) introduce the VAE in "Auto-Encoding Variational Bayes." The decoder (generative model) is one of three core components, alongside the encoder (recognition model) and the reparameterization trick.

The paper states: "The generative model p_\theta(x|z), also called the decoder, learns to reconstruct the input from the latent representation." The decoder outputs the parameters of a distribution over x, and the reconstruction loss derives from the negative log-likelihood of this distribution.

The decoder appears in the ELBO objective:

\mathcal{L}(\theta, \phi; x) = \mathbb{E}_{q_\phi(z|x)}[\log p_\theta(x|z)] - D_{KL}(q_\phi(z|x) \| p(z))

The first term is the expected reconstruction log-likelihood, which the decoder directly controls. Maximizing this term pushes the decoder to produce reconstructions that match the input. The decoder's weights \theta only appear in the first term; the second term regularizes the encoder.

The paper emphasizes that "the true posterior p_\theta(z|x) is intractable" when the decoder is a neural network with nonlinear hidden layers. This intractability motivates the variational approach: computing the exact posterior requires integrating over all possible z values weighted by the decoder likelihood, and this integral has no closed-form solution for nonlinear decoders.

When the decoder output is treated as the mean of a Gaussian p_\theta(x|z) = \mathcal{N}(x; \mu_\theta(z), I), the log-likelihood simplifies to a negative squared error. For binary data like binarized MNIST, a Bernoulli likelihood with sigmoid output and binary cross-entropy loss is used instead.


Numerical Example

Consider a decoder with latent dimension L = 2 and output dimension D = 4. The weight matrix W has shape (2, 4).

Setup

W = \begin{bmatrix} 0.3 & -0.1 & 0.5 & 0.2 \\ -0.2 & 0.4 & 0.1 & -0.3 \end{bmatrix}

and a single latent vector z = \begin{bmatrix} 1.0 & -0.5 \end{bmatrix}.

Computing the Reconstruction

The reconstruction is \hat{x} = z \cdot W, a matrix multiplication of shape (1, 2) \times (2, 4) = (1, 4). For each output dimension j, compute the dot product of z with column j of W:

\hat{x}_0 = 1.0 \times 0.3 + (-0.5) \times (-0.2) = 0.3 + 0.1 = 0.4

\hat{x}_1 = 1.0 \times (-0.1) + (-0.5) \times 0.4 = -0.1 - 0.2 = -0.3

\hat{x}_2 = 1.0 \times 0.5 + (-0.5) \times 0.1 = 0.5 - 0.05 = 0.45

\hat{x}_3 = 1.0 \times 0.2 + (-0.5) \times (-0.3) = 0.2 + 0.15 = 0.35

Result

\hat{x} = \begin{bmatrix} 0.4 & -0.3 & 0.45 & 0.35 \end{bmatrix}

A 2-dimensional latent vector has been expanded to a 4-dimensional reconstruction. Each output value is a linear combination of the two latent dimensions, weighted by the corresponding column of W.

Batch Example

With two latent vectors z = \begin{bmatrix} 1.0 & -0.5 \\ 0.0 & 1.0 \end{bmatrix}, the first row produces [0.4, -0.3, 0.45, 0.35] as above. The second row with z = [0, 1] outputs [-0.2, 0.4, 0.1, -0.3], which is simply the second row of W. Each row of W acts as a basis vector in the data space, and the latent coordinates z specify how to combine these basis vectors.


Generation

The entire point of the VAE framework is generation: producing new data points that resemble the training distribution but are not copies of any training example. The decoder makes this possible.

The Generative Process

  1. Sample from the prior: draw z \sim \mathcal{N}(0, I) in the latent space

  2. Decode: compute \hat{x} = z \cdot W

  3. Output: \hat{x} is the generated data point

No encoder is needed at generation time. The encoder's role is purely during training, where it maps data points to latent codes for the decoder to learn from. Once trained, the decoder operates independently with random z samples.

Why the Prior Matters

The choice of prior p(z) = \mathcal{N}(0, I) is critical. During training, the KL divergence term pushes the encoder's output distribution toward \mathcal{N}(0, I), so the decoder sees approximately standard-normally distributed z values. At generation time, sampling z \sim \mathcal{N}(0, I) produces inputs from the same distribution the decoder was trained on, ensuring reasonable outputs.

If the encoder were allowed to map training data to arbitrary, tightly clustered regions (which would happen without the KL term), random samples from \mathcal{N}(0, I) would land in regions the decoder has never seen, producing nonsensical output. The KL regularization ensures the decoder's training distribution matches its generation distribution.


Pitfalls


Examples

Example 1

Input
z = [[0.5,-0.3],[1,0.2]], W_dec = [[0.4,-0.2,0.1],[0.3,0.5,-0.4]], b_dec = [0.1,-0.1,0.2]
Output
[[0.552308,0.413382,0.591459],[0.636453,0.450166,0.554779]]
Explanation
The affine decoder logits are converted into Bernoulli feature probabilities by sigmoid.

Example 2

Input
z = [[-1,0.5],[0,2]], W_dec = [[0.6],[-0.2]], b_dec = [0.05]
Output
[[0.34299],[0.413382]]

Example 3

Input
z = [[0,0,0]], W_dec = [[0.1,0.2],[-0.3,0.4],[0.5,-0.2]], b_dec = [-0.4,0.7]
Output
[[0.401312,0.668188]]

Hints

  1. Broadcast b_dec across the rows of z @ W_dec.
  2. Apply np.exp inside the sigmoid expression.

Requirements

Constraints

Starter Code

import numpy as np

def vae_decoder(z: np.ndarray, W_dec: np.ndarray, b_dec: np.ndarray) -> np.ndarray:
    """
    Returns the float64 reconstruction with shape (B, D).
    """
    pass

Test Cases

CaseMatches
Two reconstructionspublic
Single output coordinatepublic
Zero latent vectorpublic