Dropout和BN(层归一化)详解

无论是机器学习,还是深度学习,模型过拟合是很常见的问题,解决手段无非是两个层面,一个是算法层面,一个是数据层面。数据层面一般是使用数据增强手段,算法层面不外乎是:正则化、模型集成、earlystopping、dropout、BN等,本文重点详细讲解一下dropout和BN。

Dropout

背景

在2012年,Hinton在其论文《Improving neural networks by preventing co-adaptation of feature detectors》中提出Dropout。当一个复杂的前馈神经网络被训练在小的数据集时,容易造成过拟合。为了防止过拟合,可以通过阻止特征检测器的共同作用来提高神经网络的性能。
Dropout可以作为训练深度神经网络的一种trick供选择。在每个训练批次中,通过忽略一半的特征检测器(让一半的隐层节点值为0),可以明显地减少过拟合现象。这种方式可以减少特征检测器(隐层节点)间的相互作用,检测器相互作用是指某些检测器依赖其他检测器才能发挥作用。

Dropout说的简单一点就是:我们在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。

Dropout具体工作流程

假设我们要训练这样一个神经网络,如图2所示。
在这里插入图片描述
输入是x输出是y,正常的流程是:我们首先把x通过网络前向传播,然后把误差反向传播以决定如何更新参数让网络进行学习。使用Dropout之后,过程变成如下:(1)首先随机(临时)删掉网络中一半的隐藏神经元,输入输出神经元保持不变(图3中虚线为部分临时被删除的神经元)
在这里插入图片描述
(2) 然后把输入x通过修改后的网络前向传播,然后把得到的损失结果通过修改的网络反向传播。一小批训练样本执行完这个过程后,在没有被删除的神经元上按照随机梯度下降法更新对应的参数(w,b)。(3)然后继续重复这一过程:. 恢复被删掉的神经元(此时被删除的神经元保持原样,而没有被删除的神经元已经有所更新). 从隐藏层神经元中随机选择一个一半大小的子集临时删除掉(备份被删除神经元的参数)。. 对一小批训练样本,先前向传播然后反向传播损失并根据随机梯度下降法更新参数(w,b) (没有被删除的那一部分参数得到更新,删除的神经元参数保持被删除前的结果)。不断重复这一过程。

dropout的实现细节

Dropout的具体工作流程上面已经详细的介绍过了,但是具体怎么让某些神经元以一定的概率停止工作(就是被删除掉)?代码层面如何实现呢?下面,我们具体讲解一下Dropout代码层面的一些公式推导及代码实现思路。(1)在训练模型阶段无可避免的,在训练网络的每个单元都要添加一道概率流程。
在这里插入图片描述
没有Dropout的网络计算公式:
在这里插入图片描述
采用Dropout的网络计算公式:
在这里插入图片描述
上面公式中Bernoulli函数是为了生成概率r向量,也就是随机生成一个0、1的向量。代码层面实现让某个神经元以概率p停止工作,其实就是让它的激活函数值以概率p变为0。比如我们某一层网络神经元的个数为1000个,其激活函数输出值为y1、y2、y3、…、y1000,我们dropout比率选择0.4,那么这一层神经元经过dropout后,1000个神经元中会有大约400个的值被置为0。

训练和预测的差异

模型训练完了,总是要上线预测的,从上面讲述的dropout原理中我们知道,我们训练的时候会随机的丢弃一些神经元,但是预测的时候就没办法随机丢弃了,如果丢弃一些神经元,这会带来结果不稳定的问题,也就是给定一个测试数据,有时候输出a有时候输出b,结果不稳定,这是实际系统不能接受的,用户可能认为模型预测不准。那么如何解决呢?一种”补偿“的方案就是每个神经元的权重都乘以一个系数,使得训练数据和测试数据在总体上的期望是一致的。一般有两种实现方式,分别如下:

  • 比如一个神经元的输出是x,假设p为每个神经元为保留的概率,那么在训练的时候它有p的概率参与训练,(1-p)的概率丢弃,那么它训练时输出的期望是p*x+(1-p)*0=px,在预测的时候,不会随机丢弃一些神经元,每个神经元都会参与预测,如果把神经元的权重乘以p,那么数学期望就是px,这样一来,训练和预测时,数学期望都是px,总体保持一致;
  • 再比如一个神经元的输出也是x,假设p为每个神经元为丢弃的概率,那么在训练的时候它有1-p的概率参与训练,p的概率被丢弃,如果给每个神经元权重都乘以1/(1-p),那么它训练时输出的期望是[(1-p)x+p0]/(1-p)=x,预测时不需要对神经元权重进行放缩,这样一来,训练和预测时,数学期望都是x,总体保持一致;

