Normalizing Flows Invertible Transformations Density Estimation

# Normalizing Flows: Invertible Transformations & Density Estimation

## Introduction & Motivation

Normalizing flows: learn invertible transformations. Convert simple distribution → complex distribution. Density estimation: compute likelihood efficiently. Inverse: sample by transforming simple samples. Affine coupling: efficient invertible layers. Applications: generative modeling, variational inference, Bayesian deep learning.

Motivation: Likelihood-based: maximum likelihood training. Flows: tractable density + invertibility.

Applications: Generative modeling, variational inference, uncertainty quantification.

---

## Core Concepts & Theory

### Flow Transformation

z → x via invertible T; x = T(z), z = T^(-1)(x).

### Change of Variables

Density transformation via Jacobian determinant.

### Coupling Layers

Affine/nonlinear coupling; efficient invertible.

---

## Mathematical Formulation

Change of variables:
$$p_X(x) = p_Z(T^{-1}(x)) \left| \det \frac{dT^{-1}}{dx} ight|$$

Log probability:
$$\log p(x) = \log p(z) - \sum_{i=1}^K \log |\det J_i|$$

Affine coupling:
$$x^{(l)} = z^{(l)} \odot e^{s(z^{(l-1)})} + t(z^{(l-1)})$$

---

## Advanced Theory & Extensions

### Neural Spline Flows

Learned monotonic spline transformations; flexible.

### Invertible ResNets

Residual connections + invertibility; deep networks.

### Autoregressive Flows

Sequential transformation; tractable Jacobian.

---

## Computational Considerations

Forward pass: O(K·d) for K flows, d dimension.

Jacobian determinant: O(d³) via LU decomposition (or efficient).

Training: O(K·d) forward + backward; reasonable.

---

## Practical Implementation Strategies

### Coupling Strategy

Alternate feature masking; ensure invertibility.

### Jacobian Computation

Use trace estimators; avoid explicit computation.

### Prior Distribution

Standard normal common; flexible via flows.

---

## Benchmark Datasets & Evaluation

MNIST: Standard; generative quality assessment.

CelebA: Faces; likelihood vs sample quality tradeoff.

Density Estimation: Synthetic; known ground truth density.

---

## Key Challenges & Limitations

### Jacobian Computation

Expensive for large dimensions; efficient approximations needed.

### Expressiveness-Efficiency Tradeoff

Deep flows more expressive; costly Jacobian.

### Training Stability

Likelihood can diverge; careful regularization needed.

---

## Hyperparameter Tuning

Number of flows K: 4-8 typical; more = better.

Coupling architecture: Simple nets OK; 1-2 hidden layers.

Learning rate: 1e-3 standard; decay useful.

---

## Real-World Applications & Case Studies

Generative Modeling: High-dimensional density learning.

Variational Inference: Better posterior approximation.

Uncertainty Quantification: Predictive distributions.

---

## Integration with Other Methods

Flows + VAE → flexible posterior.

Flows + GAN → adversarial training of flows.

---

## Summary & Key Takeaways

Normalizing flows via invertible transformations enable tractable likelihood computation and flexible density modeling through sequential application of simple invertible layers.

Principles:
1. Invertible: both T and T^(-1) computable.
2. Change of variables: Jacobian determinant.
3. Coupling: efficient invertible parameterization.
4. Autoregressive: sequential, tractable Jacobian.
5. Tractable likelihood: maximum likelihood training.

---

---

## Appendix: Practical Labs

### Lab 1: Affine Coupling

import torch
import torch.nn as nn
import numpy as np

class AffineCoupling(nn.Module):
 def __init__(self, input_dim, hidden_dim):
 super().__init__()
 self.net = nn.Sequential(
 nn.Linear(input_dim // 2, hidden_dim),
 nn.ReLU(),
 nn.Linear(hidden_dim, input_dim)
 )

 def forward(self, x):
 """Forward: transform half, scale/shift other half"""
 x1, x2 = x[:, :x.shape[1]//2], x[:, x.shape[1]//2:]
 
 params = self.net(x1)
 s, t = params[:, :x2.shape[1]], params[:, x2.shape[1]:]
 
 y2 = x2 * torch.exp(s) + t
 y = torch.cat([x1, y2], dim=1)
 
 return y, s.sum(dim=1)

# Test
np.random.seed(42)
coupling = AffineCoupling(input_dim=20, hidden_dim=64)
x = torch.randn(8, 20)

y, log_det = coupling(x)

assert y.shape == x.shape, "Output shape matches"
print("✓ Affine coupling working")

if __name__ == "__main__":
 print("Lab 1: Coupling - PASSED")

### Lab 2: Change of Variables

import torch
import numpy as np

def change_of_variables(z, log_pz, log_det_jacobian):
 """Apply change of variables formula"""
 log_px = log_pz - log_det_jacobian.sum(dim=1)
 return log_px

# Test
np.random.seed(42)
z = torch.randn(32, 20)
log_pz = -0.5 * (z ** 2).sum(dim=1) - 10 * np.log(np.sqrt(2 * np.pi))
log_det = torch.randn(32)

log_px = change_of_variables(z, log_pz, log_det)

assert log_px.shape == (32,), "Output shape correct"
assert torch.isfinite(log_px).all(), "All finite"
print("✓ Change of variables working")

if __name__ == "__main__":
 print("Lab 2: Variables - PASSED")

### Lab 3: Invertible Transformation

import torch
import numpy as np

class SimpleInvertibleFlow:
 def __init__(self, scale=1.0):
 self.scale = scale

 def forward(self, z):
 """Forward transformation"""
 x = z * self.scale
 log_det = len(z) * np.log(self.scale)
 return x, log_det

 def inverse(self, x):
 """Inverse transformation"""
 z = x / self.scale
 return z

# Test
np.random.seed(42)
flow = SimpleInvertibleFlow(scale=2.0)

z = np.random.randn(10)
x, log_det = flow.forward(z)
z_recovered = flow.inverse(x)

assert np.allclose(z, z_recovered), "Should recover original"
print("✓ Invertible transformation working")

if __name__ == "__main__":
 print("Lab 3: Invertible - PASSED")

### Lab 4: Flow Composition

import torch
import numpy as np

def compose_flows(x, flows):
 """Apply sequence of flows"""
 log_det_total = 0
 
 for flow in flows:
 x, log_det = flow(x)
 log_det_total = log_det_total + log_det.sum()
 
 return x, log_det_total

# Test
np.random.seed(42)

class DummyFlow:
 def __call__(self, x):
 return x * 0.5, torch.ones(len(x)) * np.log(0.5)

flows = [DummyFlow(), DummyFlow()]
x = torch.randn(8, 20)

y, log_det = compose_flows(x, flows)

assert y.shape == x.shape, "Output shape correct"
assert np.isfinite(log_det), "Log det finite"
print("✓ Flow composition working")

if __name__ == "__main__":
 print("Lab 4: Composition - PASSED")

Go deeper with CFSGPT

Get AI-powered deep-dives, save terms, and run advanced simulations — free account.

Create Free Account