【深度学习】卷积神经网络
【深度学习】卷积神经网络
从MLP到CNN
线性变换和非线性激活
神经网络最初始的形态是假定输入与结果之间存在一个线性方程组:
在这个基础上可以改成多层:
这样做就是一个简单地多层感知结构,并且每一层之间都是全连接的
但是有一个问题,这样做的话,全过程都是完全线性的,而现实中的问题当然有非线性的要素
所以引入了非线性激活函数,在MLP中常用的就是ReLU
这个函数常用的其中一个原因是它在反向传播的时候导数比较简单
以下就是一个简单的MLP的网络:

局限性
- 不具有平移不变性
拿一张图来看,MLP的线性层都是全连接层,也就是说权重的计算都是对于每一个像素单独加权,那么这个网络就无法对一个平移的图像做出尽可能相同的结果
因而需要找一种组件能够保证无论同样的物体怎么平移,都可以识别出来
- 不具有局部性
依旧是全连接层自身的问题,两端数据每个点之间都会有连接,没有距离远近的概念,因为权重的计算彼此之间都是独立的,这样无法了解到一些局部的信息
就在这样的需求之下,卷积产生了
重要组件
卷积层
首先给一个简单的数值例子

这里的“*”代表的是互相关运算,比如结果的第一格
所谓的“卷积”其实就是对于输入和卷积核进行互相关运算,其中卷积核窗口的形状是\(k_h \times k_w\),假设输入大小是\(n_h \times n_w\),则输出大小就是:
这样的一个卷积层有时被称为特征映射(feature map),因为它可以被视为一个输入映射到下一层的空间维度的转换器
而在这样的卷积层中,我们称能影响前向传播时某一元素计算的所有元素为感受野,比如结果“19”处的感受野是对应的4个元素
其中卷积核是需要训练的对象,通过对于卷积核设置随机初始值,给其不同通道的输入,最终可以分化出尽可能多样的特征,给到下一层网络进行更多的处理
这样的做法可以发现,同样的卷积核扫过图像时,得到的数值是平移不变的,并且由于小感受野的存在,可以得到一些局部信息
与此同时,卷积还需要一些其他参数方便计算
- 填充(padding)
如果直接把原始图像丢给卷积层,可以预见边缘的信息可能会被忽略,解决这个问题的方法就是填充(padding)
填充就是在图像边缘填上默认元素(一般是0),这样在计算的时候就能把边缘的像素信息保存下来
卷积神经网络在实现过程中通常使用奇数卷积核,因为这不仅能让填充更加对称,并且书写上也有便利,对于二位张量来说,奇数卷积核可以代表以某个像素为中心的互相关运算
- 步幅(stride)
高分辨率的图像实际上假如每一步都正常扫过,会得到很多冗余信息,所以就会采用步幅减少计算的次数
到此为止,torch的对应接口就都可以理解了:
conv2d = nn.Conv2d(1, 1, kernel_size = 3, padding = 1, stride = 2);
除此之外,设计神经网络的过程中有时会使用1×1卷积核,这当然无法得到局部信息,因此它的作用主要在调整网络的通道数量和控制模型复杂性上
一般来讲,神经网络可以设置多输入通道多输出通道,而对于多输入通道,同一个卷积核可以对它们进行互相关运算之后再相加
那么假如有一个3通道的输入,用两个卷积核进行计算,输出就会有2个通道,如果同时用的是1×1卷积核,那么就可以在不改变输入形状的前提下调整通道数量了,这是用于对齐通道和调整规模的
汇聚层(池化层)
图像的分辨率很高的时候,卷积哪怕能够识别特征,可能也会对物体的位置很敏感
池化层的产生就有两个目的:降低卷积层对于位置的敏感性,同时降低对空间降采样表示的敏感性
简单来说,池化层可以用来把特征进行压缩,使得特征更容易被辨认
池化层主要分为两种:
- 最大池化
- 平均池化
顾名思义,就不过多就是了,就是对于一个区域取最大值/平均值
池化层也有填充和步幅,接口如下:
pool2d = nn.MaxPool2d(3, padding = 1, stride = 2)
卷积神经网络(LeNet)
先看一下网络的结构

