目录
1.批量归一化
2.代码
2.1从零开始实现
2.2简洁实现
正向是forward函数(从底向上),算backward的时候是从上往下。出现的问题就是梯度在上面的时候比较大,在下面就比较小,上面就会很快收敛,下面收敛很慢,每次更新下面的靠近数据的东西,这些东西会尝试去抽取那些比较底层的特征,比如局部边缘等很简单的纹理信息,上面就是一些高层语义的信息。因此上面更新收敛就很快,下面更新就很慢,但是每次下面一变上面得重新开始训练,所以上面就白学了。每次底部一变顶部就要跟着变,所以收敛就比较慢。
所以思考,我们能不能在学习底部得时候(改变底部的特征的时候),避免顶部会不断的重新训练。这就是批量归一化考虑的问题。
核心的思想就是,B就是小批量所有下标的索引,对小批量里的所有样本求和再除以批量大小就是均值,和是可学习的参数。假设分布(均值为0,方差为1)不是那么适合的话,可以通过学习一个新的均值和方差来使得值对神经网络要好一点,但是会限定住和的变化不要太猛烈。
批量归一化是个线性变换,把均值方差弄得比较好。对于全连接,假如是2维的输入,每一行是样本,每一列是特征,批归一化作用在特征上,对每一个特征计算一个标量的均值标量的方差,然后把特征变成均值为0方差为1。对每一个全连接的输出或者输入都做一个这样子的事情,而不是只作用在数据上,另外也会用学到的和重新作用一下对均值和方差做一下矫正。
对于卷积层,作用在通道维,比如1*1的卷积层,等价于一个全连接层,对每一个像素,一个像素不是有多通道吗,如果通道数是100的话,这个像素其实是有一个长为100维的向量,可以认为向量是像素的一个特征。对于有高宽的输入来说,里面的每一个像素就是一个样本,对于卷积层来讲,假设输入是批量大小,乘以通道数,乘以高,乘以宽的话,样本数就是批量大小乘以高和宽,里面所有的像素都是一个样本。对应的通道就是特征。
import torch
from torch import nn
from d2l import torch as d2l
"""从头开始实现一个具有张量的批量规范化层"""
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
#moving_mean, moving_var可以看作个数据集上的均值和方差,而不是小批量上的均值和方差,
#做推理的时候用的。eps就是为了避免除0的一个东西。momentum就是用来更新moving_mean, moving_var
#的
# 通过autograd来判断当前模式是训练模式还是预测模式
if not torch.is_grad_enabled():
# 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
X_hat=(X-moving_mean)/torch.aqrt(moving_var+eps)
#解释一下为什么不用批量的均值,因为做in_first的时候可能就是一张图片或者是一个样本
#而不是一个批量。就算不出来
else:
assert len(X.shape) in (2,4)
#要么等于2,就是全连接层,要么等于4就是卷积层
if len(X.shape)==2:
# 使用全连接层的情况,计算特征维上的均值和方差
#对于2维输入。0维为样本,1维(列)为特征,就是计算每一列上的均值和方差
mean=X.mean(dim=0)
#按行求均值,就是对于每一列都算一个均值出来(跨行对同一列元素求均值)
var=((X-mean)**2).mean(dim=0)
else:
# 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
# 这里我们需要保持X的形状以便后面可以做广播运算,这里的mean是当前小批量的均值
mean=X.mean((dim=0,2,3),keepdim=True)
var=((X-mean)**2).mean((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
#创建一个正确的BatchNorm层
class BatchNorm(nn.module):
def __init__(self,num_features,num_dims):
# num_features:完全连接层的输出数量或卷积层的输出通道数。
# num_dims:2表示完全连接层,4表示卷积层
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):
if self.moving_mean.device!=X.device
# 如果X不在内存上,将moving_mean和moving_var复制到X所在显存上
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"""
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
nn.Linear(84, 10))
#和以前一样,我们将在Fashion-MNIST数据集上训练网络。 这个代码与我们第一次训练LeNet
#( 6.6节)时几乎完全相同,主要区别在于学习率大得多。
lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
"""结果输出:
loss 0.273, train acc 0.899, test acc 0.807
32293.9 examples/sec on cuda:0"""
#让我们来看看从第一个批量规范化层中学到的拉伸参数gamma和偏移参数beta。
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
"""结果输出:
(tensor([0.4863, 2.8573, 2.3190, 4.3188, 3.8588, 1.7942], device='cuda:0',
grad_fn=),
tensor([-0.0124, 1.4839, -1.7753, 2.3564, -3.8801, -2.1589], device='cuda:0',
grad_fn=))"""
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))
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
"""结果输出:
loss 0.267, train acc 0.902, test acc 0.708
50597.3 examples/sec on cuda:0
"""