比如上图,一般数据都是在下面的,而损失函数在最上面
forward:数据从下往上传
backward:求梯度的时候从上往下,由于梯度一般是一个比较小的数,那累成起来,等到了最下面的时候就会变得很小,也就是说梯度在上面一般比较大,而等到了下面就会变小了
因为上面层的梯度比较大,每次更新的时候,上面的梯度就会不断地去更新,因为你的学习率(假设是一个定值),而下面的梯度比较小,那对权重的更新就会比较小。那问题就是上面的东西会很快的收敛,而下面的收敛的会比较慢。导致的问题就是:每一次去更新下面的数据的时候,而下面的层会尝试去抽取一些底层的特征,比如一些局部的边缘,一些很简单的纹理的信息等,上面的层是一些高层语义的信息,但是你每次下面的一变,上面的虽然已经收敛了,但是也要重新变,(相当于上面的权重白学了)
问题:在训练持续的过程中,顶部的变化比较快,底部的变化比较慢,在底部不断持续地变化过程中需要不断地去持续训练顶部
在学习底部的特征的时候,能否避免顶部不断重复地训练,这就是BN要考虑的问题
问题整理:
BN的核心思想:
在讲损失和数值稳定性的时候,说到数据分布会变化,方差和均值整个分布会在不同的层之间会变化,那一个简单的办法就是把每一层的均值和方差给固定住,不管你的梯度也好输出也好,假设他们都符合同一个分布,那它数据的整体的形状相对来说是比较稳定的 ,就不会带来特别大的变化,那在学习中间细微的变动的时候就会比较容易
μ
^
B
=
1
∣
B
∣
∑
i
∈
B
x
i
σ
^
B
2
=
1
∣
B
∣
∑
i
∈
B
(
x
i
−
μ
^
B
)
2
+
ϵ
ϵ
\epsilon
ϵ是为了防止其为0而加的一个很小的数
对应的每一个特征,或者说每一列,就有一个
γ
β
\gamma\,\beta
γβ与之对应
如果其作用在全连接或者卷积的输出上时,BN会作用在激活函数前面,批量归一化是一个线性变换,而激活函数是一个非线性变换,所以BN就是把你的均值方差拉的比较好,让你变换不那么剧烈。
也可以作用在全连接层和卷积层输入上,即对你的输入做一个线性变换,让你的方差均值变的比较好。
对于全连接层,输入的数据为: X n × d X_{n\times d} Xn×d,其中每一行是一个样本,每一列是一个特征,那BN是作用在每一个列上,每一列都会得到对应的标量的方差和均值,即均值为0方差为1,(在做数据预处理的时候经常会使用此操作)
对于卷积层,是作用在通道维上:把通道当成这个像素点的一个特征,对于输入的一个图片,里面的每一个像素都是一个样本,如果输入的图片为(B, C, H, W),那样本的数量就是 B × H × W B\times H\times W B×H×W,即整个批量里面所有的像素都是一个样本,那每个像素对应的通道就是特征
momentum是用来更新moving_mean,moving_var的,通常是一个固定的值
eps也通常是一个固定的值
moving_mean,var是全局的均值方差
2对应全连接层,两个维度(batch, 全连接层大小)
4对应卷积,4个维度(batch,channel, H,W)
(2, 4)是一个元组,里面只有数字2和4
mean = X.mean(dim=(0, 2, 3),keepdim=True)
# 是说在(B,H,W)上求均值,求出来的mean的维度保持不变,即也是4维的,是一个1*channel*1*1的
moving_mean = momentum * moving_mean + (1.0 - momentum)*mean
# 吴恩达指数移动加权平均
# 卡尔曼滤波
# 带Momentum的SGD也是这个思路
因为moving_mean/var不在param里面,所以要自己判断Moving_mean在哪里的device上
应用BN于LeNet模型
注意:BN在激活函数前
最后的全连接不用加BN,加不加都没有关系,不用加BN线性变换