在全连接层中,有线性层和非线性激活,并且卷积层后也有一个非线性层
中间有一个部分,为了将卷积块的输出传递给稠密块,我们需要把输入转化成二维的,因而在全连接层之前有一个Flatten()的步骤
后续其实就是一个多层感知机
写成一个类就是:
# models/lenet.py
import torch.nn as nn
class LeNet(nn.Module):
def __init__(self):
super().__init__()
self.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.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10)
)
def forward(self, x):
return self.net(x)
深度卷积神经网络(AlexNet)
AlexNet和LeNet设计理念很相似,有两个比较显著的差异:
- AlexNet比LeNet要深得多,由8层组成:5个卷积层、2个全连接隐藏层和1个全连接输出层
- AlexNet使用ReLU而非Sigmoid作为激活函数
下面深入了解ALexNet的细节
模型设计

激活函数
相对于Sigmoid,ReLU一方面前向传播的时候运算比较简单,另一方面导数在非负部分总为1,不像Sigmoid函数在接近0或1时容易梯度消失
容量控制和预处理
AlexNet通过暂退法控制全连接层的模型复杂度
暂退法在torch中接口如下:
nn.Dropout(p = 0.5)
底层实现是随机地把一层中的一些结点无效化(置零),因为全连接层中有占比很大的无效结点,但它们又会提高复杂度,这时候把这些结点无效化就可以降低运算复杂度的同时保证信息的完整性
使用块的网络
VGG
VGG首先基于块进行设计

然后把块进行堆叠就可以得到VGG

NiN(Net in Net)
LeNet、AlexNet、VGG都是通过一系列的卷积层与汇聚层来提取空间结构特征,最后用全连接层对这些特征进行处理,但是全连接层可能会完全放弃表征的空间结构
所以为了继续保存空间结构,NiN提出的方案是在每个参数的通道上分别使用多层感知机
NiN块如下:
卷积层 --> 1×1卷积层 --> 1×1卷积层
接着网络构建就是:
重复3次(NiN块 --> 3×3最大汇聚层) --> 全局平均汇聚层
含并行结构的网络(GoogLeNet)
GoogLeNet解决的问题是:多大的卷积核是有利的?
GoogLeNet的解决方法是:我全都要
这个解决方案就是Inception块,对于输入会经过四种卷积层:
1. 1×1卷积层
2. 1×1卷积层 --> 3×3卷积层 填充1
3. 1×1卷积层 --> 5×5卷积层 填充2
4. 3×3最大汇聚层 填充1 --> 1×1填充层
最终这四个卷积层汇集到一个合并层处理
接着GoogLeNet模型就是把Inception块和卷积层、汇聚层的组件结合,最终用全连接层得到结果
批量规范化
训练深度神经网络有个困难,就是在特别短的时间内很难使它们收敛
批量规范化就是一个常用的方案,能加速批量规范化收敛
首先有个明显的问题是,神经网络在运算过程中数据变化是个黑盒,一方面初始的数据处理需要比较慎重,另一方面其中可能会有某一层的可变值远大于另一层,这样可能需要对学习率进行补偿
最后是更深层的网络很复杂,容易过拟合,所以正则化非常重要
从形式上来讲,对于一个输入\(\text x \in B\),来自某个小批量\(B\)
其中需要学习的参数是拉伸参数\(\gamma\)和便宜参数\(\beta\)
然后\(\hat \mu_B\)和\(\hat \sigma_B\)可以通过输入得到
其中\(\epsilon\)是噪声,这是个经验使然的东西,在实践过程中发现噪声似乎让训练更加高效
残差网络(ResNet)
在我的理解中,此前的网络都只有相邻层的信息交互,而这样带来的结果就是计算过程中的“历史信息”无法得到保留
因此残差网络考虑将本来输入输出是\(x \rightarrow f(x)\)的映射变成\(x \rightarrow f(x) - x\),然后把输入的信息\(x\)给到给到再下一层

在ResNet的设计中有两种残差块:图中版本和跨层通路加上1×1卷积层的版本,以调整通道和分辨率
其他的设计就和传统的卷积网络相似了,一开始卷积、规范化、汇聚,最后再全局汇聚并通过全连接层给出结果

浙公网安备 33010602011771号