代码实现如下:
我们对keras中Dropout实现函数做一些修改,让dropout函数可以单独运行。

# coding:utf-8
import numpy as np
def dropout(x, level):
    if level < 0. or level >= 1:  # level是概率值,必须在0~1之间
        raise ValueError('Dropout level must be in interval [0, 1[.')
    retain_prob = 1. - level
    # 我们通过binomial函数,生成与x一样的维数向量。binomial函数就像抛硬币一样,我们可以把每个神经元当做抛硬币一样
    # 硬币 正面的概率为p,n表示每个神经元试验的次数
    # 因为我们每个神经元只需要抛一次就可以了所以n=1,size参数是我们有多少个硬币。
    random_tensor = np.random.binomial(n=1, p=retain_prob, size=x.shape)  # 即将生成一个0、1分布的向量,0表示这个神经元被屏蔽,不工作了,也就是dropout了
    print(random_tensor)
    x *= random_tensor
    print(x)
    x /= retain_prob
    return x
# 对dropout的测试,大家可以跑一下上面的函数,了解一个输入x向量,经过dropout的结果  
x = np.asarray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float32)
dropout(x, 0.4)

当前Dropout被大量利用于全连接网络,而且一般认为设置为0.5或者0.3,而在卷积网络隐藏层中由于卷积自身的稀疏化以及稀疏化的ReLu函数的大量使用等原因,Dropout策略在卷积网络隐藏层中使用较少。总体而言,Dropout是一个超参,需要根据具体的网络、具体的应用领域进行尝试。

为什么dropout可以防止过拟合

  • 在某种程度上,dropout相当于模型融合。dropout工作中,每次随机丢弃一部分神经元,每次丢弃都是随机的,不一样,相当于每次形成的网络结构都不一样,更新的参数都不一样,最后的预测结果类似于多个网络模型的集成的结果;
  • dropout随机丢弃一部分神经元,减少了网络结构中的需要更新的参数,有利于减少过拟合;
  • dropout随机丢弃一部分神经元,相当于这部分神经元对应的特征直接舍弃了,可以理解成从所有特征中挑选了一部分特征进行训练,每次选的特征集还不一样,这就是类似于RF中的列采样了,增强了模型的泛化能力,减少过拟合;

dropout就讲到这里,下面讲BN。

深度学习中的归一化(BN、LN、IN、GN)

为什么要归一化

神经网络学习过程的本质就是为了学习数据分布,如果我们没有做归一化处理,那么每一批次训练数据的分布不一样,从大的方向上看,神经网络则需要在这多个分布中找到平衡点,从小的方向上看,由于每层网络输入数据分布在不断变化,这也会导致每层网络在找平衡点,显然,神经网络就很难收敛了。当然,如果我们只是对输入的数据进行归一化处理(比如将输入的图像除以255,将其归到0到1之间),只能保证输入层数据分布是一样的,并不能保证每层网络输入数据分布是一样的,所以也需要在神经网络的中间层加入归一化处理。神经网络学习过程本质上就是为了学习数据分布,如果训练数据与测试数据的分布不同,网络的泛化能力就会严重降低。

四种归一化

在深度学习中,有多种归一化,接下来,我们先用一个示意图来形象的表现BN、LN、IN和GN的区别,在输入图片的维度为(NCHW)中,HW是被合成一个维度,这个是方便画出示意图,C和N各占一个维度。
在这里插入图片描述

