from typing import Any import jax import jax.numpy as jnp from flax import linen as nn Array = jax.Array class ResBlock(nn.Module): features: int groups: int = 8 @nn.compact def __call__(self, x: Array) -> Array: residual = x if x.shape[-1] != self.features: residual = nn.Conv(self.features, kernel_size=(1, 1))(residual) h = nn.GroupNorm(num_groups=min(self.groups, self.features))(x) h = nn.swish(h) h = nn.Conv(self.features, kernel_size=(3, 3))(h) h = nn.GroupNorm(num_groups=min(self.groups, self.features))(h) h = nn.swish(h) h = nn.Conv(self.features, kernel_size=(3, 3))(h) return h + residual class Downsample(nn.Module): features: int @nn.compact def __call__(self, x: Array) -> Array: return nn.Conv(self.features, kernel_size=(3, 3), strides=(2, 2))(x) class Upsample(nn.Module): features: int @nn.compact def __call__(self, x: Array) -> Array: b, h, w, _ = x.shape x = jax.image.resize(x, (b, h * 2, w * 2, x.shape[-1]), method="nearest") return nn.Conv(self.features, kernel_size=(3, 3))(x) class Encoder(nn.Module): latent_dim: int base_channels: int = 64 channel_mults: tuple[int, ...] = (1, 2, 4) @nn.compact def __call__(self, x: Array) -> tuple[Array, Array]: h = nn.Conv(self.base_channels, kernel_size=(3, 3))(x) for i, mult in enumerate(self.channel_mults): features = self.base_channels * mult h = ResBlock(features)(h) h = ResBlock(features)(h) if i < len(self.channel_mults) - 1: h = Downsample(features)(h) h = Downsample(self.base_channels * self.channel_mults[-1])(h) h = nn.GroupNorm(num_groups=8)(h) h = nn.swish(h) h = h.reshape(h.shape[0], -1) mu = nn.Dense(self.latent_dim)(h) log_var = nn.Dense(self.latent_dim)(h) log_var = jnp.clip(log_var, -10.0, 10.0) return mu, log_var class Decoder(nn.Module): out_channels: int base_channels: int = 64 channel_mults: tuple[int, ...] = (1, 2, 4) image_size: int = 32 @nn.compact def __call__(self, z: Array) -> Array: n_down = len(self.channel_mults) start = self.image_size // (2**n_down) features_top = self.base_channels * self.channel_mults[-1] h = nn.Dense(start * start * features_top)(z) h = h.reshape(z.shape[0], start, start, features_top) h = Upsample(features_top)(h) for i, mult in enumerate(reversed(self.channel_mults)): features = self.base_channels * mult h = ResBlock(features)(h) h = ResBlock(features)(h) if i < len(self.channel_mults) - 1: next_features = self.base_channels * list(reversed(self.channel_mults))[i + 1] h = Upsample(next_features)(h) h = nn.GroupNorm(num_groups=8)(h) h = nn.swish(h) h = nn.Conv(self.out_channels, kernel_size=(3, 3))(h) return nn.sigmoid(h) class VAE(nn.Module): latent_dim: int = 128 in_channels: int = 3 base_channels: int = 64 channel_mults: tuple[int, ...] = (1, 2, 4) image_size: int = 32 obs_sigma: float = 0.1 def setup(self): self.encoder = Encoder( latent_dim=self.latent_dim, base_channels=self.base_channels, channel_mults=self.channel_mults, ) self.decoder = Decoder( out_channels=self.in_channels, base_channels=self.base_channels, channel_mults=self.channel_mults, image_size=self.image_size, ) def encode(self, x: Array) -> tuple[Array, Array]: return self.encoder(x) def decode(self, z: Array) -> Array: return self.decoder(z) def reparameterize(self, rng: Array, mu: Array, log_var: Array) -> Array: eps = jax.random.normal(rng, mu.shape) return mu + jnp.exp(0.5 * log_var) * eps def __call__(self, x: Array, rng: Array) -> dict[str, Array]: mu, log_var = self.encode(x) z = self.reparameterize(rng, mu, log_var) x_hat = self.decode(z) return {"x_hat": x_hat, "mu": mu, "log_var": log_var, "z": z} def sample_prior(self, rng: Array, n: int) -> Array: z = jax.random.normal(rng, (n, self.latent_dim)) return self.decode(z) def latent_log_prior(z: Array) -> Array: """Standard normal prior log density, summed over latent dimensions.""" d = z.shape[-1] return -0.5 * (jnp.sum(z * z, axis=-1) + d * jnp.log(2 * jnp.pi))