如果你在找一个batch norm 神经网络手把手的编写指南,那么这篇文章会帮到你的。
如何成为调参侠
到目前为止,我们接触到的hyperparameter有:
- learning rate: α
- momentum 参数: β
- Adam参数: β1和 β2以及ε
- 神经网络层数: L
- 神经网络隐藏层neuron数:n[l]
- learning rate decay参数
- min-batch size
这些hyperparameter重要性排序:
最重要的: learning rate: α
比较重要的: momentum 参数: β 神经网络层数: L 神经网络隐藏层neuron数:n[l]
次重要的: 神经网络隐藏层neuron数 learning rate decay参数
基本不需调整的 β1和 β2以及ε
这不是严格且快速的标准,我认为,其它深度学习的研究者可能会很不同意我的观点或有着不同的直觉
如何搜索hyperparameter
-
使用随机搜索,而不是在网格中定点搜索(因为可以针对重要的参数搜索到更加多的值的可能性,下图可见学习率比alpha重要的多)
image.png -
先整体粗略搜索,再到表现好的区域精细搜索。
image.png
在搜索的时候,我们一般不是按合理区间的均匀分布去搜索。原因是假如合理区间是【0.001, 1】 那么在0.001的时候我们的精度要尽可能的小。然后是在搜0.01的量级精度可以放大。最后是搜0.1的量级。我们希望数据是按【0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1】这种格式分布。
所以我们需要用log来
又比如β的合理区间是0.9到0.999。β取值0.9和0.905的区别很小,相当于取最近10个或10.5个均值的差异。但同样增加0.005,0.994和0.999就截然不同,前者相当于对最近200个数值的加权平均,而后者相当于最近1000个数值的加权平均。
可以这样实现:
r = -2 * np.random.rand() -1 # r取值在[-3,-1]的均匀分布
1-β = 10^r
β = 1 - 10^r
最后根据自己手中的计算资源不同,可以用2种不同策略:
-
Panda(熊猫策略):对一个模型先后修改参数,查看其表现,最终选择最好的参数。就像熊猫一样,一次只抚养一个后代。
-
Caviar(鱼子酱策略):计算资源足够,可以同时运行很多模型实例,采用不同的参数,然后最终选择一个好的。类似鱼类,一次下很多卵,自动竞争成活。
Batch 正则化
在机器学习算法中,我们会将输入数据X做 Normalization 处理,使得每个属性都在同一个尺度上,从而加快梯度下降的速度。
同样在深度学习网络中,也会对输入数据X做 Normalization 处理,但网络中有隐藏层,隐藏层内的输入是上一层的输出,同样面临隐藏层的输入数据不在同一尺度上的问题。 因此最简单的办法是对每一层的输入,即上一层activation function的输出做normalization。
Batch Norm的实现 实践中,是对z进行normalization,具体算如下
image.png
然后对每一个样本i的z进行标准化(分母增加ε是为了防止方差为0,一般取10^-8)
image.png从normalization的目的看,已经完成了。但实际上在深度网络中,我们希望每一层拥有不同的分布(即不同的μ和σ2),尤其是对于sigmoid等activation function来说,z如果处于均值为0,方差为1的范围,无法充分利用到activation function的non-linear特性,因为在这范围,sigmoid函数近似的表现为线性函数。因此会再次处理
image.png
处理后,z~(i)将服从均值为β,方差为γ的分布。 需要注意的是,β和γ不是超参,而是梯度下降需学习的参数。
接下来,用z~(i)代替z(i)输入到activation function计算。
那么对于整个神经网络而言,前向传播算法会变成如下形式:
(由于β会再次对Z设置均值,因此原来的bias参数b就可以省略了(作用其实和β重复了),所以在应用batch-norm的情况下,参数b就省略了,实际要训练的参数只有W,β和γ,对应的流程如下)
image.png
为什么batch norm 有效
Batch Norm总体上有三个作用:
-
首先,起到了normalization的作用,同对输入数据X的normalization作用类似。可以加速学习
-
让每一层的学习,一定程度解耦了前层参数和后层参数,让各层更加独立的学习。无论前一层如何变,本层输入的数据总是保持稳定的均值和方差。(主要原因)
-
有轻微的regularization effect(虽然不是BN算法的本意,尽是顺带的副作用)。 对于min-batch来说,每一个mini-batch采用的mean/variance不一样,相当于对z的计算引入了一定的噪声,类似于dropout算法,对a的计算引入了噪声,让下游的hidden units不过度依赖于上层的某个hidden unit。mini-batch的size越大,regularization effect越小。
测试时的batch norm
BN算法在训练时是一个批次的数据训练,能算出每一层Z的均值和方差;而在测试时,输入的则是单个数据,单条数据没法做均值和方差的计算,怎么在测试期输入均值和方差呢?
思路:在training阶段,就通过exponentially weighted average的方法,顺便计算μ和σ^2针对每一个epoch的加权平均。 最终用迭代完成后的这个加权平均作为test阶段的μ和σ^2
自己动手实现一下,新的前向传播,和反向传播的神经网络
这块是吴恩达的实验里缺失的一个小遗憾。不过我们已经有了自己的神经网络底层框架。自己动手搭建一个也不是难事。(一个周末后回来,真的还是挺难的)
首先我们可以根据课上的PPT,更新前向传播算法, 初始化,以及更新参数的代码。因为用了BATCH NORM, 我们可以把原来神经网络里的b参数给去掉。它的功能会由batchnorm中的beta取代。
这里有一个思想,就是我们只在relu层做batchnorm,因为最后一层是为了分类的,我们没有必要把本来很大的数值,给往小的收缩。分类预测是1,就是1.不存在梯度消失的问题。因为是最后一层也不会影响梯度下降的速度。
所以有了如下代码
image.png
前向传播,在之前的代码里,我们已经有了线性层和非线性层,我们要在中间加一层batchnorm层
image.png
然后我们可以单独来实现这个函数,由于函数中有2个变量需要被一直更新。原因见上面提到测试时需要使用。他是会在整个训练周期被更新的参数。这2个参数会在test的时候被用到。
整个前向传播代码如下:
def batchnorm_forward(Z, gamma, beta, bn_param):
D, N = Z.shape
mode = bn_param['mode']
eps = bn_param.get('eps', 1e-5)
momentum = bn_param.get('momentum', 0.9)
running_mean = bn_param.get('running_mean', np.zeros((D, 1), dtype=Z.dtype))
running_var = bn_param.get('running_var', np.zeros((D, 1), dtype=Z.dtype))
if mode == 'train':
mean = np.mean(Z, axis=1, keepdims=True)
var = np.var(Z, axis=1, keepdims=True)
Z_norm = (Z - mean) / np.sqrt(var + eps)
Zn = gamma * Z_norm + beta
cache = [Z, gamma, beta, Z_norm, mean, var, eps]
running_mean = momentum * running_mean + (1 - momentum) * mean
running_var = momentum * running_var + (1 - momentum) * var
# TEST:要用整个训练集的均值、方差
elif mode == 'test':
Z_norm = (Z - running_mean) / np.sqrt(running_var + eps)
Zn = gamma * Z_norm + beta
cache = None
else:
raise ValueError('Invalid forward batchnorm mode "%s"' % mode)
bn_param['running_mean'] = running_mean
bn_param['running_var'] = running_var
return Zn, cache
代码bn_param表示每一层做batchnorm要用的参数。我们需要在整个训练的每次迭代过程都用到。所以这个参数会从神经网络的主框架处传进来。
image.png训练完之后,记得修改MODE,随后返回给APP层
image.png
综上前向传播就搞定了。下面我们需要根据前向传播的步骤,画出图,然后用链式法则和偏导来求出反向传播的算法。
反向传播公式推导
image.png首先我们把正向传播的4个公式给写好。我们的目标是要更新β和γ,还有得到batch norm回去的dz
前2个的导数我们只需要根据公式4,就可以求了。顺便根据公式4,我们还可以求出dZn
那么我们该怎么求出dz呢?
image.png
因为上面计算图中ABC,到Z~时每条线路都是z在里面发挥的作用。所以偏导会是这3条线路的偏导之和。
图中写了三角形的编号,9是我们要求的。为了求9,我们需要知道3,6,7,4,8,5
因为前向公式2,我们求出4.
因为前向公式1, 我们求出5
因为前向公式3, 我们求出6.
还缺7和8
image.png
因为σ^2 对Z~ 只有B线路起到了作用。所以我们用链式法则把它写出来即可。
μ对Z~ 有A,B两条线路都发挥了作用,所以我们需要分别链式法则2条线路然后求和。
所以我们就有了
7 = 3 * 9
8 = 3 * 10 + 7 * 11
7,8 被展开后
我们要求的就是9,10,11
分别根据公式3,公式3, 公式2 求得。
最后我们来用代码实现一下
有了这2个函数后,下面我们要做的就是对他们进行梯度检验。确保自己推导的公式和代码没有问题。
梯度检验的时候,我们要重头使用mode train的参数去重头更新计算。
image.png
随后因为我们有些参数使用了NONE,所以我们要对原来的一些help function做enhance
image.png
image.png
然后就是一些艰苦的调试,最后终于把梯度给调对了。
如果自己实现的小伙伴遇到了困难可以用我的程序来做对照, 我自己花了非常多的时间去debug,因为这块我在网上确实没有搜到基于吴恩达之前课件写法的batchnorm的实现,而且最终的COST输出,也是我们所不知道,如果梯度检验发现问题,不知道问题出现在哪个环节,还是比较难调的
项目地址
之前我们需要30000次迭代,这次我使用了超参如下:
image.png
最后完成了的效果如图(其中之一)
image.png
softmax regression
假设要分类的个数记为C, 下图C=4,最后一层神经元的个数设置为C,每一个神经元输出每一个class的概率
image.png image.png
image.png
虽然看起来输入是向量,输出的也是向量,但与sigmoid或tanh等不一样。后者,只是vectorization的结果,实际每个neuron的activation计算是相互独立,互不影响的。但对于soft layer,并不相互独立,输入是整个z[L]因为中间需要对所有的t进行加总,因此任何一个neuron的z值改变,都会影响所有neuron的a值。
Softmax regression将logistic regression推广到C个分类。如果C=2,softmax则退化为logistic regression。
我们来看下这块代码如何编写
前向传播的最后一层,需要引入SOFTMAX
image.png
softmax 的函数如下(return 的Z代表activation cache):
def softmax(Z):
A = np.exp(Z - np.max(Z, axis=0, keepdims=True))
A /= np.sum(A, axis=0, keepdims=True)
return A, Z
里面有个让求最大值,并都减去。目标是为了防止EXP 过大,我们让最大的指数系数为0,因为最后求的是各个值占整体的比率。所以做这个操作是不会影响比率分配的。
下面就是这个操作的LOSS FUNCTION的实现,和反向传播最后以层的偏导计算(还记得我们SIGMOID 是 A-Y吗)
image.png
多分类的 Softmax 回归模型与二分类的 Logistic 回归模型只有输出层上有一点区别。经过不太一样的推导过程,仍有
dZ[L]=A[L]−Y
反向传播过程的其他步骤也和 Logistic 回归的一致。
总结
本章虽然文字不多,但是是非常花功夫的一章。
我们了解了神经网络里有的超参,以及他们的意义。该如何调试?
我们不再用吴恩达提供的完形填空,自己在原有的神经网络中,自己增加了一层batch norm层,并实践了梯度检验确保程序正确,最后得到了更快的运行速度,以及更好的算法效果的batchnorm神经网络
最后我们自己在这个网络中额外添加了SOFTMAX的功能。
网友评论