def batchnorm(self, Ylogits, is_test, iteration, offset, convolutional=False):
"""
:param Ylogits:
:param is_test:
:param iteration:
:param offset:
:param convolutional:
:return:
"""
exp_moving_avg = tf.train.ExponentialMovingAverage(0.999, iteration) # adding the iteration prevents from averaging across non-existing iterations
bnepsilon = 1e-5
if convolutional:
mean, variance = tf.nn.moments(Ylogits, [0, 1, 2])
else:
mean, variance = tf.nn.moments(Ylogits, [0])
update_moving_averages = exp_moving_avg.apply([mean, variance]) # update exp_moving_avg
m = tf.cond(is_test, lambda: exp_moving_avg.average(mean), lambda: mean) # 0.999*exp_moving_avg.average(mean)+(1-0.999)*exp_moving_avg
v = tf.cond(is_test, lambda: exp_moving_avg.average(variance), lambda: variance)
Ybn = tf.nn.batch_normalization(Ylogits, m, v, offset, None, bnepsilon)
return Ybn, update_moving_averages
最近看程序,看到一段batchnorm的程序不是很理解,所以又学习了一下。
Internal Covariate Shift:网络训练过程中参数不断改变导致后续每一层输入的分布也发生变化,而学习的过程又要使每一层适应输入的分布,因此我们不得不降低学习率、小心地初始化。
(因为白化需要计算协方差矩阵、求逆等操作,计算量很大,此外,反向传播时,白化操作不一定可导。)
batch normalization用于减少Internal Covariate Shift,防止梯度弥散,还可加速模型的训练(激活层之前)
- 训练时存在权重共享,把一整张特征图当做一个神经元进行处理,求所有样本所对应的一个特征图的所有神经元的平均值、方差,然后对这个特征图神经元做归一化(减少计算量)
- 网络训练完成,参数固定不变;网络在预测时,测试样本经过中间各层时,用到的是训练好的μ、β来进行归一化处理,对于均值,直接计算batch的平均值;然后对标准差采用batch的无偏估计
第四个公式主要是变换重构,网络中间层的数据的真实分布是一个Sigmoid分布,按上式强制归一化处理后,会破坏分布,通过重构参数让网络可以学习恢复出原始网络所要学习的特征分布
随机梯度下降法对于训练深度网络简单高效,但需要人为的选择一些参数,比如学习率、参数初始化、权重衰减系数、dropout比例等。这些参数的选择对训练结果至关重要,浪费很多时间在调参上。BN算法的强大之处表现在:
- 可以选择比较大的初始学习率,让训练速度飙涨。以前需要慢慢调整学习率,甚至在网络训练到一半时,还需要想着学习率进一步调小的比例,选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;
- 过拟合中dropout、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;
- 不需要使用局部响应归一化层,因为BN本身就是一个归一化网络层;
- 可以把训练数据彻底打乱(防止每批训练的时候,某一个样本都经常被挑选到)。
tf.train.ExponentialMovingAverage(decay, steps)
moving average类似于卷积的操作,因此我们就可以在卷积核上做文章,最普通的卷积核就是每个数据的权重都相同,但是在实际中明显越近的数据价值越大,因此要让越近的数据权重越大,越远的数据权重越小,在这里使用的权重衰减策略就是指数衰减。具体的,- α控制衰减速率。
- Yt表示时间t时的原始值
- St表示经过EMA后的时间t的值
综上,EMA就是把一系列的离散数据点转化为更“好”的数据点,它的后面的数据点会记住前面的数据点的一些信息,离得越远记得越少。
tf.train.ExponentialMovingAverage是用来让模型不被更新的太快的一种方法,用于更新参数,就是采用滑动平均的方法更新参数。这个函数初始化需要提供一个衰减速率(decay),用于控制模型的更新速度。这个函数还会维护一个影子变量(也就是更新参数后的参数值),这个影子变量的初始值就是这个变量的初始值,影子变量值的更新方式如下:
shadow_variable = decay * shadow_variable + (1-decay) * variable
shadow_variable是影子变量,variable表示待更新的变量,也就是变量被赋予的值,decay为衰减速率。decay一般设为接近于1的数(0.99,0.999)。decay使得shadow variable具有一定的缓冲能力,不会变化太快
decay越大模型越稳定,因为decay越大,参数更新的速度就越慢,趋于稳定。
tf.train.ExponentialMovingAverage 这个函数还提供了自动更新decay的计算方式(让前期训练加快,可以计入num_update参数):
decay= min(decay,(1+steps)/(10+steps))
每次更新完以后,影子变量的值更新,varible的值就是你设定的值。如果在下一次运行这个函数的时候你不在指定新的值,那就不变,影子变量更新。如果指定,那就variable改变,影子变量也改变
mean, variance = tf.nn.moments(Ylogits, [0])
def moments(x, axes, name=None, keep_dims=False)
(在某一维度上求均值方差)
- x 可以理解为我们输出的数据,形如 [batchsize, height, width, kernels]
- axes 表示在哪个维度上求解,是个list,例如 [0, 1, 2]
- name 就是个名字
- keep_dims 是否保持维度
update_moving_averages = exp_moving_avg.apply([mean, variance]) # update exp_moving_avg
返回一个op,这个op用来更新moving_average,与exp_moving_avg.average()
函数不能换位置,在它之前。
执行时才对参数[mean, variance]
更新
m = tf.cond(is_test, lambda: exp_moving_avg.average(mean), lambda: mean)
v = tf.cond(is_test, lambda: exp_moving_avg.average(variance), lambda: variance)
训练和测试时的不同:训练时,均值和方差由mini-batch直接获得;测试时,均值和方差通过样本的滑动平均值获得。
exp_moving_avg.average()
:此op用来返回当前的moving_average,这个参数不能是list
假设有一串时间序列 {a1, a2, a3,⋯, at, at+1, ⋯}
t时刻的平均值为mv(t)=(a1 + a2 + ⋯ + at) / t
t+1时刻的平均值为mv(t+1)=(a1 + a2 + ⋯ + at + a(t+1))/(t+1) = (t * mv(t) + a(t+1)) / (t+1) = (t / (t+1)) * mv(t) + (1 / (t+1)) * a(t+1)
令decay = t / (t+1), 则mv(t+1)=decay∗mv(t)+(1−decay)∗a(t+1)
exp_moving_avg.average()
函数执行时才计算公式,输出的是新的shadow_variable
,在这个程序中就是0.999*exp_moving_avg.average(mean)(原来的)+(1-0.999)*updata_mean(新得到的均值)
https://blog.csdn.net/whitesilence/article/details/75667002
Ybn = tf.nn.batch_normalization(Ylogits, m, v, offset, None, bnepsilon)
def batch_normalization(x, mean, variance, offset, scale, variance_epsilon, name=None):
offset和scale需要训练