model.train
与model.val
和 Batch Normalization
的关系
文章目录
1、model.train
与 model.eval
的区别(参考博客)
(1)model.train
: model.train
的作用是启用Batch Normalization
和 Dropout
。如果模型中有BN
层和Dropout
,则需要在模型训练时添加model.train()
, 用来保证BN
层能够学习到每一个Batch
数据的均值与方差(具体计算过程见下文)。对于Dropout
, model.train
是随机取一部分网络连接来训练更新参数,从而增强网络的泛化能力。
(2)model.eval
: model.eval
的作用是不启用Batch Normalization
和 Dropout
。如果模型中有BN
层和Dropout
,则一般需要在模型测试或者验证的时候添加model.eval
。model.eval
会将Batch Normalization
内继承至Module
的self.training
参数置为false
, 则保证BN
层在测试的阶段不会再进行输入数据均值和方差的计算,即保证在测试过程中BN层所使用的均值和方差不变且为训练过程中保存的均值和方差。对于Dropout
, model.eval
是利用了全部的网络连接,即不会随机舍弃神经元。
对于Dropout
,操作相对简单,不用过多解释。因为现在大多数的模型使用了BN
层,则可以不用过多理会Dropout
和L2
正则项参数的选择,甚至可以将这两项去除。
在使用BN
的时候,要注意model.train
与model.eval
的区别,训练完train
数据集样本后,在使用该模型参数进行测试前,需要加上model.eval()
,否则的话,有输入数据,即使不进行训练,BN
层的均值和方差也会改变,进而可能造成测试精度下降。但是也有部分情况会出现加上model.eval
在训练过程中精度高而在验证或者测试的时候结果较差,甚至一团糟,这部分原因和解决方案可以参考第三节。
2、Batch Normalization
2.1 为什么需要使用Batch Normalization
(主要参考这篇博客)
随机梯度下降法对于训练深度网络简单高效,但是它有个毛病,就是需要我们人为的去选择参数,比如学习率、参数初始化、权重衰减系数、Dropout
比例等。这些参数的选择对训练结果至关重要,以至于我们很多时间都浪费在这些的调参上。BN
算法的强大之处在下面几个方面:
- 可以选择较大的学习率,使得训练速度增长很快,具有快速收敛性。
- 可以不去理会
Dropout
,L2
正则项参数的选择,如果选择使用BN
,甚至可以去掉这两项。 - 去掉局部响应归一化层(
LRN
)。(AlexNet
中使用的方法,BN
层出来之后这个就不再用了) - 可以把训练数据打乱,防止每批训练的时候,某一个样本被经常挑选到。(我的理解是使用
BN
层更多的是学习数据样本的分布,某一两个样本多次被挑选到不会影响整体的数据特征分布)
我们都知道在神经网络训练开始前,都要对输入数据做一个归一化处理,原因在于神经网络学习过程本质就是为了学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低;另外一方面,一旦每批训练数据的分布各不相同(batch
梯度下降),那么网络就要在每次迭代都去学习适应不同的分布,这样将会大大降低网络的训练速度,这也正是为什么我们需要对数据都要做一个归一化预处理的原因。
对于深度网络的训练是一个复杂的过程,只要网络的前面几层发生微小的改变,那么后面几层就会被累积放大下去。一旦网络某一层的输入数据的分布发生改变,那么这一层网络就需要去适应学习这个新的数据分布,所以如果训练过程中,训练数据的分布一直在发生变化,那么将会影响网络的训练速度。
所以BN
层就是要解决上述问题,通过在网络每一层输入的时候,又插入了一个归一化层,也就是先做一个归一化处理,然后再进入网络的下一层。然而其实实现起来并不是那么简单的。**如果是仅仅使用上面的归一化公式,对网络某一层A
的输出数据做归一化,然后送入网络下一层B
,这样是会影响到本层网络A所学习到的特征的。**打个比方,比如我网络中间某一层学习到特征数据本身就分布在 S
型激活函数的两侧,你强制把它给我归一化处理、标准差也限制在了1,把数据变换成分布于 s
函数的中间部分,这样就相当于我这一层网络所学习到的特征分布被你搞坏了,这可怎么办?于是BN
作者使出了一招惊天地泣鬼神的招式:变换重构,引入了可学习参数
γ
\gamma
γ 和
β
\beta
β ,这就是算法关键之处。通过参数
γ
\gamma
γ 和
β
\beta
β 可以恢复出原始的某一层所学到的特征。
此外,这篇博客也讲到batch Normalization
解决了梯度消失,梯度爆炸等问题。值得深入学习
2.2 Batch Normalization
基本原理:
Batch Normalization
层需要计算一个miniBatch input feature(x)
中所有元素的均值
μ
\mu
μ 和方差
σ
\sigma
σ ,然后对
x
i
x_i
xi减去均值除以标准差,最后利用可学习的参数
γ
\gamma
γ 和
β
\beta
β 进行仿射变换,即可得到最终的BN
输出
y
i
y_i
yi
公式如下:
①计算均值和方差:
μ
B
←
1
m
∑
i
=
1
m
x
i
\mu _{B}\leftarrow \frac{1}{m}\sum_{i=1}^{m}x_{i} \\
μB←m1i=1∑mxi
σ
B
2
←
1
m
∑
i
=
1
m
(
x
i
−
μ
B
)
2
\sigma _{B}^{2} \leftarrow \frac{1}{m} \sum_{i=1}^{m}\left ( x_{i}- \mu_{B} \right)^ 2
σB2←m1i=1∑m(xi−μB)2
②样本数据标准化处理:
x
i
^
←
x
i
−
μ
B
σ
B
2
+
ϵ
\widehat{x_i}\leftarrow \frac{x_i-\mu_{B}}{\sqrt{\sigma_{B}^{2}+\epsilon }}
xi
←σB2+ϵxi−μB
③进行平移和缩放处理,引入
γ
\gamma
γ 和
β
\beta
β 两个可训练参数,使网络可以学习恢复出原始网络所要学习的数据特征分布。
y
i
←
γ
∗
x
i
^
+
β
≡
B
N
γ
,
β
(
x
i
)
y_{i}\leftarrow \gamma \ast \widehat{x_{i}} + \beta \equiv BN_{\gamma,\beta}(x_{i})
yi←γ∗xi
+β≡BNγ,β(xi)
其完整公式如下所示:
x ^ = γ ∗ ( x − μ ) σ 2 + ϵ + β \widehat{x} = \gamma \ast \frac{ \left ( x-\mu \right )}{\sqrt{\sigma^{2}+\epsilon }}+\beta x =γ∗σ2+ϵ(x−μ)+β
其代码实现如下:
m = K.mean(X, axis=-1, keepdims=True)#计算均值
std = K.std(X, axis=-1, keepdims=True)#计算标准差
X_normed = (X - m) / (std + self.epsilon)#归一化
out = self.gamma * X_normed + self.beta#重构变换
由上述公式可以看出,当训练时Batch Size
设置较小的话,所计算出的均值
μ
\mu
μ 和方差
σ
\sigma
σ 不能较好的反应整个数据集的特征分布,因此,可学习的参数
γ
\gamma
γ 和
β
\beta
β 不能够很好的恢复出原始的某一层所学到的特征,在测试时,如果是单张图片,使用model.eval固定均值
μ
\mu
μ 和方差
σ
\sigma
σ 时,不能很好的将单张图片进行归一化,进而使用参数
γ
\gamma
γ 和
β
\beta
β 无法很好的进行重构变换(注意此处,有些博客会说使用model.eval
固定的是可学习参数
γ
\gamma
γ 和
β
\beta
β ,实际上这两个参数测试的时候因为没有反向传播,不管使用model.train
还是model.eval
都是不会变化的,只有在模型训练的时候有梯度反向传播才会进行学习训练)。
当训练时batch size
设置比较大时,BN
层中可学习的参数每次所使用的均值和方差更接近全局的数据,因此可以很好的反应全局的数据分布,使用单张图片测试时结果也是相差不大的。
1.尽量将batch size
设置大一些(2的整数次幂),32,64,128,256,能多大就多大,但是一般不要超过512.
2.如果设备受限,导致batch size
只能设置很小(1,2),那测试时可以把eval()
去掉,前提是网络结构中并没有dropout
层。这里可以理解为训练时batch size
设置的很小,如1、2,其实每次minibatch
太小,在训练时训练出的均值和方差其实与单张图片测试时相差很大,去掉eval()
使用默认train()
可以不固定均值和方差。
3、常见的问题及解决方案
(1)为什么有时测试时使用model.eval()
会使得准确率降低?(原文链接)
原因①:可能是由于batch size
设置较小,如1,2,训练时针对BN
层计算的均值和方差只使用很小的mini batch
数据,测试时使用的是单张图片进行测试,使用model.eval()
固定了均值和方差,所以不能很好的对该图片进行归一化,就会导致准确率下降。
原因②: 参考:https://www.jianshu.com/p/a29b86b6ab0b 。作者:ZhengHsin
这个错误可能是由于在batchnorm
中的动量(momentum
)设置而导致。在进行推理时,设置较大的动量值可以解决这个问题。如下:nn.BatchNorm2d(out_planes, eps=1e-3, momentum=1))
momentum作用是更新 running_mean
,running_var
时的动量,默认0.1。更新公式是:
x
n
e
w
=
(
1
−
m
o
m
e
n
t
u
m
)
×
x
c
u
r
+
m
o
m
e
n
t
u
m
×
x
b
a
t
c
h
x_{new}=(1-momentum)\times x_{cur}+momentum\times x_{batch}
xnew=(1−momentum)×xcur+momentum×xbatch
其中,
x
n
e
w
x_{new}
xnew代表更新后的running_mean
和running_var
,
x
c
u
r
x_{cur}
xcur 表示更新前的running_mean
和running_var
,
x
b
a
t
c
h
x_{batch}
xbatch 表示当前batch
的均值和无偏样本方差。
可能还有很多别的原因,欢迎讨论。
(2)Batch Normalization
的均值和方差对谁求?
1、对feature map
的channel
方向求均值和方差, 假设batch size=n
, feature map
的shape= (w, h, c)
, 其中c
是channel
个数, 则会对c
个n*w*h
的特征分别求出c
个均值和方差.
2、同样的,为了减少参数量, 一个channel
(相当于一种卷积核提取的同一种特征)只对应一组可学习的参数γ
、β
, 所以对于一层BN
层, 可学习的参数为2*c
个.
参考博客:
https://blog.csdn.net/qq_52852138/article/details/123769937
https://zhuanlan.zhihu.com/p/357075502
https://blog.csdn.net/weixin_44479045/article/details/124307200
https://blog.csdn.net/ygfrancois/article/details/90382459
https://blog.csdn.net/qq_52852138/article/details/123769937
https://blog.csdn.net/hjimce/article/details/50866313
https://zhuanlan.zhihu.com/p/337732517