Normalizations

Normalizzare i dati di input per renderli distribuzioni gaussiane uniformi, aiuta nel processo di training. Prevede il calcolo di media e varianza di un insieme di dati di input.
Ci sono varie tecniche di normalizzazione, le più usate sono:

Batch Normalization

Media e varianza vengono calcolate attraverso le istanze del batch, cioè lungo le colonne del batch.
Dipende da batch_size.
es.: se abbiamo un tensore di input del tipo:

x = torch.tensor([[ 2.,  4.,  6.],    # x.shape = [3, 3]
                  [ 8., 10., 12.],
                  [14., 16., 18.]])

# la media è per dim 0
mean = x.mean(0, keepdim=True)  # tensor([[ 8., 10., 12.]])
# dove: 8 = (2 + 8 + 14) / 3
#       10 = (4 + 10 + 16) / 3
#      
 ...
# la varianza è per dim 0
xvar = x.var(0, keepdim=True)

Layer Normalization

Media e varianza vengono calcolate per ogni istanza del batch, cioè lungo le righe del batch.
Non dipende da batch_size.
es., considerando sempre il tensore t:

# la media è per dim 1
mean = x.mean(1, keepdim=True)  # tensor([[4.],
                                #         [10.],
                                #         [16.]] )

# dove: 4 = (2 + 4 + 6) / 3
#       10 = (8 + 10 + 12) / 3
#       ...

# la varianza è per dim 1
xvar = x.var(1, keepdim=True)

Group Normalization

Il batch viene suddiviso in gruppi num_groups e vengono normalizzate le features di ogni gruppo.
Non dipende da batch_size.
es.:


num_groups = 4
B, T = x.shape
x = x.view(B, num_groups, -1) # ripartizione in gruppi

mean = x.mean(-1, keepdim=True)
var = x.var(-1, keepdim=True)

esempio di utilizzo dei vari tipi di normalizzazione:

import torch
from torch import nn
import torch.nn.functional as F
from functools import partial

def batch_norm(x):
    mean = x.mean(0, keepdim=True)
    var = x.var(0, unbiased=False, keepdim=True)
    x_norm = (x - mean) / (var + 1e-5).sqrt()
    return x_norm

def layer_norm(x):
    mean = x.mean(1, keepdim=True)
    var = x.var(1, unbiased=False, keepdim=True)
    x_norm = (x - mean) / (var + 1e-5).sqrt()
    return x_norm

def group_norm(x, num_groups):
    N, C = x.shape
    x = x.view(N, num_groups, -1)
    mean = x.mean(-1, keepdim=True)
    var = x.var(-1, unbiased=False, keepdim=True)
    x_norm = (x - mean) / (var + 1e-5).sqrt()
    x_norm = x_norm.view(N, C)
    return x_norm

class MLP(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim, norm_func):
        super().__init__()
        self.linear1 = nn.Linear(input_dim, hidden_dim)
        self.norm_func = norm_func
        self.linear2 = nn.Linear(hidden_dim, output_dim)
        
    def forward(self, x):
        x = self.linear1(x)
        x = self.norm_func(x)
        x = F.relu(x)
        x = self.linear2(x)
        return x

# Create a random tensor with size (batch_size, input_dim)
x = torch.randn(32, 100)

# Create the MLP models with batch norm, layer norm, and group norm
model_bn = MLP(100, 64, 10, batch_norm)
model_ln = MLP(100, 64, 10, layer_norm)
model_gn = MLP(100, 64, 10, partial(group_norm, num_groups=4))

# Pass the input tensor through the models
output_bn = model_bn(x)
output_ln = model_ln(x)
output_gn = model_gn(x)

# Print the outputs
print("Output with batch norm:\n", output_bn)
print("\nOutput with layer norm:\n", output_ln)
print("\nOutput with group norm:\n", output_gn)