批量归一化
训练深层神经网络是十分困难的,特别是在较短的时间内使他们收敛更加棘手。 批量归一化(batch normalization)这是一种流行且有效的技术,可持续加速深层网络的收敛速度。 批量归一化使得我们能够训练 100 层以上的网络。
1 训练深层网络
首先,数据预处理的方式通常会对最终结果产生巨大影响。使用真实数据时,我们的
第一步是标准化输入特征,使其平均值为0,方差为1。这种标准化可以很好地与我们的优化器配合使用,因为它可以将参数的量级进行统一。
第二,对于典型的多层感知机或卷积神经网络。当我们训练时,中间层中的变量(例如,多层感知机中的仿射变换输出)可能具有更广的变化范围:不论是沿着从输入到输出的层,跨同一层中的单元,或是随着时间的推移,模型参数的随着训练更新变幻莫测。 批量归一化的发明者非正式地假设,这些变量分布中的这种偏移可能会阻碍网络的收敛。 直观地说,我们可能会猜想,如果一个层的可变值是另一层的 100 倍,这可能需要对学习率进行补偿调整。
第三,更深层的网络很复杂,容易过拟合。 这意味着正则化变得更加重要。
批量归一化应用于单个可选层(也可以应用到所有层),其原理如下:在每次训练迭代中,我们首先归一化输入,即通过减去其均值并除以其标准差,其中两者均基于当前小批量处理。 接下来,我们应用比例系数和比例偏移。 正是由于这个基于批量统计的标准化,才有了批量归一化的名称。
请注意,如果我们尝试使用大小为 1 的小批量应用批量归一化,我们将无法学到任何东西。 这是因为在减去均值之后,每个隐藏单元将为 0。 所以,只有使用足够大的小批量,批量归一化这种方法才是有效且稳定的。 请注意,在应用批量归一化时,批量大小的选择可能比没有批量归一化时更重要
正向传播是从数据到损失函数,反向传播是从损失函数到数据,反向传播的时候刚开始梯队可能很大,但是随着反向传播要乘以记录的梯度(偏导)就会越来越小。因此越靠近损失函数收敛的就越快
越靠近数据收敛的越慢。这样导致的问题是每次底层(数据)训练完上层(损失函数)就要拟合数据
就会导致收敛比较慢。这里引出了一个问题,我们可以在学习底部层(数据层)的时候能不能避免变化顶部层(损失函数)
量归一化 BN 根据以下表达式转换 x
另外,批量归一化图层在”训练模式“(通过小批量统计数据归一化)和“预测模式”(通过数据集统计归一化)中的功能不同。 在训练过程中,我们无法得知使用整个数据集来估计平均值和方差,所以只能根据每个小批次的平均值和方差不断训练模型。 而在预测模式下,可以根据整个数据集精确计算批量归一化所需的平均值和方差。
对于全连接层行为样本每一列为特征
对于卷积层每一个像素可以看成一个样本,样本数可以看成批量大小高宽那么对于每一个像素样本对应的特征数就是它所对应的通道数所对应位置的像素
批量归一化加速收敛但不改变模型精度
2 代码
import torch
from torch import nn
from d2l import torch as d2l
#X层的输入, gamma和beta需要学习的参数 moving_mean全局均值 moving_var全集方差(可以理解为整个数据集上的均值和方差)
#momentum(取固定数字)用来更新moving_mean和moving_var eps表示批量归一化里面的那个一不西诺
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: #2D卷积的情况
# 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
# 这里我们需要保持X的形状以便后面可以做广播运算
mean = X.mean(dim=(0, 2, 3), keepdim=True) #沿着通道维求均值
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
#创建一个BatchNorm图层
class BatchNorm(nn.Module):
# `num_features`:完全连接层的输出数量或卷积层的输出通道数。
# `num_dims`:2表示完全连接层,4表示卷积层
def __init__(self, num_features, num_dims): #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) #放到GPU上
self.moving_var = self.moving_var.to(X.device) #放到GPU上
# 保存更新过的 `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.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
nn.MaxPool2d(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))
#训练
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())
d2l.plt.show()
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。