Course contents 02 / 33

02 / 33 · Foundations

Multivariate Gaussian distributions

Mean vector, covariance matrix, the bivariate density, and sampling any Gaussian from standard noise with a matrix square root, with the PyTorch parametrization used in the first fitting experiments.

On this page 7

A generative model is a distribution we can sample from and, ideally, evaluate (What generative modeling is). Before neural networks enter, the course works with the simplest distribution that is still interesting in several dimensions: the multivariate Gaussian. It is the running example of the first notes, where it is both the target we draw data from and the model we fit to that data. It also comes back in almost every later model: the prior of a VAE, the noise of a diffusion model, the base distribution of a normalizing flow and the feature statistics behind FID.

The opening block of the course has three steps:

  1. generate data from a known distribution (this note);
  2. measure how far two distributions are from each other (Entropy, cross-entropy and KL divergence);
  3. fit a parametrized 2-D distribution to the data by gradient descent on such a distance (Fitting distributions with KL).

Notation. Capital letters such as XX and ZZ denote random vectors; bold lowercase letters such as x\mathbf x and z\mathbf z denote points of the plane, the values those vectors take. Bold capitals such as Σ\boldsymbol\Sigma, A\mathbf A and L\mathbf L are matrices, I\mathbf I is the identity matrix, A⊤\mathbf A^{\top} the transpose and ∣Σ∣|\boldsymbol\Sigma| the determinant. We write X∼N(μ,σ2)X \sim \mathcal N(\mu, \sigma^2) for a univariate normal with mean μ\mu and variance σ2\sigma^2, and X∼N(μ,Σ)X \sim \mathcal N(\boldsymbol\mu, \boldsymbol\Sigma) for a multivariate normal with mean vector μ\boldsymbol\mu and covariance matrix Σ\boldsymbol\Sigma: the second argument is always the variance or covariance, never the standard deviation. Densities are written pp and qq; from the PyTorch section on, PP is the fixed target distribution with density pp and QQ the trainable model with density qq.

The covariance matrix#

Take a random column vector in two dimensions and its mean:

X=(X1X2),μ=E[X]=(μ1μ2).X = \begin{pmatrix} X_1 \\ X_2 \end{pmatrix}, \qquad \boldsymbol{\mu} = \mathbb E[X] = \begin{pmatrix} \mu_{1} \\ \mu_{2} \end{pmatrix}.

Its spread is described by the 2×22 \times 2 variance–covariance matrix Σ=E[(X−μ)(X−μ)⊤]\boldsymbol\Sigma = \mathbb E\bigl[(X-\boldsymbol\mu)(X-\boldsymbol\mu)^{\top}\bigr]:

Σ=(Var⁡(X1)Cov⁡(X1,X2)Cov⁡(X1,X2)Var⁡(X2))=(σ12ρ σ1σ2ρ σ1σ2σ22),\begin{aligned} \boldsymbol{\Sigma} &= \begin{pmatrix} \operatorname{Var}(X_1) & \operatorname{Cov}(X_1, X_2) \\ \operatorname{Cov}(X_1, X_2) & \operatorname{Var}(X_2) \end{pmatrix} \\[4pt] &= \begin{pmatrix} \sigma_{1}^2 & \rho\,\sigma_{1}\sigma_{2} \\ \rho\,\sigma_{1}\sigma_{2} & \sigma_{2}^2 \end{pmatrix}, \end{aligned}

where ρ\rho is the correlation coefficient between X1X_1 and X2X_2. The matrix is symmetric, because Cov⁡(X1,X2)=Cov⁡(X2,X1)\operatorname{Cov}(X_1, X_2) = \operatorname{Cov}(X_2, X_1). It is also positive semidefinite: for any fixed vector a\mathbf a the variance Var⁡(a⊤X)=a⊤Σ a\operatorname{Var}(\mathbf a^{\top}X) = \mathbf a^{\top}\boldsymbol\Sigma\,\mathbf a cannot be negative. Three free numbers hold every variance and covariance of the vector.

The example used throughout this note is a Gaussian centred at the origin with

μ=(00),Σ=(10.50.51):\boldsymbol{\mu} = \begin{pmatrix} 0 \\ 0 \end{pmatrix}, \qquad \boldsymbol{\Sigma} = \begin{pmatrix} 1 & 0.5 \\ 0.5 & 1 \end{pmatrix}:

