CNN用BN, RNN用LN
BN又叫纵向规范化,LN又叫横向规范化
LN也是因为Transformer才为人们所熟知的
BN并不适用于RNN等动态网络和batchsize较小的时候效果不好。Layer Normalization(LN)的提出有效的解决BN的这两个问题。LN和BN不同点是归一化的维度是互相垂直的
下图的N是bs,F是H和W拍平后的维,也叫L
叫Layer norm,其实是对单个样本做的,对batch的每一个样本做, 如果一个batch有n个feature,他就做n次。就像BN有c个channel时做c次一样。
之所以叫layer norm是因为三维的时候,一个样本就是一层
为什么Transformer用LN而不用BN
时序特征并不能用Batch Normalization,因为一个batch中的序列有长有短。就像图中画的,蓝线是BN取的,橙线是LN取的
如果使用BN,由于为了补齐长短不一的样例而添加进去的0使得较长序列中词语的含义向量规模相对变小,较短序列中的词转换为含义向量的规模相对变大。平白无故增添了误差抖动。
BatchNorm就是通过对batch size这个维度归一化来让分布稳定下来。
LayerNorm则是通过对Hidden size这个维度归一化来让某层的分布稳定。
使用LN保证每个序列中词语转成的含义向量在同一规模上
此外,BN 的一个缺点是需要较大的 batchsize 才能合理估训练数据的均值和方差,这导致内存很可能不够用,同时它也很难应用在训练数据长度不同的 RNN 模型上。
LN需要注意的地方
- 不再有running_mean和running_var
- gamma和beta为逐元素的
LayerNorm中不会像BatchNorm那样跟踪统计全局的均值方差,因此train()和eval()对LayerNorm没有影响
其实在eval模式下,只有BatchNorm会屏蔽,其他Norm函数不会屏蔽
LN在PyTorch中的实现
torch.nn.LayerNorm(normalized_shape, eps=1e-05, elementwise_affine=True, device=None, dtype=None)
- normalized_shape:(int/list/torch.Size)该层的特征维度,即要被标准化的维度。
- eps:分母修正项。
- elementwise_affine:是否需要affine transform,这里也提醒你是逐元素的仿射变换。
LN的参数
对于image
import torch from torch import nn N, C, H, W = 20, 5, 10, 10 input = torch.randn(N, C, H, W) layer_norm = nn.LayerNorm([C, H, W]) # Normalize over the last three dimensions (i.e. the channel and spatial dimensions) output = layer_norm(input)
对于NLP
import torch from torch import nn batch, sentence_length, embedding_dim = 20, 5, 10 input = torch.randn(batch, sentence_length, embedding_dim) layer_norm = nn.LayerNorm(embedding_dim) output = layer_norm(input)
手动实现
import torch import torch.nn as nn ln_layer = nn.LayerNorm(normalized_shape=[3,20,20], elementwise_affine=False) input = torch.randn(8, 3, 20, 20) ln_outputs = ln_layer(input) mean = torch.mean(input, dim=[1,2,3], keepdim=True) var = torch.var(input, dim=[1,2,3], keepdim=True, unbiased=False) output = (input-mean) / (torch.sqrt(var) + ln_layer.eps) assert torch.allclose(ln_outputs, output, rtol=1e-4)
偶尔还会报错,多运行几次。
这说明我们自己算的,虽然计算方法是对的,在数值上还和官方实现的有1e-6,1e-7等的差异,因为官方的实现可能是C语言底层,然后开了cuda.benmark等优化
为什么用torch.var和torch.sqrt(torch.var) 而不是直接用torch.std, 因为官网的公式是这么写的
带参数的写法
import torch from torch import nn class LayerNorm(nn.Module): def __init__(self, dim, eps=1e-6): super().__init__() self.size = dim # 使用两个可以学习的参数来进行 normalisation #self.alpha即self.weight self.alpha = nn.Parameter(torch.ones(3,20,20)) self.bias = nn.Parameter(torch.zeros(3,20,20)) # self.weight = Parameter(torch.Tensor(*normalized_shape)) # self.bias = Parameter(torch.Tensor(*normalized_shape)) self.eps = eps def forward(self, x): norm = self.alpha * (x - x.mean(dim=[1,2,3], keepdim=True)) \ / (x.std(dim=[1,2,3], keepdim=True) + self.eps) + self.bias return norm ln_layer = nn.LayerNorm(normalized_shape=[3,20,20], elementwise_affine=False) input = torch.randn(8, 3, 20, 20) ln_outputs = ln_layer(input) my_lnlayer = LayerNorm(dim=3) input = torch.randn(8, 3, 20, 20) output = my_lnlayer(input) assert torch.allclose(ln_outputs, output, rtol=1e-4)
【关于 BatchNorm vs LayerNorm】那些你不知道的事-技术圈
为什么Transformer用LN
对于使用场景来说,BN在MLP和CNN上使用的效果都比较好,在RNN这种动态文本模型上使用的比较差。
BN在MLP中的应用。 BN是对每个特征在batch_size上求的均值和方差,如果BN应用到NLP任务,相当于是在对默认了在同一个位置的单词对应的是同一种特征
layer_norm针对的是文本的长度,整条序列的文本,所以比bn好