jcm/models/normalization.py (117 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.
"""Normalization layers."""
import flax.linen as nn
import functools
import jax.nn.initializers as init
import jax.numpy as jnp
def get_normalization(config, conditional=False):
"""Obtain normalization modules from the config file."""
norm = config.model.normalization
if conditional:
if norm == "InstanceNorm++":
return functools.partial(
ConditionalInstanceNorm2dPlus, num_classes=config.model.num_classes
)
else:
raise NotImplementedError(f"{norm} not implemented yet.")
else:
if norm == "InstanceNorm":
return InstanceNorm2d
elif norm == "InstanceNorm++":
return InstanceNorm2dPlus
elif norm == "VarianceNorm":
return VarianceNorm2d
elif norm == "GroupNorm":
return nn.GroupNorm
else:
raise ValueError("Unknown normalization: %s" % norm)
class VarianceNorm2d(nn.Module):
"""Variance normalization for images."""
bias: bool = False
@staticmethod
def scale_init(key, shape, dtype=jnp.float32):
normal_init = init.normal(0.02)
return normal_init(key, shape, dtype=dtype) + 1.0
@nn.compact
def __call__(self, x):
variance = jnp.var(x, axis=(1, 2), keepdims=True)
h = x / jnp.sqrt(variance + 1e-5)
h = h * self.param("scale", VarianceNorm2d.scale_init, (1, 1, 1, x.shape[-1]))
if self.bias:
h = h + self.param("bias", init.zeros, (1, 1, 1, x.shape[-1]))
return h
class InstanceNorm2d(nn.Module):
"""Instance normalization for images."""
bias: bool = True
@nn.compact
def __call__(self, x):
mean = jnp.mean(x, axis=(1, 2), keepdims=True)
variance = jnp.var(x, axis=(1, 2), keepdims=True)
h = (x - mean) / jnp.sqrt(variance + 1e-5)
h = h * self.param("scale", init.ones, (1, 1, 1, x.shape[-1]))
if self.bias:
h = h + self.param("bias", init.zeros, (1, 1, 1, x.shape[-1]))
return h
class InstanceNorm2dPlus(nn.Module):
"""InstanceNorm++ as proposed in the original NCSN paper."""
bias: bool = True
@staticmethod
def scale_init(key, shape, dtype=jnp.float32):
normal_init = init.normal(0.02)
return normal_init(key, shape, dtype=dtype) + 1.0
@nn.compact
def __call__(self, x):
means = jnp.mean(x, axis=(1, 2))
m = jnp.mean(means, axis=-1, keepdims=True)
v = jnp.var(means, axis=-1, keepdims=True)
means_plus = (means - m) / jnp.sqrt(v + 1e-5)
h = (x - means[:, None, None, :]) / jnp.sqrt(
jnp.var(x, axis=(1, 2), keepdims=True) + 1e-5
)
h = h + means_plus[:, None, None, :] * self.param(
"alpha", InstanceNorm2dPlus.scale_init, (1, 1, 1, x.shape[-1])
)
h = h * self.param(
"gamma", InstanceNorm2dPlus.scale_init, (1, 1, 1, x.shape[-1])
)
if self.bias:
h = h + self.param("beta", init.zeros, (1, 1, 1, x.shape[-1]))
return h
class ConditionalInstanceNorm2dPlus(nn.Module):
"""Conditional InstanceNorm++ as in the original NCSN paper."""
num_classes: int = 10
bias: bool = True
@nn.compact
def __call__(self, x, y):
means = jnp.mean(x, axis=(1, 2))
m = jnp.mean(means, axis=-1, keepdims=True)
v = jnp.var(means, axis=-1, keepdims=True)
means_plus = (means - m) / jnp.sqrt(v + 1e-5)
h = (x - means[:, None, None, :]) / jnp.sqrt(
jnp.var(x, axis=(1, 2), keepdims=True) + 1e-5
)
normal_init = init.normal(0.02)
zero_init = init.zeros
if self.bias:
def init_embed(key, shape, dtype=jnp.float32):
feature_size = shape[1] // 3
normal = (
normal_init(key, (shape[0], 2 * feature_size), dtype=dtype) + 1.0
)
zero = zero_init(key, (shape[0], feature_size), dtype=dtype)
return jnp.concatenate([normal, zero], axis=-1)
embed = nn.Embed(
num_embeddings=self.num_classes,
features=x.shape[-1] * 3,
embedding_init=init_embed,
)
else:
def init_embed(key, shape, dtype=jnp.float32):
return normal_init(key, shape, dtype=dtype) + 1.0
embed = nn.Embed(
num_embeddings=self.num_classes,
features=x.shape[-1] * 2,
embedding_init=init_embed,
)
if self.bias:
gamma, alpha, beta = jnp.split(embed(y), 3, axis=-1)
h = h + means_plus[:, None, None, :] * alpha[:, None, None, :]
out = gamma[:, None, None, :] * h + beta[:, None, None, :]
else:
gamma, alpha = jnp.split(embed(y), 2, axis=-1)
h = h + means_plus[:, None, None, :] * alpha[:, None, None, :]
out = gamma[:, None, None, :] * h
return out