一文搞懂 卷积神经网络 批归一化 丢弃法

批归一化(Batch Normalization)

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

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

  • 使学习快速进行(能够使用较大的学习率
  • 降低模型对初始值的敏感
  • 从一定程度上抑制过拟合

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

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

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

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

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

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

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

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

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

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

In [ ]

# 输入数据形状是 [N, K]时的示例
import numpy as np
import paddle
from paddle.nn import BatchNorm1D
# 创建数据
data = np.array([[1,2,3], [4,5,6], [7,8,9]]).astype('float32')
# 使用BatchNorm1D计算归一化的输出
# 输入数据维度[N, K],num_features等于K
bn = BatchNorm1D(num_features=3)    
x = paddle.to_tensor(data)
y = bn(x)
print('output of BatchNorm1D Layer: \n {}'.format(y.numpy()))

# 使用Numpy计算均值、方差和归一化的输出
# 这里对第0个特征进行验证
a = np.array([1,4,7])
a_mean = a.mean()
a_std = a.std()
b = (a - a_mean) / a_std
print('std {}, mean {}, \n output {}'.format(a_mean, a_std, b))

# 建议读者对第1和第2个特征进行验证,观察numpy计算结果与paddle计算结果是否一致
  • 示例二: 当输入数据形状是 [ N , C , H , W ]时, 一般对应卷积层的输出,示例代码如下所示

这种情况下会沿着C这一维度进行展开,分别对每一个通道计算N个样本中总共N×H×W个像素点的均值和方差,数据和参数对应如下:

  • 输入 x, [N, C, H, W]
  • 输出 y, [N, C, H, W]
  • 均值 μ B,[C, ]
  • 方差 σ B², [C, ]
  • 缩放参数 γ, [C, ]
  • 平移参数 β, [C, ]

小窍门:

可能有读者会问:“BatchNorm里面不是还要对标准化之后的结果做仿射变换吗,怎么使用Numpy计算的结果与BatchNorm算子一致?” 这是因为BatchNorm算子里面自动设置初始值γ=1,β=0,这时候仿射变换相当于是恒等变换。在训练过程中这两个参数会不断的学习,这时仿射变换就会起作用。


# 输入数据形状是[N, C, H, W]时的batchnorm示例
import numpy as np
import paddle
from paddle.nn import BatchNorm2D

# 设置随机数种子,这样可以保证每次运行结果一致
np.random.seed(100)
# 创建数据
data = np.random.rand(2,3,3,3).astype('float32')
# 使用BatchNorm2D计算归一化的输出
# 输入数据维度[N, C, H, W],num_features等于C
bn = BatchNorm2D(num_features=3)
x = paddle.to_tensor(data)
y = bn(x)
print('input of BatchNorm2D Layer: \n {}'.format(x.numpy()))
print('output of BatchNorm2D Layer: \n {}'.format(y.numpy()))

# 取出data中第0通道的数据,
# 使用numpy计算均值、方差及归一化的输出
a = data[:, 0, :, :]
a_mean = a.mean()
a_std = a.std()
b = (a - a_mean) / a_std
print('channel 0 of input data: \n {}'.format(a))
print('std {}, mean {}, \n output: \n {}'.format(a_mean, a_std, b))

# 提示:这里通过numpy计算出来的输出
# 与BatchNorm2D算子的结果略有差别,
# 因为在BatchNorm2D算子为了保证数值的稳定性,
# 在分母里面加上了一个比较小的浮点数epsilon=1e-05

- 预测时使用BatchNorm

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

例如样本A、样本B作为一批样本计算均值和方差,与样本A、样本C和样本D作为一批样本计算均值和方差,得到的结果一般来说是不同的。那么样本A的预测结果就会变得不确定,这对预测过程来说是不合理的。

解决方法是在训练过程中将大量样本的均值和方差保存下来,预测时直接使用保存好的值而不再重新计算

实际上,在BatchNorm的具体实现中,训练时会计算均值和方差的移动平均值。在飞桨中,默认是采用如下方式计算:

BatchNorm的变体包括:层归一化(Layer Normalization, LN)、组归一化(Group Normalization, GN)、实例归一化(Instance Normalization, IN),通过下图进行比较,

其中N知batch size、H和W分别表示特征图的高度和宽度、C表示特征图的通道数,蓝色像素表示使用相同的均值和方差进行归一化

图14:归一化方法

  • LN:对[C,W,H]维度求均值方差进行归一化,即在通道方向做归一化,与batch size大小无关,在小batch size上效果可能更好
  • GN:先对通道方向进行分组,然后每个组内对[ C i C_{i} ,W,H]维度进行归一化,也与batch size大小无关
  • IN:只对[H,W]维度进行归一化,图像风格化任务适合使用IN算法

图14来源于Yuxin Wu, Kaiming He,Group Normalization

丢弃法(Dropout)

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

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

图15 Dropout示意图

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

  • downscale_in_infer

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

  • upscale_in_train

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

在飞桨Dropout API中,通过mode参数来指定用哪种方式对神经元进行操作,

paddle.nn.Dropout(p=0.5, axis=None, mode="upscale_in_train”, name=None)

主要参数如下:

  • p (float) :将输入节点置为0的概率,即丢弃概率,默认值:0.5。该参数对元素的丢弃概率是针对于每一个元素而言,而不是对所有的元素而言。举例说,假设矩阵内有12个数字,经过概率为0.5的dropout未必一定有6个零。
  • mode(str) :丢弃法的实现方式,有'downscale_in_infer'和'upscale_in_train'两种,默认是'upscale_in_train'。

说明:

不同框架对于Dropout的默认处理方式可能不同,读者可以查看API详细了解。


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

从上述代码的输出可以发现,经过dropout之后,tensor中的某些元素变为了0,这个就是dropout实现的功能,通过随机将输入数据的元素置0,消除减弱了神经元节点间的联合适应性,增强模型的泛化能力。

Dropout的变体有很多,如:DropConnect、Standout、Gaussian Dropout、Spatial Dropout、Cutout、Max-Drop、RNNDrop、循环Drop等,下面简单介绍其中几种算法:

  • DropConnect是由L. Wan等人提出,没有直接在神经元上应用dropout,而是在连接神经元的权重和偏置上应用,只能应用在全连接层
  • Standout是由L. J. Ba和B. Frey提出,第 i i 层丢弃一部分神经元的概率 p p 不是恒定的,根据权重的值,是自适应的,权重越大被丢弃的概率也越大
  • Spatial Dropout是由 J. Tompson等人提出,考虑相邻像素之间的高度相关性,不再进行单个像素进行dropout,而是在特征图上进行dropout
  • Cutout是由T. DeVries和G. W. Taylor提出,通过对图像的隐藏区域进行泛化从而防止过拟合,提高神经网络的鲁棒性(指系统或者器件在不同的环境或者条件下,能够保持其功能或者性能的特性和整体性能

小结

学习完这些概念,您就具备了搭建卷积神经网络的基础。下一节,我们将应用这些基础模块,一起完成图像分类中的典型应用 — 医疗图像中的眼疾筛查任务的模型搭建。

作业

1 计算卷积中一共有多少次乘法和加法操作

输入数据形状是[10,3,224,224],卷积核k_h = k_w = 3,输出通道数为6464,步幅stride=1,填充p_h = p_w = 1。

则完成这样一个卷积,一共需要做多少次乘法和加法操作?

  • 提示

先看输出一个像素点需要做多少次乘法和加法操作,然后再计算总共需要的操作次数。

  • 提交方式

请回复乘法和加法操作的次数,例如:乘法1000,加法1000

2 计算网络层的输出数据和参数的形状

网络结构定义如下面的代码所示,输入数据形状是[10,3,224,224],

请分别计算每一层的输出数据形状,以及各层包含的参数形状

In [ ]

# 定义 SimpleNet 网络结构
import paddle
from paddle.nn import Conv2D, MaxPool2D, Linear
import paddle.nn.functional as F

class SimpleNet(paddle.nn.Layer):
    def __init__(self, num_classes=1):
        #super(SimpleNet, self).__init__(name_scope)
        self.conv1 = Conv2D(in_channels=3, out_channels=6, kernel_size=5, stride=1, padding=2)
        self.max_pool1 = MaxPool2D(kernel_size=2, tride=2)
        self.conv2 = Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=2)
        self.max_pool2 = MaxPool2D(kernel_size=2, tride=2)
        self.fc1 = Linear(in_features=50176, out_features=64)
        self.fc2 = Linear(in_features=64, out_features=num_classes)        

    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.max_pool1(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.max_pool2(x)
        x = paddle.reshape(x, [x.shape[0], -1])
        x = self.fc1(x)
        x = F.sigmoid(x)
        x = self.fc2(x)
        return x
  • 提示,第一层卷积 c o n v 1 conv1 ,各项参数如下:

输出特征图的形状是[N,Cout,Hout,Wout]=[10,6,224,224]

请将下面的表格补充完整:

名称

w形状

w参数个数

b形状

b参数个数

输出形状

conv1

[6,3,5,5]

450

[6]

6

[10, 6, 224, 224]

pool1

[10, 6, 112, 112]

conv2

pool2

fc1

fc2

  • 提交方式:将表格截图发到讨论区

靠《填充》那章下面的公式:Hout=H+ph1+ph2−kh+1、Wout=W+pw1+pw2−kw+1

pool和fc都不会做

本节以上一节介绍的ResNet来完成眼疾识别任务为例,介绍一个基本的计算机视觉任务研发全流程,主要涵盖如下内容:

  • 基本的计算机视觉任务研发全流程:介绍基本的计算机视觉任务研发全流程。
  • 眼疾数据集:介绍数据集结构及数据预处理方法。
  • ResNet网络:如何应用眼疾数据集进行模型训练和测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软工菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值