目录
批量归一化
损失出现在最后,后面的层训练较快。
数据在最底部
底部的层训练较慢
底部层一变化,所以都得跟着变
最后的那些层需要重新学习多次
导致收敛变慢
我们可以在学习底部层的时候避免变化顶部层吗?
固定小批量里面的均值和方差。
B就是小批量大小
然后再做额外的调整(可学习的参数λ和β)
向量
x
i
x_i
xi减去均值然后除以标准差就是正则化,由原始分布近似限定为均值为0方差为1的正态分布。
可学习的参数λ和β
作用在
①全连接层和卷积层输出上,激活函数前
(全连接层 or 卷积层输出➡批量归一化层➡激活函数)
②全连接层和卷积层输入上
对全连接层,作用在特征维
例:假设是一个二维的输入,则每一行是样本,每一列是特征。对于全连接层的话,对于每一个特征计算一个标量的均值、标量的方差。然后把这个特征变成均值为0、方差为1.
对于卷积层,作用在通道维
理解:例如1×1的卷积等价于全连接层,等价的意思是说 对于每一个像素它不是有多个通道吗,每个像素的通道数是100的话,这个像素其实是有一个长为100维的一个向量,那么可以认为这个向量是这个像素的一个特征。那么可以认为对于一个输入有高宽来说,里面的每个像素就是一个样本。所以对于卷积层来说,假设输入大小为批量大小×高×宽×通道数的话,那么样本数为批量大小×高×宽,就是整个批量里面所有的像素都是一个样本,那么它对应的通道就是你的特征。所以1×1的卷积就是说,把它拉成一个二维矩阵,在做一个全连接就是1×1卷积。
① 在每个批量里,1个像素是1个样本。与像素(样本)对应的通道维,就是特征维。
② 所以不是对单个通道的特征图做均值方差,是对单个像素的不同通道做均值方差。
③ 输入9个像素(3x3), 输出3通道,以通道作为列分量,每个像素都对应3列(输出通道=3),可以列出表格,按列求均值和方差,其实和全连接层一样的。即像素为样本,通道为特征。
批量归一化在做什么?
最初论文是想用它来减少内部协变量转移
后续有论文指出它可能就是通过在每个小批量里加入噪音来控制模型复杂度
为什么这两个东西是噪音?
① 这个东西(噪音,即:红色方框画的)其实是在每一个随机的小批量上计算得到的,就是一个随机的均值和随机的方差,因为每次都是随机取样,所以均值和方差是当前样本的均值和方差,所以噪音是比较大的。所以对 x i x_i xi一减一除(做了一些随机的偏移和缩放)。算出来的统计量也可以说是随机的。
② 因为每次取得batch中的数据都是不同的,所以在batch中计算的均值和方差也是不同的,所以引入了随机性。
因此没必要跟丢弃法混合使用
总结
①批量归一化固定小批量中的均值和方差,然后学习出适合的偏移和缩放
(当每一个层的均值和方差都固定后,就不会出现像之前学习率太大的话,靠近loss上面的梯度太大,就梯度爆炸了,学习率太小的话,靠近数据的梯度太小了,就算不动(梯度消失)。)
②可以加速收敛速度,但一般不改变模型精度。
批量归一化从零实现代码
import torch
from torch import nn
from d2l import torch as d2l
# X为输入,gamma、beta为学的参数。moving_mean、moving_var为全局的均值、方差。eps为避免除0的参数。momentum为更新moving_mean、moving_var的。
def batch_norm(X,gamma,beta,moving_mean,moving_var,eps,momentum):
# 'is_grad_enabled' 来判断当前模式是训练模式还是预测模式。
# 就是在做推理的时候不需要反向传播,所以不需要计算梯度
if not torch.is_grad_enabled():
# 做推理时,可能没有一个批量,可能只有一个图片进来,那该怎么办?图片还好一点,如果只是一个样本进来算不出来这个东西,所以这里用的全局的均值、方差。
# 在预测中,一般用整个预测数据集的均值和方差。加eps为了避免方差为0,除以0了。
X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
else: # 训练模式
# 确保输入X的维度是二维(全连接层,批量大小+特征=2)或四维(卷积层,批量数+通道数+图片高+图片宽=4)。
assert len(X.shape) in (2,4)
# 2 表示有两个维度,样本和特征,表示全连接层(batch_size, feature)
if len(X.shape) == 2:
# 对每个特征(列)计算均值和方差。
# 按行求均值,即对每一列求一个均值出来。mean为1Xn的行向量
mean = X.mean(dim=0)
# 方差也是行向量
var = ((X-mean)**2).mean(dim=0)
# 4 表示卷积层
else:
mean = X.mean(dim=(0,2,3),keepdim=True) # 0为批量大小,1为输出通道,2、3为高宽。这里是沿着通道维度求均值,0->batch内不同样本,2 3 ->同一通道层的所有值求均值,获得一个1xnx1x1的4D向量。
var = ((X-mean)**2).mean(dim=(0,2,3),keepdim=True) # 同样对批量维度、高宽取方差。每个通道的每个像素位置 计算均值方差。
# 使用计算出的均值和方差对X进行归一化,然后应用缩放和平移参数gamma和beta。
# 训练用的是计算出来的均值、方差,而推理用的是全局的均值、方差。
X_hat = (X-mean) / torch.sqrt(var + eps)
# 累加,将计算的均值累积到全局的均值上,更新moving_mean
moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
# 当前全局的方差与当前算的方差做加权平均,最后会无限逼近真实的方差。仅训练时更新,推理时不更新。
moving_var = momentum * moving_var + (1.0 - momentum) * var
Y = gamma * X_hat + beta # Y 为归一化后的输出
# .data的使用是为了确保返回的是Tensor的数据部分,而不是Tensor本身
return Y, moving_mean.data, moving_var.data
创建一个正确的BatchNorm图层
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
应用BatchNorm于LeNet模型
net = nn.Sequential(
# BatchNorm接受6个特征作为输入(与上一个卷积层的输出通道数相匹配),并且由于num_dims=4,它被设计为处理四维输入(N, C, H, W),其中N是批量大小,C是通道数,H和W是高度和宽度。
nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
# 输入通道数变为6(因为上一个卷积层的输出是6个特征图),输出通道数变为16。
nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
# 这是一个全连接层,它将展平后的特征向量映射到120个输出节点上
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时几乎完全相同,主要区别在于学习率大得多。
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())# 变快是指收敛所需的迭代步数变少了,但每次迭代计算量更大了呀,所以从时间上来讲跑得慢了
结果:
拉伸参数gamma和参数beta
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
结果:
简明实现
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())
结果:
问题
①Xavier和normalization(归一化)、BN(批量归范化)有什么区别?
Xavier是对权重初始化,BN是对隐藏层输出的数据归一化。
归一化normalization就是均值为0、方差为1,加入一些防止爆掉的项,叫做正则项。
②为什么加了batch normlization收敛时间变短?
加上batch normlization会使它的梯度变大,导致可以使用更大的学习率,所以对权重的更新变的更快。
③batch normlization可以加在激活函数之后吗?
一般不用在激活函数之后,因为它是一个对输入输出的线性变换。