6.1 卷积神经网络简介
# 图6-1是用一个比较简单的卷积神经网络对手写输入数据进行分类
# 由卷积层(conv2d), 池化层(MaxPool2d)和全连接层(Linear)叠加而成
import torch.nn as nn
import torch.nn.functional as F
# 这个地方的cuda:0 实际上并不是0号GPU,他取决于CUDA_VISIBLE_DEVICES
# 然后逻辑GPU和物理GPU有一个对应关系
# 如果CUDA_VISIBLE_DEVICES为2,1,3
# 那么CUDA:0就是2号GPU, CUDA:1 就是1号GPU CUDA:3 就是3号GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
class CNNNet(nn.Module):
def __init__(self):
super(CNNNet, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=16,kernel_size=5, stride=1)
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(in_channels=16, out_channels=36, kernel_size=3, stride=1)
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
# nn.Linear是一个类,使用时进行类的实例化
# 实例化的时候,nn.Linear需要输入两个参数,
# in_features为上一层神经元的个数,out_features为这一层的神经元个数
self.fc1 = nn.Linear(1296,128)
self.fc2 = nn.Linear(128,10)
def forward(self, x):
x = self.pool1(F.relu(self.conv1(x)))
x = self.pool2(F.relu(self.conv2(x)))
print(x.shape)
# 其维度进行随意变化的话,可以使用view()方法,
# 比如我想将其维度变化成:1*6,则使用方法view(1,6)即可。
x = x.view(-1, 36*6*6)
x = F.relu(self.fc2(F.relu(self.fc1(x))))
return x
net = CNNNet()
net = net.to(device)
6.2 卷积层
6.2.1 卷积核
卷积核是整个卷积过程的核心,比较简单的卷积核或过滤器有Horizontalfilter, Verticalfilter, Sobel Filter等,这些过滤器能够检测图像的水平边缘,垂直边缘,增强图像中心区域权重等
6.6.2步幅
卷积核的值在移动的过程中都是共享的,卷积神经网络采用参数共享的方法大大降低了参数的数量。
- 假设输入数据大小为n, 过滤器为f*f, 步幅为s,填充为0
则输出大小为(n-f)/s + 1
n-f即得到余下有多少个列,除以s即得到还可以移动几次,如果不能整除,则向下取整,然后+1是指加上第一次卷积 - 假设输入数据大小为n, 过滤器为f*f, 步幅为s,填充为p
则输出大小为(n+2p-f)/s + 1
6.2.3 填充
要进行padding的原因:
- 图像缩小
- 和中间图像信息相比,边缘图像信息丢失。padding可以防止图像边缘部分的特征信息丢失
根据是否拓展Padding又分为Same, Valid.
采用Same方式时,对图片扩展并补0,(即保证输入和输出大小相同)
采用Valid方式时,不对图片进行扩展(即no padding)
在实际训练过程中一般选择Same方式,使用Same不会丢失信息。设补0的圈数为p,输入数据大小为n, 过滤器大小为f, 步幅大小为s,则有:
因为要保证输入n, 输出也为n,而经过填充p后的实际输出为n+2p-f+1
n = n+2p-f+1 所以p = (f-1)/2
f 一般为奇数,1.因为如果f是偶数,则填充不对称2.f为奇数,便于指出过滤器的中心位置(虽然用偶数的过滤器也许可以取得较好的结果)
6.2.4 多通道上的卷积
彩色图片就3通道,RGB,3通道图片的卷积运算与单通道图片的卷积运算基本一致,对于3通道的RGB图片,其对应的滤波器算子同样也是3通道的。
例如上图是663,表示高度height, 宽度width,通道数 channel。过程是将每个单通道(RGB)与对应的filter进行卷积运算求和,然后再将3通道的和相加,得到输出图片的一个像素值。
不同滤波器组卷积得到不同的输出,个数由滤波器组决定,图6-11中只有1个滤波器组,所以结果只有一个;如果有2个333的filter,则会得到442的输出
6.2.5 激活函数
卷积神经网络与标准的神经网络类似,为保证其非线性,也需要使用激活函数,即在卷积运算后,把输出值另加偏移量,输入到激活函数,然后作为下一层的输入
常见的激活函数为:nn.Sigmoid, nn.ReLU, nn.LeakyReLU, nn.Tanh
6.2.6 卷积函数
torch.nn.Conv2d
dilation:其默认值为1
这种情况实际为Pytorch中,dilation = 2的情况。
特别注意:在Pytorch中,dilation = 1等同于没有dilation的标准卷积。
6.2.7 转置卷积
转置卷积(Transposed Convolution)也叫反卷积(Deconvolution)或部分跨越卷积(Fractionally-Strided Convolution)
通过卷积的正向传播的图像一般越来越小,记为下采样(Downsampled).卷积的方向传播实际上就是一种转置卷积,它是上采样(up-Sampling)
转置卷积详解1
总结:
转置卷积的作用:上采样
这里不能使用
I
=
O
C
−
1
I = OC^{-1}
I=OC−1,因为不能保证C可逆,可逆只对方阵而言,但并不能保证C是方阵。所以放宽条件:不要求还原卷积前的数据,只要求还原卷积前的大小即可
torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros')
6.3 池化层
池化(Pooling)又称为下采样,通过卷积层获得图像特征后,理论上可以直接使用这些特征训练分类器。但这样计算量太大,且容易产生过拟合现象。
故要对卷积层进行池化处理,有3中方法:
1。最大池化Max Pooling:选择Pooling窗口中的最大值作为采样值
2。均值池化Mean Pooling:将Pooling窗口中的所有值相加取平均,以平均值作为采样值
3。全局最大(或均值)池化:与平常最大或最小池化相对而言,全局池化是对整个特征图的池化而不是在移动窗口范围内的池化
池化的作用:
在CNN中可用来减小尺寸,提高运算速度,减小噪声影响,让各特征更具有健壮性。
池化层比卷积层更简单,它没有卷积运算,只是在滤波器算子滑动区域内取最大值或平均值。
池化的作用则体现在降采样:保留显著特征,降低特征维度,增大感受野。
深度网络越往后面越能捕捉到物体的语义信息,这种语义信息是建立在较大的感受野基础上。
6.3.1 局部池化
在移动窗口内的池化被称为局部池化
nn.MaxPool2d
nn.AvgPool2d -----更常用
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
6.3.2 全局池化
全局池化也分为最大或平均池化。
全局平均池化(Global Average Pooling, GAP),不以窗口的形式取均值,而是以特征图为单位进行均质化,即一个特征图输出一个值
使用全局平均池化代替CNN中传统的全连接层。在使用卷积层的识别任务中,全局平均池化能够为每一个特定的类别生成一个特征图(Feature Map)
GAP的优势在于:
- 各个类别与Feature Map之间的联系更加直观(相比于全连接层的黑箱来说),Feature Map被转化为分类概率也更加容易,因为在GAP中不用调参,避免了过拟合问题。
- GAP汇总了空间信息,因此对输入的空间转换鲁棒性更强。所以目前卷积网络中最后几个全连接层,大都用GAP替换
- 全局最大池化层(GlobalMaxPooling2D)在pytorch中没有对应名称的池化层,但可以使用pytorch中的自适应池化层(AdaptiveMaxPool2d(1)或nn.AdaptiveAvgPool2d(1))来实现
6.4 现代经典网络
6.4.1 LeNet-5 模型
(1)模型架构
输出层–卷积层–池化层–卷积层–池化层–全连接层–全连接层–输出
(2)模型特点
- 每个卷积层包含3部分:卷积,池化,和非线性激活函数
- 使用卷积提取空间特征
- 采用降采样(Subsample)的平均池化层(Average Pooling)
- 使用双曲正切(Tanh)的激活函数
- 最后使用MLP作为分类器
Tanh比sigmoid函数晚诞生,sigmoid函数的一个缺点就是输出不以0为中心,使得收敛变慢。
而tanh则解决了这个问题,它是一个奇函数
tanh
(
x
)
=
sinh
(
x
)
cosh
(
x
)
=
e
x
−
e
−
x
e
x
+
e
−
x
\tanh (x)=\frac{\sinh (x)}{\cosh (x)}=\frac{e^{x}-e^{-x}}{e^{x}+e^{-x}}
tanh(x)=cosh(x)sinh(x)=ex+e−xex−e−x
尽管tanh函数和sigmoid函数存在梯度消失的问题,但是与之类似,如果函数的梯度过大又会导致梯度爆炸的问题,显然tanh和sigmoid的导函数非常有界,根据导数公式,很容易得出t a n h ′ ( x ) ∈ [ 0 , 1 ] 所以完全不用担心因为使用激活函数而产生梯度爆炸的问题。
- LeNet-5论文阅读,博客编写
- AlexNet论文阅读,博客编写
- VGG论文阅读,博客编写
- GoogleNet论文阅读,博客编写
- ResNet论文阅读,博客编写
- [ ]