Batch Normalization

1.BN的计算就是把每个通道的NHW单独拿出来归一化处理
2.针对每个channel我们都有一组γ,β,所以可学习的参数为2*C
3.当batch size越小,BN的表现效果也越不好,因为计算过程中所得到的均值和方差不能代表全局

Layer Normalizaiton

1.LN的计算就是把每个CHW单独拿出来归一化处理,不受batchsize 的影响
2.常用在RNN网络,但如果输入的特征区别很大,那么就不建议使用它做归一化处理

Instance Normalization

1.IN的计算就是把每个HW单独拿出来归一化处理,不受通道和batchsize 的影响
2.常用在风格化迁移,但如果特征图可以用到通道之间的相关性,那么就不建议使用它做归一化处理

Group Normalizatio

1.GN的计算就是把先把通道C分成G组,然后把每个gHW单独拿出来归一化处理,最后把G组归一化之后的数据合并成CHW
2.GN介于LN和IN之间,当然可以说LN和IN就是GN的特列,比如G的大小为1或者为C

BN、LN、IN和GN这四个归一化的计算流程几乎是一样的,可以分为四步:
1.计算出均值
2.计算出方差
3.归一化处理到均值为0,方差为1
4.变化重构,恢复出这一层网络所要学到的分布

在这里插入图片描述

训练的时候,是根据输入的每一批数据来计算均值和方差,那么测试的时候,平均值和方差是怎么来的?对于均值来说直接计算所有训练时batch 均值的平均值;然后对于标准偏差采用每个batch 方差的无偏估计。
在这里插入图片描述

为什么BN可以缓解过拟合

原论文中的解释是这么的:
When training with Batch Normalization, a training example is seen in conjunction with other examples in the mini-batch, and the training network no longer producing deterministic values for a given training example. In our experiments, we found this effect to be advantageous to the generalization of the network. 大概意思是:在训练中,BN的使用使得一个mini-batch中的所有样本都被关联在了一起,因此网络不会从某一个训练样本中生成确定的结果。这句话什么意思呢?意思就是同样一个样本的输出不再仅仅取决于样本本身,也取决于跟这个样本属于同一个mini-batch的其它样本。同一个样本跟不同的样本组成一个mini-batch,它们的输出是不同的(仅限于训练阶段,在inference阶段是没有这种情况的)。我把这个理解成一种数据增强:同样一个样本在超平面上被拉扯,每次拉扯的方向的大小均有不同。不同于数据增强的是,这种拉扯是贯穿数据流过神经网络的整个过程的,意味着神经网络每一层的输入都被数据增强处理了。

BN的实现细节

在BN的实现中,有几个比较重要的参数,分别是running_mean、running_var、beta、gamma,动量momentum,下面分别说明。

  • running_mean和running_var只在训练时前向传播中更新,每个batch数据中更新一次,不属于可学习的参数,因为不参与反向传播的更新;
  • beta、gamma在反向传播中更新,属于可学习的参数,也是BN中唯一需要学习的参数;
  • momentum,负责参与running_mean和running_var的更新,是running_mean和running_var的滑动平均系数,具体更新公式,看下面代码;

假设我们的初始输入数据是个2维矩阵,如下:

data = np.array([[1, 2],
                 [1, 3],
                 [1, 4]]).astype(np.float32)

初始化过程:

class MyBN:
    def __init__(self, momentum, eps, num_features):
        """
        初始化参数值
        :param momentum: 追踪样本整体均值和方差的动量
        :param eps: 防止数值计算错误
        :param num_features: 特征数量
        """
        # 对每个batch的mean和var进行追踪统计
        self._running_mean = 0
        self._running_var = 1
        # 更新self._running_xxx时的动量
        self._momentum = momentum
        # 防止分母计算为0
        self._eps = eps
        # 对应论文中需要更新的beta和gamma,采用pytorch文档中的初始化值
        self._beta = np.zeros(shape=(num_features, ))
        self._gamma = np.ones(shape=(num_features, ))

