• 百度PaddlePaddle入门-19(卷积神经网络入门B)


    批归一化(Batch Normalization)

    批归一化方法方法(Batch Normalization,BatchNorm)是由Ioffe和Szegedy于2015年提出的,已被广泛应用在深度学习中,其目的是对神经网络中间层的输出进行标准化处理,使得中间层的输出更加稳定。

    通常我们会对神经网络的数据进行标准化处理,处理后的样本数据集满足均值为0,方差为1的统计分布,这是因为当输入数据的分布比较固定时,有利于算法的稳定和收敛。对于深度神经网络来说,由于参数是不断更新的,即使输入数据已经做过标准化处理,但是对于比较靠后的那些层,其接收到的输入仍然是剧烈变化的,通常会导致数值不稳定,模型很难收敛。BatchNorm能够使神经网络中间层的输出变得更加稳定,并有如下三个优点:

    • 使学习快速进行(能够使用较大的学习率)

    • 降低模型对初始值的敏感性

    • 从一定程度上抑制过拟合

    BatchNorm主要思路是在训练时按mini-batch为单位,对神经元的数值进行归一化,使数据的分布满足均值为0,方差为1。具体计算过程如下:

    1. 计算mini-batch内样本的均值

    uB <- 1/m * Sigma ( x(i))  (i=1->m)

    其中x(i)表示mini-batch中的第i个样本。

    例如输入mini-batch包含3个样本,每个样本有2个特征,分别是:

    x(1)=(1,2),  x(2)=(3,6),  x(3)=(5,10)

    对每个特征分别计算mini-batch内样本的均值:

    μB0=(1+3+5)/3=3,   μB1=(2+6+10)/3=6

    则样本均值是:

    μB=(μB0,μB1)=(3,6)

    2. 计算mini-batch内样本的方差

    σB2←1/m*∑(i=1,m) (x(i)−μB)^2

    上面的计算公式先计算一个批次内样本的均值μB和方差σB2,然后再对输入数据做归一化,将其调整成均值为0,方差为1的分布。

    对于上述给定的输入数据x(1),x(2),x(3),可以计算出每个特征对应的方差:

    σB0 ^2=1/3⋅((1−3)^2+(3−3)^2+(5−3)^2)=8/3

    σB1 ^2=1/3⋅((2−6)^2+(6−6)^2+(10−6)^2)=32/3

    则样本方差是:

    σB ^2=(σB0 ^2,σB1 ^2)=(8/3,32/3)

    3. 计算标准化之后的输出

    x^(i)← (x(i)−μB) / sqrt(σB^2+ϵ)

    其中ϵ是一个微小值(例如1e−7),其主要作用是为了防止分母为0。

    对于上述给定的输入数据x(1),x(2),x(3),可以计算出标准化之后的输出:

    x^(1)=((1−3)/sqrt(8/3),  (2−6)/sqrt(32/3))=(−sqrt(3/2),  −swrt(3/2))

    x^(2)=(3−3)/sqrt(8/3), ( 6−6)/sqrt(32/3))=(0,  0)                

    x^(3)=(5−3)/sqrt(8/3),  (10−6)/sqrt(32/3))=(sqrt(3/2),  sqrt(3/2))   

    • 读者可以自行验证由x^(1),x^(2),x^(3)构成的mini-batch,是否满足均值为0,方差为1的分布。

    如果强行限制输出层的分布是标准化的,可能会导致某些特征模式的丢失,所以在标准化之后,BatchNorm会紧接着对数据做缩放和平移。

    yi←γ x^i+β

    其中γβ是可学习的参数,可以赋初始值γ=1,β=0,在训练过程中不断学习调整。

    上面列出的是BatchNorm方法的计算逻辑,下面针对两种类型的输入数据格式分别进行举例。飞桨支持输入数据的维度大小为2、3、4、5四种情况,这里给出的是维度大小为2和4的示例。

    • 示例一: 当输入数据形状是[N,K]时,一般对应全连接层的输出,示例代码如下所示。

    这种情况下会分别对K的每一个分量计算N个样本的均值和方差,数据和参数对应如下:

    • 输入 x, [N, K]
    • 输出 y, [N, K]
    • 均值 μB,[K, ]
    • 方差 σB^2, [K, ]
    • 缩放参数γ, [K, ]
    • 平移参数β, [K, ]

     “BatchNorm里面不是还要对标准化之后的结果做仿射变换吗,怎么使用Numpy计算的结果与BatchNorm算子一致?

    ” 这是因为BatchNorm算子里面自动设置初始值γ=1,β=0,这时候仿射变换相当于是恒等变换。在训练过程中这两个参数会不断的学习,这时仿射变换就会起作用。

     1 # 输入数据形状是[N, C, H, W]时的batchnorm示例
     2 import numpy as np
     3 
     4 import paddle
     5 import paddle.fluid as fluid
     6 from paddle.fluid.dygraph.nn import BatchNorm
     7 
     8 # 设置随机数种子,这样可以保证每次运行结果一致
     9 np.random.seed(100)
    10 # 创建数据
    11 data = np.random.rand(2,3,3,3).astype('float32')
    12 # 使用BatchNorm计算归一化的输出
    13 with fluid.dygraph.guard():
    14     # 输入数据维度[N, C, H, W],num_channels等于C
    15     bn = BatchNorm('bn', num_channels=3)
    16     x = fluid.dygraph.to_variable(data)
    17     y = bn(x)
    18     print('input of BatchNorm Layer: 
     {}'.format(x.numpy()))
    19     print('output of BatchNorm Layer: 
     {}'.format(y.numpy()))
    20 
    21 # 取出data中第0通道的数据,
    22 # 使用numpy计算均值、方差及归一化的输出
    23 a = data[:, 0, :, :]
    24 a_mean = a.mean()
    25 a_std = a.std()
    26 b = (a - a_mean) / a_std
    27 print('channel 0 of input data: 
     {}'.format(a))
    28 print('std {}, mean {}, 
     output: 
     {}'.format(a_mean, a_std, b))
    29 
    30 # 提示:这里通过numpy计算出来的输出
    31 # 与BatchNorm算子的结果略有差别,
    32 # 因为在BatchNorm算子为了保证数值的稳定性,
    33 # 在分母里面加上了一个比较小的浮点数epsilon=1e-05

    输出结果:

    input of BatchNorm Layer: 
     [[[[0.54340494 0.2783694  0.4245176 ]
       [0.84477615 0.00471886 0.12156912]
       [0.67074907 0.82585275 0.13670659]]
    
      [[0.5750933  0.89132196 0.20920213]
       [0.18532822 0.10837689 0.21969749]
       [0.9786238  0.8116832  0.17194101]]
    
      [[0.81622475 0.27407375 0.4317042 ]
       [0.9400298  0.81764936 0.33611196]
       [0.17541045 0.37283206 0.00568851]]]
    
    
     [[[0.25242636 0.7956625  0.01525497]
       [0.5988434  0.6038045  0.10514768]
       [0.38194343 0.03647606 0.89041156]]
    
      [[0.98092085 0.05994199 0.89054596]
       [0.5769015  0.7424797  0.63018394]
       [0.5818422  0.02043913 0.21002658]]
    
      [[0.5446849  0.76911515 0.25069523]
       [0.2858957  0.8523951  0.9750065 ]
       [0.8848533  0.35950786 0.59885895]]]]
    output of BatchNorm Layer: 
     [[[[ 0.4126078  -0.46198368  0.02029109]
       [ 1.4071034  -1.3650038  -0.97940934]
       [ 0.832831    1.344658   -0.9294571 ]]
    
      [[ 0.2520175   1.2038351  -0.84927964]
       [-0.9211378  -1.1527538  -0.8176896 ]
       [ 1.4666051   0.96413004 -0.961432  ]]
    
      [[ 0.9541142  -0.9075856  -0.36629617]
       [ 1.37925     0.9590063  -0.6945517 ]
       [-1.2463869  -0.5684581  -1.8291974 ]]]
    
    
     [[[-0.5475932   1.2450331  -1.3302356 ]
       [ 0.5955492   0.6119205  -1.0335984 ]
       [-0.12019944 -1.2602081   1.5576957 ]]
    
      [[ 1.473519   -1.2985382   1.2014993 ]
       [ 0.25745988  0.7558342   0.41783488]
       [ 0.27233088 -1.4174379  -0.8467981 ]]
    
      [[ 0.02166975  0.79234385 -0.98786545]
       [-0.86699003  1.0783203   1.4993572 ]
       [ 1.1897788  -0.6142123   0.20769882]]]]
    channel 0 of input data: 
     [[[0.54340494 0.2783694  0.4245176 ]
      [0.84477615 0.00471886 0.12156912]
      [0.67074907 0.82585275 0.13670659]]
    
     [[0.25242636 0.7956625  0.01525497]
      [0.5988434  0.6038045  0.10514768]
      [0.38194343 0.03647606 0.89041156]]]
    std 0.418368607759, mean 0.303022772074, 
     output: 
     [[[ 0.41263014 -0.46200886  0.02029219]
      [ 1.4071798  -1.3650781  -0.9794626 ]
      [ 0.8328762   1.3447311  -0.92950773]]
    
     [[-0.54762304  1.2451009  -1.3303081 ]
      [ 0.5955816   0.61195374 -1.0336547 ]
      [-0.12020606 -1.2602768   1.5577804 ]]]
    View Code

    预测时使用BatchNorm

    上面介绍了在训练过程中使用BatchNorm对一批样本进行归一化的方法,但如果使用同样的方法对需要预测的一批样本进行归一化,则预测结果会出现不确定性。

    例如样本A、样本B作为一批样本计算均值和方差,与样本A、样本C和样本D作为一批样本计算均值和方差,得到的结果一般来说是不同的。那么样本A的预测结果就会变得不确定,这对预测过程来说是不合理的。解决方法是在训练过程中将大量样本的均值和方差保存下来,预测时直接使用保存好的值而不再重新计算。实际上,在BatchNorm的具体实现中,训练时会计算均值和方差的移动平均值。在飞桨中,默认是采用如下方式计算:

    saved_uB <- saved_uB * 0.9 + uB * (1-0.9)

    saved_thedaB^2 <- savedthedaB^2 * 0.9 + thedaB^2 * (1-0.9)

    在训练过程的最开始将saved_uB,saved_thedaB^2 设置为0,每次输入一批新的样本,计算出uB,thedaB^2,然后通过上面的公式更新saved_uB,saved_thedaB^2,在训练的过程中不断的更新它们的值,并作为BatchNorm层的参数保存下来。预测的时候将会加载参数saved_uB,saved_thedaB^2,用他们来代替uB,thedaB^2(average,variance)


    丢弃法(Dropout)

    丢弃法(Dropout)是深度学习中一种常用的抑制过拟合的方法,其做法是在神经网络学习过程中,随机删除一部分神经元。训练时,随机选出一部分神经元,将其输出设置为0,这些神经元将不对外传递信号。

    下图是Dropout示意图,左边是完整的神经网络,右边是应用了Dropout之后的网络结构。应用Dropout之后,会将标了× imes×的神经元从网络中删除,让它们不向后面的层传递信号。在学习过程中,丢弃哪些神经元是随机决定,因此模型不会过度依赖某些神经元,能一定程度上抑制过拟合。

    在预测场景时,会向前传递所有神经元的信号,可能会引出一个新的问题:训练时由于部分神经元被随机丢弃了,输出数据的总大小会变小了。比如:计算其L1范数会比不使用Dropout时变小,但是预测时却没有丢弃神经元,这将导致训练和预测时数据的分布不一样。为了解决这个问题,飞桨支持如下两种方法:

    • 1 downgrade_in_infer

    训练时以比例rrr随机丢弃一部分神经元,不向后传递它们的信号;预测时向后传递所有神经元的信号,但是将每个神经元上的数值乘以 (1−r)

    • 2 upscale_in_train

    训练时以比例rrr随机丢弃一部分神经元,不向后传递它们的信号,但是将那些被保留的神经元上的数值除以 (1−r);预测时向后传递所有神经元的信号,不做任何处理。

    在飞桨dropout API中,paddle.fluid.layers.dropout通过dropout_implementation参数来指定用哪种方式对神经元进行操作,dropout_implementation参数的可选值是'downgrade_in_infer'或'upscale_in_train',缺省值是'downgrade_in_infer'。

    下面这段程序展示了经过dropout之后输出数据的形式。

     1 # dropout操作
     2 import numpy as np
     3 
     4 import paddle
     5 import paddle.fluid as fluid
     6 
     7 # 设置随机数种子,这样可以保证每次运行结果一致
     8 np.random.seed(100)
     9 # 创建数据[N, C, H, W],一般对应卷积层的输出
    10 data1 = np.random.rand(2,3,3,3).astype('float32')
    11 # 创建数据[N, K],一般对应全连接层的输出
    12 #reshape(-1,3)数组新的shape属性应该要与原来的配套,如果等于-1的话,那么Numpy会根据剩下的维度计算出数组的另外一个shape属性值。 
    13 data2 = np.arange(1,13).reshape([-1, 3]).astype('float32')
    14 # 使用dropout作用在输入数据上
    15 with fluid.dygraph.guard():
    16     x1 = fluid.dygraph.to_variable(data1)
    17     out1_1 = fluid.layers.dropout(x1, dropout_prob=0.5, is_test=False)
    18     out1_2 = fluid.layers.dropout(x1, dropout_prob=0.5, is_test=True)
    19 
    20     x2 = fluid.dygraph.to_variable(data2)
    21     out2_1 = fluid.layers.dropout(x2, dropout_prob=0.5, 
    22                     dropout_implementation='upscale_in_train')
    23     out2_2 = fluid.layers.dropout(x2, dropout_prob=0.5, 
    24                     dropout_implementation='upscale_in_train', is_test=True)
    25     
    26     print('x1 {}, 
     out1_1 
     {}, 
     out1_2 
     {}'.format(data1, out1_1.numpy(),  out1_2.numpy()))
    27     #print('x2 {}, 
     out2_1 
     {}, 
     out2_2 
     {}'.format(data2, out2_1.numpy(),  out2_2.numpy()))

    结果输出:

    x1 [[[[0.54340494 0.2783694  0.4245176 ]
       [0.84477615 0.00471886 0.12156912]
       [0.67074907 0.82585275 0.13670659]]
    
      [[0.5750933  0.89132196 0.20920213]
       [0.18532822 0.10837689 0.21969749]
       [0.9786238  0.8116832  0.17194101]]
    
      [[0.81622475 0.27407375 0.4317042 ]
       [0.9400298  0.81764936 0.33611196]
       [0.17541045 0.37283206 0.00568851]]]
    
    
     [[[0.25242636 0.7956625  0.01525497]
       [0.5988434  0.6038045  0.10514768]
       [0.38194343 0.03647606 0.89041156]]
    
      [[0.98092085 0.05994199 0.89054596]
       [0.5769015  0.7424797  0.63018394]
       [0.5818422  0.02043913 0.21002658]]
    
      [[0.5446849  0.76911515 0.25069523]
       [0.2858957  0.8523951  0.9750065 ]
       [0.8848533  0.35950786 0.59885895]]]], 
     out1_1 
     [[[[0.         0.         0.        ]
       [0.84477615 0.00471886 0.12156912]
       [0.         0.         0.13670659]]
    
      [[0.5750933  0.89132196 0.20920213]
       [0.18532822 0.         0.21969749]
       [0.9786238  0.8116832  0.        ]]
    
      [[0.81622475 0.         0.        ]
       [0.9400298  0.         0.33611196]
       [0.17541045 0.37283206 0.00568851]]]
    
    
     [[[0.25242636 0.         0.01525497]
       [0.         0.         0.        ]
       [0.38194343 0.         0.        ]]
    
      [[0.         0.         0.        ]
       [0.         0.         0.        ]
       [0.         0.         0.        ]]
    
      [[0.5446849  0.76911515 0.25069523]
       [0.2858957  0.8523951  0.9750065 ]
       [0.8848533  0.35950786 0.        ]]]], 
     out1_2 
     [[[[0.27170247 0.1391847  0.2122588 ]
       [0.42238808 0.00235943 0.06078456]
       [0.33537453 0.41292638 0.0683533 ]]
    
      [[0.28754666 0.44566098 0.10460106]
       [0.09266411 0.05418845 0.10984875]
       [0.4893119  0.4058416  0.08597051]]
    
      [[0.40811238 0.13703687 0.2158521 ]
       [0.4700149  0.40882468 0.16805598]
       [0.08770522 0.18641603 0.00284425]]]
    
    
     [[[0.12621318 0.39783126 0.00762749]
       [0.2994217  0.30190226 0.05257384]
       [0.19097172 0.01823803 0.44520578]]
    
      [[0.49046043 0.02997099 0.44527298]
       [0.28845075 0.37123984 0.31509197]
       [0.2909211  0.01021957 0.10501329]]
    
      [[0.27234244 0.38455757 0.12534761]
       [0.14294785 0.42619756 0.48750326]
       [0.44242665 0.17975393 0.29942948]]]]
    View Code
  • 相关阅读:
    RabbitMQ介绍
    idea根据Ctrl和滑轮改变字体大小
    com.fasterxml.jackson.databind.exc.InvalidDefinitionException
    Git出现Push rejected: Push to origin/master was rejected
    java单机操作redis3.2.10和集群操作增删改查
    java操作hbase1.3.1的增删改查
    Gobblin采集kafka数据
    使用连接池和缓存机制,处理连接数据库操作
    用cmd导入oracle的.dmp文件和修改oracle管理员密码
    oracle to_date函数和mysql DATE_FORMAT函数用法
  • 原文地址:https://www.cnblogs.com/yuzaihuan/p/12432485.html
Copyright © 2020-2023  润新知