前面也讲过部分Batch Normalize的内容,单独拿出来成文,是因为感觉这方法非常赞,加快训练速度十几倍不等,模型越复杂越受益。
一句话总结BN:对每层输入加同分布约束,再加参数线性变换学习其表达能力。
Problem :: Internal Covariate Shift
神经网络训练的难题之一,在前层参数变化时,每层的输入分布也随之变化。这就造成了当层的训练困难,老是变来变去,还能不能好好地玩耍啊~通常地解决思路是使用很小的学习率,并且小心地初始化。
Batch Normalize的基本思想是,将每层的输入做std-normalize, y=x−x¯var(x)√ y = x − x ¯ v a r ( x ) ,使得变换后,是 N(0 1) N ( 0 1 ) 高斯分布。
假设输入m-size的batch样本 x x ,对其中的所有特征都做如下处理:
只是将输入约束到同分布下,岂不是所有层的输入都一样了,那还学个毛线啊。在normalize上,Sergey更向前走了一步,一不做二不休,干脆再搞它俩参数 γ γ 和 β β ,将输入恢复出来,通过不断学习调整这两个参数,使得重构输入的能力也能被搞定。
误差后传,传递的是误差项 ∗ ∗ 导数项 ∗ ∗ 其他项;传递的动作是链式法则的体现。随着传递路径越长,越容易出现为0的现象。
原因1: sigmoid激活函数,自身带有的饱和域(导数近似为0)。
原因2: 串联网络本身的参数累乘所带来的梯度消失问题没有解决掉。
ReLU激活函数,只是解决了由于激活函数的饱和域(导数近似0)所带来的的梯度消失问题。
BN方法,会将训练早期的数据约束到[-1,+1]范围内,避开了饱和区域,也能解决饱和域带来的梯度消失问题。
在非卷积网络中,通常是放到激活函数前面,处理之后做激活函数输入: σ(BN(wx)) σ ( B N ( w x ) ) 。
notice:由于 BN(wx+b)=BN(wx) B N ( w x + b ) = B N ( w x ) ,会将bias去掉,后面的 β β 起到类似bias的作用。
在卷积网络中,则要服从卷积的特性。利用固定filter-blank抽取某一特征feature-map,相当于每个f-map是一个抽取之后的特征(是个整体),那么对只需要对feature-map加统一的 γ γ 和 β β 即可,同样放到激活函数前。详细如下图。
假设权重参数 w w 变为 aw a w ,对新旧参数下的 x x 求导如下:
对于大学习率learning rate, BN是更为友好的。
⎧⎩⎨⎪⎪BN(wx)=γ(wx)−wx¯¯¯¯¯¯¯var(wx)√+βBN(awx)=γawx−awx¯¯¯¯¯¯¯¯¯¯var(awx)√+β { B N ( w x ) = γ ( w x ) − w x ¯ v a r ( w x ) + β B N ( a w x ) = γ a w x − a w x ¯ v a r ( a w x ) + β ==> ⎧⎩⎨⎪⎪∂BN(wx)∂w=rxvar(wx)√∂BN(awx)∂(aw)=rxvar(awx)√ { ∂ B N ( w x ) ∂ w = r x v a r ( w x ) ∂ B N ( a w x ) ∂ ( a w ) = r x v a r ( a w x )
得到:
假设其中 ZLj=BN(wLj∗hL−1)=rLj∗∑iwLj,ihL−1i−wLjhL−1¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯var(wLjhL−1)√+βLj Z j L = B N ( w j L ∗ h L − 1 ) = r j L ∗ ∑ i w j , i L h i L − 1 − w j L h L − 1 ¯ v a r ( w j L h L − 1 ) + β j L
对 wLj,i w j , i L 求导得: ∂Zj∂wLj,i=rLjhL−1ivar(wLjhL−1)√ ∂ Z j ∂ w j , i L = r j L h i L − 1 v a r ( w j L h L − 1 ) ;若无BN,则 ∂Zj∂wLj,i=hL−1i ∂ Z j ∂ w j , i L = h i L − 1
对 βLj β j L 求导得: ∂BN∂βLj=1 ∂ B N ∂ β j L = 1 。
对 γLj γ j L 求导得: ∂BN∂γLj=wLjhL−1var(wLjhL−1)√ ∂ B N ∂ γ j L = w j L h L − 1 v a r ( w j L h L − 1 )
在网络中误差后传如下: h=h(z) h = h ( z )
W(L) W ( L ) 记做第 L L 层的权重,连接 L−1 L − 1 和 L L 之间的权重, γ(L)和βL γ ( L ) 和 β L 作为本层学习的标准差向量和偏倚向量,其各自的维度等于本层节点数。
a)计算 L L 层的输出误差:
a.1) δ′(L)={∑δ(L+1)W(L+1)h−y,后层传递过来的δ,本层直接差h−y(最后一层时) δ ′ ( L ) = { ∑ δ ( L + 1 ) W ( L + 1 ) , 后 层 传 递 过 来 的 δ h − y , 本 层 直 接 差 h − y ( 最 后 一 层 时 )
a.2) δ(L)=δ′(L)∗h′(L)∗r(L)var(W(L)h(L−1))√ δ ( L ) = δ ′ ( L ) ∗ h ′ ( L ) ∗ r ( L ) v a r ( W ( L ) h ( L − 1 ) ) ,乘本层激活导数,再乘BN对w的导数。
b)更新 w w : ΔW(L)=h(L−1)∗δ(L) Δ W ( L ) = h ( L − 1 ) ∗ δ ( L ) 前层输出*本层的 δ δ 。
c)更新 β β : Δβ(L)=δ′(L)∗h′(L) Δ β ( L ) = δ ′ ( L ) ∗ h ′ ( L )
d)更新 γ γ : Δγ(L)=δ′(L)∗h′(L)∗w(L)h(L−1)var(w(L)h(L−1))√ Δ γ ( L ) = δ ′ ( L ) ∗ h ′ ( L ) ∗ w ( L ) h ( L − 1 ) v a r ( w ( L ) h ( L − 1 ) )
实际计算时,会在分母的 var v a r 上叠加个随机很小的正数,防止分母为0。最开始初始化时,每对 γ和β γ 和 β 分别都初始化到1和0附近。注意,批量计算均值和方差。
补充梯度求导,与paper里面的对比。不是特别明白为什么论文里有对估计参数 u和σ u 和 σ 的求导。
训练过程:用batch-GD训练;记录每个 batchB b a t c h B 的 uB和σB u B 和 σ B ,用以最后估计全局的 u和σ u 和 σ 。
这个问题的由来:因为预测是单样本依次预测。不存在batch训练的情况,所以预测是与训练不同的,如何利用训练过程及结果参数是个问题。
预测时,计算总体的均值和方差是不实际的,也是无法实现的,因为无法采样到所有样本。用总采样来估计总体的均值和方差呢?也是需要大量计算的,在训练过程中的batch下的均值 uB u B 和方差 σB σ B ,可以加以利用来估计总体,如下:
moving_mean = gamma * moving_mean + (1-gamma)* mean
moving_var = gamma * moving_var + (1-gamma)* var
Sergey友情提示了几个方法,提高BN的效力。
增大学习率,学得快还安全;去掉dropout,节省时间;去掉L2正则,但是个人觉得不要去最好,没有理由证明BN能够代替L2;样本集多次shuf,避免同batch总是一样的样本;减少图片变形处理,说的理由不充分;去掉local Response Normalize。
已有研究说明[2],针对Internal Covariate Shift问题,将输入做whiten处理,可以收敛地更快。疑问,真有必要whiten么?ICS只是权重变换,影响输出的分布而已,将分布搞得一致不就好了,还用着考虑各个输出变量间的相关关系么,后续层反正也不care变量间的相关性。
whiten的处理非常严格:要求去掉各个变量间的相关性;且每个变量的variance=1。
whiten定义:将一组随机变量 (X1,X2,....,Xn) ( X 1 , X 2 , . . . . , X n ) 通过 Y=WX Y = W X 变换为 → → (Y1,Y2,...,Ym) ( Y 1 , Y 2 , . . . , Y m ) ,称后者为白噪声,其协方差矩阵为单位对角矩阵,各变量间无相关性( ∃非零系数向量 ∃ 非 零 系 数 向 量 ,使得 aY=0 a Y = 0 成立), Var[Ym]=1 V a r [ Y m ] = 1 。
whiten的求解:假设X有non-singular 协方差矩阵M,and mean=0。
方法一: W=M−1/2 W = M − 1 / 2 ZCA白化。
方法二:cholesky分解 of M−1 M − 1 。
方法三:特征分解 of M M (PCA类方法)。
其他相关变换:
decorrelation transform:去相关,但是对variance不处理。
standard transform: mean=0,variance=1;相关性不处理。
corloring transform:白化的逆向处理,将白噪声处理成具有指定协方差矩阵的变换。
具体例子见参考3.
而BN使用标准化变换,不再考虑相关性,又把全局均值和方差用batch均值和方差来代替,省了大量的计算需要,并且效果还很不错。
BN本质,使得同分布;约束范围(量纲归一)。
## model ##
training=True
with tf.variable_scope('layer-1', reuse = tf.AUTO_REUSE):
y1 = tf.layers.dense(inputs = x, units = 128, use_bias=False, \
activation = tf.nn.relu, \
kernel_regularizer = regularizer, \
bias_regularizer = None)
y1 = tf.layers.batch_normalization(y1, training= training)
with tf.variable_scope('layer-2', reuse = tf.AUTO_REUSE):
y2 = tf.layers.dense(inputs = y1, units = 64, use_bias=False, \
activation = tf.nn.relu, \
kernel_regularizer = regularizer, \
bias_regularizer = None)
y2 = tf.layers.batch_normalization(y2, training= training)
with tf.variable_scope('layer-3', reuse = tf.AUTO_REUSE):
y3 = tf.layers.dense(inputs = y2, units = 2, use_bias=True, \
activation = None, \
kernel_regularizer = regularizer, \
bias_regularizer = regularizer)
logits = y3
prob_all = tf.nn.softmax(logits, 1)
## when training ##
tf.losses.softmax_cross_entropy(onehot_labels=y, logits=logits)
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
## tf.control_dependencies(a): b # a执行后,b才会执行 ##
with tf.control_dependencies(update_ops):
losses = tf.get_collection('losses')
total_loss = tf.add_n(losses, name='total_loss')
grads = optimizer.compute_gradients(total_losses)
optimizer.apply_gradients(grads, global_step)
## save ## moving_var 不会被存到tf.trainable_variables里面 ##
saver = tf.train.Saver(tf.global_variables())
## when predict ##
training=False