jcm/models/layers.py (499 lines of code) (raw):
# coding=utf-8
# Copyright 2020 The Google Research Authors.
#
# 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.
# pylint: skip-file
"""Common layers for defining score networks.
"""
import functools
import math
import string
from typing import Any, Sequence, Optional
import flax.linen as nn
import jax
import jax.nn as jnn
import jax.numpy as jnp
def get_act(config):
"""Get activation functions from the config file."""
if config.model.nonlinearity.lower() == "elu":
return nn.elu
elif config.model.nonlinearity.lower() == "relu":
return nn.relu
elif config.model.nonlinearity.lower() == "lrelu":
return functools.partial(nn.leaky_relu, negative_slope=0.2)
elif config.model.nonlinearity.lower() == "swish":
return nn.swish
else:
raise NotImplementedError("activation function does not exist!")
def ncsn_conv1x1(x, out_planes, stride=1, bias=True, dilation=1, init_scale=1.0):
"""1x1 convolution with PyTorch initialization. Same as NCSNv1/v2."""
init_scale = 1e-10 if init_scale == 0 else init_scale
kernel_init = jnn.initializers.variance_scaling(
1 / 3 * init_scale, "fan_in", "uniform"
)
kernel_shape = (1, 1) + (x.shape[-1], out_planes)
bias_init = lambda key, shape: kernel_init(key, kernel_shape)[0, 0, 0, :]
output = nn.Conv(
out_planes,
kernel_size=(1, 1),
strides=(stride, stride),
padding="SAME",
use_bias=bias,
kernel_dilation=(dilation, dilation),
kernel_init=kernel_init,
bias_init=bias_init,
)(x)
return output
def default_init(scale=1.0):
"""The same initialization used in DDPM."""
scale = 1e-10 if scale == 0 else scale
return jnn.initializers.variance_scaling(scale, "fan_avg", "uniform")
def ddpm_conv1x1(x, out_planes, stride=1, bias=True, dilation=1, init_scale=1.0):
"""1x1 convolution with DDPM initialization."""
bias_init = jnn.initializers.zeros
output = nn.Conv(
out_planes,
kernel_size=(1, 1),
strides=(stride, stride),
padding="SAME",
use_bias=bias,
kernel_dilation=(dilation, dilation),
kernel_init=default_init(init_scale),
bias_init=bias_init,
)(x)
return output
def ncsn_conv3x3(x, out_planes, stride=1, bias=True, dilation=1, init_scale=1.0):
"""3x3 convolution with PyTorch initialization. Same as NCSNv1/NCSNv2."""
init_scale = 1e-10 if init_scale == 0 else init_scale
kernel_init = jnn.initializers.variance_scaling(
1 / 3 * init_scale, "fan_in", "uniform"
)
kernel_shape = (3, 3) + (x.shape[-1], out_planes)
bias_init = lambda key, shape: kernel_init(key, kernel_shape)[0, 0, 0, :]
output = nn.Conv(
out_planes,
kernel_size=(3, 3),
strides=(stride, stride),
padding="SAME",
use_bias=bias,
kernel_dilation=(dilation, dilation),
kernel_init=kernel_init,
bias_init=bias_init,
)(x)
return output
def ddpm_conv3x3(x, out_planes, stride=1, bias=True, dilation=1, init_scale=1.0):
"""3x3 convolution with DDPM initialization."""
bias_init = jnn.initializers.zeros
output = nn.Conv(
out_planes,
kernel_size=(3, 3),
strides=(stride, stride),
padding="SAME",
use_bias=bias,
kernel_dilation=(dilation, dilation),
kernel_init=default_init(init_scale),
bias_init=bias_init,
)(x)
return output
###########################################################################
# Functions below are ported over from the NCSNv1/NCSNv2 codebase:
# https://github.com/ermongroup/ncsn
# https://github.com/ermongroup/ncsnv2
###########################################################################
class CRPBlock(nn.Module):
"""CRPBlock for RefineNet. Used in NCSNv2."""
features: int
n_stages: int
act: Any = nn.relu
@nn.compact
def __call__(self, x):
x = self.act(x)
path = x
for _ in range(self.n_stages):
path = nn.max_pool(
path, window_shape=(5, 5), strides=(1, 1), padding="SAME"
)
path = ncsn_conv3x3(path, self.features, stride=1, bias=False)
x = path + x
return x
class CondCRPBlock(nn.Module):
"""Noise-conditional CRPBlock for RefineNet. Used in NCSNv1."""
features: int
n_stages: int
normalizer: Any
act: Any = nn.relu
@nn.compact
def __call__(self, x, y):
x = self.act(x)
path = x
for _ in range(self.n_stages):
path = self.normalizer()(path, y)
path = nn.avg_pool(
path, window_shape=(5, 5), strides=(1, 1), padding="SAME"
)
path = ncsn_conv3x3(path, self.features, stride=1, bias=False)
x = path + x
return x
class RCUBlock(nn.Module):
"""RCUBlock for RefineNet. Used in NCSNv2."""
features: int
n_blocks: int
n_stages: int
act: Any = nn.relu
@nn.compact
def __call__(self, x):
for _ in range(self.n_blocks):
residual = x
for _ in range(self.n_stages):
x = self.act(x)
x = ncsn_conv3x3(x, self.features, stride=1, bias=False)
x = x + residual
return x
class CondRCUBlock(nn.Module):
"""Noise-conditional RCUBlock for RefineNet. Used in NCSNv1."""
features: int
n_blocks: int
n_stages: int
normalizer: Any
act: Any = nn.relu
@nn.compact
def __call__(self, x, y):
for _ in range(self.n_blocks):
residual = x
for _ in range(self.n_stages):
x = self.normalizer()(x, y)
x = self.act(x)
x = ncsn_conv3x3(x, self.features, stride=1, bias=False)
x += residual
return x
class MSFBlock(nn.Module):
"""MSFBlock for RefineNet. Used in NCSNv2."""
shape: Sequence[int]
features: int
interpolation: str = "bilinear"
@nn.compact
def __call__(self, xs):
sums = jnp.zeros((xs[0].shape[0], *self.shape, self.features))
for i in range(len(xs)):
h = ncsn_conv3x3(xs[i], self.features, stride=1, bias=True)
if self.interpolation == "bilinear":
h = jax.image.resize(
h, (h.shape[0], *self.shape, h.shape[-1]), "bilinear"
)
elif self.interpolation == "nearest_neighbor":
h = jax.image.resize(
h, (h.shape[0], *self.shape, h.shape[-1]), "nearest"
)
else:
raise ValueError(f"Interpolation {self.interpolation} does not exist!")
sums = sums + h
return sums
class CondMSFBlock(nn.Module):
"""Noise-conditional MSFBlock for RefineNet. Used in NCSNv1."""
shape: Sequence[int]
features: int
normalizer: Any
interpolation: str = "bilinear"
@nn.compact
def __call__(self, xs, y):
sums = jnp.zeros((xs[0].shape[0], *self.shape, self.features))
for i in range(len(xs)):
h = self.normalizer()(xs[i], y)
h = ncsn_conv3x3(h, self.features, stride=1, bias=True)
if self.interpolation == "bilinear":
h = jax.image.resize(
h, (h.shape[0], *self.shape, h.shape[-1]), "bilinear"
)
elif self.interpolation == "nearest_neighbor":
h = jax.image.resize(
h, (h.shape[0], *self.shape, h.shape[-1]), "nearest"
)
else:
raise ValueError(f"Interpolation {self.interpolation} does not exist")
sums = sums + h
return sums
class RefineBlock(nn.Module):
"""RefineBlock for building NCSNv2 RefineNet."""
output_shape: Sequence[int]
features: int
act: Any = nn.relu
interpolation: str = "bilinear"
start: bool = False
end: bool = False
@nn.compact
def __call__(self, xs):
rcu_block = functools.partial(RCUBlock, n_blocks=2, n_stages=2, act=self.act)
rcu_block_output = functools.partial(
RCUBlock,
features=self.features,
n_blocks=3 if self.end else 1,
n_stages=2,
act=self.act,
)
hs = []
for i in range(len(xs)):
h = rcu_block(features=xs[i].shape[-1])(xs[i])
hs.append(h)
if not self.start:
msf = functools.partial(
MSFBlock, features=self.features, interpolation=self.interpolation
)
h = msf(shape=self.output_shape)(hs)
else:
h = hs[0]
crp = functools.partial(
CRPBlock, features=self.features, n_stages=2, act=self.act
)
h = crp()(h)
h = rcu_block_output()(h)
return h
class CondRefineBlock(nn.Module):
"""Noise-conditional RefineBlock for building NCSNv1 RefineNet."""
output_shape: Sequence[int]
features: int
normalizer: Any
act: Any = nn.relu
interpolation: str = "bilinear"
start: bool = False
end: bool = False
@nn.compact
def __call__(self, xs, y):
rcu_block = functools.partial(
CondRCUBlock,
n_blocks=2,
n_stages=2,
act=self.act,
normalizer=self.normalizer,
)
rcu_block_output = functools.partial(
CondRCUBlock,
features=self.features,
n_blocks=3 if self.end else 1,
n_stages=2,
act=self.act,
normalizer=self.normalizer,
)
hs = []
for i in range(len(xs)):
h = rcu_block(features=xs[i].shape[-1])(xs[i], y)
hs.append(h)
if not self.start:
msf = functools.partial(
CondMSFBlock,
features=self.features,
interpolation=self.interpolation,
normalizer=self.normalizer,
)
h = msf(shape=self.output_shape)(hs, y)
else:
h = hs[0]
crp = functools.partial(
CondCRPBlock,
features=self.features,
n_stages=2,
act=self.act,
normalizer=self.normalizer,
)
h = crp()(h, y)
h = rcu_block_output()(h, y)
return h
class ConvMeanPool(nn.Module):
"""ConvMeanPool for building the ResNet backbone."""
output_dim: int
kernel_size: int = 3
biases: bool = True
@nn.compact
def __call__(self, inputs):
output = nn.Conv(
features=self.output_dim,
kernel_size=(self.kernel_size, self.kernel_size),
strides=(1, 1),
padding="SAME",
use_bias=self.biases,
)(inputs)
output = (
sum(
[
output[:, ::2, ::2, :],
output[:, 1::2, ::2, :],
output[:, ::2, 1::2, :],
output[:, 1::2, 1::2, :],
]
)
/ 4.0
)
return output
class MeanPoolConv(nn.Module):
"""MeanPoolConv for building the ResNet backbone."""
output_dim: int
kernel_size: int = 3
biases: bool = True
@nn.compact
def __call__(self, inputs):
output = inputs
output = (
sum(
[
output[:, ::2, ::2, :],
output[:, 1::2, ::2, :],
output[:, ::2, 1::2, :],
output[:, 1::2, 1::2, :],
]
)
/ 4.0
)
output = nn.Conv(
features=self.output_dim,
kernel_size=(self.kernel_size, self.kernel_size),
strides=(1, 1),
padding="SAME",
use_bias=self.biases,
)(output)
return output
class ResidualBlock(nn.Module):
"""The residual block for defining the ResNet backbone. Used in NCSNv2."""
output_dim: int
normalization: Any
resample: Optional[str] = None
act: Any = nn.elu
dilation: int = 1
@nn.compact
def __call__(self, x):
h = self.normalization()(x)
h = self.act(h)
if self.resample == "down":
h = ncsn_conv3x3(h, h.shape[-1], dilation=self.dilation)
h = self.normalization()(h)
h = self.act(h)
if self.dilation > 1:
h = ncsn_conv3x3(h, self.output_dim, dilation=self.dilation)
shortcut = ncsn_conv3x3(x, self.output_dim, dilation=self.dilation)
else:
h = ConvMeanPool(output_dim=self.output_dim)(h)
shortcut = ConvMeanPool(output_dim=self.output_dim, kernel_size=1)(x)
elif self.resample is None:
if self.dilation > 1:
if self.output_dim == x.shape[-1]:
shortcut = x
else:
shortcut = ncsn_conv3x3(x, self.output_dim, dilation=self.dilation)
h = ncsn_conv3x3(h, self.output_dim, dilation=self.dilation)
h = self.normalization()(h)
h = self.act(h)
h = ncsn_conv3x3(h, self.output_dim, dilation=self.dilation)
else:
if self.output_dim == x.shape[-1]:
shortcut = x
else:
shortcut = ncsn_conv1x1(x, self.output_dim)
h = ncsn_conv3x3(h, self.output_dim)
h = self.normalization()(h)
h = self.act(h)
h = ncsn_conv3x3(h, self.output_dim)
return h + shortcut
class ConditionalResidualBlock(nn.Module):
"""The noise-conditional residual block for building NCSNv1."""
output_dim: int
normalization: Any
resample: Optional[str] = None
act: Any = nn.elu
dilation: int = 1
@nn.compact
def __call__(self, x, y):
h = self.normalization()(x, y)
h = self.act(h)
if self.resample == "down":
h = ncsn_conv3x3(h, h.shape[-1], dilation=self.dilation)
h = self.normalization(h, y)
h = self.act(h)
if self.dilation > 1:
h = ncsn_conv3x3(h, self.output_dim, dilation=self.dilation)
shortcut = ncsn_conv3x3(x, self.output_dim, dilation=self.dilation)
else:
h = ConvMeanPool(output_dim=self.output_dim)(h)
shortcut = ConvMeanPool(output_dim=self.output_dim, kernel_size=1)(x)
elif self.resample is None:
if self.dilation > 1:
if self.output_dim == x.shape[-1]:
shortcut = x
else:
shortcut = ncsn_conv3x3(x, self.output_dim, dilation=self.dilation)
h = ncsn_conv3x3(h, self.output_dim, dilation=self.dilation)
h = self.normalization()(h, y)
h = self.act(h)
h = ncsn_conv3x3(h, self.output_dim, dilation=self.dilation)
else:
if self.output_dim == x.shape[-1]:
shortcut = x
else:
shortcut = ncsn_conv1x1(x, self.output_dim)
h = ncsn_conv3x3(h, self.output_dim)
h = self.normalization()(h, y)
h = self.act(h)
h = ncsn_conv3x3(h, self.output_dim)
return h + shortcut
###########################################################################
# Functions below are ported over from the DDPM codebase:
# https://github.com/hojonathanho/diffusion/blob/master/diffusion_tf/nn.py
###########################################################################
def get_timestep_embedding(timesteps, embedding_dim, max_positions=10000):
assert len(timesteps.shape) == 1 # and timesteps.dtype == tf.int32
half_dim = embedding_dim // 2
# magic number 10000 is from transformers
emb = math.log(max_positions) / (half_dim - 1)
# emb = math.log(2.) / (half_dim - 1)
emb = jnp.exp(jnp.arange(half_dim, dtype=jnp.float32) * -emb)
# emb = tf.range(num_embeddings, dtype=jnp.float32)[:, None] * emb[None, :]
# emb = tf.cast(timesteps, dtype=jnp.float32)[:, None] * emb[None, :]
emb = timesteps[:, None] * emb[None, :]
emb = jnp.concatenate([jnp.sin(emb), jnp.cos(emb)], axis=1)
if embedding_dim % 2 == 1: # zero pad
emb = jnp.pad(emb, [[0, 0], [0, 1]])
assert emb.shape == (timesteps.shape[0], embedding_dim)
return emb
class NIN(nn.Module):
num_units: int
init_scale: float = 0.1
@nn.compact
def __call__(self, x):
in_dim = int(x.shape[-1])
W = self.param(
"W", default_init(scale=self.init_scale), (in_dim, self.num_units)
)
b = self.param("b", jnn.initializers.zeros, (self.num_units,))
y = contract_inner(x, W) + b
assert y.shape == x.shape[:-1] + (self.num_units,)
return y
def _einsum(a, b, c, x, y):
einsum_str = "{},{}->{}".format("".join(a), "".join(b), "".join(c))
return jnp.einsum(einsum_str, x, y)
def contract_inner(x, y):
"""tensordot(x, y, 1)."""
x_chars = list(string.ascii_lowercase[: len(x.shape)])
y_chars = list(string.ascii_uppercase[: len(y.shape)])
assert len(x_chars) == len(x.shape) and len(y_chars) == len(y.shape)
y_chars[0] = x_chars[-1] # first axis of y and last of x get summed
out_chars = x_chars[:-1] + y_chars[1:]
return _einsum(x_chars, y_chars, out_chars, x, y)
class AttnBlock(nn.Module):
"""Channel-wise self-attention block."""
normalize: Any
@nn.compact
def __call__(self, x):
B, H, W, C = x.shape
h = self.normalize()(x)
q = NIN(C)(h)
k = NIN(C)(h)
v = NIN(C)(h)
w = jnp.einsum("bhwc,bHWc->bhwHW", q, k) * (int(C) ** (-0.5))
w = jnp.reshape(w, (B, H, W, H * W))
w = jax.nn.softmax(w, axis=-1)
w = jnp.reshape(w, (B, H, W, H, W))
h = jnp.einsum("bhwHW,bHWc->bhwc", w, v)
h = NIN(C, init_scale=0.0)(h)
return x + h
class Upsample(nn.Module):
with_conv: bool = False
@nn.compact
def __call__(self, x):
B, H, W, C = x.shape
h = jax.image.resize(x, (x.shape[0], H * 2, W * 2, C), "nearest")
if self.with_conv:
h = ddpm_conv3x3(h, C)
return h
class Downsample(nn.Module):
with_conv: bool = False
@nn.compact
def __call__(self, x):
B, H, W, C = x.shape
if self.with_conv:
x = ddpm_conv3x3(x, C, stride=2)
else:
x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2), padding="SAME")
assert x.shape == (B, H // 2, W // 2, C)
return x
class ResnetBlockDDPM(nn.Module):
"""The ResNet Blocks used in DDPM."""
act: Any
normalize: Any
out_ch: Optional[int] = None
conv_shortcut: bool = False
dropout: float = 0.5
@nn.compact
def __call__(self, x, temb=None, train=True):
B, H, W, C = x.shape
out_ch = self.out_ch if self.out_ch else C
h = self.act(self.normalize()(x))
h = ddpm_conv3x3(h, out_ch)
# Add bias to each feature map conditioned on the time embedding
if temb is not None:
h += nn.Dense(out_ch, kernel_init=default_init())(self.act(temb))[
:, None, None, :
]
h = self.act(self.normalize()(h))
h = nn.Dropout(self.dropout)(h, deterministic=not train)
h = ddpm_conv3x3(h, out_ch, init_scale=0.0)
if C != out_ch:
if self.conv_shortcut:
x = ddpm_conv3x3(x, out_ch)
else:
x = NIN(out_ch)(x)
return x + h