卷积神经网络基础

本文最后更新于:1 年前

卷积

形状

[N,C,H,W]

步幅(stride)

卷积核运算过程中==移动的距离大小==。
卷积后的输出尺寸计算公式如下:
$$ H_{out}= \frac{H+2p_{h}-k_{h}}{s_{h}}+1 $$
$$ W_{out}= \frac{W+2p_{w}-k_{w}}{s_{w}}+1 $$

其中 $p_h$ 和 $p_w$ 分别为高度和宽度填充,$k_h$ 和 $k_w$ 分别为卷积核的高宽大小,$s_h$ 和 $s_w$ 分别为高度和宽度步幅

[!NOTE] 提示
步幅越大,特征越小,stride=2,则特征缩小一半

批量卷积

对RGB三通道,同时输入一个批次(batch)张图片做卷积运算,每张图像卷积后通道叠在一起(不是叠加),如下图所示:
image.png|525

感受野

感受野是指输出特征图上的像素点所能感受到的输入数据的范围。

从下图可以看出感受野的大小:
image.png|250
网络越深,越深层特征图的感受野越大,主要从输入的主对角线反映。

[!NOTE] 提示
对于图像分类任务来说,感受野的大小与网络的性能存在一定的关系。感受野越大,网络可以获取到更广泛的上下文信息,从而更好地理解图像。但过大的感受野对图像分类也会有一定的影响,主要表现在以下两个方面:

  1. 过大的感受野会引入过多的噪声和干扰信号。当感受野越大时,网络就会考虑更远的像素,这些像素可能与目标物体或图案无关,甚至会背离目标。这些噪声和干扰信号会干扰网络的学习,从而导致分类准确性的降低。
  1. 过大的感受野会降低网络的细节敏感度。当感受野的大小超出图像细节的尺度范围时,网络无法有效地捕捉到细节信息,从而导致分类准确性的降低。尤其是对于一些小尺寸物体和图案,小细节对分类结果的影响会更加显著。

因此,对于图像分类任务来说,选择适当的感受野大小非常重要。通常,在保证网络全局感知能力的同时,应该将感受野限制在合适的尺度范围内,以避免过度拟合和过大感受野带来的问题。同时,还可以结合多尺度特征提取的方法,综合利用不同感受野下的信息,以获取更全面、更准确的特征,提高分类精度。

Paddle API卷积

image.png|400

案例1-边缘检测二值图

检测图像黑白分界线。

image.png|150

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
with fluid. dygraph. guard():
#创建初始化权重参数w
w = np.array([1, 0, -1], dtype= 'float32')
#将权重参数调整成维度为[cout, cin, kh, kw]的四维张里
w = w.reshape([1, 1, 1, 3])
#创建卷积算子,设置输出通道数,卷积核大小,和初始化权重参数
# filter_size = [1, 3]表示kh = 1, kw=3
#创建卷积算子的时候,通过参数属性param_ attr, 指定参数初始化方式
#这里的初始化方式时,从numpy. ndarray初始化卷积参数
#num_channels:输入通道数;num_filters:输出通道数;filter_size:卷积核高宽,param_attr:初始化参数
conv = Conv2D(num_channels=1, num_filters=1, filter_size=[1,3],param_attr=fluid.ParamAttr(
initializer=NumpyArrayInitializer(value=w)))
#创建输入图片,图片左边的像素点取值为1,右边的像素点取值为0
img = np.ones([50,50], dtype='f1oat32')
img[:, 30:] = 0.
#将图片形状调整为[N(bach_size), C(chanel), H, W]的形式
x = img.reshape([1,1,50,50])
#将numpy.ndarray转化成paddle中的tensor
x = fluid.dygraph.to_variable(x)
#使用卷积算子作用在输入图片上
y = conv(x)
#将输出tensor转化为numpy.ndarray
out = y.numpy()

查看卷积层参数:conv.parameters()
image.png|450


案例2-边缘检测RGB彩图

图片初始读入的形状为[H,W,C](垂直像素,水平像素,通道数),需要调整为[N,C,H,W]格式。
卷积核:
image.png|175

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
img = Image.open("./work/ images/section1000000098520. jpg')
with fluid.dygraph.guard():
#设置卷积核参数
w = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]], dtype= 'float32')/8
w = w.reshape([1, 1, 3, 3])
#由于输入通道数是3,将卷积核的形状从[1,1,3,3]调整为[1p3,p,3]
w = np.repeat(w,3,axis=1)、
#创建卷积算子,输出通道数为1,卷积核大小为3x3,
#并使用上面的设置好的数值作为卷积核权重的初始化参数
conv = Conv2D(num_channels=3, num_filters=1, filter_size=[3,3],param_attr=fluid.ParamAttr(initializer=NumpyArrayInitializer(value=w)))
#将读入的图片转化为float32类型的numpy.ndarray
x = np.array(img).astype('float32')
#图片读入成ndarry时,形状是[H, W, C],
#将通道这一维度调整到最前面
x = np.transpose(x, (2,0,1))
#将数据形状调整为[N, C, H, W]格式
x = x.reshape(1, 3, img.height, img.width)
x = fluid.dygraph.to_variable(x)#变为张量格式
y = conv(x)
out = y.numpy()#张量转numpy数组

案例3-均值模糊

卷积核:
image.png|200
效果对比:
image.png|200


池化

形状

[N,C,H,W]

池化是使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出
理解:取输入的局部统计特征作为输出,==可以掩盖变化的细节==。

池化方法

  1. 平均池化
    image.png|200

  2. 最大池化
    image.png|200

优点

  1. 池化的好处是当输入数据做出少量平移时,经过池化函数后的大多数输出还能保持不变,池化能够帮助输入的表示近似不变
  2. 由于池化之后特征图会变得更小,如果后面连接的是全连接层,能有效的减小神经元的个数,节省存储空间并提高计算效率

相关参数

  • 池化窗口大小:$pool_size = [k_h,k_w]$
  • 池化窗口滑动步幅:$pool_stride = [stride_ h, stride_w]$
  • 图片填充:$padding=[ph,pw]$
  • 比较常见的参数配置是:$k_n =k_w=2,stride_h=stride_w=2,p_h=p_w=0$
  • 采用这样的设置将会使得输出图片高和宽都减半

特点

  • 没有学习参数
  • 通道数不变,每个通道独立进行池化
  • 微小的位置变化具有鲁棒性

激活函数

通常在卷积或者全连接这样的线形操作之后,会加上一个非线性的函数,作用在每一个神经元的输出上,从而实现非线性变换的效果。

sigmoid激活函数

说明: Sigmoid函数只有在x接近于0的地方,导数才比较大,但最大值也只有1/4;在X的数值非常大或者非常小的地方,导数都接近于0
$$\begin{array}
\text { 反向传播 } \frac{\partial L}{\partial x}=\frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial x}
\text { 这将导致 } \frac{\partial L}{\partial x} \ \text { 会显著的小于 } \frac{\partial L}{\partial y}
这将导致张会显著的小于光
\end{array}$$

  1. 如果X是非常大整数或者非常小的负数,则x的梯度将接近于0
  2. 即使x的数值接近于0,其梯度最大不超过y的梯度的1/4,如果有多层网络使用Sigmoid激活函数,将导致较靠前的那些层,梯度变得非常的小
    在神经网络里面,将这种经过反向传播之后,梯度值衰减到接近于0的现象称作梯度消失现象
    $$y=\frac{1}{1+e^{-x}}$$

image.png|250


ReLU激活函数

$$
y= \begin{cases}0, & (x<0) \ x, & (x \geq 0)\end{cases}
$$
image.png|250

[!note]

  • 在x>0的地方,ReLU函数的导数为1,能够将y的梯度完整的传递给×,而不会引起梯度消失
  • 在神经网络发展的早期Sigmoid函数用的比较多,而目前用的较多的激活函数是ReLU

批归一化BatchNorm

数据分布和模型的数值稳定性

模型收敛:需要稳定的数据分布

[!note]
浅层神经网络—>对输入数据做标准化(也称作归一化)

                深度神经网络--->仅仅标准化输入数据还不够

image.png

Batch Normalization 提升数值稳定性

对中间层的输出做标准化,可以保证在网络学习的过程中,网络层的输出具有稳定的分布。

优点

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

归一化公式

每一项减去均值,除以方差加一个无穷小的数($10^{-6}$)防止方差为0无意义.
$$
\hat{x_i} \leftarrow \frac{x_i-\mu_B}{\sqrt{\left(\sigma_B^2+\epsilon\right)}}
$$
对标准化输出进行平移和缩放
$$
y_i \leftarrow \gamma \hat{x}_i+\beta
$$
其中$\gamma$和$\beta$是可学习的参数,可以赋初始值$\gamma=1$,$\beta=0$,在训练过程中自动不断学习调整.

预测时使用BatchNorm

[!解决方案]
训练时计算在整个数据集上的均值和方差,并将结果保存预测时不计算样本内均值和方差,而是使用训练时保存的值

  1. 训练通过滚动平均的方式,计算在整个数据集上的均值和方差并保存
  2. 预测时直接加在训练时保存的均值和方差,而不用在样本内计算

丢弃法Dropout

简介

防止过拟合的方法
训练阶段:每次随机的删除一部分神经元,不向前传播其所携带的信息,相当于每次都是让不同的模型在学习
测试阶段:向前传播所有神经元的信息,相当于让这些不同的模型一起工作
image.png|350

存在问题

训练时随机丢弃了一部分神经元的信息,输出数据的总大小变小了
预测时不丢弃神经元,导致预测和训练时的数据分布不一样

解决方案

  1. ==downgrade_in_infer==
    训练随机丢弃一部分神经元;预测时不丢弃神经元,但把它们数值变小
  2. ==upscale_in_train==
    训练随机丢弃一部分神经元,但是把保留的那些神经元数值放大预测原样输出所有神经元的信息

    Paddle中默认是downgrade_in_infer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#设置随机数种子,这样可以保证每次运行结果一致
np.random.seed(100)
#创建数据N,C,H,WM,一般对应卷积层的输出
data1 = np.random.rand(2,3,3,3).astype('float32')
#创建数据N,K),一般对应全连接层的输出
data2 = np.arange(1,13).reshape([-1,3]).astype('float32')
#使用dropout作用在输入数据上
with fluid.dygraph.guard():
x1 = fluid.dygraph.to_variable(data1)
out1_1 = fluid.layers.dropout(x1,dropout_prob=0.5,is_test=False)
out1_2 = fluid.layers.dropout(x1,dropout_prob=0.5,is_test=True)

x2 = fluid.dygraph.to_variable(data2)
out2_1 = fluid.layers.dropout(x2,dropout_prob=0.5,\
dropout_implementation='upscale_in_train')
out2_2 = fluid.layers.dropout(x2,dropout_prob=0.5,\
dropout_implementation='upscale_in_train',is_test=True)

全连接层

将输入全部转为1维进行线性求和

形状

[N,C]

[!NOTE] 数据变换
尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W]
x = paddle.reshape(x, [x.shape[0], -1])


卷积神经网络基础
https://alleyf.github.io/2023/03/8cda404ed402.html
作者
alleyf
发布于
2023年3月17日
更新于
2023年7月4日
许可协议