目录
一:回顾
上一篇我们讲解了GoogLeNet以及它使用的NiN块,使用1x1卷积层和全局平均池化层来替代全连接层,以提取更多的空间结构信息,并减少参数数量。GoogLeNet是2015年提出,吸收了NiN中串联网络的思想,并在此基础上做了改进,提出了Inception块,在GoogleNet中,基本的卷积块被称为Inception块(Inception block)。
二:批量规范化
训练深层神经网络是十分困难的,特别是在较短的时间内使他们收敛更加棘手。 本节将介绍批量规范化(batch normalization),这是一种流行且有效的技术,可持续加速深层网络的收敛速度。 再结合在将介绍的残差块resnet,批量规范化使得研究人员能够训练100层以上的网络。
训练深层网络
为什么需要批量规范化层呢?让我们来回顾一下训练神经网络时出现的一些实际挑战。首先,数据预处理的方式通常会对最终结果产生巨大影响。 回想一下我们应用多层感知机来预测房价的例子。 使用真实数据时,我们的第一步是标准化输入特征,使其平均值为0,方差为1。 直观地说,这种标准化可以很好地与我们的优化器配合使用,因为它可以将参数的量级进行统一。
第二,对于典型的多层感知机或卷积神经网络。当我们训练时,中间层中的变量(例如,多层感知机中的仿射变换输出)可能具有更广的变化范围:不论是沿着从输入到输出的层,跨同一层中的单元,或是随着时间的推移,模型参数的随着训练更新变幻莫测。 批量规范化的发明者非正式地假设,这些变量分布中的这种偏移可能会阻碍网络的收敛。 直观地说,我们可能会猜想,如果一个层的可变值是另一层的100倍,这可能需要对学习率进行补偿调整。
第三,更深层的网络很复杂,容易过拟合。 这意味着正则化变得更加重要。
批量规范化应用于单个可选层(也可以应用到所有层),其原理如下:在每次训练迭代中,我们首先规范化输入,即通过减去其均值并除以其标准差,其中两者均基于当前小批量处理。 接下来,我们应用比例系数和比例偏移。 正是由于这个基于批量统计的标准化,才有了批量规范化的名称。
请注意,如果我们尝试使用大小为1的小批量应用批量规范化,我们将无法学到任何东西。 这是因为在减去均值之后,每个隐藏单元将为0。 所以,只有使用足够大的小批量,批量规范化这种方法才是有效且稳定的。 请注意,在应用批量规范化时,批量大小的选择可能比没有批量规范化时更重要。
是小批量β的样本均值,是小批量β的样本标准差。 应用标准化后,生成的小批量的平均值为0和单位方差为1。 由于单位方差(与其他一些魔法数)是一个主观的选择,因此我们通常包含 拉伸参数(scale)Γ和偏移参数(shift)β,它们的形状与x相同。 请注意,γ和β是需要与其他模型参数一起学习的参数。
均值和方差分别为:
接着,将输入数据进行标准化处理,epsilon为了避免分母为0而加上的一个小值。得到:
最后,将标准化后的数据进行缩放和平移,即:
这个是李沐老师书上的公式,没有加上epsilon,但实际上要加。
但是解决梯度后,又出现了退化问题,可以通过残差(residual模块)解决这个问题,退化具体是个什么问题? 退化就是网络越深反而识别错误率提高的现象 ,但是不是用了RELU吗,怎么还会梯度消失梯度爆炸呢? 因为用了relu只是可能不会梯度消失,但是有可能梯度爆炸, 而且ReLU可能会导致我们的原始特征的不可逆损失,不可逆的特征损失会导致网络的退化。
在推断阶段,我们希望能够使用一个统计量来代替当前批次数据的均值和方差,从而减少计算量。具体来说,我们需要在训练阶段记录每个Batch Normalization层的均值和方差的统计量,即所有训练数据上该层特征图的均值和方差的平均值和方差。
在我们训练过程中,均值和方差是通过计算当前批次数据得到的记为为和,而我们的验证以及预测过程中所使用的均值方差是一个统计量记为和。和的具体更新策略如下,其中momentum默认取0.1:
层数越深的时候,出现了梯度消失或者梯度爆炸,因为
等高线::
为什么损失的图画出来是一条一条的等高线?损失函数通常是一个在多维空间中的曲面,其高度表示了当前参数下的损失大小。当使用梯度下降等优化算法更新参数时,我们需要找到在该曲面上的最低点,也就是损失最小的位置。为了可视化这个曲面,我们可以在多维空间中采样一些点,计算它们的损失值,并将这些点连接起来形成一条等高线。这条等高线的每一个点都表示了该点处的损失大小,相邻点之间的高度差表示损失函数在该方向上的变化大小。因此,我们可以通过观察等高线的形状和分布来了解损失函数的性质,如是否存在多个局部最优解、是否存在梯度消失等问题。
在圆上任取一点(x,y),它对应的切线的斜率可以用该点的切线的斜率公式计算,即:
梯度方向是该点等高线的切线的垂线(该点的法线)。因为根据梯度的定义,得到法线跟梯度的表达式是一样的,所以法线向量就是梯度。
举例: 一根萝卜,相当于曲线,然后找到某一点,切开,得到等高线,那么梯度就是在这个等高线、水平面上的这个点的垂线的方向,那就是梯度。
当你后面的layer按照前面的laye的参数γ学好之后,但是当你后面的layer学好后,前面的layer又变了,所以BN的好处就是固定住一个范围。
解决ICS这个问题:因为lr小可能有用,但是训练变得很慢,不好用,用BN可以处理ICS这个问题。因为input_x输入数据的数值是固定的,但是layer的ouput_mean和variation是不断变化的,因为在整个trianing过程中, 你的network的参数是一直变化的,所以出现了BN。
gpu加速运算batch的原理是:平行运算:把输入拼接在一起,乘上w1,
batch一定要够大
仅仅用sigmoid的话是不能train的,需要加上BN后才能达到粉色的那种情况。
代码测试:自己写的代码跟pytorch源代码进行结果的对比
import numpy as np
import torch.nn as nn
import torch
def bn_process(feature, mean, var):
feature_shape = feature.shape
for i in range(feature_shape[1]):
# [batch, channel, height, width]
feature_t = feature[:, i, :, :]#计算每一个通道的mean和variance
print('第{}次的feature_t数据是什么??{}'.format(i+1,feature_t))
mean_t = feature_t.mean()
# 总体标准差
std_t1 = feature_t.std()#计算每一个通道的mean和variance
# 样本标准差
std_t2 = feature_t.std(ddof=1)#计算每一个通道的mean和variance
# bn process
# 这里记得加上eps和pytorch保持一致
feature[:, i, :, :] = (feature[:, i, :, :] - mean_t) / np.sqrt(std_t1 ** 2 + 1e-5)
# update calculating mean and var
mean[i] = mean[i] * 0.9 + mean_t * 0.1
var[i] = var[i] * 0.9 + (std_t2 ** 2) * 0.1
print('第{}次的数据标准化是什么??{}'.format(i+1,feature[:, i, :, :]))
print()
print('最后的feature是什么?',feature)
feature1 = torch.randn(2, 2, 2, 2)
calculate_mean = [0.0, 0.0]
calculate_var = [1.0, 1.0]
print()
bn_process(feature1.numpy().copy(), calculate_mean, calculate_var)
bn = nn.BatchNorm2d(2, eps=1e-5)#num_features: 输入张量中每个样本的特征数。eps: 在标准差上加上一个小的值,以避免除以零的情况。
output = bn(feature1)
print('output 是什么?',output)
最后发现两个结果是一样的,只是进度不一样。
第1次的feature_t数据是什么??[[[-0.7585968 0.7913021 ]
[ 0.5666532 -1.1607316 ]]
[[-0.48988745 -0.44479802]
[-1.5271071 -0.9390716 ]]]
第1次的数据标准化是什么??[[[-0.34879375 1.7042251 ]
[ 1.4066517 -0.88146734]]
[[ 0.00714258 0.06686869]
[-1.3667738 -0.5878534 ]]]
第2次的feature_t数据是什么??[[[ 0.9847098 0.06495863]
[-0.44869336 -1.3772765 ]]
[[-1.6782545 0.25469238]
[ 0.10938011 0.26012498]]]
第2次的数据标准化是什么??[[[ 1.4485505 0.3506511 ]
[-0.26249114 -1.3709333 ]]
[[-1.7302082 0.5771347 ]
[ 0.40367663 0.5836196 ]]]
最后的feature是什么? [[[[-0.34879375 1.7042251 ]
[ 1.4066517 -0.88146734]]
[[ 1.4485505 0.3506511 ]
[-0.26249114 -1.3709333 ]]]
[[[ 0.00714258 0.06686869]
[-1.3667738 -0.5878534 ]]
[[-1.7302082 0.5771347 ]
[ 0.40367663 0.5836196 ]]]]
output 是什么? tensor([[[[-0.3488, 1.7042],
[ 1.4067, -0.8815]],
[[ 1.4486, 0.3507],
[-0.2625, -1.3709]]],
[[[ 0.0071, 0.0669],
[-1.3668, -0.5879]],
[[-1.7302, 0.5771],
[ 0.4037, 0.5836]]]], grad_fn=<NativeBatchNormBackward0>)
三:完整代码测试:以LeNet为例子,增加BN层的效果
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))
start_time = time.time()
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
end_time = time.time()
print("训练一共使用了{:.2f}分钟".format((end_time-start_time)/60))
d2l.plt.show()
四:争议
直观地说,批量规范化被认为可以使优化更加平滑。 然而,我们必须小心区分直觉和对我们观察到的现象的真实解释。 回想一下,我们甚至不知道简单的神经网络(多层感知机和传统的卷积神经网络)为什么如此有效。 即使在暂退法和权重衰减的情况下,它们仍然非常灵活,因此无法通过常规的学习理论泛化保证来解释它们是否能够泛化到看不见的数据。
五:小结
-
在模型训练过程中,批量规范化利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出值更加稳定。
-
批量规范化在全连接层和卷积层的使用略有不同。
-
批量规范化层和暂退层一样,在训练模式和预测模式下计算不同。
-
批量规范化有许多有益的副作用,主要是正则化。另一方面,”减少内部协变量偏移“的原始动机似乎不是一个有效的解释。
所有项目代码+UI界面
视频,笔记和代码,以及注释都已经上传网盘,放在主页置顶文章