接触CNN也一段时间了,最近也到了秋招期间,面试的时候可能会问到的一些内容需要做一个整理
参考了一个大神的博客,感觉讲的很深入也很好理解。我这里主要是对他的博客做一个自己的归纳整理,主要是为了方便自己去理解,也欢迎大家一起讨论自己的理解。
这里给出大神的博客地址:https://blog.csdn.net/qq_25737169/article/details/79048516
归纳整理如下:
1:深度神经网络主要学习的是训练数据的分布,并能够在测试集上做很好的fahu泛化。但是数据在经过每层卷积层和relu层计算后,其数据分布也在发生变化,这种现象称之为InternalInternal Covariate ShiftShift(内部协变量移位),也就是说经过每一次迭代更新参数后,上一层网络的输出数据经过这一层网络计算后,数据的分布会发生变化,这就会为下一层的网络学习带来困难
2:在batchnorm产生之前,针对由于分布变化导致学习困难的问题,主要解决办法是使用较小的学习率,和小心的初始化参数,对数据做白化处理,但是治标不治本,不能根本解决问题。
3:所谓数据分布,分为两种情况,一种在输入数据分布不一样,我们叫Covariate ShiftShift,比如训练的数据和测试的数据本身分布就不一样,那么训练后的模型就很难泛化到测试集上。另一种分布不一样是在输入数据经过网络内部计算后,分布发生了变化,这样导致数据变得不稳定,从而导致网络寻找最优解的过程变得缓慢,训练速度会下降。如下图所示:
我们知道在网络初始化的时候,初始的w,b一般都很小,略大于0,如果我们将 a 图的数据归一化到 c 图的原点附近,那么,网络拟合y = wx+b时,b就相对很容易就能从初始的位置找到最优值,如果在将 c 图的数据做一个小小的拉伸,转换为 d 图的数据,此时,数据之间的相对差异性变大,拟合y = wx+b这条划分线时,w相对容易从初始位置找到最优值。这样会使训练速度加快。
4:但是归一化有很多种方式,batchnorm只是其中一种,那么现在有一个问题,假如我直接对网络的每一层输入做一个符合正态分布的归一化,然后输入数据的分布本身不是呈正态分布或者不是呈该正态分布,那会这样会容易导致后边的网络学习不到输入数据的分布特征了,因为,费劲心思学习到的特征分布被这么暴力的归一化了,因此直接对每一层做归一化显示不合理。但是稍作修改,加入可训练的参数做归一化,那就是BatchNorm实现的了
5:batchnorm顾名思义是对每batch个数据同时做一个norm,batchnorm是怎么做的,来看下边伪代码:
可以看出第一步:先求出此次批量数据 x x 的均值, μβ=1m∑mi=1xi μ β = 1 m ∑ i = 1 m x i
第二步:求出此次批量数据的方差, σβ2=1m∑i=1m(xi−μβ)2 σ β 2 = 1 m ∑ i = 1 m ( x i − μ β ) 2
第三步:接下来就是对 x x 做归一化,得到 xi− x i −
第四步:最重要的一步,引入缩放和平移变量 γ γ 和 β β ,计算归一化后的值, yi=γxi−+β y i = γ x i − + β
如果不加 γ γ 和 β β ,直接归一化,是会打乱原有数据的分布,容易导致网络学不到任何东西,但是加入这两个参数后,事情就不一样了。先考虑特殊情况,假设 γ γ 是batch的方差, β β 是batch的均值,那么 yi=γxi−+β y i = γ x i − + β 得到的 yi y i 就是还原到了归一化之前的 x x ,也就是缩放平移到了归一化前的分布,相当于batchnorm没有改变任何分布没有起作用。所以,加入了 γ γ 和 β β 这两个参数后的batchnorm,保证了每一次数据归一化后还保留有之前学习来的特征分布,同时又能完成归一化的操作,加速训练。
看下边batchnorm的一个简单代码:
def Batchnorm_simple_for_train(x, gamma, beta, bn_param):
"""
param:x : 输入数据,设shape(B,L)
param:gama : 缩放因子 γ
param:beta : 平移因子 β
param:bn_param : batchnorm所需要的一些参数
eps : 接近0的数,防止分母出现0
momentum : 动量参数,一般为0.9, 0.99, 0.999
running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备
running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备
"""
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_mean=x.mean(axis=0) # 计算x的均值
x_var=x.var(axis=0) # 计算方差
x_normalized=(x-x_mean)/np.sqrt(x_var+eps) # 归一化
results = gamma * x_normalized + beta # 缩放平移
running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_var = momentum * running_var + (1 - momentum) * x_var
#记录新的值
bn_param['running_mean'] = running_mean
bn_param['running_var'] = running_var
return results , bn_param
看完这个代码是不是对batchnorm有了一个清晰的理解,首先计算均值和方差,然后归一化,然后缩放和平移,完事!但是这是在训练中完成的任务,每次训练给一个批量,然后计算批量的均值方差,但是在测试的时候可不是这样,测试的时候每次只输入一张图片,这怎么计算批量的均值和方差,于是,就有了代码中下面两行,在训练的时候实现计算好mean、var在测试的时候直接拿来用就行,不用计算均值和方差。
running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_var = momentum * running_var + (1 - momentum) * x_var
所以测试的时候是下边这样的:
def Batchnorm_simple_for_test(x, gamma, beta, bn_param):
"""
param:x : 输入数据,设shape(B,L)
param:gama : 缩放因子 γ
param:beta : 平移因子 β
param:bn_param : batchnorm所需要的一些参数
eps : 接近0的数,防止分母出现0
momentum : 动量参数,一般为0.9, 0.99, 0.999
running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备
running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备
"""
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_normalized=(x-running_mean )/np.sqrt(running_var +eps) # 归一化
results = gamma * x_normalized + beta # 缩放平移
return results , bn_param
下边附上tensorflow BatchNorm的一段源码,代码来源于知乎,这里加入注释帮助阅读。
def batch_norm_layer(x, train_phase, scope_bn):
with tf.variable_scope(scope_bn):
# 新建两个变量,平移、缩放因子
beta = tf.Variable(tf.constant(0.0, shape=[x.shape[-1]]), name='beta', trainable=True)
gamma = tf.Variable(tf.constant(1.0, shape=[x.shape[-1]]), name='gamma', trainable=True)
# 计算此次批量的均值和方差
axises = np.arange(len(x.shape) - 1)
batch_mean, batch_var = tf.nn.moments(x, axises, name='moments')
# 滑动平均做衰减
ema = tf.train.ExponentialMovingAverage(decay=0.5)
def mean_var_with_update():
ema_apply_op = ema.apply([batch_mean, batch_var])
with tf.control_dependencies([ema_apply_op]):
return tf.identity(batch_mean), tf.identity(batch_var)
# train_phase 训练还是测试的flag
# 训练阶段计算runing_mean和runing_var,使用mean_var_with_update()函数
# 测试的时候直接把之前计算的拿去用 ema.average(batch_mean)
mean, var = tf.cond(train_phase, mean_var_with_update,
lambda: (ema.average(batch_mean), ema.average(batch_var)))
normed = tf.nn.batch_normalization(x, mean, var, beta, gamma, 1e-3)
return normed
上边倒数第二行的函数:tf.nn.batch_normalization()就是计算batchnorm的过程啦,定义如下所示:
def batch_normalization(x,
mean,
variance,
offset,
scale,
variance_epsilon,
name=None):
with ops.name_scope(name, "batchnorm", [x, mean, variance, scale, offset]):
inv = math_ops.rsqrt(variance + variance_epsilon)
if scale is not None:
inv *= scale
return x * inv + (offset - mean * inv
if offset is not None else -mean * inv)
这个函数的功能就是计算 γ(x−μ)σ+β γ ( x − μ ) σ + β
BatchNorm的优点总结: