这两天看完了第六章:卷积神经网络,巧的是最近上的专业选修课刚讲完卷积神经网络,什么卷积层池化层听得云里雾里的,这一章正好帮我讲解了基础的知识。
第六章:卷积神经网络
6.1 从全连接层到卷积
在之前的学习中,我们主要学习的是全连接层,在处理二维图像中的做法是将图像平展为一维数组,但这样显然没有考虑到图像的空间性,而卷积,就是将图像相近像素之间互相关性提取出来,让机器能够从图像中学习到更多有效信息的重要模型。
在之前学到的多层感知机可以有效处理表格数据,但对于高维数据,多层感知机存在许多限制,比如,缺乏平移不变性和局部性。
修改全连接层,将全连接层的权重W改为具有平移不变性(对无论哪个位置的输入,都采取同样操作)。
然后我们就可以得到卷积操作。
接着再改写一下形式,对图像的处理还需要局部性,即获取某个位置的信息,我们不能让偏移到离这个位置很远的地方去。
这就是卷积层的基本形式,就像是对二维图像每个一定区域内的数值执行固定权重的全连接一样,满足了平移不变性和局部性。
其中V叫做卷积核或滤波器。
其实这不是严格意义的数学里的卷积,但是非常类似,所以用了这个名字。
卷积也可有多通道,即对相同数据使用不同参数的卷积层,有的用于识别边缘,有的用于识别纹理,或者比如用卷积处理图像的时候,图像的颜色通常包含三原色,这时可以用三通道卷积层分别提取三种颜色特征。
卷积通常比全连接参数少,但依旧获得高效用。
6.2 图像卷积
这里举了一个简单的例子,实现了简单的卷积:
X:
array([[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.]])
比如提取这个“图像”的边缘,我们就可以用到卷积,将这个数组进行K=torch.tensor([[1.0, -1.0]])的卷积,进行卷积运算后得到:
Y:
array([[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.]])
但在实际中我们不可能每一个都人工设计卷积,所以要使用进行训练的方法。
conv2d = nn.Conv2d(1, 1, kernel_size = (1, 2), bias = False)X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2for i in range(10):Y_hat = conv2d(X)l = (Y_hat - Y)**2conv2d.zero_grad()l.sum().backward()conv2d.weight.data[:] -= lr * conv2d.weight.gradprint(f'epoch {i+1}, loss {l.sum():.3f}')
这时输出conv2d.weight.data.reshape(1, 2),结果为:
tensor([[ 1.0010, -0.9739]])
与手动设计的卷积核非常相近。
最后介绍了特征映射和感受野两个概念,特征映射是指如上面例子所示输出的卷积层,因为它可以被视为一个输入映射到下一空间维度的转换器,感受野是指最初的输入中用于计算的所有元素(可能比输入元素多)。
6.3 填充与步幅
填充是为了解决进行卷积中常常丢失边缘特征而提出的,将输入数组的外围增添一层或多层0元素,可以有效解决丢失边缘像素的问题。
步幅的引用是为了高效滑动数组,步幅决定计算卷积时每次向左向下平移多少距离。
可以通过这样实现:
conv2d = nn.Conv2d(1, 1, kernel_size = (3, 5), padding = (0, 1), stride = (3, 4))
这里的参数分别为输入通道、输出通道、卷积核形状、填充大小、步幅大小。
6.4 多输入多输出通道
上面说了卷积也可有多通道,多通道输入例子:
将输入的不同通道用不同卷积核进行卷积运算,最后再将两个结果相加。
多通道输出就是将同一输入按不同卷积核进行运算,最后得到两个不同结果。
还有一个经常使用到的1×1卷积,虽然它失去了很多卷积的特点,更像一个全连接层,但它可以用于特征降维、调整网络层通道数量、控制模型复杂性。
6.5 汇聚层(池化层)
提起卷积神经网络经常会出现池化层这个概念,以前一直都不明白这是什么东西,今天算是明白了。
分为最大汇聚和平均汇聚,池化层是没有参数这些东西的,它就是简单的计算输入数据在某一区域的最大值或平均值。
它的主要作用是减弱卷积层对位置的过度敏感。
一个池化层的代码:
def pool2d(X, pool_size, mode):p_h, p_w = pool_sizeY = torch.zeros(X.shape[0] - p_h +1, X.shape[0] - p_w +1)for i in range(Y.shape[0]):for j in range(Y.shape[1]):if mode == 'max':Y[i, j] = X[i: i + p_h, j: j + p_w].max()elif mode == 'avg':Y[i: j] = X[i: i + p_h, j: j + p_w].mean() return Y
执行
pool2d(X, (2, 2), 'avg')
就可以得到X经过平均汇聚层的输出。
和卷积层一样,池化层也可以设置填充和步幅:
pool2d = nn.MaxPool2d(3, padding = 1, stride = 2)
pool2d(X)
同样也可有多个通道,不过池化层的输入通道数和输出通道数是相同的。
6.6 卷积神经网络(LeNet)
用卷积层代替全连接层的一个好处就是:可以在图像中保留空间结构,并且使模型更简洁,所需参数更少。
LeNet是最早发明的卷积神经网络之一。
它由以下两个部分组成:卷积编码器、全连接层稠密块。
卷积编码器由两个卷积块构成,每个卷积块包含一个卷积层、一个sigmoid激活函数、一个平均汇聚层。
全连接层稠密块由三个全连接层构成。
用Sequential块实现就是:
net = nn.Sequential(nn.Conv2d(1, 6, kernel_size = 5, padding = 2),nn.Sigmoid(),nn.AvgPool2d(kernel_size = 2, stride = 2),nn.Conv2d(6, 16, kernel_size = 5),nn.Sigmoid(),nn.AvgPool2d(kernel_size = 2, stride = 2),nn.Flatten(),nn.Dense(120, activation='sigmoid'),nn.Dense(84, activation='sigmoid'),nn.Dense(10)
)
然后就可以进行模型训练评估。