unit variances and correlation ρ=0.5\rho = 0.5, so the cloud leans along the diagonal x1=x2x_1 = x_2.

Why a full matrix and not just two variances?#

Two variances describe how far the cloud spreads along each coordinate axis. That is enough only when the coordinates are uncorrelated, and then the cloud is stretched along the axes. The off-diagonal entry records what the variances cannot: how the coordinates move together. It tilts the cloud.

In Figure 1 the right cloud is the left one rotated by 45°. The two clouds have the same eigenvalues (1.5 and 0.5), but both axis variances of the right cloud equal 1. Those are exactly the axis variances of the round N(0,I)\mathcal N(\mathbf 0, \mathbf I) cloud, so two variances alone cannot tell the tilted cloud from a circle. Only Cov⁡(X1,X2)=0.5\operatorname{Cov}(X_1, X_2) = 0.5 records the tilt.

Figure

A diagonal matrix only stretches along the axes; covariance tilts the cloud

Axis-aligned Gaussian cloud next to a tilted, correlated cloudTwo scatter plots of 2-D Gaussian samples with their 1-sigma and 2-sigma ellipses. The left cloud has covariance diag(1.5, 0.5): it is wider than tall and aligned with the axes. Braces under the horizontal axis and beside the vertical axis mark the spreads, each spanning plus and minus one standard deviation and labelled plus or minus the square root of Var(X1) = 1.5 and of Var(X2) = 0.5. The right cloud has covariance with unit variances and covariance 0.5. It leans along the diagonal x1 = x2. Its braces span plus and minus 1 along both axes, because Var(X1) = Var(X2) = 1, the same as for an untilted circle. Arrows along the eigenvectors show the semi-axes of the 1-sigma ellipse, of lengths square root of 1.5 along the diagonal and square root of 0.5 across it.Variances onlyCov(X₁, X₂) = 0x₁x₂1σ2σ±√Var(X₁) = ±√1.5±√Var(X₂)= ±√0.5With covarianceCov(X₁, X₂) = 0.5x₁x₂1σ2σ±√Var(X₁) = ±1±√Var(X₂)= ±1x₁ = x₂√1.5√0.5Axis-aligned Gaussian cloud next to a tilted, correlated cloudTwo scatter plots of 2-D Gaussian samples with their 1-sigma and 2-sigma ellipses. The left cloud has covariance diag(1.5, 0.5): it is wider than tall and aligned with the axes. Braces under the horizontal axis and beside the vertical axis mark the spreads, each spanning plus and minus one standard deviation and labelled plus or minus the square root of Var(X1) = 1.5 and of Var(X2) = 0.5. The right cloud has covariance with unit variances and covariance 0.5. It leans along the diagonal x1 = x2. Its braces span plus and minus 1 along both axes, because Var(X1) = Var(X2) = 1, the same as for an untilted circle. Arrows along the eigenvectors show the semi-axes of the 1-sigma ellipse, of lengths square root of 1.5 along the diagonal and square root of 0.5 across it.Variances onlyCov(X₁, X₂) = 0x₁x₂1σ2σ±√Var(X₁) = ±√1.5±√Var(X₂)= ±√0.5With covarianceCov(X₁, X₂) = 0.5x₁x₂1σ2σ±√Var(X₁) = ±1±√Var(X₂)= ±1x₁ = x₂√1.5√0.5
Both clouds are drawn from the same standard-normal pairs (260, minus the few that fall outside either window). Left: Σ = diag(1.5, 0.5), independent coordinates, so the spread is described by Var(X₁) and Var(X₂) alone. Right: Σ = [[1, 0.5], [0.5, 1]], the running example. Its eigenvalues are 1.5 and 0.5, so it is the left cloud rotated by 45°. Its variances along the axes are both 1, and only the off-diagonal entry records the tilt. Braces span ±1 standard deviation, ±√Var, and dashed guides drop to them from where the 1σ ellipse touches its extremes; solid and dashed curves are the 1σ and 2σ ellipses.

The bivariate density#

When Σ\boldsymbol\Sigma is invertible (positive definite, not just semidefinite), the joint density on the whole plane R2\mathbb R^2 is

p(x)=12π∣Σ∣×exp⁡ ⁣(−12 (x−μ)⊤ Σ−1 (x−μ)),\begin{aligned} p(\mathbf x) &= \frac{1}{2\pi\sqrt{|\boldsymbol\Sigma|}} \\[4pt] &\times \exp\!\left(-\tfrac12\, (\mathbf{x}-\boldsymbol\mu)^{\top}\,\boldsymbol\Sigma^{-1}\,(\mathbf{x}-\boldsymbol\mu)\right), \end{aligned}

where x=(x1,x2)⊤\mathbf x = (x_1, x_2)^{\top}, Σ−1\boldsymbol\Sigma^{-1} is the inverse and ∣Σ∣|\boldsymbol\Sigma| the determinant of the covariance matrix. The shapes in the quadratic form are

(x−μ)⊤⏟1×2  Σ−1⏟2×2  (x−μ)⏟2×1,\underbrace{(\mathbf{x}-\boldsymbol\mu)^{\top}}_{1\times 2}\; \underbrace{\boldsymbol\Sigma^{-1}}_{2\times 2}\; \underbrace{(\mathbf{x}-\boldsymbol\mu)}_{2\times 1},

so the exponent is a scalar. In dd dimensions the normalizing constant becomes (2π)−d/2∣Σ∣−1/2(2\pi)^{-d/2}|\boldsymbol\Sigma|^{-1/2}, and d=2d = 2 gives the expression above (Bishop, 2006, §2.3).

The level sets of the density, where the squared Mahalanobis distance (x−μ)⊤Σ−1(x−μ)(\mathbf{x}-\boldsymbol\mu)^{\top}\boldsymbol\Sigma^{-1}(\mathbf{x}-\boldsymbol\mu) equals k2k^2, are ellipses (Murphy, 2022, §3.2). Figures 1 and 3 draw them as the 1σ1\sigma (k=1k = 1) and 2σ2\sigma (k=2k = 2) contours. The probability inside such an ellipse follows from the map of the sampling section below. Writing X=μ+Σ1/2ZX = \boldsymbol\mu + \boldsymbol\Sigma^{1/2} Z with Z∼N(0,I)Z \sim \mathcal N(\mathbf 0, \mathbf I), and using that Σ1/2\boldsymbol\Sigma^{1/2} is symmetric with Σ1/2Σ−1Σ1/2=I\boldsymbol\Sigma^{1/2}\boldsymbol\Sigma^{-1}\boldsymbol\Sigma^{1/2} = \mathbf I, the squared distance becomes Z⊤Z=Z12+Z22Z^{\top}Z = Z_1^2 + Z_2^2, the squared length of standard noise. The standard density 12πe−r2/2\frac{1}{2\pi}e^{-r^2/2} depends only on the radius rr, so in polar coordinates

Pr⁡(Z12+Z22≤k2)=∫0kr e−r2/2 dr=1−e−k2/2.\begin{aligned} \Pr\bigl(Z_1^2 + Z_2^2 \le k^2\bigr) &= \int_0^{k} r\,e^{-r^2/2}\,dr \\[4pt] &= 1 - e^{-k^2/2}. \end{aligned}

This is the CDF of the χ2\chi^2 distribution with two degrees of freedom, evaluated at k2k^2. So the 1σ1\sigma ellipse holds 1−e−1/2≈39%1 - e^{-1/2} \approx 39\% of the probability and the 2σ2\sigma ellipse 1−e−2≈86%1 - e^{-2} \approx 86\%, not the 68% and 95% of one-dimensional intervals.

For the running example

∣Σ∣=1−0.52=0.75,Σ−1=10.75(1−0.5−0.51)=(4/3−2/3−2/34/3),\begin{aligned} |\boldsymbol\Sigma| &= 1 - 0.5^2 = 0.75, \\[4pt] \boldsymbol\Sigma^{-1} &= \frac{1}{0.75}\begin{pmatrix} 1 & -0.5 \\ -0.5 & 1 \end{pmatrix} \\[4pt] &= \begin{pmatrix} 4/3 & -2/3 \\ -2/3 & 4/3 \end{pmatrix}, \end{aligned}

and the peak density is p(μ)=1/(2π0.75)≈0.184p(\boldsymbol\mu) = 1/(2\pi\sqrt{0.75}) \approx 0.184.

Sampling: from standard noise to any Gaussian#

