赞
踩
神经网络学习的本质就是学习数据的分布。如果没有对数据进行归一化处理,那么每一批次训练的数据的分布就有可能不一样。从大的方面来讲,神经网络需要在多个分布中找到一个合适的平衡点;从小的方面来说,由于每层网络的输入数据在不断的变化,这会导致不容易找到合适的平衡点,最终使得构建的神经网络模型不容易收敛。当然,如果只是对输入数据做归一化,这样只能保证数据在输入层是一致的,并不能保证每层网络的输入数据分布是一致的,所以在神经网络模型的中间层也需要加入归一化处理。
本文将介绍4种归一化方法,使用numpy实现归一化过程,并将结果和torch对应的结果对比:
目录
深度学习归一化原理较为简单,对于输入数据x,计算其均值E[x]和方差Var[x],通过下式进行计算即可。但是需要注意的是,引入了2个可训练参数缩放因子γ和平移因子β,引入的可学习因子提升了模型的表达能力。
在深度学习网络训练过程中,由于数据集量级较大,常常把数据集分批次送入网络训练,那么在BN层计算均值和方差的时候,就没办法一次性计算出整个数据集的均值和方差,在mini batch训练阶段并无太大影响,因为mini batch训练时的方差和均值就是根据mini batch内的数据计算出的,但是当模型用于推理时,使用的均值和方差是训练集的均值和方差,于是需要一个方法来计算数据集的均值和方差,通常使用滑动平均法(momentum一般为0.1)计算全局均值和方差:
训练和推理阶段的区别:
训练阶段根据mini batch的数据计算均值和方差,并使用滑动平均法计算全局均值和方差;推理阶段使用训练阶段计算的全局均值和方差参与计算。
这里选择torch.nn.
BatchNorm2d
为例,对于(N, C, H, W)的特征图(下图可以理解为[N ,C, H*W]),保留通道C计算均值和方差,故有2C个可训练参数(C个γ和C个β),2C个不可训练参数(mean和var)。
numpy实现BatchNorm2D,并测试与torch.nn.BatchNorm2D是否前向对其,代码如下:
- import torch
- import numpy as np
-
-
- def batch_normalize2d(data, alpha=1.0, beta=0.0, eps=1e-5):
- u = np.mean(data, axis=(0, 2, 3), keepdims=True) # [1, C, 1, 1]
- variance = np.var(data, axis=(0, 2, 3), keepdims=True) # [1, C, 1, 1]
- alpha = np.zeros_like(u) + alpha
- beta = np.zeros_like(u) + beta
- data = alpha * (data - u) / np.sqrt(variance + eps) + beta
- return data
-
-
- np.random.seed(10001)
- in_channels = 10
- data = (np.random.rand(4, in_channels, 100, 100) * 3 - 1.5).astype('float32')
- alpha = 1.0
- beta = 0.0
- eps = 1e-5
- np_normed_data = batch_normalize2d(data, alpha=alpha, beta=beta, eps=eps)
- norm_layer = torch.nn.BatchNorm2d(num_features=in_channels, eps=eps, momentum=None)
- torch_normed_data = norm_layer(torch.from_numpy(data))
- print("implement error:", np.mean(np.abs(np_normed_data - torch_normed_data.detach().numpy())))
- assert np.allclose(np_normed_data, torch_normed_data.detach().numpy()), "Normalized value not equal."
- # 输出:
- # implement error: 3.778329e-08
注意:LayerNorm的均值和方差是根据单个数据计算的,所以不需要计算全局均值和全局方差。
这里选择torch.nn.LayerNorm为例(normalized_shape=[C,H,W]),对于(N, C, H, W)的特征图(下图可以理解为[N ,C, H*W]),保留通道N计算均值和方差。
注意与BatchNorm2d的不同:
当torch.nn.LayerNorm的elementwise_affine为True时,γ和β的参数数量分别时C*H*W。
当torch.nn.LayerNorm的elementwise_affine为False时,没有γ和β参数。
均值和方差根据输入数据计算,不需要在训练集上用滑动平均方法计算,所以不保存均值和方差。
numpy实现LayerNorm,并测试与torch.nn.LayerNorm是否前向对其,代码如下:
吐槽一下:自己实现的时候γ和β参数的维度和一开始想的不一样(一开始以为和batch_norm2d一样,那应该是2N个可学习参数,实际上是根据元素数量来的),看了源码才发现没有均值和方差这2个参数。
- import torch
- import numpy as np
-
-
- def layer_normalize2d(data, alpha=1.0, beta=0.0, eps=1e-5):
- u = np.mean(data, axis=(1, 2, 3), keepdims=True) # [N, 1, 1, 1]
- variance = np.var(data, axis=(1, 2, 3), keepdims=True) # [N, 1, 1, 1]
- alpha = np.zeros(shape=(1,) + data.shape[1:]) + alpha
- beta = np.zeros(shape=(1,) + data.shape[1:]) + beta
- data = alpha * (data - u) / np.sqrt(variance + eps) + beta
- return data
-
-
- np.random.seed(10001)
- in_channels = 10
- data = (np.random.rand(4, in_channels, 100, 100) * 3 - 1.5).astype('float32')
- alpha = 1.0
- beta = 0.0
- eps = 1e-5
- np_normed_data = layer_normalize2d(data, alpha=alpha, beta=beta, eps=eps)
- norm_layer = torch.nn.LayerNorm(normalized_shape=data.shape[1:], eps=eps, elementwise_affine=True)
- torch_normed_data = norm_layer(torch.from_numpy(data))
- print("LayerNorm weight shape: ", norm_layer.weight.shape, ", bias shape: ", norm_layer.bias.shape)
- print("implement error:", np.mean(np.abs(np_normed_data - torch_normed_data.detach().numpy())))
- assert np.allclose(np_normed_data, torch_normed_data.detach().numpy()), "Normalized value not equal."
- # 输出:
- # LayerNorm weight shape: torch.Size([10, 100, 100]) , bias shape: torch.Size([10, 100, 100])
- # implement error: 6.919199475696921e-08
此处以torch.nn.InstanceNorm2d(track_running_stats=True, affine=True)为例,对于(N, C, H, W)的特征图(下图可以理解为[N ,C, H*W]),保留通道N和C计算均值和方差。
相信有很多朋友都是根据下面这张图来理解Instance Norm,一眼看上去mean和variance的维度应该是[N, C],但是打印出runing_mean和runing_var维度一看,竟然是[C]!What?
好家伙,经过多次测试,终于搞明白了,原来在归一化计算(一、归一化 中的公式)的时候,均值和方差维度是[N, C],但是因为batch_size可能会变化,所以running_mean和running_var保存的时候把[N,C]的均值和方差取了个均值,所以runing_mean和runing_var维度是[C]。
numpy实现InstanceNorm,并测试与torch.nn.InstanceNorm2d是否前向对其,代码如下:
吐槽一下:维度的问题网上找了很久没找到想要的答案,最后一下一下试出来的。
- import torch
- import numpy as np
-
-
- def instance_normalize2d(data, alpha=1.0, beta=0.0, eps=1e-5):
- u = np.mean(data, axis=(2, 3), keepdims=True) # [N, C, 1, 1]
- variance = np.var(data, axis=(2, 3), keepdims=True) # [N, C, 1, 1]
- alpha = np.zeros_like(u) + alpha
- beta = np.zeros_like(u) + beta
- # print(u[:, :, 0, 0].mean(axis=0), variance[:, :, 0, 0].mean(axis=0))
- data = alpha * (data - u) / np.sqrt(variance + eps) + beta
- return data
-
-
- np.random.seed(10001)
- in_channels = 10
- data = (np.random.rand(4, in_channels, 100, 100) * 3 - 1.5).astype('float32')
- alpha = 1.0
- beta = 0.0
- eps = 1e-5
- np_normed_data = instance_normalize2d(data, alpha=alpha, beta=beta, eps=eps)
- norm_layer = torch.nn.InstanceNorm2d(num_features=data.shape[1], eps=eps, momentum=1.0, track_running_stats=True, affine=True)
- torch_normed_data = norm_layer(torch.from_numpy(data))
- print("running_mean shape:", norm_layer.running_mean.shape, "running_var shape:", norm_layer.running_var.shape)
- print("weight shape:", norm_layer.weight.shape, "bias shape:", norm_layer.bias.shape)
- print("implement error:", np.mean(np.abs(np_normed_data - torch_normed_data.detach().numpy())))
- assert np.allclose(np_normed_data, torch_normed_data.detach().numpy()), "Normalized value not equal."
- # 输出:
- # running_mean shape: torch.Size([10]) running_var shape: torch.Size([10])
- # weight shape: torch.Size([10]) bias shape: torch.Size([10])
- # implement error: 3.8745e-08
InstanceNorm就是GroupNorm的特例,当Group Norm分组和channels相同时,就是instance norm,当分组为1时,就是LayerNorm。
GroupNorm可以参考上方的InstanceNorm和LayerNorm修改。
BatchNorm2d — PyTorch 1.10 documentation
GroupNorm — PyTorch 1.10 documentation
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。