卷积

卷积有两组输入:特征图和卷积核,依据输入特征和卷积核的形状、Layout不同、计算方式的不同,在Fluid里,有针对变长序列特征的一维卷积,有针对定长图像特征的二维(2D Conv)、三维卷积(3D Conv),同时也有卷积计算的逆向过程。

Conv2D、Conv3D

Conv2D将在神经网络中构建一个二维卷积层(Convolution2D Layer),其根据输入、滤波器参数(num_filters、filter_size)、步长(stride)、填充(padding)、膨胀系数(dilation)、组数(groups)参数来计算得到输出特征图。输入和输出是 NCHW 格式,N是批数据大小,C是特征图个数,H是特征图高度,W是特征图宽度。滤波器的维度是 [M, C, H, W] ,M是输出特征图个数,C是输入特征图个数,H是滤波器高度,W是滤波器宽度。如果组数大于1,C等于输入特征图个数除以组数的结果。如果提供了偏移属性和激活函数类型,卷积的结果会和偏移相加,激活函数会作用在最终结果上。详情请参考: 卷积 。 对每个输入 X ,有等式:

📃 卷积神经网络 - 图1

其中:

  • 📃 卷积神经网络 - 图2 :输入特征图, NCHW 格式的 Tensor
  • 📃 卷积神经网络 - 图3 :滤波器,维度为 [M, C, H, W] 的 Tensor
  • 📃 卷积神经网络 - 图4 :卷积操作
  • 📃 卷积神经网络 - 图5 :偏移值,2-D Tensor ,维度为 [M,1]
  • 📃 卷积神经网络 - 图6 :激活函数
  • 📃 卷积神经网络 - 图7 :输出值, OutX 的维度可能不同

输出维度计算
**

  • 输入:
    输入维度: 📃 卷积神经网络 - 图8
    滤波器维度:📃 卷积神经网络 - 图9
  • 输出:
    输出维度: 📃 卷积神经网络 - 图10
  • 其中

📃 卷积神经网络 - 图11

接口定义:

  1. class paddle.fluid.dygraph.Conv2D(
  2. num_channels,
  3. num_filters,
  4. filter_size,
  5. stride=1,
  6. padding=0,
  7. dilation=1,
  8. groups=None,
  9. param_attr=None,
  10. bias_attr=None,
  11. use_cudnn=True,
  12. act=None,
  13. dtype='float32'
  14. )

参数说明:

  • num_channels (int) - 输入图像的通道数。
  • num_filters (int) - 滤波器的个数,和输出特征图个数相同。
  • filter_size (int|tuple) - 滤波器大小。如果 filter_size 是一个元组,则必须包含两个整型数,分别表示滤波器高度和宽度。否则,表示滤波器高度和宽度均为 filter_size
  • stride (int|tuple, 可选) - 步长大小。如果 stride 为元组,则必须包含两个整型数,分别表示垂直和水平滑动步长。否则,表示垂直和水平滑动步长均为 stride 。默认值:1。
  • padding (int|tuple, 可选) - 填充大小。如果 padding 为元组,则必须包含两个整型数,分别表示竖直和水平边界填充大小。否则,表示竖直和水平边界填充大小均为 padding 。默认值:0。
  • dilation (int|tuple, 可选) - 膨胀系数大小。如果 dialation 为元组,则必须包含两个整型数,分别表示垂直和水平膨胀系数。否则,表示垂直和水平膨胀系数均为 dialation 。默认值:1。
  • groups (int, 可选) - 二维卷积层的组数。根据Alex Krizhevsky的深度卷积神经网络(CNN)论文中的分组卷积:当group=2,滤波器的前一半仅和输入特征图的前一半连接。滤波器的后一半仅和输入特征图的后一半连接。默认值:1。
  • param_attr (ParamAttr, 可选) - 指定权重参数属性的对象。默认值为None,表示使用默认的权重参数属性。具体用法请参见 ParamAttr
  • bias_attr (ParamAttr|bool, 可选) - 指定偏置参数属性的对象。默认值为None,表示使用默认的偏置参数属性。具体用法请参见 ParamAttr
  • use_cudnn (bool, 可选) - 是否用cudnn核,只有已安装cudnn库时才有效。默认值:True。
  • act (str, 可选) - 应用于输出上的激活函数,如tanh、softmax、sigmoid,relu等,支持列表请参考 激活函数 ,默认值:None。
  • dtype (str, 可选) - 数据类型,可以为”float32”或”float64”。默认值:”float32”。

调用示例:

  1. Conv2D(num_channels=1, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')

Conv3D与Conv2D类似,只是输入的时候多了一个D(特征深度),其输入输出为:

  • 输入:
    输入维度: 📃 卷积神经网络 - 图12
    滤波器维度:📃 卷积神经网络 - 图13
  • 输出:
    输出维度: 📃 卷积神经网络 - 图14
  • 其中

📃 卷积神经网络 - 图15

参考:Conv2Dconv2dConv3Dconv3d

池化

池化的作用是对输入特征做下采样和降低过拟合。降低过拟合是减小输出大小的结果,它同样也减少了后续层中的参数的数量。

池化通常只需要将前一层的特征图作为输入,此外需要一些参数来确定池化具体的操作。在PaddlePaddle中我们同样通过设定池化的大小,方式,步长,是否是全局池化,是否使用cudnn,是否使用ceil函数计算输出等参数来选择具体池化的方式。 PaddlePaddle中有针对定长图像特征的二维(pool2d)、三维卷积(pool3d),RoI池化(roi_pool),以及针对序列的序列池化(sequence_pool),同时也有池化计算的反向过程。

Pool2D、Pool3D

Pool2D将在神经网络中构建一个二维池化层,并使用上述输入参数的池化配置,为二维空间池化操作,根据 input , 池化类型 pool_type , 池化核大小 pool_size , 步长 pool_stride ,填充 pool_padding 这些参数得到输出。 输入X和输出Out是NCHW格式,N为批大小,C是通道数,H是特征高度,W是特征宽度。参数( ksize, strides, paddings )含有两个整型元素。分别表示高度和宽度上的参数。输入X的大小和输出Out的大小可能不一致。

接口定义:

  1. class paddle.fluid.dygraph.Pool2D(
  2. pool_size=-1,
  3. pool_type='max',
  4. pool_stride=1,
  5. pool_padding=0,
  6. global_pooling=False,
  7. use_cudnn=True,
  8. ceil_mode=False,
  9. exclusive=True
  10. )

参数说明:

  • pool_size (int|list|tuple, 可选) - 池化核的大小。如果它是一个元组或列表,它必须包含两个整数值, (pool_size_Height, pool_size_Width)。若为一个整数,则它的平方值将作为池化核大小,比如若pool_size=2, 则池化核大小为2x2。默认值:-1。
  • pool_type (str, 可选) - 池化类型,可以是”max“对应max-pooling,“avg”对应average-pooling。默认为”max“。
  • pool_stride (int|list|tuple, 可选) - 池化层的步长。如果它是一个元组或列表,它将包含两个整数,(pool_stride_Height, pool_stride_Width)。若为一个整数,则表示H和W维度上stride均为该值。默认值为1。
  • pool_padding (int|list|tuple, 可选) - 填充大小。如果它是一个元组或列表,它必须包含两个整数值,(pool_padding_on_Height, pool_padding_on_Width)。若为一个整数,则表示H和W维度上padding均为该值。默认值为1。
  • global_pooling (bool, 可选)- 是否用全局池化。如果global_pooling = True, pool_sizepool_padding 将被忽略,默认False。
  • use_cudnn (bool, 可选)- 是否用cudnn核,只有已安装cudnn库时才有效。默认True。
  • ceil_mode (bool, 可选)- 是否用ceil函数计算输出高度和宽度。如果设为False,则使用floor函数。默认为False。
  • exclusive (bool, 可选) - 是否在平均池化模式忽略填充值。默认为True。

输入输出的形状

输入:
X shape:📃 卷积神经网络 - 图16
输出:
Out shape:📃 卷积神经网络 - 图17

如果 ceil_mode = false:
📃 卷积神经网络 - 图18
如果 ceil_mode = true:
📃 卷积神经网络 - 图19
如果 exclusive = false:
📃 卷积神经网络 - 图20
如果 exclusive = true:
📃 卷积神经网络 - 图21

调用示例:

  1. Pool2D(pool_size=2, pool_stride=2, pool_type='max')

参考:

批归一化

BatchNorm

BatchNorm接口实现了批归一化层(Batch Normalization Layer)的功能,可用作卷积和全连接操作的批归一化函数,根据当前批次数据按通道计算的均值和方差进行归一化。

接口定义:

  1. class paddle.fluid.dygraph.BatchNorm(
  2. num_channels,
  3. act=None,
  4. is_test=False,
  5. momentum=0.9,
  6. epsilon=1e-05,
  7. param_attr=None,
  8. bias_attr=None,
  9. dtype='float32',
  10. data_layout='NCHW',
  11. in_place=False,
  12. moving_mean_name=None,
  13. moving_variance_name=None,
  14. do_model_average_for_mean_and_var=False,
  15. use_global_stats=False,
  16. trainable_statistics=False
  17. )

参数说明:

  • num_channels (int) - 指明输入 Tensor 的通道数量。
  • act (str, 可选) - 应用于输出上的激活函数,如tanh、softmax、sigmoid,relu等,支持列表请参考 激活函数 ,默认值为None。
  • is_test (bool, 可选) - 指示是否在测试阶段,非训练阶段使用训练过程中统计到的全局均值和全局方差。默认值:False。
  • momentum (float, 可选) - 此值用于计算 moving_meanmoving_var 。默认值:0.9。更新公式如上所示。
  • epsilon (float, 可选) - 为了数值稳定加在分母上的值。默认值:1e-05。
  • param_attr (ParamAttr, 可选) - 指定权重参数属性的对象。默认值为None,表示使用默认的权重参数属性。具体用法请参见 ParamAttr
  • bias_attr (ParamAttr, 可选) - 指定偏置参数属性的对象。默认值为None,表示使用默认的偏置参数属性。具体用法请参见 ParamAttr
  • dtype (str, 可选) - 指明输入 Tensor 的数据类型,可以为float32或float64。默认值:float32。
  • data_layout (string, 可选) - 指定输入数据格式,数据格式可以为“NCHW”或者“NHWC”。默认值:“NCHW”。
  • in_place (bool, 可选) - 指示 batch_norm 的输出是否可以复用输入内存。默认值:False。
  • moving_mean_name (str, 可选) - moving_mean 的名称,存储全局均值。如果将其设置为None, batch_norm 将随机命名全局均值;否则, batch_norm 将命名全局均值为 moving_mean_name 。默认值:None。
  • moving_variance_name (string, 可选) - moving_var 的名称,存储全局方差。如果将其设置为None, batch_norm 将随机命名全局方差;否则, batch_norm 将命名全局方差为 moving_variance_name 。默认值:None。
  • do_model_average_for_mean_and_var (bool, 可选) - 指示是否为mean和variance做模型均值。默认值:False。
  • use_global_stats (bool, 可选) – 指示是否使用全局均值和方差。在预测或测试模式下,将 use_global_stats 设置为true或将 is_test 设置为true,这两种行为是等效的。在训练模式中,当设置 use_global_stats 为True时,在训练期间也将使用全局均值和方差。默认值:False。
  • trainable_statistics (bool, 可选) - eval模式下是否计算mean均值和var方差。eval模式下,trainable_statistics为True时,由该批数据计算均值和方差。默认值:False。

关于BatchNorm的计算过程

当useglobal_stats = False时,![](https://cdn.nlark.com/yuque/__latex/a66a36f1940d897caecd2c2cf266db80.svg#card=math&code=%5Cmu%5Cbeta&height=16&width=18)和📃 卷积神经网络 - 图22是minibatch的统计数据。计算公式如下:

📃 卷积神经网络 - 图23

  • 📃 卷积神经网络 - 图24 : 批输入数据
  • 📃 卷积神经网络 - 图25 : 当前批次数据的大小

当useglobal_stats = True时,![](https://cdn.nlark.com/yuque/__latex/a66a36f1940d897caecd2c2cf266db80.svg#card=math&code=%5Cmu%5Cbeta&height=16&width=18)和📃 卷积神经网络 - 图26是全局(或运行)统计数据(moving_mean和moving_variance),通常来自预先训练好的模型。计算公式如下:

📃 卷积神经网络 - 图27

归一化函数公式如下:
📃 卷积神经网络 - 图28

  • ϵ : 添加较小的值到方差中以防止除零
  • γ : 可训练的比例参数
  • β : 可训练的偏差参数

参考:BatchNorm

输入数据形状是[N,K]的示例

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

  • 输入 x, [N, K]
  • 输出 y, [N, K]
  • 均值 📃 卷积神经网络 - 图29,[K, ]
  • 方差 📃 卷积神经网络 - 图30, [K, ]
  • 缩放参数📃 卷积神经网络 - 图31, [K, ]
  • 平移参数📃 卷积神经网络 - 图32, [K, ]

使用paddle中BatchNorm的代码实现:

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

输出:

  1. output of BatchNorm Layer:
  2. [[-1.2247438 -1.2247438 -1.2247438]
  3. [ 0. 0. 0. ]
  4. [ 1.2247438 1.2247438 1.2247438]]

使用numpy验证计算结果的代码实现:

  1. import numpy as np
  2. # 使用Numpy计算均值、方差和归一化的输出
  3. a = np.array([[1,2,3], [4,5,6], [7,8,9]]).astype('float32')
  4. a_mean = a.mean(axis=0)
  5. a_std = a.std(axis=0)
  6. b = (a - a_mean) / a_std
  7. print('mean is: \n {}'.format(a_mean))
  8. print('std is: \n {}'.format(a_std))
  9. print('output: is \n {}'.format(b))

输出:

  1. mean is:
  2. [4. 5. 6.]
  3. std is:
  4. [2.4494898 2.4494898 2.4494898]
  5. output: is
  6. [[-1.2247448 -1.2247448 -1.2247448]
  7. [ 0. 0. 0. ]
  8. [ 1.2247448 1.2247448 1.2247448]]

之所以两种计算结果会有些许差别,是因为BatchNorm计算标准化的时候加入了一个极小量📃 卷积神经网络 - 图33,上面有提到过。

输入数据形状是[𝑁,𝐶,𝐻,𝑊]的示例

这种情况下会沿着C这一维度进行展开,分别对每一个通道计算N个样本中总共📃 卷积神经网络 - 图34个像素点的均值和方差,数据和参数对应如下:

  • 输入 x, [N, C, H, W]
  • 输出 y, [N, C, H, W]
  • 均值 📃 卷积神经网络 - 图35,[C, ]
  • 方差 📃 卷积神经网络 - 图36, [C, ]
  • 缩放参数📃 卷积神经网络 - 图37, [C, ]
  • 平移参数📃 卷积神经网络 - 图38, [C, ]

示例如下:

  1. # 输入数据形状是[N, C, H, W]时的batchnorm示例
  2. import numpy as np
  3. import paddle
  4. import paddle.fluid as fluid
  5. from paddle.fluid.dygraph.nn import BatchNorm
  6. # 设置随机数种子,这样可以保证每次运行结果一致
  7. np.random.seed(100)
  8. # 创建数据
  9. data = np.random.rand(2,1,3,3).astype('float32')
  10. print('input is: \n {}'.format(data))
  11. # 使用BatchNorm计算归一化的输出
  12. with fluid.dygraph.guard():
  13. # 输入数据维度[N, C, H, W],num_channels等于C
  14. bn = BatchNorm(num_channels=1)
  15. x = fluid.dygraph.to_variable(data)
  16. y = bn(x)
  17. print('output of BatchNorm Layer: \n {}'.format(y.numpy()))
  18. # 使用numpy验证输出结果
  19. mean = data.mean()
  20. std = data.std()
  21. out = (data - mean) / std
  22. print('output is: \n {}'.format(out))

结果可自行运行验证。

预测时使用BatchNorm

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

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

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

📃 卷积神经网络 - 图39
📃 卷积神经网络 - 图40

在训练过程的最开始将📃 卷积神经网络 - 图41📃 卷积神经网络 - 图42设置为0,每次输入一批新的样本,计算出📃 卷积神经网络 - 图43📃 卷积神经网络 - 图44,然后通过上面的公式更新📃 卷积神经网络 - 图45📃 卷积神经网络 - 图46,在训练的过程中不断的更新它们的值,并作为BatchNorm层的参数保存下来。预测的时候将会加载参数📃 卷积神经网络 - 图47📃 卷积神经网络 - 图48,用他们来代替📃 卷积神经网络 - 图49📃 卷积神经网络 - 图50

丢弃法

Dropout

丢弃或者保持输入的每个元素独立。Dropout是一种正则化手段,通过在训练过程中阻止神经元节点间的相关性来减少过拟合。根据给定的丢弃概率,dropout操作符按丢弃概率随机将一些神经元输出设置为0,其他的仍保持不变。

Dropout层可以删除,提高执行效率。

接口定义:

  1. class paddle.fluid.dygraph.Dropout(
  2. p=0.5,
  3. seed=None,
  4. dropout_implementation='downgrade_in_infer',
  5. is_test=False
  6. )

参数说明:

  • p (float32,可选) - 输入单元的丢弃概率,即输入单元设置为0的概率。默认值:0.5
  • seed (int,可选) - 整型数据,用于创建随机种子。如果该参数设为None,则使用随机种子。注:如果给定一个整型种子,始终丢弃相同的输出单元。训练过程中勿用固定不变的种子。默认值:None。
  • dropout_implementation(str,可选) - 丢弃单元的方式,有两种’downgrade_in_infer’和’upscale_in_train’两种选择,默认:’downgrade_in_infer’。具体作用可以参考一下描述。
    1. downgrade_in_infer(default), 在预测时减小输出结果
      • train: out = input * mask
      • inference: out = input * (1.0 - p)
    2. (mask是一个张量,维度和输入维度相同,值为0或1,值为0的比例即为 p )
    3. upscale_in_train, 增加训练时的结果
      • train: out = input * mask / ( 1.0 - p )
      • inference: out = input
    4. (mask是一个张量,维度和输入维度相同,值为0或1,值为0的比例即为 p
  • is_test (bool,可选) - 标记是否是测试阶段。此标志仅对静态图模式有效。对于动态图模式,请使用 eval() 接口。默认:False。

参考:Dropout

downgrade_in_infer

使用downgrade_in_infer,将在预测时减小输出结果:

  1. import paddle.fluid as fluid
  2. from paddle.fluid.dygraph.base import to_variable
  3. import numpy as np
  4. # 设置随机数种子,这样可以保证每次运行结果一致
  5. np.random.seed(100)
  6. data = np.arange(1,13).reshape([-1, 3]).astype('float32')
  7. print('origin data is: \n {}'.format(data))
  8. with fluid.dygraph.guard():
  9. x = to_variable(data)
  10. m = fluid.dygraph.Dropout(p=0.5)
  11. droped_train = m(x)
  12. # 切换到 eval 模式
  13. m.eval()
  14. droped_eval = m(x)
  15. print('droped_train is: \n {}'.format(droped_train.numpy()))
  16. print('droped_eval is: \n {}'.format(droped_eval.numpy()))

输出:

  1. origin data is:
  2. [[ 1. 2. 3.]
  3. [ 4. 5. 6.]
  4. [ 7. 8. 9.]
  5. [10. 11. 12.]]
  6. droped_train is:
  7. [[ 1. 2. 0.]
  8. [ 4. 5. 6.]
  9. [ 7. 8. 9.]
  10. [10. 0. 0.]]
  11. droped_eval is:
  12. [[0.5 1. 1.5]
  13. [2. 2.5 3. ]
  14. [3.5 4. 4.5]
  15. [5. 5.5 6. ]]

可以看出,在训练时,随机丢弃了数组中的一些元素(丢弃的元素值为0),在预测时,所有数据的值均减半(设置了p为0.5)。

upscale_in_train

使用upscale_in_train,将在训练时将数值放大,预测时保持不变:

  1. import paddle.fluid as fluid
  2. from paddle.fluid.dygraph.base import to_variable
  3. import numpy as np
  4. # 设置随机数种子,这样可以保证每次运行结果一致
  5. np.random.seed(100)
  6. data = np.arange(1,13).reshape([-1, 3]).astype('float32')
  7. print('origin data is: \n {}'.format(data))
  8. with fluid.dygraph.guard():
  9. x = to_variable(data)
  10. m = fluid.dygraph.Dropout(p=0.5, dropout_implementation = 'upscale_in_train')
  11. droped_train = m(x)
  12. # 切换到 eval 模式
  13. m.eval()
  14. droped_eval = m(x)
  15. print('droped_train is: \n {}'.format(droped_train.numpy()))
  16. print('droped_eval is: \n {}'.format(droped_eval.numpy()))

输出:

  1. origin data is:
  2. [[ 1. 2. 3.]
  3. [ 4. 5. 6.]
  4. [ 7. 8. 9.]
  5. [10. 11. 12.]]
  6. droped_train is:
  7. [[ 0. 4. 0.]
  8. [ 0. 0. 12.]
  9. [ 0. 16. 0.]
  10. [ 0. 0. 0.]]
  11. droped_eval is:
  12. [[ 1. 2. 3.]
  13. [ 4. 5. 6.]
  14. [ 7. 8. 9.]
  15. [10. 11. 12.]]

可以看出,在训练时,随机丢弃了数组中的一些元素(丢弃的元素值为0),但保留的值放大了两倍(设置了p为0.5),在预测时,所有数据的值保持不变。

参考资料