这是我参与8月更文挑战的第8天,活动详情查看: 8月更文挑战html
Batch Normalization(批量归一化)是深度学习中常常用到的一种归一化方法python
咱们知道Sigmoid函数在定义域为
内导数趋于0,因为容易出现梯度消失的现象,所以ReLU函数使用的较多 markdown
但在某些场合不可避免的要去使用Sigmoid函数,所以咱们但愿能把输入的 值控制在有效的区间内,进行一个等效变化,使这些值均匀的分布在0附近,这样输入到Sigmoid函数后就能大几率避免梯度消失网络
以下面左侧的图所示, 的值处于一个比较小的区间 的值处于一个比较大的区间,所以对于 来讲,少许的变化就会对Loss产生急剧的变化,而对 来讲,变化就会相对小一点,能够看下面的“等高线”图,沿着纵轴方向变化的话,等高线会急剧的变化,沿着横轴方向变化,等高线的变化就稍平缓一点app
但若是如右图所示,输入的值区间都很接近,这样 和 对Loss的影响就比较接近,就会造成一种“圆形”路径,在这种状况进行搜索的时候,无论从哪一个点出发,梯度的方向都是指向全局最小解的方向,这种搜索过程会比较快一些,并且更稳定函数
Batch Normalization较多的应用于两个方面post
normalize = transforms.Normalize(mean=ean[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
复制代码
mean有三个值,分别对应RGB三个通道的均值,具体的Normalize过程就是学习
目前常见的Normalization有四种,将输入的图像shape记为[N,C,H,W],这几个方法主要区别是: 优化
以下图,输入数据是6张3通道784个像素点的数据,将其分到三个通道上,在每一个通道上也就是[6,784]的数据,而后分别获得和通道数同样多的统计数据均值 和标准差 。将每一个像素值减去 ,除以 就变换成了近似于 分布的数据,以后再用参数 和 将其变化到近似 的分布ui
和
只是样本中的统计数据,是没有梯度信息的,不过会保存在运行参数里。而
和
属于训练的参数,是有梯度信息的
Batch Normalize的规范化写法为 前三步是batch normalization的工序,通过这三步之后数据就近似于标准正态分布
,可是后面的公式还有一个反向操做,将normalize后的数据再平移和扩展,让数据近似于
,这位为了让神经网络本身去学着使用和修改这两个扩展参数,这样神经网络就能本身慢慢琢磨出前面的normalization操做到底有没有起到优化的作哟个,若是没有起到优化的做用,就用
和
来抵消一些normalization的操做
下面具体看一下在PyTorch中如何实现Batch Normalize
import torch
import torch.nn as nn
import torch.nn.functional as F
# 随机生成一个Batch的模拟,100张16通道784像素点的数据
# 均匀分布U(0~1)
x = torch.rand(128, 16, 784)
# 将28*28变为打平为一维的784
layer = nn.BatchNorm1d(16)
# Batch Normalization层,由于输入是将高度H和宽度W合成了一个维度,因此这里用1d
# 由于Batch Norm的参数直接是由channel数量得来的,所以这里直接给定了channel的数量为16,后续会输出16个channel的统计信息
out = layer(x) # f orward
print(out.shape)
print(layer.running_mean) # 全局的均值
print(layer.running_var) # 全局的方差
复制代码
运行结果
tensor([0.0500, 0.0498, 0.0499, 0.0501, 0.0500, 0.0502, 0.0500, 0.0500, 0.0500,
0.0500, 0.0499, 0.0501, 0.0500, 0.0499, 0.0499, 0.0502])
tensor([0.9084, 0.9084, 0.9083, 0.9084, 0.9084, 0.9083, 0.9083, 0.9083, 0.9083,
0.9083, 0.9084, 0.9084, 0.9083, 0.9083, 0.9083, 0.9083])
复制代码
注意layer.running_mean
和layer.running_var
获得的是全局的均值和方差,不是当前Batch上的,只不过这里只跑了一个Batch而已因此它就是这个Batch上的。如今尚未办法直接查看某个Batch上的这两个统计量的值
x = torch.randn(1, 16, 7, 7) # 1张16通道的7乘7的图像
# Batch Normalization层,由于输入是有高度H和宽度W的,因此这里用2d
layer = nn.BatchNorm2d(16) # 传入通道数
out = layer(x)
print(out.shape)
print(layer.running_mean) # 全局的均值
print(layer.running_var) # 全局的方差
print(layer.weight) # weight也就是前面公式里的gamma
print(layer.bias) # bias也就是前面公式里的beta
复制代码
运行结果
torch.Size([1, 16, 7, 7])
tensor([ 0.0187, 0.0125, -0.0032, 0.0032, 0.0034, 0.0031, 0.0231, -0.0024,
0.0002, 0.0194, -0.0097, 0.0177, 0.0324, -0.0013, 0.0128, -0.0086])
tensor([0.9825, 0.9799, 0.9984, 0.9895, 0.9992, 0.9809, 0.9919, 0.9769, 0.9928,
0.9949, 1.0055, 1.0368, 0.9867, 0.9904, 1.0097, 0.9910])
Parameter containing:
tensor([0.5925, 0.5662, 0.1066, 0.0073, 0.9517, 0.0476, 0.0416, 0.2041, 0.8666,
0.6467, 0.7665, 0.0300, 0.9050, 0.8024, 0.2816, 0.1745],
requires_grad=True)
Parameter containing:
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
requires_grad=True)
复制代码
注意这里的layer.weight
和layer.bias
是当前batch上的
若是在定义层时使用了参数affine=False
,那么就是固定
和
不自动学习,这时参数layer.weight
和layer.bias
将是None
使用了 和 以后,最后获得的分布是往 上靠的,而不是往 上靠的
使用了Batch Normalization让Converge(收敛)的速度加快了,这个能够直观理解,使用了靠近0的部分的Sigmoid激活,其梯度信息更大了。而且可以获得一个更好的解
提高了Robust(鲁棒性),这使得网络更加稳定,这能够从最前面第二张图所示来直观理解,若是参数有大有小,解空间像左边同样,那么稍微调整学习率可能就发生抖动(如图中左侧椭圆解空间上下方向走,且学习率太大时)或者训练速度太慢(如图中右侧椭圆解空间左右方向走,且学习率过小时)。这让超参数的调整没有那么敏感