Loading... ## 前言 Normalization Layer在现代深度神经网络中是不可或缺的部分, 特别对于深层或者大规模网络的训练意义重大. 特别的, Normaliazation Layer拥有多种不同的实现, 适用于不同类型的神经网络中. 比如CNN中广泛使用BN/GN, 而RNN中广泛使用LN. 总的来说, Normalization Layer在神经网络中的作用可以概括如下: * 加速训练过程 * 在神经网络中,如果每层的数据分布差异都很大的话,将会导致训练过程难以收敛; 而规范化层可以将隐层数据转换到一个相近分布上(均值为零, 方差为1),这样就降低了后面每一层的学习难度, 使得训练更加稳定, 收敛更快。 * Internal Covariate Shift (ICS) 问题:在训练的过程中,激活函数会改变各层数据的分布,随着网络的加深,这种改变(差异)会越来越大,使模型训练起来特别困难,收敛速度很慢,会出现梯度消失的问题。 * 控制梯度爆炸防止梯度消失 * 在深层网络中,梯度经过多层传播后,容易出现梯度消失或梯度爆炸的问题。通过在网络中插入规范化层,可以有效控制反向传播时梯度的范围,减轻这一问题。 * 防止过拟合(是常用的正则化方法) * 规范化层减少了模型对输入数据尺度的敏感性,提高了模型的稳定性。此外,它还可以起到轻微的正则化效果,减少模型对小批量数据中噪声的敏感度,从而提高模型的泛化能力。 * 允许更复杂模型的训练 * 通过提高训练增加稳定性和改善梯度流动,规范化层可以使得训练更深、更复杂的网络成为可能。 --- *补充: 概念区分* * 归一化与标准化 归一化: 将数值范围映射到[0, 1]之间, 比如将RGB图像(数值范围0-255)映射成0-1之间浮点数矩阵送入到神经网络中进行训练或者推理. 标准化: 常用的方法是z-score标准化,经过处理后的数据均值为0,标准差为1 综上: 标准化和归一化两者都是对样本进行平移缩放 --- 将输入的feature map shape记为[**B**atch(N), **C**hannel, **H**eight, **W**idth],这几个方法主要的区别就是在于: [**Batch Normalization**](https://arxiv.org/pdf/1502.03167.pdf)(BN,2015年):沿batch方向上,对 **(N、H、W) **做归一化,保留`通道C`的维度, * 优点:适用于CNN * 缺点:对较小的batch size效果不好,不适用于RNN [**Layer Normalization**](https://arxiv.org/pdf/1607.06450v1.pdf)(LN,2016年):沿Channel方向上,对 **(C、H、W) **做归一化,保留`通道N`的维度 * 优点:适用序列模型,如:RNN * 缺点:不适应输入变化很大的数据,大Batch较差 [**Instance Normalization**](https://arxiv.org/pdf/1607.08022.pdf)(IN,2017年):在图像像素上,对 **(H、W) **做归一化 * 优点:适用图像风格迁移 * 缺点:不适应通道之间的相关性较强数据 [**Group Normalization**](https://arxiv.org/pdf/1803.08494.pdf)(GN,2018年):将channel分组,对 **(C/G、H、W) **做归一化,在不同的Batch Size下具有较大的稳定性,而GN在中、大Batch Size下的性能略差于BN。 * 优点:不同Batch Size下具有较大的稳定性 * 缺点:在大Batch 下性能略差于BN [**Switchable Normalization**](https://arxiv.org/pdf/1806.10779.pdf)(SN,2018年):将BN、LN、IN结合,赋予权重,让网络自己去学习归一化层应该使用什么方法 * 优点:集BN、IN、LN优点于一身 * 缺点:训练复杂 [**Positional Normalization**](https://arxiv.org/abs/1907.04312v2)(PN,2019年):提出了位置归一化算法来计算生成网络沿信道维数的统计量; * 优点:在生成网络中表现较好 * 缺点:不适应视觉任务 **Batch Group Normalization**(BGN,2020年): * 优点:解决Batch Size退化和饱和的问题 * 缺点:暂不清楚 FRN(2020) * 优点:不受batch size的影响。 * 缺点:暂不清楚 SaBN(2021) * 优点:解决多类别不平衡问题 * 缺点:小批量它的性能会下降 ![](https://zoe.red/usr/uploads/2024/04/1210901287.png) 立方体shape为(N, H, W, C)的像素点,N为batch size轴,C为通道轴。蓝色的像素群表示 使用这些像素值来计算平均值和方差进行归一化 下面开始分别对常见Normaliazation Layer实现分别展开介绍. --- ## BN, Batch Normalization 示意图: ![](https://zoe.red/usr/uploads/2024/04/3396261607.png) ### 提出原因 1、在训练神经网络过程中,通常输入batch个数据进行训练,这样每个batch具有不同的分布,使模型训练起来相对困难。 2、Internal Covariate Shift (ICS) 问题:在训练深层网络时,激活函数会改变各层数据的分布以及量级,随着网络的加深,这种改变会越来越大,模型不稳定不容易收敛,甚至可能出现梯度消失的问题。 那我们就看看下面的两个动图, 这就是在每层神经网络有无 batch normalization 的区别 ![](https://zoe.red/usr/uploads/2024/04/1245994002.jpg) 图\* 没有BN的模型权值分布 ![](https://zoe.red/usr/uploads/2024/04/685629282.jpg) 图\* 使用BN的模型权值分布 没有normalization 的输出数据很多都等于0,导致后面的神经元“死掉”,起不到任何作用。 ### 原理 **BN的主要思想**:沿着通道维度,在batch维度上,计算(N, H, W)均值和方差,然后对feature map进行归一化,这样不仅数据分布一致,而且避免发生梯度消失。保证每一次数据经过归一化后还保留原有学习来的特征,同时又能完成归一化操作,加速训练。 ![](https://zoe.red/usr/uploads/2024/04/1210855364.png) 其操作可以分成2步, * **Standardization**:首先对$m$个$x$进行 Standardization,得到 zero mean unit variance的分布$\hat{x}$。 * **scale and shift**:然后再对$\hat{x}$进行scale and shift,缩放并平移到新的分布y,具有新的均值β方差γ。 **BN的使用**: BN的一个问题是训练时batch size一般较大,但是测试时batch size一般为1,而均值和方差的计算依赖batch,这将导致训练和测试不一致。BN的解决方案是**在训练时估计一个均值和方差量来作为测试时的归一化参数**,一般对每次mini-batch的均值和方差进行指数加权平均来得到这个量。这样一来就解决了训练和测试的不一致性**,在训练的时候计算均值和方差,在推理的使用使用均值和方差** **BN的使用位置**:激活函数之前,ResNet V1:conv>BN>Relu结构;resnet v2采用BN>Relu>conv结构。但无论如何都是在激活前使用BN。 **代码实现** * 手动实现一个BatchNorm2D层的PyTorch版本 * 支持前向与反向传播 * 待验证? ```python import torch class BatchNorm2dFunction(torch.autograd.Function): @staticmethod def forward(ctx, input, gamma, beta, running_mean, running_var, eps, momentum): # 计算均值和方差 mean = input.mean([0, 2, 3], keepdim=True) var = input.var([0, 2, 3], keepdim=True, unbiased=False) # 更新运行均值和方差 running_mean[:] = momentum * mean + (1 - momentum) * running_mean running_var[:] = momentum * var + (1 - momentum) * running_var # 标准化 input_normalized = (input - mean) / torch.sqrt(var + eps) output = gamma * input_normalized + beta # 保存上下文以供反向传播使用 ctx.save_for_backward(input, input_normalized, gamma, beta, mean, var) ctx.eps = eps return output @staticmethod def backward(ctx, grad_output): input, input_normalized, gamma, beta, mean, var = ctx.saved_tensors eps = ctx.eps # 获取批量大小、特征数量、高度和宽度 N, C, H, W = grad_output.size() grad_input = grad_gamma = grad_beta = None # 计算对输入的梯度 if ctx.needs_input_grad[0]: grad_input_normalized = grad_output * gamma grad_var = (-0.5 * grad_input_normalized * (input - mean) * torch.pow(var + eps, -1.5)).sum([0, 2, 3], keepdim=True) grad_mean = (-grad_input_normalized / torch.sqrt(var + eps)).sum([0, 2, 3], keepdim=True) - 2 * grad_var * (input - mean).mean([0, 2, 3], keepdim=True) grad_input = grad_input_normalized / torch.sqrt(var + eps) + 2.0 * grad_var * (input - mean) / N / H / W + grad_mean / N / H / W # 计算对gamma的梯度 # 梯度的链式法则: d_y/d_{\gamma} = \hat{x} # 注意这里为元素级别乘法, 对应位置元素直接相乘 grad_output * input_normalized if ctx.needs_input_grad[1]: grad_gamma = (grad_output * input_normalized).sum([0, 2, 3], keepdim=True) # 计算对beta的梯度 # 梯度的链式法则: d_y/d_{\beta} = 1 if ctx.needs_input_grad[2]: grad_beta = grad_output.sum([0, 2, 3], keepdim=True) return grad_input, grad_gamma, grad_beta, None, None, None, None, None class BatchNorm2d(torch.nn.Module): def __init__(self, num_features, eps=1e-5, momentum=0.1): super(BatchNorm2d, self).__init__() self.num_features = num_features self.eps = eps self.momentum = momentum self.gamma = torch.nn.Parameter(torch.ones(num_features, 1, 1)) self.beta = torch.nn.Parameter(torch.zeros(num_features, 1, 1)) self.register_buffer('running_mean', torch.zeros(num_features, 1, 1)) self.register_buffer('running_var', torch.ones(num_features, 1, 1)) def forward(self, input): return BatchNorm2dFunction.apply(input, self.gamma, self.beta, self.running_mean, self.running_var, self.eps, self.momentum) # 示例使用 if __name__ == '__main__': # 创建一个BatchNorm2d层 bn = BatchNorm2d(num_features=3) # 创建一个输入张量 input = torch.randn(2, 3, 4, 4, requires_grad=True) # 前向传播 output = bn(input) # 反向传播 output.backward(torch.randn_like(output)) ``` ## LN 示意图: ![](https://zoe.red/usr/uploads/2024/04/4115558190.png) **代码实现** * 手动实现一个LayerNorm2d2D层的PyTorch版本 * 支持前向与反向传播 * 与BN明显不同的是, LN中不在需要mean/var相关参数 * 待验证? ```python import torch class LayerNorm2dFunction(torch.autograd.Function): @staticmethod def forward(ctx, input, gamma, beta, eps): N, C, H, W = input.size() mean = input.mean([1, 2, 3], keepdim=True) var = input.var([1, 2, 3], keepdim=True, unbiased=False) # 标准化 input_normalized = (input - mean) / torch.sqrt(var + eps) ctx.save_for_backward(input, input_normalized, gamma, beta, mean, var) ctx.eps = eps # 缩放和偏移 output = gamma * input_normalized + beta return output @staticmethod def backward(ctx, grad_output): input, input_normalized, gamma, beta, mean, var = ctx.saved_tensors eps = ctx.eps N, C, H, W = grad_output.size() grad_input = grad_gamma = grad_beta = None if ctx.needs_input_grad[0]: # 计算输入的梯度 grad_input_normalized = grad_output * gamma grad_var = (-0.5 * grad_input_normalized * (input - mean) / (var + eps).pow(1.5)).sum(dim=[1, 2, 3], keepdim=True) grad_mean = (-grad_input_normalized / torch.sqrt(var + eps)).sum(dim=[1, 2, 3], keepdim=True) - 2 * grad_var * (input - mean).mean(dim=[1, 2, 3], keepdim=True) grad_input = grad_input_normalized / torch.sqrt(var + eps) + 2.0 * grad_var * (input - mean) / (C * H * W) + grad_mean / (C * H * W) if ctx.needs_input_grad[1]: # 对gamma的梯度 grad_gamma = (grad_output * input_normalized).sum(dim=[1, 2, 3], keepdim=True) if ctx.needs_input_grad[2]: # 对beta的梯度 grad_beta = grad_output.sum(dim=[1, 2, 3], keepdim=True) return grad_input, grad_gamma, grad_beta, None class LayerNorm2d(torch.nn.Module): def __init__(self, num_features, eps=1e-5): super(LayerNorm2d, self).__init__() self.gamma = torch.nn.Parameter(torch.ones(1, num_features, 1, 1)) self.beta = torch.nn.Parameter(torch.zeros(1, num_features, 1, 1)) self.eps = eps def forward(self, input): return LayerNorm2dFunction.apply(input, self.gamma, self.beta, self.eps) ``` ## IN 公式: 示意图: ![](https://zoe.red/usr/uploads/2024/04/2714310946.png) ## GN ... 公式: 示意图: ## SN ... 公式: 示意图: ## 论文原文 1. [Batch Normalization](https://arxiv.org/abs/1502.03167) 2. [Layer Normalizaiton](https://arxiv.org/pdf/1607.06450v1.pdf) 3. [Instance Normalization](https://arxiv.org/pdf/1607.08022.pdf) 4. [Group Normalization](https://arxiv.org/pdf/1803.08494.pdf) 5. [Switchable Normalization](https://arxiv.org/pdf/1806.10779.pdf) ## 参考 https://www.cnblogs.com/LXP-Never/p/11566064.html#blogTitle0 https://www.cvmart.net/community/detail/1569 https://blog.csdn.net/lch551218/article/details/114295057 https://blog.csdn.net/qq_34440148/article/details/119864538 THE END 本文作者:将夜 本文链接:https://zoe.red/2024/496.html 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。 最后修改:2024 年 04 月 09 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