In short. To sample N(μ,Σ)\mathcal N(\boldsymbol\mu, \boldsymbol\Sigma), draw Z∼N(0,I)Z \sim \mathcal N(\mathbf 0, \mathbf I) and return μ+AZ\boldsymbol\mu + \mathbf A Z for any matrix A\mathbf A with AA⊤=Σ\mathbf A\mathbf A^{\top} = \boldsymbol\Sigma; in code A\mathbf A is the Cholesky factor. The rest of this section builds the square root and proves the recipe twice, by the change of variables and by the moments. Readers who know affine maps of Gaussians from the Probability course can treat those two proofs as optional and go straight to the worked example.

In one dimension, standardization and its inverse are

Z=X−μσ∼N(0,1)for X∼N(μ,σ2),X=μ+σZ∼N(μ,σ2)for Z∼N(0,1).\begin{aligned} Z &= \frac{X - \mu}{\sigma} \sim \mathcal N(0, 1) \quad \text{for } X \sim \mathcal N(\mu, \sigma^2),\\[4pt] X &= \mu + \sigma Z \sim \mathcal N(\mu, \sigma^2) \quad \text{for } Z \sim \mathcal N(0, 1). \end{aligned}

So to sample any normal distribution it is enough to sample the standard one, stretch by σ\sigma and shift by μ.\mu.

Figure

Standardization and its inverse in one dimension

Standard normal density and a shifted, stretched normal densityTwo bell curves on one axis. The taller grey curve is the standard normal Z ~ N(0, 1), centred at 0. The lower, wider red curve is X ~ N(2, 1.5²), centred at mu = 2 with a horizontal arrow of length sigma = 1.5 drawn at the inflection height. The point z = 1 on the axis is connected by a curved arrow labelled X = mu + sigma Z to the point x = 3.5, and a second arrow below the axis labelled Z = (X − mu) / sigma goes back.−3−2−101234567Z ~ N(0, 1)X ~ N(μ, σ²)μ = 2, σ = 1.5μσX = μ + σZZ = (X − μ) / σStandard normal density and a shifted, stretched normal densityTwo bell curves on one axis. The taller grey curve is the standard normal Z ~ N(0, 1), centred at 0. The lower, wider red curve is X ~ N(2, 1.5²), centred at mu = 2 with a horizontal arrow of length sigma = 1.5 drawn at the inflection height. The point z = 1 on the axis is connected by a curved arrow labelled X = mu + sigma Z to the point x = 3.5, and a second arrow below the axis labelled Z = (X − mu) / sigma goes back.−20246Z ~ N(0, 1)X ~ N(μ, σ²)μ = 2, σ = 1.5μσX = μ + σZZ = (X − μ) / σ
The point z = 1 of the standard normal maps to x = μ + σz = 3.5 for μ = 2, σ = 1.5; dividing (x − μ) by σ maps it back. Both points sit one standard deviation above their own mean.

The bivariate case needs a matrix that plays the role of σ\sigma.

Spectral decomposition. By the spectral theorem, every real symmetric matrix, and in particular every covariance matrix, can be written as

Σ=U Λ U⊤,\boldsymbol\Sigma = \mathbf U\,\boldsymbol\Lambda\,\mathbf U^{\top},

where Λ\boldsymbol\Lambda is diagonal with the eigenvalues of Σ\boldsymbol\Sigma and the columns of the orthogonal matrix U\mathbf U are the matching normalized eigenvectors. The eigenvalues are non-negative because Σ\boldsymbol\Sigma is positive semidefinite, so we can define the matrix square root

Σ1/2=U Λ1/2 U⊤,\boldsymbol\Sigma^{1/2} = \mathbf U\,\boldsymbol\Lambda^{1/2}\,\mathbf U^{\top},

where Λ1/2\boldsymbol\Lambda^{1/2} holds the square roots of the eigenvalues. The root is itself symmetric, because (UΛ1/2U⊤)⊤=UΛ1/2U⊤(\mathbf U\boldsymbol\Lambda^{1/2}\mathbf U^{\top})^{\top} = \mathbf U\boldsymbol\Lambda^{1/2}\mathbf U^{\top}. Since U⊤U=I\mathbf U^{\top}\mathbf U = \mathbf I, we get Σ1/2Σ1/2=UΛU⊤=Σ\boldsymbol\Sigma^{1/2}\boldsymbol\Sigma^{1/2} = \mathbf U\boldsymbol\Lambda\mathbf U^{\top} = \boldsymbol\Sigma.

Transformation. If Z∼N(0,I)Z \sim \mathcal N(\mathbf 0, \mathbf I), then

