美文网首页
吴恩达 超参数调试、手把手实现自己的BatchNorm神经网络

吴恩达 超参数调试、手把手实现自己的BatchNorm神经网络

作者: 西部小笼包 | 来源:发表于2021-01-12 20:37 被阅读0次

    如果你在找一个batch norm 神经网络手把手的编写指南,那么这篇文章会帮到你的。

    如何成为调参侠

    到目前为止,我们接触到的hyperparameter有:

    1. learning rate: α
    2. momentum 参数: β
    3. Adam参数: β1和 β2以及ε
    4. 神经网络层数: L
    5. 神经网络隐藏层neuron数:n[l]
    6. learning rate decay参数
    7. min-batch size

    这些hyperparameter重要性排序:

    最重要的: learning rate: α

    比较重要的: momentum 参数: β 神经网络层数: L 神经网络隐藏层neuron数:n[l]

    次重要的: 神经网络隐藏层neuron数 learning rate decay参数

    基本不需调整的 β1和 β2以及ε

    这不是严格且快速的标准,我认为,其它深度学习的研究者可能会很不同意我的观点或有着不同的直觉

    如何搜索hyperparameter

    1. 使用随机搜索,而不是在网格中定点搜索(因为可以针对重要的参数搜索到更加多的值的可能性,下图可见学习率比alpha重要的多)


      image.png
    2. 先整体粗略搜索,再到表现好的区域精细搜索。


      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总体上有三个作用:

    1. 首先,起到了normalization的作用,同对输入数据X的normalization作用类似。可以加速学习

    2. 让每一层的学习,一定程度解耦了前层参数和后层参数,让各层更加独立的学习。无论前一层如何变,本层输入的数据总是保持稳定的均值和方差。(主要原因)

    3. 有轻微的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 求得。
    最后我们来用代码实现一下

    image.png

    有了这2个函数后,下面我们要做的就是对他们进行梯度检验。确保自己推导的公式和代码没有问题。

    梯度检验的时候,我们要重头使用mode train的参数去重头更新计算。


    image.png

    随后因为我们有些参数使用了NONE,所以我们要对原来的一些help function做enhance


    image.png
    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的功能。

    相关文章

      网友评论

          本文标题:吴恩达 超参数调试、手把手实现自己的BatchNorm神经网络

          本文链接:https://www.haomeiwen.com/subject/ucfgoktx.html