## 1 介绍 标准化使得数据保持标准正态分布,标准化后使得数据分布在均值为0,方差为1上。 $$x = \frac{x-\mu_\beta}{\sqrt{\sigma^{2}+\epsilon}}$$ 标准化是将数据通过去均值实现中心化的处理,根据凸优化理论与数据概率分布相关知识,数据中心化符合数据分布规律,更容易取得训练之后的泛化效果,同时能够在尽可能短的时间内实现网络收敛。 ## 2 不同的Norm方式对比 不同的norm方式可以作用在不同的维度上面,达到相应的标准化效果。从下图中可以看到主要可以分成以下4中Norm方式,下面依次介绍这四种方式,并且给出相应的代码实现和测试结果。 1. Batch Norm 该方式是针对**不同channels**的所有样本的所有像素点进行norm操作 2. Layer Norm 该方式是针对**不同样本**的所有channels的所有像素点进行norm操作 3. Instance Norm 该方式是对**不同样本不同通道**的所有像素点进行norm操作 4. Group Norm 该方式类似于layer norm操作,首先按照channel将其分成多个group,然后针对**不同样本**在组内一**部分channel**的所有像素点进行norm操作。  ## 3 Batch Norm batch norm是我们最为熟悉的标准化方式。batch norm对于一个拥有d维的输入x,我们将对它的每一个维度进行标准化处理。” 假设我们输入的x是RGB三通道的彩色图像,那么这里的d就是输入图像的channels即d=3,标准化处理也就是分别对我们的R通道,G通道,B通道进行处理。  如上图所示,假设输入2个feature map,每个feature map的channel=2。我们将其输入到BN层进行标准化操作。BN是在每个channel上进行,因此讲所有feature的相同channel提取出来,组成x1和x2每个共8个元素。在不同channel里面计算其均值和方差,再根据均值和方差对其进行标准化操作即可。 ### 3.1 指数滑动平均 **Problem: 在训练的时候可以根据上图进行处理,但是在预测的时候batchsize=1,如何进行BN操作呢?** 在我们训练网络的过程中,我们是通过一个batch一个batch的数据进行训练的,但是我们在预测过程中通常都是输入一张图片进行预测,此时batch size为1,如果在通过上述方法计算均值和方差就没有意义了。所以我们在训练过程中要去不断的计算每个batch的均值和方差,**使用移动平均(moving average)的方法记录统计的均值和方差**,在训练完后我们可以近似认为所统计的均值和方差就等于整个训练集的均值和方差。然后**在验证以及预测过程中,就使用统计得到的均值和方差进行标准化处理。**  更新策略采用momentum的指数滑动平均,其中momentum取**0.1**,也就是以0.9的比例保持原有的均值统计量,以0.1的比例加入新数据的均值。计算均值和标准差均同理采用指数滑动平均的方式。 初始化均值为0,标准差为1 ### 3.2 引入拉伸(gamma)和偏置(beta)参数 对网络某一层的输出数据做归一化,然后送入网络下一层B,这样是会影响到本层网络A所学习到的特征的。打个比方,比如我网络中间某一层学习到特征数据本身就分布在S型激活函数的两侧,你强制把它给我归一化处理、标准差也限制在了1,把数据变换成分布于s函数的中间部分,这样就相当于我这一层网络所学习到的特征分布被你搞坏了,这可怎么办?于是BN层引入了可学习参数γ、β。 $$y = \gamma x + \beta$$ 初始化的gamma为0, beta为1 ### 3.3 BN层代码实现 构建BN层代码如下所示, 注意要点如下所示: 1 其中需要记录的参数是mean和std,采用register_buffer进行记录,无需梯度计算。而gamma和beta需要进行梯度计算,使用Parameter。 2 区分训练和测试的情况,训练的化需要不断更新记录的mean和std, 测试的时候直接使用进行计算即可 ```python class batch_norm(nn.Module): def __init__(self, in_channel, momentum=0.1, eps=1e-5): super(batch_norm, self).__init__() self.momentum = momentum self.eps = eps self.register_buffer('mean', torch.zeros(in_channel)) self.register_buffer('var', torch.ones(in_channel)) self.gamma = nn.Parameter(torch.ones(in_channel)) self.beta = nn.Parameter(torch.zeros(in_channel)) def forward(self, x): batch, channel, h, w = x.size() for i in range(channel): feature_t = x[:, i, :, :] if self.training: mean_t = torch.mean(feature_t) # 总体标准差 std_t1 = torch.std(feature_t, unbiased=False) # 样本标准差 std_t2 = torch.std(feature_t, unbiased=True) # 进行标准化操作 x[:, i, :, :] = (x[:, i, :, :] - mean_t) / torch.sqrt(std_t1 ** 2 + self.eps) # 进行拉伸gamma和缩放beta x[:, i, :, :] = self.gamma[i] * x[:, i, :, :] + self.beta[i] # 更新动量 self.mean[i] = (1 - self.momentum) * self.mean[i] + self.momentum * mean_t self.var[i] = (1 - self.momentum) * self.var[i] + self.momentum * std_t2 ** 2 else: # 进行标准化操作 x[:, i, :, :] = (x[:, i, :, :] - self.mean[i]) / torch.sqrt(self.var[i] + self.eps) return x ``` 下面输入数据进行对比验证。 ```python x1 = torch.randn(2, 2, 2, 2) x2 = x1.clone() bn_1 = batch_norm(in_channel=x1.shape[1], momentum=0.1, eps=1e-5) bn_2 = nn.BatchNorm2d(num_features=x2.shape[1], momentum=0.1, eps=1e-5) print(bn_1(x1)) print(bn_2(x2)) ``` 可以看到输出结果, 与BatchNorm2d结果一致: ```python tensor([[[[-0.2849, -1.3318], [-0.6046, 2.1104]], [[ 1.8089, -1.5609], [-0.3134, -0.4205]]], ... tensor([[[[-0.2849, -1.3318], [-0.6046, 2.1104]], [[ 1.8089, -1.5609], [-0.3134, -0.4205]]], ``` ### 3.4 BN层使用的注意事项 (1)batch size尽可能设置大点,设置小后表现可能很糟糕,设置的越大求的均值和方差越接近整个训练集的均值和方差。 (2)建议将bn层放在卷积层(Conv)和激活层(例如Relu)之间,且卷积层不用bias。即使采用了bias也会被BN层通过标准化操作,将其分布函数平移回去。 ### 3.5 多卡多机使用的Sync-BN层 后期补充 ### 3.6 前向反向传播推导 后期补充 ## 4 LayerNorm 上一章节介绍了Batch Normalization的原理,本章节讲解Layer Normalization。Layer Normalization是针对自然语言处理领域提出的,例如像RNN循环神经网络。为什么不使用直接BN呢,因为在RNN这类时序网络中,时序的长度并不是一个定值(网络深度不一定相同),比如每句话的长短都不一定相同,所有很难去使用BN,所以作者提出了Layer Normalization(注意,在图像处理领域中BN比LN是更有效的。但现在很多人将自然语言领域的模型用来处理图像,比如Vision Transformer,Swin-Transformer这些基于transformer模型,一般还是沿用了原模型中的Layer Norm标准化层。 ### 4.1 不同之处 layer norm层从公式上面看与BN层没有任何区别,只是采取的维度不同。 $$y = \frac{x - E|X|}{\sqrt{Var|X| + \epsilon}} * \gamma + \beta$$ 该公式与之前的公式是一致的,在LayerNorm中,针对每个单独的样本N的所有channel的所有HW进行操作。 但是该方式无需保存记录均值和方差。因为该方式进行的维度是在跨channel的维度的单个样本,因此对单个样本记录均值和方差没有意义。 ### 4.2 代码实现 官方给出的LayerNorm更多是面向NLP任务,在NLP任务中其维度一般是[batchsize, num, channel], 每个样本的维度放在最后一维度。但是在CV中,常见的排列方式在是[batchsize, channel, height, weight],因此如果想要把LN用于cv任务中,需要进行维度的重新排列。 ```python class layer_norm(nn.Module): def __init__(self, type='channel_first', eps=1e-5): super(layer_norm, self).__init__() if type not in ['channel_first', 'channel_last']: raise ValueError("type should be channel_first orchannel_last") self.type = type self.eps = eps self.gamma = nn.Parameter(torch.ones(1)) self.beta = nn.Parameter(torch.zeros(1)) def forward(self, x): if self.type == 'channel_last': return F.layer_norm(x, normalized_shape=x.shape[-1]) else: batch, channel, h, w = x.size() mean = torch.mean(x, dim=0) var = torch.var(x, dim=0) for i in range(batch): x[i, :, :, :] = (x[i, :, :, :] - mean[i]) / torch.sqrt(var[i] + self.eps) x = x * self.gamma + self.beta return x ``` 可以采用channel 最后编辑:2024年04月23日 ©著作权归作者所有 赞 0 分享
最新回复