X=μ+Σ1/2Z∼N(μ,Σ).X = \boldsymbol\mu + \boldsymbol\Sigma^{1/2} Z \sim \mathcal N(\boldsymbol\mu, \boldsymbol\Sigma).

More generally, any square matrix A\mathbf A with AA⊤=Σ\mathbf A\mathbf A^{\top} = \boldsymbol\Sigma works; Σ1/2\boldsymbol\Sigma^{1/2} is one, because it is symmetric and squares to Σ\boldsymbol\Sigma. For positive definite Σ\boldsymbol\Sigma such an A\mathbf A is invertible, since (det⁡A)2=∣Σ∣>0(\det\mathbf A)^2 = |\boldsymbol\Sigma| > 0, and the change-of-variables formula gives the density of X=μ+AZX = \boldsymbol\mu + \mathbf A Z (Murphy, 2022, §2.8). We substitute z=A−1(x−μ)\mathbf z = \mathbf A^{-1}(\mathbf x - \boldsymbol\mu) into the standard density 12πe−z⊤z/2\frac{1}{2\pi}e^{-\mathbf z^{\top}\mathbf z/2} and divide by ∣det⁡A∣=∣Σ∣1/2|\det\mathbf A| = |\boldsymbol\Sigma|^{1/2}:

p(x)=12π ∣det⁡A∣exp⁡ ⁣(−12 z⊤z)=12π∣Σ∣×exp⁡ ⁣(−12 (x−μ)⊤ Σ−1 (x−μ)),\begin{aligned} p(\mathbf x) &= \frac{1}{2\pi\,|\det\mathbf A|}\exp\!\left(-\tfrac12\,\mathbf z^{\top}\mathbf z\right) \\[4pt] &= \frac{1}{2\pi\sqrt{|\boldsymbol\Sigma|}} \\[4pt] &\times \exp\!\left(-\tfrac12\, (\mathbf{x}-\boldsymbol\mu)^{\top}\,\boldsymbol\Sigma^{-1}\,(\mathbf{x}-\boldsymbol\mu)\right), \end{aligned}

because z⊤z=(x−μ)⊤(A−1)⊤A−1(x−μ)\mathbf z^{\top}\mathbf z = (\mathbf x-\boldsymbol\mu)^{\top}(\mathbf A^{-1})^{\top}\mathbf A^{-1}(\mathbf x-\boldsymbol\mu) and (A−1)⊤A−1=(AA⊤)−1=Σ−1(\mathbf A^{-1})^{\top}\mathbf A^{-1} = (\mathbf A\mathbf A^{\top})^{-1} = \boldsymbol\Sigma^{-1}. This is the density of N(μ,Σ)\mathcal N(\boldsymbol\mu, \boldsymbol\Sigma) from the previous section. (For a singular Σ\boldsymbol\Sigma the same map defines a degenerate Gaussian concentrated on a line, which has no density on the plane.) The first two moments give a quick check. The mean is μ+A E[Z]=μ\boldsymbol\mu + \mathbf A\,\mathbb E[Z] = \boldsymbol\mu, and the covariance is

Cov⁡(μ+AZ)=A Cov⁡(Z) A⊤=A I A⊤=Σ.\begin{aligned} \operatorname{Cov}(\boldsymbol\mu + \mathbf A Z) &= \mathbf A\,\operatorname{Cov}(Z)\,\mathbf A^{\top} \\ &= \mathbf A\,\mathbf I\,\mathbf A^{\top} = \boldsymbol\Sigma . \end{aligned}

The symmetric root Σ1/2\boldsymbol\Sigma^{1/2} is one choice. For positive definite Σ\boldsymbol\Sigma there is exactly one lower-triangular L\mathbf L with positive diagonal and LL⊤=Σ\mathbf L\mathbf L^{\top} = \boldsymbol\Sigma, the Cholesky factor (Golub and Van Loan, 2013, §4.2). It is the usual choice in code: a finite algorithm computes it in about d3/3d^3/3 floating-point operations for a d×dd \times d matrix, while an eigendecomposition needs an iterative method. Unlike the symmetric root, it requires positive definiteness: torch.linalg.cholesky fails on a singular covariance, and MultivariateNormal accepts only a positive definite covariance_matrix. PyTorch’s MultivariateNormal samples exactly this way: it stores the Cholesky factor of the covariance it receives and returns μ+LZ\boldsymbol\mu + \mathbf L Z with Z∼N(0,I)Z \sim \mathcal N(\mathbf 0, \mathbf I) (PyTorch documentation and source). The same pattern, noise in and a deterministic transformation out, is the reparameterization trick of the VAE and the one-step noising formula of DDPM.

