bn的优势:
(1)更大的学习率(传统方法太大的learning rate容易导致梯度explode/vanish,或者get stuck in poor local)
(2)不再需要dropout
(3)less careful about initialization
但是BN不仅仅加BN层,还要修改以下的东西才能更快:
(1)learning rate 赋予更大的初值,且下降得更快。(比如将learning rate从0.0015扩大5倍到0.0075,下降快6倍)
(2)Remove Droupout
(3)Reduce L2 weight decay。(比如每次除5)
(4)Remove LRN
(5、6)其他。。看论文
ResNet 有用到BN,其在CIFAR-10网络中参数为:
20,34,44,56层使用:
learning rate=0.1,在32k和48k iterations时/10。
l2 weght decay=1e-4
110层使用:0.01learning rat用于warm up training,直到training error小于80%。
一、tf.nn.batch_normalization
Tensorflow 提供了Batch Normalization的API。但是,这个API很灵活,灵活的后果就是我们需要自己去定义所有的参数。
(比如,提供给此API的Tensor,居然需要我们自己去计算mean和variance)
tf.nn.batch_normalization(
x, #Tensor,对它执行BN操作
mean, #Tensor,一般为x的平均数,float32。
variance, #Tensor,一般为x的方差,float32。
offset, #Tensor,beta值,BN的shift操作。一般初始为0
scale, #Tensor,gamma值,BN的scale操作。一般初始为1
variance_epsilon, #float。小的实数防止除0出现。
name=None
)
"""
返回值(Tensor):
y= (x-mean)/sqrt(variance^2+variance_epsilon)*scale+offset。
但是mean和variance需要自己提前计算,
而tensorflow又提供了另一个API来计算mean和variance。(当然我们也可以自己瞎搞一个)
"""
这个API完全按照论文的思路设计,且更加灵活(比如mean和variance可以设置为其他值而不是x的均值和方差,beta和gamma也是如此)。
(见下图):
二、Tensor平均数和方差计算tf.nn.moments
由于上述的API需要手动计算mean和variance,所以就用到了这个API。
tf.nn.moments(
x, #Tensor,要计算mean和variance的变量
axes, #要处理的维度。BN一般就是所有的维度。即[d for d in range(len(x.get_shape())]
shift=None,
name=None,
keep_dims=False
)
三、例子
import tensorflow as tf
sess=tf.Session()
x=tf.constant([[1,5],[10,100]],dtype=tf.float32)
#维度
axes=[d for d in range(len(x.get_shape()))]
#beta gamma参数
beta= tf.get_variable("beta",shape=[],initializer=tf.constant_initializer(0.0))
gamma=tf.get_variable("gamma",shape=[],initializer=tf.constant_initializer(1.0))
sess.run(tf.global_variables_initializer())
#计算mean和variance,并执行BN操作
x_mean,x_variance=tf.nn.moments(x,axes)
y=tf.nn.batch_normalization(x,x_mean,x_variance,beta,gamma,1e-10,"bn")
#查看最终值
y_mean,y_variance=tf.nn.moments(y,axes)
x_val,xm_val,xv_val,y_val,ym_val,yv_val=sess.run([x,x_mean,x_variance,y,y_mean,y_variance])
print("*********执行BN前的Variable x:************")
print("x=%s\n x mean=%s\n x variance=%s" %(x_val,xm_val,xv_val))
print("*********执行BN后的Variable y:************")
print("y=%s \n y mean=%s\n y variance=%s" %(y_val,ym_val,yv_val))
执行结果为:
*********执行BN前的Variable x:************
x=[[ 1. 5.]
[ 10. 100.]]
x mean=29.0
x variance=1690.5
*********执行BN后的Variable y:************
y=[[-0.68100518 -0.58371872]
[-0.46211064 1.72683454]]
y mean=0.0
y variance=1.0
x=[[ 1. 5.]
[ 10. 100.]]
x mean=29.0
x variance=1690.5
*********执行BN后的Variable y:************
y=[[-0.68100518 -0.58371872]
[-0.46211064 1.72683454]]
y mean=0.0
y variance=1.0
可知道x经过BN处理后得到y,y的均值为0,方差变成1了(beta为0,gamma为1时)。
这里我们可以修改下beta和gamma的初始值,则y的平均值会变成beta,方差会变成gamma^2。
四、BN层放置顺序
BN网络中,一个卷积层或全连接层中,对于输入x,有3步中间操作:BN操作、weight操作、ReLu操作。这三种操作的顺序该怎么排列。
原论文的说法是:在Any layer previously received x as input, now received BN(x),但一个卷积层中的子层呢?
对于2*con16 =》 2*conv32=》2*conv64=》fc-10 在MNIST中试了下三种顺序:
(1) x -> bn -> weight -> relu
(2) x -> bn -> relu -> weight
(3) x -> weight ->bn -> relu
最后发现效果都挺好的,可能是这个数据集太简单了,有待以后继续测试。。。
不过在Resnet 1k网络中,第2种方法比第3种效果更好(在有shortcut的情况)。
论文地址:https://arxiv.org/pdf/1603.05027.pdf
五、BN在Mnist效果对比
由于Mnist太简单,正常CNN网络加不加BN层效果不明显。
所以我们需要给网络模型增加训练难度:把ReLu替换成Sigmoid。
(使用Sigmoid会让训练无比的慢,起码慢了百八十倍了~我一开始还以为网络出问题了。。ReLu真的强大!)
其他参数