jcm/sde_lib.py (362 lines of code) (raw):

# Copyright 2023 (c) OpenAI. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Abstract SDE classes, Reverse SDE, and VE/VP SDEs.""" import abc import jax.numpy as jnp import jax import numpy as np from .utils import batch_mul def get_sde(config): if config.training.sde.lower() == "vpsde": sde = VPSDE( beta_min=config.model.beta_min, beta_max=config.model.beta_max, N=config.model.num_scales, ) elif config.training.sde.lower() == "subvpsde": sde = subVPSDE( beta_min=config.model.beta_min, beta_max=config.model.beta_max, N=config.model.num_scales, ) elif config.training.sde.lower() == "vesde": sde = VESDE( sigma_min=config.model.sigma_min, sigma_max=config.model.sigma_max, N=config.model.num_scales, ) elif config.training.sde.lower() == "kvesde": sde = KVESDE( t_min=config.model.t_min, t_max=config.model.t_max, N=config.model.num_scales, rho=config.model.rho, data_std=config.model.data_std, ) else: raise NotImplementedError(f"SDE {config.training.sde} unknown.") return sde class SDE(abc.ABC): """SDE abstract class. Functions are designed for a mini-batch of inputs.""" def __init__(self, N): """Construct an SDE. Args: N: number of discretization time steps. """ super().__init__() self.N = N @property @abc.abstractmethod def T(self): """End time of the SDE.""" pass @abc.abstractmethod def sde(self, x, t): pass @abc.abstractmethod def marginal_prob(self, x, t): """Parameters to determine the marginal distribution of the SDE, $p_t(x)$.""" pass @abc.abstractmethod def prior_sampling(self, rng, shape): """Generate one sample from the prior distribution, $p_T(x)$.""" pass @abc.abstractmethod def prior_logp(self, z): """Compute log-density of the prior distribution. Useful for computing the log-likelihood via probability flow ODE. Args: z: latent code Returns: log probability density """ pass def discretize(self, x, t): """Discretize the SDE in the form: x_{i+1} = x_i + f_i(x_i) + G_i z_i. Useful for reverse diffusion sampling and probabiliy flow sampling. Defaults to Euler-Maruyama discretization. Args: x: a JAX tensor. t: a JAX float representing the time step (from 0 to `self.T`) Returns: f, G """ dt = 1 / self.N drift, diffusion = self.sde(x, t) f = drift * dt G = diffusion * jnp.sqrt(dt) return f, G def reverse(self, score_fn, probability_flow=False): """Create the reverse-time SDE/ODE. Args: score_fn: A time-dependent score-based model that takes x and t and returns the score. probability_flow: If `True`, create the reverse-time ODE used for probability flow sampling. """ N = self.N T = self.T sde_fn = self.sde discretize_fn = self.discretize # Build the class for reverse-time SDE. class RSDE(self.__class__): def __init__(self): self.N = N self.probability_flow = probability_flow @property def T(self): return T def sde(self, x, t): """Create the drift and diffusion functions for the reverse SDE/ODE.""" drift, diffusion = sde_fn(x, t) score = score_fn(x, t) drift = drift - batch_mul( diffusion**2, score * (0.5 if self.probability_flow else 1.0) ) # Set the diffusion function to zero for ODEs. diffusion = jnp.zeros_like(t) if self.probability_flow else diffusion return drift, diffusion def discretize(self, x, t): """Create discretized iteration rules for the reverse diffusion sampler.""" f, G = discretize_fn(x, t) rev_f = f - batch_mul( G**2, score_fn(x, t) * (0.5 if self.probability_flow else 1.0) ) rev_G = jnp.zeros_like(t) if self.probability_flow else G return rev_f, rev_G return RSDE() class VPSDE(SDE): def __init__(self, beta_min=0.1, beta_max=20, N=1000): """Construct a Variance Preserving SDE. Args: beta_min: value of beta(0) beta_max: value of beta(1) N: number of discretization steps """ super().__init__(N) self.beta_0 = beta_min self.beta_1 = beta_max self.N = N self.discrete_betas = jnp.linspace(beta_min / N, beta_max / N, N) self.alphas = 1.0 - self.discrete_betas self.alphas_cumprod = jnp.cumprod(self.alphas, axis=0) self.sqrt_alphas_cumprod = jnp.sqrt(self.alphas_cumprod) self.sqrt_1m_alphas_cumprod = jnp.sqrt(1.0 - self.alphas_cumprod) @property def T(self): return 1 def sde(self, x, t): beta_t = self.beta_0 + t * (self.beta_1 - self.beta_0) drift = -0.5 * batch_mul(beta_t, x) diffusion = jnp.sqrt(beta_t) return drift, diffusion def marginal_prob(self, x, t, high_precision=True): log_mean_coeff = ( -0.25 * t**2 * (self.beta_1 - self.beta_0) - 0.5 * t * self.beta_0 ) if high_precision: mean = batch_mul( jnp.where( jnp.abs(log_mean_coeff) <= 1e-3, 1 + log_mean_coeff, jnp.exp(log_mean_coeff), ), x, ) std = jnp.where( jnp.abs(log_mean_coeff) <= 1e-3, jnp.sqrt(-2.0 * log_mean_coeff), jnp.sqrt(1 - jnp.exp(2.0 * log_mean_coeff)), ) else: mean = batch_mul(jnp.exp(log_mean_coeff), x) std = jnp.sqrt(1 - jnp.exp(2 * log_mean_coeff)) return mean, std def prior_sampling(self, rng, shape): return jax.random.normal(rng, shape) def prior_logp(self, z): shape = z.shape N = np.prod(shape[1:]) logp_fn = lambda z: -N / 2.0 * jnp.log(2 * np.pi) - jnp.sum(z**2) / 2.0 return jax.vmap(logp_fn)(z) def prior_entropy(self, z): shape = z.shape entropy = jnp.ones(shape) * (0.5 * jnp.log(2 * np.pi) + 0.5) entropy = entropy.reshape((z.shape[0], -1)) return jnp.sum(entropy, axis=-1) def discretize(self, x, t): """DDPM discretization.""" timestep = (t * (self.N - 1) / self.T).astype(jnp.int32) beta = self.discrete_betas[timestep] alpha = self.alphas[timestep] sqrt_beta = jnp.sqrt(beta) f = batch_mul(jnp.sqrt(alpha), x) - x G = sqrt_beta return f, G def likelihood_importance_cum_weight(self, t, eps=1e-5): exponent1 = 0.5 * eps * (eps - 2) * self.beta_0 - 0.5 * eps**2 * self.beta_1 exponent2 = 0.5 * t * (t - 2) * self.beta_0 - 0.5 * t**2 * self.beta_1 term1 = jnp.where( jnp.abs(exponent1) <= 1e-3, -exponent1, 1.0 - jnp.exp(exponent1) ) term2 = jnp.where( jnp.abs(exponent2) <= 1e-3, -exponent2, 1.0 - jnp.exp(exponent2) ) return 0.5 * ( -2 * jnp.log(term1) + 2 * jnp.log(term2) + self.beta_0 * (-2 * eps + eps**2 - (t - 2) * t) + self.beta_1 * (-(eps**2) + t**2) ) def sample_importance_weighted_time_for_likelihood( self, rng, shape, quantile=None, eps=1e-5, steps=100 ): Z = self.likelihood_importance_cum_weight(self.T, eps=eps) if quantile is None: quantile = jax.random.uniform(rng, shape, minval=0, maxval=Z) lb = jnp.ones_like(quantile) * eps ub = jnp.ones_like(quantile) * self.T def bisection_func(carry, idx): lb, ub = carry mid = (lb + ub) / 2.0 value = self.likelihood_importance_cum_weight(mid, eps=eps) lb = jnp.where(value <= quantile, mid, lb) ub = jnp.where(value <= quantile, ub, mid) return (lb, ub), idx (lb, ub), _ = jax.lax.scan(bisection_func, (lb, ub), jnp.arange(0, steps)) return (lb + ub) / 2.0 class subVPSDE(SDE): def __init__(self, beta_min=0.1, beta_max=20, N=1000): """Construct the sub-VP SDE that excels at likelihoods. Args: beta_min: value of beta(0) beta_max: value of beta(1) N: number of discretization steps """ super().__init__(N) self.beta_0 = beta_min self.beta_1 = beta_max self.N = N @property def T(self): return 1 def sde(self, x, t, high_precision=True): beta_t = self.beta_0 + t * (self.beta_1 - self.beta_0) drift = -0.5 * batch_mul(beta_t, x) exponent = -2 * self.beta_0 * t - (self.beta_1 - self.beta_0) * t**2 discount = 1.0 - jnp.exp(exponent) if high_precision: discount = jnp.where(jnp.abs(exponent) <= 1e-3, -exponent, discount) diffusion = jnp.sqrt(beta_t * discount) return drift, diffusion def marginal_prob(self, x, t, high_precision=True): log_mean_coeff = ( -0.25 * t**2 * (self.beta_1 - self.beta_0) - 0.5 * t * self.beta_0 ) if high_precision: mean = batch_mul( jnp.where( jnp.abs(log_mean_coeff) <= 1e-3, 1.0 + log_mean_coeff, jnp.exp(log_mean_coeff), ), x, ) std = jnp.where( jnp.abs(log_mean_coeff) <= 1e-3, -2.0 * log_mean_coeff, 1 - jnp.exp(2.0 * log_mean_coeff), ) else: mean = batch_mul(jnp.exp(log_mean_coeff), x) std = 1 - jnp.exp(2.0 * log_mean_coeff) return mean, std def prior_sampling(self, rng, shape): return jax.random.normal(rng, shape) def prior_logp(self, z): shape = z.shape N = np.prod(shape[1:]) logp_fn = lambda z: -N / 2.0 * jnp.log(2 * np.pi) - jnp.sum(z**2) / 2.0 return jax.vmap(logp_fn)(z) def prior_entropy(self, z): shape = z.shape entropy = jnp.ones(shape) * (0.5 * jnp.log(2 * np.pi) + 0.5) entropy = entropy.reshape((z.shape[0], -1)) return jnp.sum(entropy, axis=-1) def likelihood_importance_cum_weight(self, t, eps=1e-5): exponent1 = 0.5 * eps * (eps * self.beta_1 - (eps - 2) * self.beta_0) exponent2 = 0.5 * t * (self.beta_1 * t - (t - 2) * self.beta_0) term1 = jnp.where( exponent1 <= 1e-3, jnp.log(exponent1), jnp.log(jnp.exp(exponent1) - 1.0) ) term2 = jnp.where( exponent2 <= 1e-3, jnp.log(exponent2), jnp.log(jnp.exp(exponent2) - 1.0) ) return 0.5 * ( -4 * term1 + 4 * term2 + (2 * eps - eps**2 + t * (t - 2)) * self.beta_0 + (eps**2 - t**2) * self.beta_1 ) def sample_importance_weighted_time_for_likelihood( self, rng, shape, quantile=None, eps=1e-5, steps=100 ): Z = self.likelihood_importance_cum_weight(self.T, eps=eps) if quantile is None: quantile = jax.random.uniform(rng, shape, minval=0, maxval=Z) lb = jnp.ones_like(quantile) * eps ub = jnp.ones_like(quantile) * self.T def bisection_func(carry, idx): lb, ub = carry mid = (lb + ub) / 2.0 value = self.likelihood_importance_cum_weight(mid, eps=eps) lb = jnp.where(value <= quantile, mid, lb) ub = jnp.where(value <= quantile, ub, mid) return (lb, ub), idx (lb, ub), _ = jax.lax.scan(bisection_func, (lb, ub), jnp.arange(0, steps)) return (lb + ub) / 2.0 class VESDE(SDE): def __init__(self, sigma_min=0.01, sigma_max=50, N=1000, linear=False): """Construct a Variance Exploding SDE. Args: sigma_min: smallest sigma. sigma_max: largest sigma. N: number of discretization steps """ super().__init__(N) self.sigma_min = sigma_min self.sigma_max = sigma_max self.linear = linear if not linear: self.discrete_sigmas = jnp.exp( np.linspace(np.log(self.sigma_min), np.log(self.sigma_max), N) ) else: self.discrete_sigmas = jnp.linspace(self.sigma_min, self.sigma_max, N) self.N = N @property def T(self): return 1 def sde(self, x, t): drift = jnp.zeros_like(x) if not self.linear: sigma = self.sigma_min * (self.sigma_max / self.sigma_min) ** t diffusion = sigma * jnp.sqrt( 2 * (jnp.log(self.sigma_max) - jnp.log(self.sigma_min)) ) else: diffusion = self.sigma_max * jnp.sqrt(2 * t) return drift, diffusion def marginal_prob(self, x, t): mean = x if not self.linear: std = self.sigma_min * (self.sigma_max / self.sigma_min) ** t else: std = t * self.sigma_max return mean, std def prior_sampling(self, rng, shape): return jax.random.normal(rng, shape) * self.sigma_max def prior_logp(self, z): shape = z.shape N = np.prod(shape[1:]) logp_fn = lambda z: -N / 2.0 * jnp.log( 2 * np.pi * self.sigma_max**2 ) - jnp.sum(z**2) / (2 * self.sigma_max**2) return jax.vmap(logp_fn)(z) def prior_entropy(self, z): shape = z.shape entropy = jnp.ones(shape) * ( 0.5 * jnp.log(2 * np.pi * self.sigma_max**2) + 0.5 ) entropy = entropy.reshape((z.shape[0], -1)) return jnp.sum(entropy, axis=-1) def discretize(self, x, t): """SMLD(NCSN) discretization.""" if not self.linear: timestep = (t * (self.N - 1) / self.T).astype(jnp.int32) sigma = self.discrete_sigmas[timestep] adjacent_sigma = jnp.where( timestep == 0, jnp.zeros_like(timestep), self.discrete_sigmas[timestep - 1], ) f = jnp.zeros_like(x) G = jnp.sqrt(sigma**2 - adjacent_sigma**2) return f, G else: return super().discretize(x, t) class KVESDE(SDE): def __init__(self, t_min=0.002, t_max=80.0, N=1000, rho=7.0, data_std=0.5): """Construct a Variance Exploding SDE as in Kerras et al. Args: t_min: smallest time t_max: largest time. N: number of discretization steps rho: parameter for time steps. """ super().__init__(N) self.t_min = t_min self.t_max = t_max self.rho = rho self.N = N self.data_std = data_std @property def T(self): return self.t_max def sde(self, x, t): drift = jnp.zeros_like(x) diffusion = jnp.sqrt(2 * t) return drift, diffusion def marginal_prob(self, x, t): mean = x std = t return mean, std def prior_sampling(self, rng, shape): return jax.random.normal(rng, shape) * self.t_max def prior_logp(self, z): shape = z.shape N = np.prod(shape[1:]) logp_fn = lambda z: -N / 2.0 * jnp.log(2 * np.pi * self.t_max**2) - jnp.sum( z**2 ) / (2 * self.t_max**2) return jax.vmap(logp_fn)(z) def prior_entropy(self, z): shape = z.shape entropy = jnp.ones(shape) * (0.5 * jnp.log(2 * np.pi * self.t_max**2) + 0.5) entropy = entropy.reshape((z.shape[0], -1)) return jnp.sum(entropy, axis=-1)