Figure

Sampling any Gaussian from standard noise

Three-step sampling pipeline: Z, then A Z, then μ + A ZThree scatter plots in the same window. First, a round cloud of standard normal noise Z centred at the origin with circular 1-sigma and 2-sigma contours. An arrow labelled times A, stretch and shear, leads to the second panel, where the cloud A Z leans along the diagonal with elliptical contours; A is the Cholesky factor L with rows (1, 0) and (0.5, 0.866). An arrow labelled plus mu, shift, leads to the third panel, where the same tilted cloud is centred at mu = (1.5, 1), marked with a cross and an arrow from the origin. One highlighted example point moves from z = (2, 1) to A z = (2, 1.87) and then to μ + A z = (3.5, 2.87).standard noisezZ ~ N(0, I)stretch and shearAzAZ ~ N(0, AAᵀ)shiftμμ + Azμ + AZ ~ N(μ, Σ)× A+ μA = L =100.50.866μ = (1.5, 1)LLᵀ = Σ = [[1, 0.5], [0.5, 1]]Three-step sampling pipeline: Z, then A Z, then μ + A ZThree scatter plots in the same window. First, a round cloud of standard normal noise Z centred at the origin with circular 1-sigma and 2-sigma contours. An arrow labelled times A, stretch and shear, leads to the second panel, where the cloud A Z leans along the diagonal with elliptical contours; A is the Cholesky factor L with rows (1, 0) and (0.5, 0.866). An arrow labelled plus mu, shift, leads to the third panel, where the same tilted cloud is centred at mu = (1.5, 1), marked with a cross and an arrow from the origin. One highlighted example point moves from z = (2, 1) to A z = (2, 1.87) and then to μ + A z = (3.5, 2.87).standard noisezZ ~ N(0, I)stretch and shearAzAZ ~ N(0, AAᵀ)shiftμμ + Azμ + AZ ~ N(μ, Σ)× A+ μA = L =100.50.866μ = (1.5, 1)LLᵀ = Σ = [[1, 0.5], [0.5, 1]]
Independent standard normal pairs Z are multiplied by a matrix A with AAᵀ = Σ (here the Cholesky factor L of the running example's Σ = [[1, 0.5], [0.5, 1]]) and then shifted by μ = (1.5, 1). The amber point is one example point z = (2, 1) followed through both steps: it moves to Az = (2, 1.87) and then to μ + Az = (3.5, 2.87). Curves are 1σ and 2σ contours; all panels share the same window.

Worked example#

For Σ=(10.50.51)\boldsymbol\Sigma = \begin{pmatrix} 1 & 0.5 \\ 0.5 & 1\end{pmatrix} the characteristic equation is (1−λ)2=0.25(1 - \lambda)^2 = 0.25, so the eigenvalues are λ1=1.5\lambda_1 = 1.5 and λ2=0.5\lambda_2 = 0.5. The eigenvectors are 12(1,1)⊤\tfrac{1}{\sqrt2}(1, 1)^{\top} and 12(1,−1)⊤\tfrac{1}{\sqrt2}(1, -1)^{\top}. The variance is largest along the diagonal x1=x2x_1 = x_2: the 1σ1\sigma ellipse has semi-axes 1.5≈1.225\sqrt{1.5} \approx 1.225 along it and 0.5≈0.707\sqrt{0.5} \approx 0.707 across it. Two valid square roots are

Σ1/2=12(1.5+0.51.5−0.51.5−0.51.5+0.5)≈(0.9660.2590.2590.966),\begin{aligned} \boldsymbol\Sigma^{1/2} &= \frac12\begin{pmatrix} \sqrt{1.5}+\sqrt{0.5} & \sqrt{1.5}-\sqrt{0.5} \\ \sqrt{1.5}-\sqrt{0.5} & \sqrt{1.5}+\sqrt{0.5} \end{pmatrix} \\[4pt] &\approx \begin{pmatrix} 0.966 & 0.259 \\ 0.259 & 0.966 \end{pmatrix}, \end{aligned} L=(100.50.75)≈(100.50.866).\mathbf L = \begin{pmatrix} 1 & 0 \\ 0.5 & \sqrt{0.75} \end{pmatrix} \approx \begin{pmatrix} 1 & 0 \\ 0.5 & 0.866 \end{pmatrix}.

Check: 0.9662+0.2592≈10.966^2 + 0.259^2 \approx 1 and 2⋅0.966⋅0.259≈0.52 \cdot 0.966 \cdot 0.259 \approx 0.5. For the Cholesky factor, LL⊤=(10.50.50.25+0.75)=Σ\mathbf L\mathbf L^{\top} = \begin{pmatrix} 1 & 0.5 \\ 0.5 & 0.25 + 0.75 \end{pmatrix} = \boldsymbol\Sigma. Both matrices turn independent standard noise into samples of the same correlated cloud.

Check yourself. Take Σ=(1−0.8−0.81)\boldsymbol\Sigma = \begin{pmatrix} 1 & -0.8 \\ -0.8 & 1\end{pmatrix}. Along which diagonal does the cloud stretch, and what is its Cholesky factor? Answer: the eigenvalues are 1.81.8 and 0.20.2, the long axis runs along x1=−x2x_1 = -x_2 with semi-axes 1.8≈1.342\sqrt{1.8} \approx 1.342 and 0.2≈0.447\sqrt{0.2} \approx 0.447, and L=(10−0.80.6)\mathbf L = \begin{pmatrix} 1 & 0 \\ -0.8 & 0.6 \end{pmatrix}, since LL⊤=(1−0.8−0.80.64+0.36)=Σ\mathbf L\mathbf L^{\top} = \begin{pmatrix} 1 & -0.8 \\ -0.8 & 0.64 + 0.36 \end{pmatrix} = \boldsymbol\Sigma.

Gaussians in PyTorch#

torch.distributions contains parametrizable distributions with methods to evaluate their (log-)density and to draw samples. The 2-D Gaussian is MultivariateNormal. The fitting experiments in Fitting distributions with KL need a Gaussian whose parameters an optimizer can change freely. The covariance makes that non-trivial: an arbitrary 2×22\times 2 matrix is generally not a valid covariance.

The fix is to learn the Cholesky factor instead of Σ\boldsymbol\Sigma. With

L=(ea0bec),LL⊤=(e2ab eab eab2+e2c),\begin{gathered} \mathbf L = \begin{pmatrix} e^{a} & 0 \\ b & e^{c} \end{pmatrix}, \\[4pt] \mathbf L\mathbf L^{\top} = \begin{pmatrix} e^{2a} & b\,e^{a} \\ b\,e^{a} & b^2 + e^{2c} \end{pmatrix}, \end{gathered}

the three raw numbers a,b,ca, b, c can take any real values. The diagonal of L\mathbf L is stored through a logarithm, so it stays positive and the triangular matrix L\mathbf L is invertible. Then LL⊤\mathbf L\mathbf L^{\top} is symmetric and positive definite: for v≠0\mathbf v \ne \mathbf 0, v⊤LL⊤v=∥L⊤v∥2>0\mathbf v^{\top}\mathbf L\mathbf L^{\top}\mathbf v = \|\mathbf L^{\top}\mathbf v\|^2 > 0 because L⊤\mathbf L^{\top} is invertible. The running example is reached with a=0a = 0, b=0.5b = 0.5 and c=ln⁡0.75≈−0.144c = \ln\sqrt{0.75} \approx -0.144; the initialization a=b=c=0a = b = c = 0 gives Σ=I\boldsymbol\Sigma = \mathbf I.

Parameters that should be learned are created with requires_grad=True, which tells PyTorch’s autograd to record operations on them and compute their gradients. The model distribution QQ has a trainable mean and trainable raw entries of L\mathbf L. The target distribution PP uses requires_grad=False, because it stays fixed. Figure 4 shows the starting point of the first fitting experiment, with each cloud coloured by its own density.

Figure

Target P and model Q before fitting

Two Gaussian sample clouds plotted as dotsA square plot with both axes from −10 to 10. A red cloud of 400 dots is centred at (−5, −5), labelled target distribution P with mean (−5, −5), identity covariance and requires_grad False. A blue cloud of 400 dots is centred at the origin, labelled train distribution Q with mean (0, 0) and covariance L L transpose equal to the identity at initialization; its parameters have requires_grad True. Dots near each centre are drawn more intensely because their own density, p(x) for P and q(x) for Q, is higher.−10−50510−10−50510train distr Qμ = (0, 0), Σ = LLᵀ = Irequires_grad=True:μ and L are trainedtarget distr Pμ = (−5, −5), Σ = Irequires_grad=False:fixedx₁x₂stronger colour = higher density:p(x) for P, q(x) for QTwo Gaussian sample clouds plotted as dotsA square plot with both axes from −10 to 10. A red cloud of 400 dots centred at (−5, −5) is the fixed target distribution P with identity covariance (requires_grad False). A blue cloud centred at the origin is the trainable distribution Q with covariance L L transpose equal to the identity at initialization (requires_grad True). Dots near each centre are drawn more intensely, following p(x) for P and q(x) for Q.−10−50510−10−50510target distr Pμ = (−5, −5), Σ = Irequires_grad=False:fixedtrain distr Qμ = (0, 0), Σ = LLᵀ = Irequires_grad=True:μ and L are trainedx₁x₂stronger colour = higher density: p(x) for P, q(x) for Q
400 samples each of the fixed target P = N((−5, −5), I) and of the trainable model Q, initialized at N(0, I), on the plot limits (−10, 10). Each dot is coloured by its own cloud’s density, p(x) for target samples and q(x) for model samples, so stronger colour means higher density.

A minimal sketch of both ways to sample the running example:

import torch
from torch import distributions as distrs

mu = torch.zeros(2)
cov = torch.tensor([[1.0, 0.5], [0.5, 1.0]])
target = distrs.MultivariateNormal(
    mu, covariance_matrix=cov)

# 1000 samples, shape (1000, 2)
x = target.sample(torch.Size([1000]))
# L = [[1, 0], [0.5, 0.866]]
L = torch.linalg.cholesky(cov)
# the same distribution, by hand
x_manual = mu + torch.randn(1000, 2) @ L.T

Each row of torch.randn(1000, 2) is one draw z⊤\mathbf z^{\top}, so @ L.T computes (Lz)⊤(\mathbf L\mathbf z)^{\top} row by row.

Notebook#

The practical part of this note follows the Distances between distributions notebook from the HSE-LAMBDA Deep Generative Models course repository. The link is pinned to a fixed commit, and cell numbers count every cell from 1.

  • Cell 2 sets up the environment: it imports Matplotlib with the fivethirtyeight style, NumPy, PyTorch with torch.distributions as distrs, and IPython’s clear_output, which the training loops later use to redraw their plots.
  • Cell 4 defines plot_2d_dots, a scatter plot of an N×2N \times 2 array on the default limits (−10,10)(-10, 10) for both axes (arguments xlim and ylim), one small dot per sample, coloured by an optional value array through the colour map cmap (default viridis).
  • Cell 5 defines the three helpers behind the parametrization above. get_parameters(mu, sigma, requires_grad) creates the mean (μ,μ)(\mu, \mu) and the raw entries a=c=ln⁡σa = c = \ln\sigma, b=0b = 0. create_distr assembles L\mathbf L and returns MultivariateNormal(mu, L @ L.T), which passes LL⊤\mathbf L\mathbf L^{\top} as covariance_matrix. sample(distr, n) returns an n×2n \times 2 tensor. Since L\mathbf L is already a valid Cholesky factor, MultivariateNormal(mu, scale_tril=L) defines the same distribution and skips the re-factorization that PyTorch performs on L @ L.T; the PyTorch documentation recommends scale_tril for this reason.
  • Cells 8–11 create the fixed target N((−5,−5),I)\mathcal N((-5,-5), \mathbf I) and the trainable model QQ initialized at N(0,I)\mathcal N(\mathbf 0, \mathbf I), and plot both clouds coloured by p(x)p(\mathbf x) and q(x)q(\mathbf x) (Figure 4). Cells 8 and 11 draw 1000 samples from each distribution, and the plots use the viridis and plasma colour maps; Figure 4 shows 400 samples per cloud and encodes density by colour strength instead. The fit itself is the subject of Fitting distributions with KL.

Next#

With a target distribution and a trainable model in hand, the remaining question is what “fit” means: we need a number that says how far QQ is from PP. The first candidate is the KL divergence, and to understand it we start from entropy in Entropy, cross-entropy and KL divergence.

References#