前向传播过程

def batch_norm(self, x):
    """
    BN向传播
    :param x: 数据
    :return: BN输出
    """
    #x为2维矩阵
    x_mean = x.mean(axis=0)
    x_var = x.var(axis=0)
    # 对应running_mean的更新公式
    self._running_mean = (1-self._momentum)*x_mean + self._momentum*self._running_mean
    self._running_var = (1-self._momentum)*x_var + self._momentum*self._running_var
    # 对应论文中计算BN的公式
    x_hat = (x-x_mean)/np.sqrt(x_var+self._eps)
    y = self._gamma*x_hat + self._beta
    return y

在pytorch中,实现函数是torch.nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True),
默认情况下,track_running_stats为True,表示训练阶段采用实时的batch均值和方差, 同时采用滑动平均来计算全局的running_mean和running_var,测试阶段采用当前的running_mean和running_var,如果track_running_stats=False,则训练阶段和测试阶段都采用实时的batch均值和方差。

也就是说:当track_running_stats为True时,那么在预测的时候,BN层的输出Y与输入X之间的关系是:Y = (X - running_mean) / sqrt(running_var + eps) * gamma + beta,此不赘言。其中gamma、beta为可学习参数(在pytorch中分别改叫weight和bias),训练时通过反向传播更新;而running_mean、running_var则是在前向时先由X计算出mean和var,再由mean和var以动量momentum来更新running_mean和running_var。所以在训练阶段,running_mean和running_var在每次前向时更新一次;在测试阶段,则通过net.eval()固定该BN层的running_mean和running_var,此时这两个值即为训练阶段最后一次前向时确定的值,并在整个测试阶段保持不变。

从这个角度看,BN具有一定的正则化效果,在Batch Normalization中,由于我们使用mini-batch的均值与方差作为对整体训练样本均值与方差的估计,尽管每一个batch中的数据都是从总体样本中抽样得到,但不同mini-batch的均值与方差会有所不同,这就为网络的学习过程中增加了随机噪音,与Dropout通过关闭神经元给网络训练带来噪音类似,在一定程度上对模型起到了正则化的效果。原作者也证明了网络加入BN后,可以丢弃Dropout,模型也同样具有很好的泛化效果。

  • 19
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
批量归一化(Batch Normalization,简称BN)是一种常用于深度神经网络的技术,旨在加速模型训练并提高模型的泛化能力。它的作用是对输入数据进行标准化处理,即将数据按特征维度减去均值,再除以标准差,以使其分布接近标准正态分布。 BN主要包括两个步骤:训练阶段和推理阶段。 在训练阶段,对于每个批次的数据,首先计算该批次数据在每个特征维度上的均值和方差,并使用这些统计量对该批次的数据进行标准化处理。然后,通过一个可学习的缩放因子和偏移项,对标准化后的数据进行线性变换,以恢复数据的表达能力。这样可以使得输入数据在网络中传播时的数值更稳定,并且减少了网络对初始参数的敏感程度。 在推理阶段,由于每个测试样本可能无法组成完整的批次,无法计算该批次数据的均值和方差。因此,在训练阶段通过累积一批次数据的均值和方差得到的统计量进行标准化处理。这种方法保持了训练阶段获得的统计特性,并且可以应用于单个测试样本。 批量归一化处理BN的优点包括: - 提高模型训练的收敛速度和稳定性。 - 减少了对初始参数选择的敏感度,使得网络更容易训练。 - 缓解了梯度消失和梯度爆炸问题。 - 有一定的正则化效果,减少了对 Dropout 等正则化技术的依赖。 需要注意的是,BN在一些情况下可能会引入一定的计算开销,且对小批次数据的效果可能不如大批次数据明显。在实际应用中,BN通常被应用于卷积神经网络(CNN)和全连接神经网络(FCN)中,并在很多深度学习任务中得到了广泛应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值