chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:19:01 +08:00
commit 3b90d1192f
2172 changed files with 594509 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
"""
---
title: Normalization Layers
summary: >
A set of PyTorch implementations/tutorials of normalization layers.
---
# Normalization Layers
* [Batch Normalization](batch_norm/index.html)
* [Layer Normalization](layer_norm/index.html)
* [Instance Normalization](instance_norm/index.html)
* [Group Normalization](group_norm/index.html)
* [Weight Standardization](weight_standardization/index.html)
* [Batch-Channel Normalization](batch_channel_norm/index.html)
* [DeepNorm](deep_norm/index.html)
"""
@@ -0,0 +1,235 @@
"""
---
title: Batch-Channel Normalization
summary: >
A PyTorch implementation/tutorial of Batch-Channel Normalization.
---
# Batch-Channel Normalization
This is a [PyTorch](https://pytorch.org) implementation of Batch-Channel Normalization from the paper
[Micro-Batch Training with Batch-Channel Normalization and Weight Standardization](https://arxiv.org/abs/1903.10520).
We also have an [annotated implementation of Weight Standardization](../weight_standardization/index.html).
Batch-Channel Normalization performs batch normalization followed
by a channel normalization (similar to a [Group Normalization](../group_norm/index.html).
When the batch size is small a running mean and variance is used for
batch normalization.
Here is [the training code](../weight_standardization/experiment.html) for training
a VGG network that uses weight standardization to classify CIFAR-10 data.
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/weight_standardization/experiment.ipynb)
"""
import torch
from torch import nn
from labml_nn.normalization.batch_norm import BatchNorm
class BatchChannelNorm(nn.Module):
"""
## Batch-Channel Normalization
This first performs a batch normalization - either [normal batch norm](../batch_norm/index.html)
or a batch norm with
estimated mean and variance (exponential mean/variance over multiple batches).
Then a channel normalization performed.
"""
def __init__(self, channels: int, groups: int,
eps: float = 1e-5, momentum: float = 0.1, estimate: bool = True):
"""
* `channels` is the number of features in the input
* `groups` is the number of groups the features are divided into
* `eps` is $\epsilon$, used in $\sqrt{Var[x^{(k)}] + \epsilon}$ for numerical stability
* `momentum` is the momentum in taking the exponential moving average
* `estimate` is whether to use running mean and variance for batch norm
"""
super().__init__()
# Use estimated batch norm or normal batch norm.
if estimate:
self.batch_norm = EstimatedBatchNorm(channels,
eps=eps, momentum=momentum)
else:
self.batch_norm = BatchNorm(channels,
eps=eps, momentum=momentum)
# Channel normalization
self.channel_norm = ChannelNorm(channels, groups, eps)
def forward(self, x):
x = self.batch_norm(x)
return self.channel_norm(x)
class EstimatedBatchNorm(nn.Module):
"""
## Estimated Batch Normalization
When input $X \in \mathbb{R}^{B \times C \times H \times W}$ is a batch of image representations,
where $B$ is the batch size, $C$ is the number of channels, $H$ is the height and $W$ is the width.
$\gamma \in \mathbb{R}^{C}$ and $\beta \in \mathbb{R}^{C}$.
$$\dot{X}_{\cdot, C, \cdot, \cdot} = \gamma_C
\frac{X_{\cdot, C, \cdot, \cdot} - \hat{\mu}_C}{\hat{\sigma}_C}
+ \beta_C$$
where,
\begin{align}
\hat{\mu}_C &\longleftarrow (1 - r)\hat{\mu}_C + r \frac{1}{B H W} \sum_{b,h,w} X_{b,c,h,w} \\
\hat{\sigma}^2_C &\longleftarrow (1 - r)\hat{\sigma}^2_C + r \frac{1}{B H W} \sum_{b,h,w} \big(X_{b,c,h,w} - \hat{\mu}_C \big)^2
\end{align}
are the running mean and variances. $r$ is the momentum for calculating the exponential mean.
"""
def __init__(self, channels: int,
eps: float = 1e-5, momentum: float = 0.1, affine: bool = True):
"""
* `channels` is the number of features in the input
* `eps` is $\epsilon$, used in $\sqrt{Var[x^{(k)}] + \epsilon}$ for numerical stability
* `momentum` is the momentum in taking the exponential moving average
* `estimate` is whether to use running mean and variance for batch norm
"""
super().__init__()
self.eps = eps
self.momentum = momentum
self.affine = affine
self.channels = channels
# Channel wise transformation parameters
if self.affine:
self.scale = nn.Parameter(torch.ones(channels))
self.shift = nn.Parameter(torch.zeros(channels))
# Tensors for $\hat{\mu}_C$ and $\hat{\sigma}^2_C$
self.register_buffer('exp_mean', torch.zeros(channels))
self.register_buffer('exp_var', torch.ones(channels))
def forward(self, x: torch.Tensor):
"""
`x` is a tensor of shape `[batch_size, channels, *]`.
`*` denotes any number of (possibly 0) dimensions.
For example, in an image (2D) convolution this will be
`[batch_size, channels, height, width]`
"""
# Keep old shape
x_shape = x.shape
# Get the batch size
batch_size = x_shape[0]
# Sanity check to make sure the number of features is correct
assert self.channels == x.shape[1]
# Reshape into `[batch_size, channels, n]`
x = x.view(batch_size, self.channels, -1)
# Update $\hat{\mu}_C$ and $\hat{\sigma}^2_C$ in training mode only
if self.training:
# No backpropagation through $\hat{\mu}_C$ and $\hat{\sigma}^2_C$
with torch.no_grad():
# Calculate the mean across first and last dimensions;
# $$\frac{1}{B H W} \sum_{b,h,w} X_{b,c,h,w}$$
mean = x.mean(dim=[0, 2])
# Calculate the squared mean across first and last dimensions;
# $$\frac{1}{B H W} \sum_{b,h,w} X^2_{b,c,h,w}$$
mean_x2 = (x ** 2).mean(dim=[0, 2])
# Variance for each feature
# $$\frac{1}{B H W} \sum_{b,h,w} \big(X_{b,c,h,w} - \hat{\mu}_C \big)^2$$
var = mean_x2 - mean ** 2
# Update exponential moving averages
#
# \begin{align}
# \hat{\mu}_C &\longleftarrow (1 - r)\hat{\mu}_C + r \frac{1}{B H W} \sum_{b,h,w} X_{b,c,h,w} \\
# \hat{\sigma}^2_C &\longleftarrow (1 - r)\hat{\sigma}^2_C + r \frac{1}{B H W} \sum_{b,h,w} \big(X_{b,c,h,w} - \hat{\mu}_C \big)^2
# \end{align}
self.exp_mean = (1 - self.momentum) * self.exp_mean + self.momentum * mean
self.exp_var = (1 - self.momentum) * self.exp_var + self.momentum * var
# Normalize
# $$\frac{X_{\cdot, C, \cdot, \cdot} - \hat{\mu}_C}{\hat{\sigma}_C}$$
x_norm = (x - self.exp_mean.view(1, -1, 1)) / torch.sqrt(self.exp_var + self.eps).view(1, -1, 1)
# Scale and shift
# $$ \gamma_C
# \frac{X_{\cdot, C, \cdot, \cdot} - \hat{\mu}_C}{\hat{\sigma}_C}
# + \beta_C$$
if self.affine:
x_norm = self.scale.view(1, -1, 1) * x_norm + self.shift.view(1, -1, 1)
# Reshape to original and return
return x_norm.view(x_shape)
class ChannelNorm(nn.Module):
"""
## Channel Normalization
This is similar to [Group Normalization](../group_norm/index.html) but affine transform is done group wise.
"""
def __init__(self, channels, groups,
eps: float = 1e-5, affine: bool = True):
"""
* `groups` is the number of groups the features are divided into
* `channels` is the number of features in the input
* `eps` is $\epsilon$, used in $\sqrt{Var[x^{(k)}] + \epsilon}$ for numerical stability
* `affine` is whether to scale and shift the normalized value
"""
super().__init__()
self.channels = channels
self.groups = groups
self.eps = eps
self.affine = affine
# Parameters for affine transformation.
#
# *Note that these transforms are per group, unlike in group norm where
# they are transformed channel-wise.*
if self.affine:
self.scale = nn.Parameter(torch.ones(groups))
self.shift = nn.Parameter(torch.zeros(groups))
def forward(self, x: torch.Tensor):
"""
`x` is a tensor of shape `[batch_size, channels, *]`.
`*` denotes any number of (possibly 0) dimensions.
For example, in an image (2D) convolution this will be
`[batch_size, channels, height, width]`
"""
# Keep the original shape
x_shape = x.shape
# Get the batch size
batch_size = x_shape[0]
# Sanity check to make sure the number of features is the same
assert self.channels == x.shape[1]
# Reshape into `[batch_size, groups, n]`
x = x.view(batch_size, self.groups, -1)
# Calculate the mean across last dimension;
# i.e. the means for each sample and channel group $\mathbb{E}[x_{(i_N, i_G)}]$
mean = x.mean(dim=[-1], keepdim=True)
# Calculate the squared mean across last dimension;
# i.e. the means for each sample and channel group $\mathbb{E}[x^2_{(i_N, i_G)}]$
mean_x2 = (x ** 2).mean(dim=[-1], keepdim=True)
# Variance for each sample and feature group
# $Var[x_{(i_N, i_G)}] = \mathbb{E}[x^2_{(i_N, i_G)}] - \mathbb{E}[x_{(i_N, i_G)}]^2$
var = mean_x2 - mean ** 2
# Normalize
# $$\hat{x}_{(i_N, i_G)} =
# \frac{x_{(i_N, i_G)} - \mathbb{E}[x_{(i_N, i_G)}]}{\sqrt{Var[x_{(i_N, i_G)}] + \epsilon}}$$
x_norm = (x - mean) / torch.sqrt(var + self.eps)
# Scale and shift group-wise
# $$y_{i_G} =\gamma_{i_G} \hat{x}_{i_G} + \beta_{i_G}$$
if self.affine:
x_norm = self.scale.view(1, -1, 1) * x_norm + self.shift.view(1, -1, 1)
# Reshape to original and return
return x_norm.view(x_shape)
@@ -0,0 +1,225 @@
"""
---
title: Batch Normalization
summary: >
A PyTorch implementation/tutorial of batch normalization.
---
# Batch Normalization
This is a [PyTorch](https://pytorch.org) implementation of Batch Normalization from paper
[Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift](https://arxiv.org/abs/1502.03167).
### Internal Covariate Shift
The paper defines *Internal Covariate Shift* as the change in the
distribution of network activations due to the change in
network parameters during training.
For example, let's say there are two layers $l_1$ and $l_2$.
During the beginning of the training $l_1$ outputs (inputs to $l_2$)
could be in distribution $\mathcal{N}(0.5, 1)$.
Then, after some training steps, it could move to $\mathcal{N}(0.6, 1.5)$.
This is *internal covariate shift*.
Internal covariate shift will adversely affect training speed because the later layers
($l_2$ in the above example) have to adapt to this shifted distribution.
By stabilizing the distribution, batch normalization minimizes the internal covariate shift.
## Normalization
It is known that whitening improves training speed and convergence.
*Whitening* is linearly transforming inputs to have zero mean, unit variance,
and be uncorrelated.
### Normalizing outside gradient computation doesn't work
Normalizing outside the gradient computation using pre-computed (detached)
means and variances doesn't work. For instance. (ignoring variance), let
$$\hat{x} = x - \mathbb{E}[x]$$
where $x = u + b$ and $b$ is a trained bias
and $\mathbb{E}[x]$ is an outside gradient computation (pre-computed constant).
Note that $\hat{x}$ has no effect on $b$.
Therefore,
$b$ will increase or decrease based
$\frac{\partial{\mathcal{L}}}{\partial x}$,
and keep on growing indefinitely in each training update.
The paper notes that similar explosions happen with variances.
### Batch Normalization
Whitening is computationally expensive because you need to de-correlate and
the gradients must flow through the full whitening calculation.
The paper introduces a simplified version which they call *Batch Normalization*.
First simplification is that it normalizes each feature independently to have
zero mean and unit variance:
$$\hat{x}^{(k)} = \frac{x^{(k)} - \mathbb{E}[x^{(k)}]}{\sqrt{Var[x^{(k)}]}}$$
where $x = (x^{(1)} ... x^{(d)})$ is the $d$-dimensional input.
The second simplification is to use estimates of mean $\mathbb{E}[x^{(k)}]$
and variance $Var[x^{(k)}]$ from the mini-batch
for normalization; instead of calculating the mean and variance across the whole dataset.
Normalizing each feature to zero mean and unit variance could affect what the layer
can represent.
As an example paper illustrates that, if the inputs to a sigmoid are normalized
most of it will be within $[-1, 1]$ range where the sigmoid is linear.
To overcome this each feature is scaled and shifted by two trained parameters
$\gamma^{(k)}$ and $\beta^{(k)}$.
$$y^{(k)} =\gamma^{(k)} \hat{x}^{(k)} + \beta^{(k)}$$
where $y^{(k)}$ is the output of the batch normalization layer.
Note that when applying batch normalization after a linear transform
like $Wu + b$ the bias parameter $b$ gets cancelled due to normalization.
So you can and should omit bias parameter in linear transforms right before the
batch normalization.
Batch normalization also makes the back propagation invariant to the scale of the weights
and empirically it improves generalization, so it has regularization effects too.
## Inference
We need to know $\mathbb{E}[x^{(k)}]$ and $Var[x^{(k)}]$ in order to
perform the normalization.
So during inference, you either need to go through the whole (or part of) dataset
and find the mean and variance, or you can use an estimate calculated during training.
The usual practice is to calculate an exponential moving average of
mean and variance during the training phase and use that for inference.
Here's [the training code](mnist.html) and a notebook for training
a CNN classifier that uses batch normalization for MNIST dataset.
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/batch_norm/mnist.ipynb)
"""
import torch
from torch import nn
class BatchNorm(nn.Module):
r"""
## Batch Normalization Layer
Batch normalization layer $\text{BN}$ normalizes the input $X$ as follows:
When input $X \in \mathbb{R}^{B \times C \times H \times W}$ is a batch of image representations,
where $B$ is the batch size, $C$ is the number of channels, $H$ is the height and $W$ is the width.
$\gamma \in \mathbb{R}^{C}$ and $\beta \in \mathbb{R}^{C}$.
$$\text{BN}(X) = \gamma
\frac{X - \underset{B, H, W}{\mathbb{E}}[X]}{\sqrt{\underset{B, H, W}{Var}[X] + \epsilon}}
+ \beta$$
When input $X \in \mathbb{R}^{B \times C}$ is a batch of embeddings,
where $B$ is the batch size and $C$ is the number of features.
$\gamma \in \mathbb{R}^{C}$ and $\beta \in \mathbb{R}^{C}$.
$$\text{BN}(X) = \gamma
\frac{X - \underset{B}{\mathbb{E}}[X]}{\sqrt{\underset{B}{Var}[X] + \epsilon}}
+ \beta$$
When input $X \in \mathbb{R}^{B \times C \times L}$ is a batch of a sequence embeddings,
where $B$ is the batch size, $C$ is the number of features, and $L$ is the length of the sequence.
$\gamma \in \mathbb{R}^{C}$ and $\beta \in \mathbb{R}^{C}$.
$$\text{BN}(X) = \gamma
\frac{X - \underset{B, L}{\mathbb{E}}[X]}{\sqrt{\underset{B, L}{Var}[X] + \epsilon}}
+ \beta$$
"""
def __init__(self, channels: int, *,
eps: float = 1e-5, momentum: float = 0.1,
affine: bool = True, track_running_stats: bool = True):
"""
* `channels` is the number of features in the input
* `eps` is $\epsilon$, used in $\sqrt{Var[x^{(k)}] + \epsilon}$ for numerical stability
* `momentum` is the momentum in taking the exponential moving average
* `affine` is whether to scale and shift the normalized value
* `track_running_stats` is whether to calculate the moving averages or mean and variance
We've tried to use the same names for arguments as PyTorch `BatchNorm` implementation.
"""
super().__init__()
self.channels = channels
self.eps = eps
self.momentum = momentum
self.affine = affine
self.track_running_stats = track_running_stats
# Create parameters for $\gamma$ and $\beta$ for scale and shift
if self.affine:
self.scale = nn.Parameter(torch.ones(channels))
self.shift = nn.Parameter(torch.zeros(channels))
# Create buffers to store exponential moving averages of
# mean $\mathbb{E}[x^{(k)}]$ and variance $Var[x^{(k)}]$
if self.track_running_stats:
self.register_buffer('exp_mean', torch.zeros(channels))
self.register_buffer('exp_var', torch.ones(channels))
def forward(self, x: torch.Tensor):
"""
`x` is a tensor of shape `[batch_size, channels, *]`.
`*` denotes any number of (possibly 0) dimensions.
For example, in an image (2D) convolution this will be
`[batch_size, channels, height, width]`
"""
# Keep the original shape
x_shape = x.shape
# Get the batch size
batch_size = x_shape[0]
# Sanity check to make sure the number of features is the same
assert self.channels == x.shape[1]
# Reshape into `[batch_size, channels, n]`
x = x.view(batch_size, self.channels, -1)
# We will calculate the mini-batch mean and variance
# if we are in training mode or if we have not tracked exponential moving averages
if self.training or not self.track_running_stats:
# Calculate the mean across first and last dimension;
# i.e. the means for each feature $\mathbb{E}[x^{(k)}]$
mean = x.mean(dim=[0, 2])
# Calculate the squared mean across first and last dimension;
# i.e. the means for each feature $\mathbb{E}[(x^{(k)})^2]$
mean_x2 = (x ** 2).mean(dim=[0, 2])
# Variance for each feature $Var[x^{(k)}] = \mathbb{E}[(x^{(k)})^2] - \mathbb{E}[x^{(k)}]^2$
var = mean_x2 - mean ** 2
# Update exponential moving averages
if self.training and self.track_running_stats:
self.exp_mean = (1 - self.momentum) * self.exp_mean + self.momentum * mean
self.exp_var = (1 - self.momentum) * self.exp_var + self.momentum * var
# Use exponential moving averages as estimates
else:
mean = self.exp_mean
var = self.exp_var
# Normalize $$\hat{x}^{(k)} = \frac{x^{(k)} - \mathbb{E}[x^{(k)}]}{\sqrt{Var[x^{(k)}] + \epsilon}}$$
x_norm = (x - mean.view(1, -1, 1)) / torch.sqrt(var + self.eps).view(1, -1, 1)
# Scale and shift $$y^{(k)} =\gamma^{(k)} \hat{x}^{(k)} + \beta^{(k)}$$
if self.affine:
x_norm = self.scale.view(1, -1, 1) * x_norm + self.shift.view(1, -1, 1)
# Reshape to original and return
return x_norm.view(x_shape)
def _test():
"""
Simple test
"""
from labml.logger import inspect
x = torch.zeros([2, 3, 2, 4])
inspect(x.shape)
bn = BatchNorm(3)
x = bn(x)
inspect(x.shape)
inspect(bn.exp_var.shape)
#
if __name__ == '__main__':
_test()
@@ -0,0 +1,64 @@
"""
---
title: CIFAR10 Experiment to try Group Normalization
summary: >
This trains is a simple convolutional neural network that uses group normalization
to classify CIFAR10 images.
---
# CIFAR10 Experiment for Group Normalization
"""
import torch.nn as nn
from labml import experiment
from labml.configs import option
from labml_nn.experiments.cifar10 import CIFAR10Configs, CIFAR10VGGModel
from labml_nn.normalization.batch_norm import BatchNorm
class Model(CIFAR10VGGModel):
"""
### VGG model for CIFAR-10 classification
This derives from the [generic VGG style architecture](../../experiments/cifar10.html).
"""
def conv_block(self, in_channels, out_channels) -> nn.Module:
return nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
BatchNorm(out_channels),
nn.ReLU(inplace=True),
)
def __init__(self):
super().__init__([[64, 64], [128, 128], [256, 256, 256], [512, 512, 512], [512, 512, 512]])
@option(CIFAR10Configs.model)
def model(c: CIFAR10Configs):
"""
### Create model
"""
return Model().to(c.device)
def main():
# Create experiment
experiment.create(name='cifar10', comment='batch norm')
# Create configurations
conf = CIFAR10Configs()
# Load configurations
experiment.configs(conf, {
'optimizer.optimizer': 'Adam',
'optimizer.learning_rate': 2.5e-4,
'train_batch_size': 64,
})
# Start the experiment and run the training loop
with experiment.start():
conf.run()
#
if __name__ == '__main__':
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
"""
---
title: MNIST Experiment to try Batch Normalization
summary: >
This trains is a simple convolutional neural network that uses batch normalization
to classify MNIST digits.
---
# MNIST Experiment for Batch Normalization
"""
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.data
from labml import experiment
from labml.configs import option
from labml_nn.experiments.mnist import MNISTConfigs
from labml_nn.normalization.batch_norm import BatchNorm
class Model(nn.Module):
"""
### Model definition
"""
def __init__(self):
super().__init__()
# Note that we omit the bias parameter
self.conv1 = nn.Conv2d(1, 20, 5, 1, bias=False)
# Batch normalization with 20 channels (output of convolution layer).
# The input to this layer will have shape `[batch_size, 20, height(24), width(24)]`
self.bn1 = BatchNorm(20)
#
self.conv2 = nn.Conv2d(20, 50, 5, 1, bias=False)
# Batch normalization with 50 channels.
# The input to this layer will have shape `[batch_size, 50, height(8), width(8)]`
self.bn2 = BatchNorm(50)
#
self.fc1 = nn.Linear(4 * 4 * 50, 500, bias=False)
# Batch normalization with 500 channels (output of fully connected layer).
# The input to this layer will have shape `[batch_size, 500]`
self.bn3 = BatchNorm(500)
#
self.fc2 = nn.Linear(500, 10)
def forward(self, x: torch.Tensor):
x = F.relu(self.bn1(self.conv1(x)))
x = F.max_pool2d(x, 2, 2)
x = F.relu(self.bn2(self.conv2(x)))
x = F.max_pool2d(x, 2, 2)
x = x.view(-1, 4 * 4 * 50)
x = F.relu(self.bn3(self.fc1(x)))
return self.fc2(x)
@option(MNISTConfigs.model)
def model(c: MNISTConfigs):
"""
### Create model
We use [`MNISTConfigs`](../../experiments/mnist.html#MNISTConfigs) configurations
and set a new function to calculate the model.
"""
return Model().to(c.device)
def main():
# Create experiment
experiment.create(name='mnist_batch_norm')
# Create configurations
conf = MNISTConfigs()
# Load configurations
experiment.configs(conf, {
'optimizer.optimizer': 'Adam',
'optimizer.learning_rate': 0.001,
})
# Start the experiment and run the training loop
with experiment.start():
conf.run()
#
if __name__ == '__main__':
main()
@@ -0,0 +1,87 @@
# [Batch Normalization](https://nn.labml.ai/normalization/batch_norm/index.html)
This is a [PyTorch](https://pytorch.org) implementation of Batch Normalization from paper
[Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift](https://arxiv.org/abs/1502.03167).
### Internal Covariate Shift
The paper defines *Internal Covariate Shift* as the change in the
distribution of network activations due to the change in
network parameters during training.
For example, let's say there are two layers $l_1$ and $l_2$.
During the beginning of the training $l_1$ outputs (inputs to $l_2$)
could be in distribution $\mathcal{N}(0.5, 1)$.
Then, after some training steps, it could move to $\mathcal{N}(0.6, 1.5)$.
This is *internal covariate shift*.
Internal covariate shift will adversely affect training speed because the later layers
($l_2$ in the above example) have to adapt to this shifted distribution.
By stabilizing the distribution, batch normalization minimizes the internal covariate shift.
## Normalization
It is known that whitening improves training speed and convergence.
*Whitening* is linearly transforming inputs to have zero mean, unit variance,
and be uncorrelated.
### Normalizing outside gradient computation doesn't work
Normalizing outside the gradient computation using pre-computed (detached)
means and variances doesn't work. For instance. (ignoring variance), let
$$\hat{x} = x - \mathbb{E}[x]$$
where $x = u + b$ and $b$ is a trained bias
and $\mathbb{E}[x]$ is an outside gradient computation (pre-computed constant).
Note that $\hat{x}$ has no effect on $b$.
Therefore,
$b$ will increase or decrease based
$\frac{\partial{\mathcal{L}}}{\partial x}$,
and keep on growing indefinitely in each training update.
The paper notes that similar explosions happen with variances.
### Batch Normalization
Whitening is computationally expensive because you need to de-correlate and
the gradients must flow through the full whitening calculation.
The paper introduces a simplified version which they call *Batch Normalization*.
First simplification is that it normalizes each feature independently to have
zero mean and unit variance:
$$\hat{x}^{(k)} = \frac{x^{(k)} - \mathbb{E}[x^{(k)}]}{\sqrt{Var[x^{(k)}]}}$$
where $x = (x^{(1)} ... x^{(d)})$ is the $d$-dimensional input.
The second simplification is to use estimates of mean $\mathbb{E}[x^{(k)}]$
and variance $Var[x^{(k)}]$ from the mini-batch
for normalization; instead of calculating the mean and variance across the whole dataset.
Normalizing each feature to zero mean and unit variance could affect what the layer
can represent.
As an example paper illustrates that, if the inputs to a sigmoid are normalized
most of it will be within $[-1, 1]$ range where the sigmoid is linear.
To overcome this each feature is scaled and shifted by two trained parameters
$\gamma^{(k)}$ and $\beta^{(k)}$.
$$y^{(k)} =\gamma^{(k)} \hat{x}^{(k)} + \beta^{(k)}$$
where $y^{(k)}$ is the output of the batch normalization layer.
Note that when applying batch normalization after a linear transform
like $Wu + b$ the bias parameter $b$ gets cancelled due to normalization.
So you can and should omit bias parameter in linear transforms right before the
batch normalization.
Batch normalization also makes the back propagation invariant to the scale of the weights
and empirically it improves generalization, so it has regularization effects too.
## Inference
We need to know $\mathbb{E}[x^{(k)}]$ and $Var[x^{(k)}]$ in order to
perform the normalization.
So during inference, you either need to go through the whole (or part of) dataset
and find the mean and variance, or you can use an estimate calculated during training.
The usual practice is to calculate an exponential moving average of
mean and variance during the training phase and use that for inference.
Here's [the training code](mnist.html) and a notebook for training
a CNN classifier that uses batch normalization for MNIST dataset.
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/batch_norm/mnist.ipynb)
@@ -0,0 +1,173 @@
"""
---
title: DeepNorm
summary: >
A PyTorch implementation/tutorial of DeepNorm from paper DeepNet: Scaling Transformers to 1,000 Layers.
---
# DeepNorm
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/deep_norm/experiment.ipynb)
This is a [PyTorch](https://pytorch.org) implementation of
the DeepNorm from the paper
[DeepNet: Scaling Transformers to 1,000 Layers](https://arxiv.org/abs/2203.00555).
The paper proposes a method to stabilize extremely deep transformers through a new normalizing function
to replace LayerNorm and a weight initialization scheme.
This combines the performance of Post-LayerNorm and the stability of Pre-LayerNorm.
Transformers with DeepNorms are supposed to be stable even without a learning rate warm-up.
The paper first shows that the changes to layer outputs (for the same input)
change gradually during stable training;
when unstable it changes rapidly during the initial training steps.
This happens with initializing weights to small values, and learning rate warm-ups where the
training is stable.
They use the idea of keeping the changes to layer outputs small to derive the new
normalization and weight initialization mechanism.
## Weight Initializations
Usually, the weights are initialized with Xavier or Kaiming initializations.
This paper scales (sets the gain) the weights by a constant $\beta$ depending on the size of the
transformer.
DeepNorm suggests scaling the weights of the two linear transforms in the
[Feed-Forward Network](../../transformers/feed_forward.html),
the value projection transform, and the output projection transform of the
attention layer.
Weights of these transforms are scaled by (has a gain equal to) $\beta$.
The scaling is implemented in the
## Normalization Function
$$x_{l + 1} = \mathop{LN}\Big( \alpha x_l + \mathop{G}_l \big(x_l, \theta_l \big)\Big)$$
where $\alpha$ is a constant that depends on the depth of the transformer,
$\mathop{LN}$ is [Layer Normalization](../layer_norm/index.html), and
$\mathop{G}_l (x_l, \theta_l)$ is the function of the $l$-th transformer sub-layer (FFN or attention).
This function is used to replace Post-LayerNorm.
## $\alpha$ and $\beta$ constants
\begin{align}
\begin{array} {c|cc|cc}
\text{Type} & \text{Enc-} \alpha & \text{Enc-} \beta & \text{Dec-} \alpha & \text{Dec-} \beta \\
\hline \\
\text{Encoder only} & (2N)^{\frac{1}{4}} & (8N)^{-\frac{1}{4}} & - & - \\
\text{Decoder only} & - & - & (2M)^{\frac{1}{4}} & (8M)^{-\frac{1}{4}} \\
\text{Enc-Dec} & 0.81 (N^4M)^{\frac{1}{16}} & 0.87 (N^4 M)^{-\frac{1}{16}} &
(3M)^{\frac{1}{4}} & (12M)^{-\frac{1}{4}} \\
\end{array}
\end{align}
Where $N$ is the number of layers in the encoder and $M$ is the number of layers in the decoder.
Refer to [the paper](https://arxiv.org/abs/2203.00555) for derivation.
[Here is an experiment implementation](experiment.html) that uses DeepNorm.
"""
from typing import Union, List
import torch
from torch import nn, Size
from labml_nn.normalization.layer_norm import LayerNorm
from labml_nn.transformers import MultiHeadAttention
from labml_nn.transformers.feed_forward import FeedForward
from labml_nn.transformers.utils import subsequent_mask
class DeepNorm(nn.Module):
"""
## DeepNorm Normalization
$$x_{l + 1} = \mathop{LN}\Big( \alpha x_l + \mathop{G}_l \big(x_l, \theta_l \big)\Big)$$
"""
def __init__(self, alpha: float, normalized_shape: Union[int, List[int], Size], *,
eps: float = 1e-5,
elementwise_affine: bool = True):
"""
:param alpha: is $\alpha$
:param normalized_shape: is the shape for LayerNorm $\mathop{LN}$
:param eps: is $\epsilon$ for LayerNorm
:param elementwise_affine: is a flag indicating whether to do an elementwise transformation in LayerNorm
"""
super().__init__()
self.alpha = alpha
# Initialize $\mathop{LN}$
self.layer_norm = LayerNorm(normalized_shape, eps=eps, elementwise_affine=elementwise_affine)
def forward(self, x: torch.Tensor, gx: torch.Tensor):
"""
:param x: is the output from the previous layer $x_l$
:param gx: is the output of the current sub-layer $\mathop{G}_l (x_l, \theta_l)$
"""
# $$x_{l + 1} = \mathop{LN}\Big( \alpha x_l + \mathop{G}_l \big(x_l, \theta_l \big)\Big)$$
return self.layer_norm(x + self.alpha * gx)
class DeepNormTransformerLayer(nn.Module):
"""
## Transformer Decoder Layer with DeepNorm
This implements a transformer decoder layer with DeepNorm.
Encoder layers will have a similar form.
"""
def __init__(self, *,
d_model: int,
self_attn: MultiHeadAttention,
feed_forward: FeedForward,
deep_norm_alpha: float,
deep_norm_beta: float,
):
"""
:param d_model: is the token embedding size
:param self_attn: is the self attention module
:param feed_forward: is the feed forward module
:param deep_norm_alpha: is $\alpha$ coefficient in DeepNorm
:param deep_norm_beta: is $\beta$ constant for scaling weights initialization
"""
super().__init__()
self.self_attn = self_attn
self.feed_forward = feed_forward
# DeepNorms after attention and feed forward network
self.self_attn_norm = DeepNorm(deep_norm_alpha, [d_model])
self.feed_forward_norm = DeepNorm(deep_norm_alpha, [d_model])
# Scale weights after initialization
with torch.no_grad():
# Feed forward network linear transformations
feed_forward.layer1.weight *= deep_norm_beta
feed_forward.layer2.weight *= deep_norm_beta
# Attention value projection
self_attn.value.linear.weight *= deep_norm_beta
# Attention output project
self_attn.output.weight *= deep_norm_beta
# The mask will be initialized on the first call
self.mask = None
def forward(self, x: torch.Tensor):
"""
:param x: are the embeddings of shape `[seq_len, batch_size, d_model]`
"""
# Create causal mask
if self.mask is None or self.mask.size(0) != len(x):
# Subsequent mask, will mask out tokens from seeing future tokens
self.mask = subsequent_mask(len(x)).to(x.device)
# Run through self attention, i.e. keys and values are from self
x = self.self_attn_norm(x, self.self_attn(query=x, key=x, value=x, mask=self.mask))
# Pass through the feed-forward network
x = self.feed_forward_norm(x, self.feed_forward(x))
#
return x
@@ -0,0 +1,274 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "AYV_dMVDxyc2",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"[![Github](https://img.shields.io/github/stars/labmlai/annotated_deep_learning_paper_implementations?style=social)](https://github.com/labmlai/annotated_deep_learning_paper_implementations)\n",
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/deep_norm/experiment.ipynb)\n",
"\n",
"## DeepNorm\n",
"\n",
"This is an experiment training Shakespeare dataset with a deep transformer using [DeepNorm](https://nn.labml.ai/normalization/deep_norm/index.html)."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "AahG_i2y5tY9",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Install the packages"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "ZCzmCrAIVg0L",
"outputId": "cf107fb2-4d50-4c67-af34-367624553421",
"pycharm": {
"name": "#%%\n"
}
},
"source": [
"!pip install labml-nn --quiet"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "SE2VUQ6L5zxI",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Imports"
]
},
{
"cell_type": "code",
"metadata": {
"id": "0hJXx_g0wS2C",
"pycharm": {
"name": "#%%\n"
}
},
"source": [
"from labml import experiment\n",
"from labml_nn.normalization.deep_norm.experiment import Configs"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "Lpggo0wM6qb-",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Create an experiment"
]
},
{
"cell_type": "code",
"metadata": {
"id": "bFcr9k-l4cAg",
"pycharm": {
"name": "#%%\n"
}
},
"source": [
"experiment.create(name=\"deep_norm\", writers={'screen'})"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "-OnHLi626tJt",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Configurations"
]
},
{
"cell_type": "code",
"metadata": {
"id": "Piz0c5f44hRo",
"pycharm": {
"name": "#%%\n"
}
},
"source": [
"conf = Configs()"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "wwMzCqpD6vkL",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"Set experiment configurations and assign a configurations dictionary to override configurations"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 17
},
"id": "e6hmQhTw4nks",
"outputId": "29634715-42f4-4405-fb11-fc9522608627",
"pycharm": {
"name": "#%%\n"
}
},
"source": [
"experiment.configs(conf, {\n",
" # Use character level tokenizer\n",
" 'tokenizer': 'character',\n",
" # Prompt separator is blank\n",
" 'prompt_separator': '',\n",
" # Starting prompt for sampling\n",
" 'prompt': 'It is ',\n",
" # Use Tiny Shakespeare dataset\n",
" 'text': 'tiny_shakespeare',\n",
"\n",
" # Use a context size of $256$\n",
" 'seq_len': 256,\n",
" # Train for 32 epochs\n",
" 'epochs': 32,\n",
" # Batch size $16$\n",
" 'batch_size': 16,\n",
" # Switch between training and validation for $10$ times per epoch\n",
" 'inner_iterations': 10,\n",
"\n",
" # Adam optimizer with no warmup\n",
" 'optimizer.optimizer': 'Adam',\n",
" 'optimizer.learning_rate': 3e-4,\n",
"})"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "EvI7MtgJ61w5",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"Set PyTorch models for loading and saving"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 255
},
"id": "GDlt7dp-5ALt",
"outputId": "e7548e8f-c541-4618-dc5a-1597cae42003",
"pycharm": {
"name": "#%%\n"
}
},
"source": [
"experiment.add_pytorch_models({'model': conf.model})"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "KJZRf8527GxL",
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Start the experiment and run the training loop."
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 1000
},
"id": "aIAWo7Fw5DR8",
"outputId": "db979785-bfe3-4eda-d3eb-8ccbe61053e5",
"pycharm": {
"name": "#%%\n"
}
},
"source": [
"# Start the experiment\n",
"with experiment.start():\n",
" conf.run()"
],
"outputs": [],
"execution_count": null
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"collapsed_sections": [],
"name": "DeepNorm",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.11"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -0,0 +1,175 @@
"""
---
title: DeepNorm Experiment
summary: >
Training a DeepNorm transformer on Tiny Shakespeare.
---
# [DeepNorm](index.html) Experiment
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/deep_norm/experiment.ipynb)
"""
import copy
import torch
import torch.nn as nn
from labml import experiment
from labml.configs import option
from labml_nn.experiments.nlp_autoregression import NLPAutoRegressionConfigs
from labml_nn.normalization.deep_norm import DeepNormTransformerLayer
from labml_nn.transformers import MultiHeadAttention
from labml_nn.transformers.feed_forward import FeedForward
class AutoregressiveTransformer(nn.Module):
"""
## Auto-Regressive model
This is a autoregressive transformer model that uses DeepNorm.
"""
def __init__(self, n_tokens: int, d_model: int, n_layers: int, layer: DeepNormTransformerLayer):
"""
:param n_tokens: is the number of tokens in the vocabulary
:param d_model: is the embedding size
:param n_layers: is the number of transformer layers
:param layer: is the layer. We use `n_layers` copies of this for the tranformer.
"""
super().__init__()
# Transformer with `n_layers` layers
self.transformer = nn.Sequential(*[copy.deepcopy(layer) for _ in range(n_layers)])
# Token embedding layer
self.emb = nn.Embedding(n_tokens, d_model)
# Readout layer
self.readout = nn.Linear(d_model, n_tokens)
def forward(self, x: torch.Tensor):
"""
:param x: are the input tokens of shape `[seq_len, batch_size]`
"""
# Get the token embeddings
x = self.emb(x)
# Transformer encoder
x = self.transformer(x)
# Get logits
x = self.readout(x)
# Return results
return x, None
class Configs(NLPAutoRegressionConfigs):
"""
## Configurations
This inherits from
[`NLPAutoRegressionConfigs`](../../experiments/nlp_autoregression.html#NLPAutoRegressionConfigs)
"""
# Model
model: AutoregressiveTransformer
# Number of layers
n_layers: int = 32
# $\alpha$ and $\beta$ for DeepNorm
deep_norm_alpha: float
deep_norm_beta: float
# Number of heads in the attention
n_heads: int = 4
# Embedding size
d_model: int = 64
# Size of each attention head
d_k: int = 16
@option(Configs.deep_norm_alpha)
def _deep_norm_alpha(c: Configs):
"""
#### Calculate $\alpha$
$\alpha = (2M)^{\frac{1}{4}}$
"""
return (2. * c.n_layers) ** (1. / 4.)
@option(Configs.deep_norm_beta)
def _deep_norm_beta(c: Configs):
"""
#### Calculate $\beta$
$\beta = (8M)^{-\frac{1}{4}}$
"""
return (8. * c.n_layers) ** -(1. / 4.)
@option(Configs.model)
def _model(c: Configs):
"""
#### Initialize the model
"""
m = AutoregressiveTransformer(c.n_tokens, c.d_model, c.n_layers,
DeepNormTransformerLayer(d_model=c.d_model,
deep_norm_alpha=c.deep_norm_alpha,
deep_norm_beta=c.deep_norm_beta,
feed_forward=FeedForward(d_model=c.d_model,
d_ff=c.d_model * 4),
self_attn=MultiHeadAttention(c.n_heads, c.d_model,
dropout_prob=0.0)))
return m.to(c.device)
def main():
"""
#### Create and run the experiment
"""
# Create experiment
experiment.create(name="deep_norm", writers={'screen', 'web_api'})
# Create configs
conf = Configs()
# Override configurations
experiment.configs(conf, {
# Use character level tokenizer
'tokenizer': 'character',
# Prompt separator is blank
'prompt_separator': '',
# Starting prompt for sampling
'prompt': 'It is ',
# Use Tiny Shakespeare dataset
'text': 'tiny_shakespeare',
# Use a context size of $256$
'seq_len': 256,
# Train for 32 epochs
'epochs': 32,
# Batch size $16$
'batch_size': 16,
# Switch between training and validation for $10$ times per epoch
'inner_iterations': 10,
# Number of layers
'n_layers': 50,
# Adam optimizer with no warmup
'optimizer.optimizer': 'Adam',
'optimizer.learning_rate': 1.25e-4,
})
# Set model(s) for saving and loading
experiment.add_pytorch_models({'model': conf.model})
# Start the experiment
with experiment.start():
# Run training
conf.run()
#
if __name__ == '__main__':
main()
@@ -0,0 +1,173 @@
"""
---
title: Group Normalization
summary: >
A PyTorch implementation/tutorial of group normalization.
---
# Group Normalization
This is a [PyTorch](https://pytorch.org) implementation of
the [Group Normalization](https://arxiv.org/abs/1803.08494) paper.
[Batch Normalization](../batch_norm/index.html) works well for large enough batch sizes
but not well for small batch sizes, because it normalizes over the batch.
Training large models with large batch sizes is not possible due to the memory capacity of the
devices.
This paper introduces Group Normalization, which normalizes a set of features together as a group.
This is based on the observation that classical features such as
[SIFT](https://en.wikipedia.org/wiki/Scale-invariant_feature_transform) and
[HOG](https://en.wikipedia.org/wiki/Histogram_of_oriented_gradients) are group-wise features.
The paper proposes dividing feature channels into groups and then separately normalizing
all channels within each group.
## Formulation
All normalization layers can be defined by the following computation.
$$\hat{x}_i = \frac{1}{\sigma_i} (x_i - \mu_i)$$
where $x$ is the tensor representing the batch,
and $i$ is the index of a single value.
For instance, when it's 2D images
$i = (i_N, i_C, i_H, i_W)$ is a 4-d vector for indexing
image within batch, feature channel, vertical coordinate and horizontal coordinate.
$\mu_i$ and $\sigma_i$ are mean and standard deviation.
\begin{align}
\mu_i &= \frac{1}{m} \sum_{k \in \mathcal{S}_i} x_k \\
\sigma_i &= \sqrt{\frac{1}{m} \sum_{k \in \mathcal{S}_i} (x_k - \mu_i)^2 + \epsilon}
\end{align}
$\mathcal{S}_i$ is the set of indexes across which the mean and standard deviation
are calculated for index $i$.
$m$ is the size of the set $\mathcal{S}_i$ which is the same for all $i$.
The definition of $\mathcal{S}_i$ is different for
[Batch normalization](../batch_norm/index.html),
[Layer normalization](../layer_norm/index.html), and
[Instance normalization](../instance_norm/index.html).
### [Batch Normalization](../batch_norm/index.html)
$$\mathcal{S}_i = \{k | k_C = i_C\}$$
The values that share the same feature channel are normalized together.
### [Layer Normalization](../layer_norm/index.html)
$$\mathcal{S}_i = \{k | k_N = i_N\}$$
The values from the same sample in the batch are normalized together.
### [Instance Normalization](../instance_norm/index.html)
$$\mathcal{S}_i = \{k | k_N = i_N, k_C = i_C\}$$
The values from the same sample and same feature channel are normalized together.
### Group Normalization
$$\mathcal{S}_i = \{k | k_N = i_N,
\bigg \lfloor \frac{k_C}{C/G} \bigg \rfloor = \bigg \lfloor \frac{i_C}{C/G} \bigg \rfloor\}$$
where $G$ is the number of groups and $C$ is the number of channels.
Group normalization normalizes values of the same sample and the same group of channels together.
Here's a [CIFAR 10 classification model](experiment.html) that uses instance normalization.
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/group_norm/experiment.ipynb)
"""
import torch
from torch import nn
class GroupNorm(nn.Module):
r"""
## Group Normalization Layer
"""
def __init__(self, groups: int, channels: int, *,
eps: float = 1e-5, affine: bool = True):
"""
* `groups` is the number of groups the features are divided into
* `channels` is the number of features in the input
* `eps` is $\epsilon$, used in $\sqrt{Var[x^{(k)}] + \epsilon}$ for numerical stability
* `affine` is whether to scale and shift the normalized value
"""
super().__init__()
assert channels % groups == 0, "Number of channels should be evenly divisible by the number of groups"
self.groups = groups
self.channels = channels
self.eps = eps
self.affine = affine
# Create parameters for $\gamma$ and $\beta$ for scale and shift
if self.affine:
self.scale = nn.Parameter(torch.ones(channels))
self.shift = nn.Parameter(torch.zeros(channels))
def forward(self, x: torch.Tensor):
"""
`x` is a tensor of shape `[batch_size, channels, *]`.
`*` denotes any number of (possibly 0) dimensions.
For example, in an image (2D) convolution this will be
`[batch_size, channels, height, width]`
"""
# Keep the original shape
x_shape = x.shape
# Get the batch size
batch_size = x_shape[0]
# Sanity check to make sure the number of features is the same
assert self.channels == x.shape[1]
# Reshape into `[batch_size, groups, n]`
x = x.view(batch_size, self.groups, -1)
# Calculate the mean across last dimension;
# i.e. the means for each sample and channel group $\mathbb{E}[x_{(i_N, i_G)}]$
mean = x.mean(dim=[-1], keepdim=True)
# Calculate the squared mean across last dimension;
# i.e. the means for each sample and channel group $\mathbb{E}[x^2_{(i_N, i_G)}]$
mean_x2 = (x ** 2).mean(dim=[-1], keepdim=True)
# Variance for each sample and feature group
# $Var[x_{(i_N, i_G)}] = \mathbb{E}[x^2_{(i_N, i_G)}] - \mathbb{E}[x_{(i_N, i_G)}]^2$
var = mean_x2 - mean ** 2
# Normalize
# $$\hat{x}_{(i_N, i_G)} =
# \frac{x_{(i_N, i_G)} - \mathbb{E}[x_{(i_N, i_G)}]}{\sqrt{Var[x_{(i_N, i_G)}] + \epsilon}}$$
x_norm = (x - mean) / torch.sqrt(var + self.eps)
# Scale and shift channel-wise
# $$y_{i_C} =\gamma_{i_C} \hat{x}_{i_C} + \beta_{i_C}$$
if self.affine:
x_norm = x_norm.view(batch_size, self.channels, -1)
x_norm = self.scale.view(1, -1, 1) * x_norm + self.shift.view(1, -1, 1)
# Reshape to original and return
return x_norm.view(x_shape)
def _test():
"""
Simple test
"""
from labml.logger import inspect
x = torch.zeros([2, 6, 2, 4])
inspect(x.shape)
bn = GroupNorm(2, 6)
x = bn(x)
inspect(x.shape)
#
if __name__ == '__main__':
_test()
@@ -0,0 +1,451 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "AYV_dMVDxyc2"
},
"source": [
"[![Github](https://img.shields.io/github/stars/labmlai/annotated_deep_learning_paper_implementations?style=social)](https://github.com/labmlai/annotated_deep_learning_paper_implementations)\n",
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/group_norm/experiment.ipynb) \n",
"\n",
"## Group Norm - CIFAR 10\n",
"\n",
"This is an experiment training a model with group norm to classify CIFAR-10 dataset."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "AahG_i2y5tY9"
},
"source": [
"Install the `labml-nn` package."
]
},
{
"cell_type": "code",
"metadata": {
"id": "ZCzmCrAIVg0L"
},
"source": [
"!pip install labml-nn"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "SE2VUQ6L5zxI"
},
"source": [
"Imports"
]
},
{
"cell_type": "code",
"metadata": {
"id": "0hJXx_g0wS2C"
},
"source": [
"import torch\n",
"import torch.nn as nn\n",
"\n",
"from labml import experiment\n",
"from labml_nn.normalization.group_norm.experiment import Configs"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "Lpggo0wM6qb-"
},
"source": [
"Create an experiment"
]
},
{
"cell_type": "code",
"metadata": {
"id": "bFcr9k-l4cAg"
},
"source": [
"experiment.create(name=\"cifar10\", comment=\"group norm\")"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "-OnHLi626tJt"
},
"source": [
"Initialize configurations"
]
},
{
"cell_type": "code",
"metadata": {
"id": "Piz0c5f44hRo"
},
"source": [
"conf = Configs()"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "wwMzCqpD6vkL"
},
"source": [
"Set experiment configurations and assign a configurations dictionary to override configurations"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 17
},
"id": "e6hmQhTw4nks",
"outputId": "50ad9e07-84f4-47cf-9d26-034448eb611b"
},
"source": [
"experiment.configs(conf, {\n",
" 'optimizer.optimizer': 'Adam',\n",
" 'optimizer.learning_rate': 2.5e-4,\n",
"})"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "KJZRf8527GxL"
},
"source": [
"Start the experiment and run the training loop."
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 882,
"referenced_widgets": [
"14841e99103e41f69dd9b709301d3204",
"60f10c7bff5c4eea845a14f3f3075e8d",
"cf2f7c0f10454901bc5b48872b364dbf",
"8755fc8b0f6b40b3b08822e0a705d403",
"c6c192b58fa242008fbc2983c7866c5f",
"6e5c4becab6b40aaafce1a4575d3199c",
"328dbbbc3cdb4163896913308059c23c",
"3bff44b4205f40119715fae60d4a04a9"
]
},
"id": "aIAWo7Fw5DR8",
"outputId": "2e82cce8-eaad-4cab-88d3-efd5d095b5b7"
},
"source": [
"with experiment.start():\n",
" conf.run()"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"metadata": {
"id": "oBXXlP2b7XZO"
},
"source": [],
"outputs": [],
"execution_count": null
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"collapsed_sections": [],
"name": "Group Norm",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"14841e99103e41f69dd9b709301d3204": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_cf2f7c0f10454901bc5b48872b364dbf",
"IPY_MODEL_8755fc8b0f6b40b3b08822e0a705d403"
],
"layout": "IPY_MODEL_60f10c7bff5c4eea845a14f3f3075e8d"
}
},
"328dbbbc3cdb4163896913308059c23c": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
},
"3bff44b4205f40119715fae60d4a04a9": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": null,
"bottom": null,
"display": null,
"flex": null,
"flex_flow": null,
"grid_area": null,
"grid_auto_columns": null,
"grid_auto_flow": null,
"grid_auto_rows": null,
"grid_column": null,
"grid_gap": null,
"grid_row": null,
"grid_template_areas": null,
"grid_template_columns": null,
"grid_template_rows": null,
"height": null,
"justify_content": null,
"justify_items": null,
"left": null,
"margin": null,
"max_height": null,
"max_width": null,
"min_height": null,
"min_width": null,
"object_fit": null,
"object_position": null,
"order": null,
"overflow": null,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"60f10c7bff5c4eea845a14f3f3075e8d": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": null,
"bottom": null,
"display": null,
"flex": null,
"flex_flow": null,
"grid_area": null,
"grid_auto_columns": null,
"grid_auto_flow": null,
"grid_auto_rows": null,
"grid_column": null,
"grid_gap": null,
"grid_row": null,
"grid_template_areas": null,
"grid_template_columns": null,
"grid_template_rows": null,
"height": null,
"justify_content": null,
"justify_items": null,
"left": null,
"margin": null,
"max_height": null,
"max_width": null,
"min_height": null,
"min_width": null,
"object_fit": null,
"object_position": null,
"order": null,
"overflow": null,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"6e5c4becab6b40aaafce1a4575d3199c": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": null,
"bottom": null,
"display": null,
"flex": null,
"flex_flow": null,
"grid_area": null,
"grid_auto_columns": null,
"grid_auto_flow": null,
"grid_auto_rows": null,
"grid_column": null,
"grid_gap": null,
"grid_row": null,
"grid_template_areas": null,
"grid_template_columns": null,
"grid_template_rows": null,
"height": null,
"justify_content": null,
"justify_items": null,
"left": null,
"margin": null,
"max_height": null,
"max_width": null,
"min_height": null,
"min_width": null,
"object_fit": null,
"object_position": null,
"order": null,
"overflow": null,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"8755fc8b0f6b40b3b08822e0a705d403": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_3bff44b4205f40119715fae60d4a04a9",
"placeholder": "",
"style": "IPY_MODEL_328dbbbc3cdb4163896913308059c23c",
"value": " 170499072/? [00:03<00:00, 54808451.08it/s]"
}
},
"c6c192b58fa242008fbc2983c7866c5f": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": "initial"
}
},
"cf2f7c0f10454901bc5b48872b364dbf": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_6e5c4becab6b40aaafce1a4575d3199c",
"max": 170498071,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_c6c192b58fa242008fbc2983c7866c5f",
"value": 170498071
}
}
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -0,0 +1,68 @@
"""
---
title: CIFAR10 Experiment to try Group Normalization
summary: >
This trains is a simple convolutional neural network that uses group normalization
to classify CIFAR10 images.
---
# CIFAR10 Experiment for Group Normalization
"""
import torch.nn as nn
from labml import experiment
from labml.configs import option
from labml_nn.experiments.cifar10 import CIFAR10Configs, CIFAR10VGGModel
class Model(CIFAR10VGGModel):
"""
### VGG model for CIFAR-10 classification
This derives from the [generic VGG style architecture](../../experiments/cifar10.html).
"""
def conv_block(self, in_channels, out_channels) -> nn.Module:
return nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
fnorm.GroupNorm(self.groups, out_channels), # new
nn.ReLU(inplace=True),
)
def __init__(self, groups: int = 32):
self.groups = groups # input param:groups to conv_block
super().__init__([[64, 64], [128, 128], [256, 256, 256], [512, 512, 512], [512, 512, 512]])
class Configs(CIFAR10Configs):
# Number of groups
groups: int = 16
@option(Configs.model)
def model(c: Configs):
"""
### Create model
"""
return Model(c.groups).to(c.device)
def main():
# Create experiment
experiment.create(name='cifar10', comment='group norm')
# Create configurations
conf = Configs()
# Load configurations
experiment.configs(conf, {
'optimizer.optimizer': 'Adam',
'optimizer.learning_rate': 2.5e-4,
})
# Start the experiment and run the training loop
with experiment.start():
conf.run()
#
if __name__ == '__main__':
main()
@@ -0,0 +1,20 @@
# [Group Normalization](https://nn.labml.ai/normalization/group_norm/index.html)
This is a [PyTorch](https://pytorch.org) implementation of
the [Group Normalization](https://arxiv.org/abs/1803.08494) paper.
[Batch Normalization](https://nn.labml.ai/normalization/batch_norm/index.html) works well for large enough batch sizes
but not well for small batch sizes, because it normalizes over the batch.
Training large models with large batch sizes is not possible due to the memory capacity of the
devices.
This paper introduces Group Normalization, which normalizes a set of features together as a group.
This is based on the observation that classical features such as
[SIFT](https://en.wikipedia.org/wiki/Scale-invariant_feature_transform) and
[HOG](https://en.wikipedia.org/wiki/Histogram_of_oriented_gradients) are group-wise features.
The paper proposes dividing feature channels into groups and then separately normalizing
all channels within each group.
Here's a [CIFAR 10 classification model](https://nn.labml.ai/normalization/group_norm/experiment.html) that uses group normalization.
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/group_norm/experiment.ipynb)
@@ -0,0 +1,122 @@
"""
---
title: Instance Normalization
summary: >
A PyTorch implementation/tutorial of instance normalization.
---
# Instance Normalization
This is a [PyTorch](https://pytorch.org) implementation of
[Instance Normalization: The Missing Ingredient for Fast Stylization](https://arxiv.org/abs/1607.08022).
Instance normalization was introduced to improve [style transfer](https://paperswithcode.com/task/style-transfer).
It is based on the observation that stylization should not depend on the contrast of the content image.
The "contrast normalization" is
$$y_{t,i,j,k} = \frac{x_{t,i,j,k}}{\sum_{l=1}^H \sum_{m=1}^W x_{t,i,l,m}}$$
where $x$ is a batch of images with dimensions image index $t$,
feature channel $i$, and
spatial position $j, k$.
Since it's hard for a convolutional network to learn "contrast normalization", this paper
introduces instance normalization which does that.
Here's a [CIFAR 10 classification model](experiment.html) that uses instance normalization.
"""
import torch
from torch import nn
class InstanceNorm(nn.Module):
r"""
## Instance Normalization Layer
Instance normalization layer $\text{IN}$ normalizes the input $X$ as follows:
When input $X \in \mathbb{R}^{B \times C \times H \times W}$ is a batch of image representations,
where $B$ is the batch size, $C$ is the number of channels, $H$ is the height and $W$ is the width.
$\gamma \in \mathbb{R}^{C}$ and $\beta \in \mathbb{R}^{C}$. The affine transformation with $gamma$ and
$beta$ are optional.
$$\text{IN}(X) = \gamma
\frac{X - \underset{H, W}{\mathbb{E}}[X]}{\sqrt{\underset{H, W}{Var}[X] + \epsilon}}
+ \beta$$
"""
def __init__(self, channels: int, *,
eps: float = 1e-5, affine: bool = True):
"""
* `channels` is the number of features in the input
* `eps` is $\epsilon$, used in $\sqrt{Var[X] + \epsilon}$ for numerical stability
* `affine` is whether to scale and shift the normalized value
"""
super().__init__()
self.channels = channels
self.eps = eps
self.affine = affine
# Create parameters for $\gamma$ and $\beta$ for scale and shift
if self.affine:
self.scale = nn.Parameter(torch.ones(channels))
self.shift = nn.Parameter(torch.zeros(channels))
def forward(self, x: torch.Tensor):
"""
`x` is a tensor of shape `[batch_size, channels, *]`.
`*` denotes any number of (possibly 0) dimensions.
For example, in an image (2D) convolution this will be
`[batch_size, channels, height, width]`
"""
# Keep the original shape
x_shape = x.shape
# Get the batch size
batch_size = x_shape[0]
# Sanity check to make sure the number of features is the same
assert self.channels == x.shape[1]
# Reshape into `[batch_size, channels, n]`
x = x.view(batch_size, self.channels, -1)
# Calculate the mean across last dimension
# i.e. the means for each feature $\mathbb{E}[x_{t,i}]$
mean = x.mean(dim=[-1], keepdim=True)
# Calculate the squared mean across first and last dimension;
# i.e. the means for each feature $\mathbb{E}[(x_{t,i}^2]$
mean_x2 = (x ** 2).mean(dim=[-1], keepdim=True)
# Variance for each feature $Var[x_{t,i}] = \mathbb{E}[x_{t,i}^2] - \mathbb{E}[x_{t,i}]^2$
var = mean_x2 - mean ** 2
# Normalize $$\hat{x}_{t,i} = \frac{x_{t,i} - \mathbb{E}[x_{t,i}]}{\sqrt{Var[x_{t,i}] + \epsilon}}$$
x_norm = (x - mean) / torch.sqrt(var + self.eps)
x_norm = x_norm.view(batch_size, self.channels, -1)
# Scale and shift $$y_{t,i} =\gamma_i \hat{x}_{t,i} + \beta_i$$
if self.affine:
x_norm = self.scale.view(1, -1, 1) * x_norm + self.shift.view(1, -1, 1)
# Reshape to original and return
return x_norm.view(x_shape)
def _test():
"""
Simple test
"""
from labml.logger import inspect
x = torch.zeros([2, 6, 2, 4])
inspect(x.shape)
bn = InstanceNorm(6)
x = bn(x)
inspect(x.shape)
#
if __name__ == '__main__':
_test()
@@ -0,0 +1,67 @@
"""
---
title: CIFAR10 Experiment to try Instance Normalization
summary: >
This trains is a simple convolutional neural network that uses instance normalization
to classify CIFAR10 images.
---
# CIFAR10 Experiment for Instance Normalization
This demonstrates the use of an instance normalization layer in a convolutional
neural network for classification. Not that instance normalization was designed for
style transfer and this is only a demo.
"""
import torch.nn as nn
from labml import experiment
from labml.configs import option
from labml_nn.experiments.cifar10 import CIFAR10Configs, CIFAR10VGGModel
from labml_nn.normalization.instance_norm import InstanceNorm
class Model(CIFAR10VGGModel):
"""
### VGG model for CIFAR-10 classification
This derives from the [generic VGG style architecture](../../experiments/cifar10.html).
"""
def conv_block(self, in_channels, out_channels) -> nn.Module:
return nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
InstanceNorm(out_channels),
nn.ReLU(inplace=True),
)
def __init__(self):
super().__init__([[64, 64], [128, 128], [256, 256, 256], [512, 512, 512], [512, 512, 512]])
@option(CIFAR10Configs.model)
def _model(c: CIFAR10Configs):
"""
### Create model
"""
return Model().to(c.device)
def main():
# Create experiment
experiment.create(name='cifar10', comment='instance norm')
# Create configurations
conf = CIFAR10Configs()
# Load configurations
experiment.configs(conf, {
'optimizer.optimizer': 'Adam',
'optimizer.learning_rate': 2.5e-4,
})
# Start the experiment and run the training loop
with experiment.start():
conf.run()
#
if __name__ == '__main__':
main()
@@ -0,0 +1,9 @@
# [Instance Normalization](https://nn.labml.ai/normalization/instance_norm/index.html)
This is a [PyTorch](https://pytorch.org) implementation of
[Instance Normalization: The Missing Ingredient for Fast Stylization](https://arxiv.org/abs/1607.08022).
Instance normalization was introduced to improve [style transfer](https://paperswithcode.com/task/style-transfer).
It is based on the observation that stylization should not depend on the contrast of the content image.
Since it's hard for a convolutional network to learn "contrast normalization", this paper
introduces instance normalization which does that.
@@ -0,0 +1,150 @@
"""
---
title: Layer Normalization
summary: >
A PyTorch implementation/tutorial of layer normalization.
---
# Layer Normalization
This is a [PyTorch](https://pytorch.org) implementation of
[Layer Normalization](https://arxiv.org/abs/1607.06450).
### Limitations of [Batch Normalization](../batch_norm/index.html)
* You need to maintain running means.
* Tricky for RNNs. Do you need different normalizations for each step?
* Doesn't work with small batch sizes;
large NLP models are usually trained with small batch sizes.
* Need to compute means and variances across devices in distributed training.
## Layer Normalization
Layer normalization is a simpler normalization method that works
on a wider range of settings.
Layer normalization transforms the inputs to have zero mean and unit variance
across the features.
*Note that batch normalization fixes the zero mean and unit variance for each element.*
Layer normalization does it for each batch across all elements.
Layer normalization is generally used for NLP tasks.
We have used layer normalization in most of the
[transformer implementations](../../transformers/gpt/index.html).
"""
from typing import Union, List
import torch
from torch import nn, Size
class LayerNorm(nn.Module):
r"""
## Layer Normalization
Layer normalization $\text{LN}$ normalizes the input $X$ as follows:
When input $X \in \mathbb{R}^{B \times C}$ is a batch of embeddings,
where $B$ is the batch size and $C$ is the number of features.
$\gamma \in \mathbb{R}^{C}$ and $\beta \in \mathbb{R}^{C}$.
$$\text{LN}(X) = \gamma
\frac{X - \underset{C}{\mathbb{E}}[X]}{\sqrt{\underset{C}{Var}[X] + \epsilon}}
+ \beta$$
When input $X \in \mathbb{R}^{L \times B \times C}$ is a batch of a sequence of embeddings,
where $B$ is the batch size, $C$ is the number of channels, $L$ is the length of the sequence.
$\gamma \in \mathbb{R}^{C}$ and $\beta \in \mathbb{R}^{C}$.
$$\text{LN}(X) = \gamma
\frac{X - \underset{C}{\mathbb{E}}[X]}{\sqrt{\underset{C}{Var}[X] + \epsilon}}
+ \beta$$
When input $X \in \mathbb{R}^{B \times C \times H \times W}$ is a batch of image representations,
where $B$ is the batch size, $C$ is the number of channels, $H$ is the height and $W$ is the width.
This is not a widely used scenario.
$\gamma \in \mathbb{R}^{C \times H \times W}$ and $\beta \in \mathbb{R}^{C \times H \times W}$.
$$\text{LN}(X) = \gamma
\frac{X - \underset{C, H, W}{\mathbb{E}}[X]}{\sqrt{\underset{C, H, W}{Var}[X] + \epsilon}}
+ \beta$$
"""
def __init__(self, normalized_shape: Union[int, List[int], Size], *,
eps: float = 1e-5,
elementwise_affine: bool = True):
"""
* `normalized_shape` $S$ is the shape of the elements (except the batch).
The input should then be
$X \in \mathbb{R}^{* \times S[0] \times S[1] \times ... \times S[n]}$
* `eps` is $\epsilon$, used in $\sqrt{Var[X] + \epsilon}$ for numerical stability
* `elementwise_affine` is whether to scale and shift the normalized value
We've tried to use the same names for arguments as PyTorch `LayerNorm` implementation.
"""
super().__init__()
# Convert `normalized_shape` to `torch.Size`
if isinstance(normalized_shape, int):
normalized_shape = torch.Size([normalized_shape])
elif isinstance(normalized_shape, list):
normalized_shape = torch.Size(normalized_shape)
assert isinstance(normalized_shape, torch.Size)
#
self.normalized_shape = normalized_shape
self.eps = eps
self.elementwise_affine = elementwise_affine
# Create parameters for $\gamma$ and $\beta$ for gain and bias
if self.elementwise_affine:
self.gain = nn.Parameter(torch.ones(normalized_shape))
self.bias = nn.Parameter(torch.zeros(normalized_shape))
def forward(self, x: torch.Tensor):
"""
`x` is a tensor of shape `[*, S[0], S[1], ..., S[n]]`.
`*` could be any number of dimensions.
For example, in an NLP task this will be
`[seq_len, batch_size, features]`
"""
# Sanity check to make sure the shapes match
assert self.normalized_shape == x.shape[-len(self.normalized_shape):]
# The dimensions to calculate the mean and variance on
dims = [-(i + 1) for i in range(len(self.normalized_shape))]
# Calculate the mean of all elements;
# i.e. the means for each element $\mathbb{E}[X]$
mean = x.mean(dim=dims, keepdim=True)
# Calculate the squared mean of all elements;
# i.e. the means for each element $\mathbb{E}[X^2]$
mean_x2 = (x ** 2).mean(dim=dims, keepdim=True)
# Variance of all element $Var[X] = \mathbb{E}[X^2] - \mathbb{E}[X]^2$
var = mean_x2 - mean ** 2
# Normalize $$\hat{X} = \frac{X - \mathbb{E}[X]}{\sqrt{Var[X] + \epsilon}}$$
x_norm = (x - mean) / torch.sqrt(var + self.eps)
# Scale and shift $$\text{LN}(x) = \gamma \hat{X} + \beta$$
if self.elementwise_affine:
x_norm = self.gain * x_norm + self.bias
#
return x_norm
def _test():
"""
Simple test
"""
from labml.logger import inspect
x = torch.zeros([2, 3, 2, 4])
inspect(x.shape)
ln = LayerNorm(x.shape[2:])
x = ln(x)
inspect(x.shape)
inspect(ln.gain.shape)
#
if __name__ == '__main__':
_test()
@@ -0,0 +1,26 @@
# [Layer Normalization](https://nn.labml.ai/normalization/layer_norm/index.html)
This is a [PyTorch](https://pytorch.org) implementation of
[Layer Normalization](https://arxiv.org/abs/1607.06450).
### Limitations of [Batch Normalization](https://nn.labml.ai/normalization/batch_norm/index.html)
* You need to maintain running means.
* Tricky for RNNs. Do you need different normalizations for each step?
* Doesn't work with small batch sizes;
large NLP models are usually trained with small batch sizes.
* Need to compute means and variances across devices in distributed training.
## Layer Normalization
Layer normalization is a simpler normalization method that works
on a wider range of settings.
Layer normalization transforms the inputs to have zero mean and unit variance
across the features.
*Note that batch normalization fixes the zero mean and unit variance for each element.*
Layer normalization does it for each batch across all elements.
Layer normalization is generally used for NLP tasks.
We have used layer normalization in most of the
[transformer implementations](https://nn.labml.ai/transformers/gpt/index.html).
@@ -0,0 +1,84 @@
"""
---
title: Weight Standardization
summary: >
A PyTorch implementation/tutorial of Weight Standardization.
---
# Weight Standardization
This is a [PyTorch](https://pytorch.org) implementation of Weight Standardization from the paper
[Micro-Batch Training with Batch-Channel Normalization and Weight Standardization](https://arxiv.org/abs/1903.10520).
We also have an [annotated implementation of Batch-Channel Normalization](../batch_channel_norm/index.html).
Batch normalization **gives a smooth loss landscape** and
**avoids elimination singularities**.
Elimination singularities are nodes of the network that become
useless (e.g. a ReLU that gives 0 all the time).
However, batch normalization doesn't work well when the batch size is too small,
which happens when training large networks because of device memory limitations.
The paper introduces Weight Standardization with Batch-Channel Normalization as
a better alternative.
Weight Standardization:
1. Normalizes the gradients
2. Smoothes the landscape (reduced Lipschitz constant)
3. Avoids elimination singularities
The Lipschitz constant is the maximum slope a function has between two points.
That is, $L$ is the Lipschitz constant where $L$ is the smallest value that satisfies,
$\forall a,b \in A: \lVert f(a) - f(b) \rVert \le L \lVert a - b \rVert$
where $f: A \rightarrow \mathbb{R}^m, A \in \mathbb{R}^n$.
Elimination singularities are avoided because it keeps the statistics of the outputs similar to the
inputs. So as long as the inputs are normally distributed the outputs remain close to normal.
This avoids outputs of nodes from always falling beyond the active range of the activation function
(e.g. always negative input for a ReLU).
*[Refer to the paper for proofs](https://arxiv.org/abs/1903.10520)*.
Here is [the training code](experiment.html) for training
a VGG network that uses weight standardization to classify CIFAR-10 data.
This uses a [2D-Convolution Layer with Weight Standardization](conv2d.html).
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/weight_standardization/experiment.ipynb)
"""
import torch
def weight_standardization(weight: torch.Tensor, eps: float):
r"""
## Weight Standardization
$$\hat{W}_{i,j} = \frac{W_{i,j} - \mu_{W_{i,\cdot}}} {\sigma_{W_{i,\cdot}}}$$
where,
\begin{align}
W &\in \mathbb{R}^{O \times I} \\
\mu_{W_{i,\cdot}} &= \frac{1}{I} \sum_{j=1}^I W_{i,j} \\
\sigma_{W_{i,\cdot}} &= \sqrt{\frac{1}{I} \sum_{j=1}^I W^2_{i,j} - \mu^2_{W_{i,\cdot}} + \epsilon} \\
\end{align}
for a 2D-convolution layer $O$ is the number of output channels ($O = C_{out}$)
and $I$ is the number of input channels times the kernel size ($I = C_{in} \times k_H \times k_W$)
"""
# Get $C_{out}$, $C_{in}$ and kernel shape
c_out, c_in, *kernel_shape = weight.shape
# Reshape $W$ to $O \times I$
weight = weight.view(c_out, -1)
# Calculate
#
# \begin{align}
# \mu_{W_{i,\cdot}} &= \frac{1}{I} \sum_{j=1}^I W_{i,j} \\
# \sigma^2_{W_{i,\cdot}} &= \frac{1}{I} \sum_{j=1}^I W^2_{i,j} - \mu^2_{W_{i,\cdot}}
# \end{align}
var, mean = torch.var_mean(weight, dim=1, keepdim=True)
# Normalize
# $$\hat{W}_{i,j} = \frac{W_{i,j} - \mu_{W_{i,\cdot}}} {\sigma_{W_{i,\cdot}}}$$
weight = (weight - mean) / (torch.sqrt(var + eps))
# Change back to original shape and return
return weight.view(c_out, c_in, *kernel_shape)
@@ -0,0 +1,60 @@
"""
---
title: 2D Convolution Layer with Weight Standardization
summary: >
A PyTorch implementation/tutorial of a 2D Convolution Layer with Weight Standardization.
---
# 2D Convolution Layer with Weight Standardization
This is an implementation of a 2 dimensional convolution layer with [Weight Standardization](./index.html)
"""
import torch
import torch.nn as nn
from torch.nn import functional as F
from labml_nn.normalization.weight_standardization import weight_standardization
class Conv2d(nn.Conv2d):
"""
## 2D Convolution Layer
This extends the standard 2D Convolution layer and standardize the weights before the convolution step.
"""
def __init__(self, in_channels, out_channels, kernel_size,
stride=1,
padding=0,
dilation=1,
groups: int = 1,
bias: bool = True,
padding_mode: str = 'zeros',
eps: float = 1e-5):
super(Conv2d, self).__init__(in_channels, out_channels, kernel_size,
stride=stride,
padding=padding,
dilation=dilation,
groups=groups,
bias=bias,
padding_mode=padding_mode)
self.eps = eps
def forward(self, x: torch.Tensor):
return F.conv2d(x, weight_standardization(self.weight, self.eps), self.bias, self.stride,
self.padding, self.dilation, self.groups)
def _test():
"""
A simple test to verify the tensor sizes
"""
conv2d = Conv2d(10, 20, 5)
from labml.logger import inspect
inspect(conv2d.weight)
import torch
inspect(conv2d(torch.zeros(10, 10, 100, 100)))
if __name__ == '__main__':
_test()
@@ -0,0 +1,444 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "AYV_dMVDxyc2"
},
"source": [
"[![Github](https://img.shields.io/github/stars/labmlai/annotated_deep_learning_paper_implementations?style=social)](https://github.com/labmlai/annotated_deep_learning_paper_implementations)\n",
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/labmlai/annotated_deep_learning_paper_implementations/blob/master/labml_nn/normalization/group_norm/experiment.ipynb) \n",
"\n",
"## Weight Standardization & Batch-Channel Normalization - CIFAR 10\n",
"\n",
"This is an experiment training a model with Weight Standardization & Batch-Channel Normalization to classify CIFAR-10 dataset."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "AahG_i2y5tY9"
},
"source": [
"Install the `labml-nn` package."
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "ZCzmCrAIVg0L",
"outputId": "8332d030-2fab-4a24-876f-152b7f99a226"
},
"source": [
"!pip install labml-nn"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "SE2VUQ6L5zxI"
},
"source": [
"Imports"
]
},
{
"cell_type": "code",
"metadata": {
"id": "0hJXx_g0wS2C"
},
"source": [
"from labml import experiment\n",
"from labml_nn.normalization.weight_standardization.experiment import CIFAR10Configs as Configs"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "Lpggo0wM6qb-"
},
"source": [
"Create an experiment"
]
},
{
"cell_type": "code",
"metadata": {
"id": "bFcr9k-l4cAg"
},
"source": [
"experiment.create(name=\"cifar10\", comment=\"WS + BCN\")"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "-OnHLi626tJt"
},
"source": [
"Initialize configurations"
]
},
{
"cell_type": "code",
"metadata": {
"id": "Piz0c5f44hRo"
},
"source": [
"conf = Configs()"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "wwMzCqpD6vkL"
},
"source": [
"Set experiment configurations and assign a configurations dictionary to override configurations"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 17
},
"id": "e6hmQhTw4nks",
"outputId": "33a5979f-70eb-4d19-e82a-a6b113316cca"
},
"source": [
"experiment.configs(conf, {\n",
" 'optimizer.optimizer': 'Adam',\n",
" 'optimizer.learning_rate': 2.5e-4,\n",
" 'train_batch_size': 64,\n",
"})"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {
"id": "KJZRf8527GxL"
},
"source": [
"Start the experiment and run the training loop."
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 933,
"referenced_widgets": [
"1dacd9c82caf40c8b2def0c5fe4f7643",
"67e37901c44b4bde9062014542cae018",
"9d57be286b7244ca9a5f74cff6c3cf4e",
"9e511aaba1204ea9985fb6f55e024039",
"61849c45ed0c4436995bb81d7efdac7e",
"7ed3376588734d64a229fd8dd6bfe06e",
"afd6865a483f4273addb343059632961",
"7b765f7623474999a8c9815e02c1cc6b"
]
},
"id": "aIAWo7Fw5DR8",
"outputId": "cf3d16aa-a9a1-4fd4-cfb4-23e8ea5ea940"
},
"source": [
"with experiment.start():\n",
" conf.run()"
],
"outputs": [],
"execution_count": null
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"collapsed_sections": [],
"name": "Weight Standardization & Batch-Channel Normalization",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"1dacd9c82caf40c8b2def0c5fe4f7643": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HBoxModel",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HBoxModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HBoxView",
"box_style": "",
"children": [
"IPY_MODEL_9d57be286b7244ca9a5f74cff6c3cf4e",
"IPY_MODEL_9e511aaba1204ea9985fb6f55e024039"
],
"layout": "IPY_MODEL_67e37901c44b4bde9062014542cae018"
}
},
"61849c45ed0c4436995bb81d7efdac7e": {
"model_module": "@jupyter-widgets/controls",
"model_name": "ProgressStyleModel",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "ProgressStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"bar_color": null,
"description_width": "initial"
}
},
"67e37901c44b4bde9062014542cae018": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": null,
"bottom": null,
"display": null,
"flex": null,
"flex_flow": null,
"grid_area": null,
"grid_auto_columns": null,
"grid_auto_flow": null,
"grid_auto_rows": null,
"grid_column": null,
"grid_gap": null,
"grid_row": null,
"grid_template_areas": null,
"grid_template_columns": null,
"grid_template_rows": null,
"height": null,
"justify_content": null,
"justify_items": null,
"left": null,
"margin": null,
"max_height": null,
"max_width": null,
"min_height": null,
"min_width": null,
"object_fit": null,
"object_position": null,
"order": null,
"overflow": null,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"7b765f7623474999a8c9815e02c1cc6b": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": null,
"bottom": null,
"display": null,
"flex": null,
"flex_flow": null,
"grid_area": null,
"grid_auto_columns": null,
"grid_auto_flow": null,
"grid_auto_rows": null,
"grid_column": null,
"grid_gap": null,
"grid_row": null,
"grid_template_areas": null,
"grid_template_columns": null,
"grid_template_rows": null,
"height": null,
"justify_content": null,
"justify_items": null,
"left": null,
"margin": null,
"max_height": null,
"max_width": null,
"min_height": null,
"min_width": null,
"object_fit": null,
"object_position": null,
"order": null,
"overflow": null,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"7ed3376588734d64a229fd8dd6bfe06e": {
"model_module": "@jupyter-widgets/base",
"model_name": "LayoutModel",
"state": {
"_model_module": "@jupyter-widgets/base",
"_model_module_version": "1.2.0",
"_model_name": "LayoutModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "LayoutView",
"align_content": null,
"align_items": null,
"align_self": null,
"border": null,
"bottom": null,
"display": null,
"flex": null,
"flex_flow": null,
"grid_area": null,
"grid_auto_columns": null,
"grid_auto_flow": null,
"grid_auto_rows": null,
"grid_column": null,
"grid_gap": null,
"grid_row": null,
"grid_template_areas": null,
"grid_template_columns": null,
"grid_template_rows": null,
"height": null,
"justify_content": null,
"justify_items": null,
"left": null,
"margin": null,
"max_height": null,
"max_width": null,
"min_height": null,
"min_width": null,
"object_fit": null,
"object_position": null,
"order": null,
"overflow": null,
"overflow_x": null,
"overflow_y": null,
"padding": null,
"right": null,
"top": null,
"visibility": null,
"width": null
}
},
"9d57be286b7244ca9a5f74cff6c3cf4e": {
"model_module": "@jupyter-widgets/controls",
"model_name": "FloatProgressModel",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "FloatProgressModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "ProgressView",
"bar_style": "success",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_7ed3376588734d64a229fd8dd6bfe06e",
"max": 170498071,
"min": 0,
"orientation": "horizontal",
"style": "IPY_MODEL_61849c45ed0c4436995bb81d7efdac7e",
"value": 170498071
}
},
"9e511aaba1204ea9985fb6f55e024039": {
"model_module": "@jupyter-widgets/controls",
"model_name": "HTMLModel",
"state": {
"_dom_classes": [],
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "HTMLModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/controls",
"_view_module_version": "1.5.0",
"_view_name": "HTMLView",
"description": "",
"description_tooltip": null,
"layout": "IPY_MODEL_7b765f7623474999a8c9815e02c1cc6b",
"placeholder": "",
"style": "IPY_MODEL_afd6865a483f4273addb343059632961",
"value": " 170499072/? [00:04<00:00, 42242006.52it/s]"
}
},
"afd6865a483f4273addb343059632961": {
"model_module": "@jupyter-widgets/controls",
"model_name": "DescriptionStyleModel",
"state": {
"_model_module": "@jupyter-widgets/controls",
"_model_module_version": "1.5.0",
"_model_name": "DescriptionStyleModel",
"_view_count": null,
"_view_module": "@jupyter-widgets/base",
"_view_module_version": "1.2.0",
"_view_name": "StyleView",
"description_width": ""
}
}
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -0,0 +1,65 @@
"""
---
title: CIFAR10 Experiment to try Weight Standardization and Batch-Channel Normalization
summary: >
This trains is a VGG net that uses weight standardization and batch-channel normalization
to classify CIFAR10 images.
---
# CIFAR10 Experiment to try Weight Standardization and Batch-Channel Normalization
"""
import torch.nn as nn
from labml import experiment
from labml.configs import option
from labml_nn.experiments.cifar10 import CIFAR10Configs, CIFAR10VGGModel
from labml_nn.normalization.batch_channel_norm import BatchChannelNorm
from labml_nn.normalization.weight_standardization.conv2d import Conv2d
class Model(CIFAR10VGGModel):
"""
### VGG model for CIFAR-10 classification
This derives from the [generic VGG style architecture](../../experiments/cifar10.html).
"""
def conv_block(self, in_channels, out_channels) -> nn.Module:
return nn.Sequential(
Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
BatchChannelNorm(out_channels, 32),
nn.ReLU(inplace=True),
)
def __init__(self):
super().__init__([[64, 64], [128, 128], [256, 256, 256], [512, 512, 512], [512, 512, 512]])
@option(CIFAR10Configs.model)
def _model(c: CIFAR10Configs):
"""
### Create model
"""
return Model().to(c.device)
def main():
# Create experiment
experiment.create(name='cifar10', comment='weight standardization')
# Create configurations
conf = CIFAR10Configs()
# Load configurations
experiment.configs(conf, {
'optimizer.optimizer': 'Adam',
'optimizer.learning_rate': 2.5e-4,
'train_batch_size': 64,
})
# Start the experiment and run the training loop
with experiment.start():
conf.run()
#
if __name__ == '__main__':
main()
@@ -0,0 +1,6 @@
# [Weight Standardization](https://nn.labml.ai/normalization/weight_standardization/index.html)
This is a [PyTorch](https://pytorch.org) implementation of Weight Standardization from the paper
[Micro-Batch Training with Batch-Channel Normalization and Weight Standardization](https://arxiv.org/abs/1903.10520).
We also have an
[annotated implementation of Batch-Channel Normalization](https://nn.labml.ai/normalization/batch_channel_norm/index.html).