目录
概述
作用过程:
源起及作用原理:
手写BN代码及作用示例
1、手撕BatchNorm代码
2、应用实例
批量归一化的本质目的:学习底部层的时候避免顶部层变化
所以只有在使用深层的网络结构的时候才会使用BN,浅层的如mlp效果不明显
优点:不会改变模型的精度,但是可以加速模型收敛
首先我们先明确引起变化的原因,是每一层的方差和均值的分布不同。所以我们要固定所有层的分布,尽量符合同一个分布。
批量归一化的作用原理:固定小批量里的均值和方差
2、再做额外的调整,输入的是实际数据xi,输出是归一化后的xi+1,其中gama和beta是可以学习的参数
1、作用在全连接层上时,作用在特征维,对每一列(每一个特征)进行处理,让这一个特征的所有数据均值为零方差为1。
2、作用在卷积层上时,是作用在通道维上面。具体点说,整个批量里所有的像素都是样本,即批量大小*高*宽是样本数,每个通道就是这个样本的一个特征,对这一个批量的一个特征进行批量归一化。是以1*1卷积层为例,得到的每个通道都是每个像素的一个特征。
首先是有人发现这个批量归一化作用在神经网络中非常Work,然后最初的推断是减小了内部协方差转移,但是经过后来的计算,发现并没有(先work后解释??)后来的论文中指出,之所以会work,是通过在每个小批量加入了一些噪音,控制模型复杂度:每个小批量是随机取的,所以计算得出的方差和均值对于整体而言就是一个噪音,是随机缩放,是随机偏移,而均值和方差是学习得到的稳定数值,所以噪音可以保证一定的随机性但又没有那么大的变化。
批量归一化一般不和dropout一起用,因为都是降低模型复杂度,所以不会那么有用了。
下面,我们从头开始实现一个具有张量的批量规范化层
import torch
from torch import nn
from d2l import torch as d2l
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
# 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
if not torch.is_grad_enabled():
# 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
else:
assert len(X.shape) in (2, 4)
#判断是全连接层还是卷积层,2代表全连接层,样本数和特征数;4代表卷积层,批量数,通道数,高宽
if len(X.shape) == 2:
# 使用全连接层的情况,计算特征维上的均值和方差
mean = X.mean(dim=0)
var = ((X - mean) ** 2).mean(dim=0)
else:
# 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
# 这里我们需要保持X的形状以便后面可以做广播运算
mean = X.mean(dim=(0, 2, 3), keepdim=True)
#1*n*高*宽
var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
# 训练模式下,用当前的均值和方差做标准化
X_hat = (X - mean) / torch.sqrt(var + eps)
# 更新移动平均的均值和方差
moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
moving_var = momentum * moving_var + (1.0 - momentum) * var
Y = gamma * X_hat + beta # 缩放和移位
return Y, moving_mean.data, moving_var.data
assert len(X.shape) in (2, 4)的作用:
判断是全连接层还是卷积层。
1、作用在全连接层上时,作用在特征维,对每一列(每一个特征)进行处理,让这一个特征的所有数据均值为零方差为1。按每一列求均值和方差
2、作用在卷积层上时,是作用在通道维上面。具体点说,整个批量里所有的像素都是样本,即批量大小*高*宽是样本数,每个通道就是这个样本的一个特征,对这一个批量的一个特征进行批量归一化。是以1*1卷积层为例,得到的每个通道都是每个像素的一个特征。
我们现在可以创建一个正确的BatchNorm
层。 这个层将保持适当的参数:拉伸gamma
和偏移beta
,这两个参数将在训练过程中更新。 此外,我们的层将保存均值和方差的移动平均值,以便在模型预测期间随后使用
class BatchNorm(nn.Module):
# num_features:完全连接层的输出数量或卷积层的输出通道数。
# num_dims:2表示完全连接层,4表示卷积层
def __init__(self, num_features, num_dims):
super().__init__()
if num_dims == 2:
shape = (1, num_features)
else:
shape = (1, num_features, 1, 1)
# 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
self.gamma = nn.Parameter(torch.ones(shape))
self.beta = nn.Parameter(torch.zeros(shape))
# 非模型参数的变量初始化为0和1
self.moving_mean = torch.zeros(shape)
self.moving_var = torch.ones(shape)
def forward(self, X):
# 如果X不在内存上,将moving_mean和moving_var
# 复制到X所在显存上
if self.moving_mean.device != X.device:
self.moving_mean = self.moving_mean.to(X.device)
self.moving_var = self.moving_var.to(X.device)
# 保存更新过的moving_mean和moving_var
Y, self.moving_mean, self.moving_var = batch_norm(
X, self.gamma, self.beta, self.moving_mean,
self.moving_var, eps=1e-5, momentum=0.9)
return Y
在LeNet中应用BatchNorm
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
nn.Linear(84, 10))