视觉处理基础
文章目录
1、卷积神经网络简介
-
卷积神经网路由一个或多个卷积层和顶端的全连通层(对应经典的神经网路)组成,同时也包括关联权重和池化层(pooling layer)等。
下面是一个卷积神经网络的例子:
上图为卷积神经网络的一般结构,其中包括卷积神经网络的常用层,如卷积层、池化层、全连接层和输出层;有些还包括其他层,如正则化层、高级层等。接下来我们就各层的结构、原理等进行详细说明。
import torch.nn as nn import torch.nn.functional as F 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) 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) 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)
2、卷积层
-
卷积层是卷积神经网络的核心层,而卷积(Convolution)又是卷积层的核心。卷积我们直观的理解,就是两个函数的一种运算,这种运算称为卷积运算。
-
输入和卷积核都是张量,卷积运算就是用卷积分别乘以输入张量中的每个元素,然后输出一个代表每个输入信息的张量。其中卷积核(kernel)又称为权重过滤器或简称过滤器(filter)。
2.1、卷积核
-
卷积核,从这个名字可以看出它的重要性,它是整个卷积过程的核心。
比较简单的卷积核或过滤器有 Horizontalfilter、Verticalfilter、Sobel filter 等。这些过滤器能够检测图像的水平边缘、垂直边缘、增强图片中心区域权重等。
-
垂直边缘检测
这个过滤器是 3x3 矩阵(注,过滤器一般是奇数阶矩阵),其特点是有值的是第 1 列和第 3 列,第 2 列为 0。经过这个过滤器作用后,就把原数据垂直边缘检测出来了。
-
水平边缘检测
这个过滤器也是 3x3 矩阵,其特点是有值的是第 1 行和第 3 行,第 2 行为 0。经过这个过滤器作用后,就把原数据水平边缘检测出来了。
过滤器对图像水平边缘检测、垂直边缘检测的效果图:
-
-
在深度学习中,过滤器的作用不仅在于检测垂直边缘、水平边缘等,还需要检测其他边缘特征
过滤器如何确定呢?过滤器类似于标准神经网络中的权重矩阵 W W W, W W W 需要通过梯度下降算法反复迭代求得。同样,在深度学习学习中,过滤器也是需要通过模型训练来得到。
卷积神经网络主要目的就是计算出这些 filter 的数值。确定得到了这些 filter 后,卷积神经网络的浅层网络也就实现了对图片所有边缘特征的检测。
2.2、步幅
- 小窗口(实际上就是卷积核或过滤器)在左边窗口中每次移动的格数(无论是自左向右移动,或自上向下移动)称为步幅(strides),在图像中就是跳过的像素个数。
- 参数 strides 是卷积神经网络中的一个重要参数,在用 PyTorch 具体实现时,strides 参数格式为单个整数或两个整数的元组(分别表示在 height 和 width 维度上的值)。
2.3、填充
-
当输入图片与卷积核不匹配时或卷积核超过图片边界时,可以采用边界填充(padding)的方法。即把图片尺寸进行扩展,扩展区域补零。
-
根据是否扩展 padding 又分为 Same、Valid。
采用 Same 方式时,对图片扩展并补 0;采用 Valid 方式时,对图片不扩展。
如何选择呢?在实际训练过程中,一般选择 Same,使用 Same 不会丢失信息。
设补 0 的圈数为 p,输入数据大小为 n,过滤器大小为 f,步幅大小为 s,则有:
p = f − 1 2 p=\frac{f-1}{2} p=2f−1
卷积后的大小为:
n + 2 p − f s + 1 \frac{n+2p-f}{s}+1 sn+2p−f+1
2.4、多通道上的卷积
-
3 通道图片的卷积运算与单通道图片的卷积运算基本一致,对于 3 通道的 RGB 图片,其对应的滤波器算子同样也是 3通道的。
例如一个图片是 6 x 6 x 3,分别表示图片的高度(height)、宽度(weight)和通道(channel)。过程是将每个单通道(R,G,B)与对应的 filter 进行卷积运算求和,然后再将 3 通道的和相加,得到输出图片的一个像素值。
-
为了实现更多边缘检测,可以增加更多的滤波器组。
下图就是两组过滤器 Filter W0 和 Filter W1。 7 ∗ 7 ∗ 3 7*7*3 7∗7∗3 输入,经过两个 3 ∗ 3 ∗ 3 3*3*3 3∗3∗3 的卷积(步幅为 2),得到了 3 ∗ 3 ∗ 2 3*3*2 3∗3∗2 的输出。Zero padding 对于图像边缘部分的特征提取是很有帮助的,可以防止信息丢失。最后,不同滤波器组卷积得到不同的输出,个数由滤波器组决定。
2.5、激活函数
-
卷积神经网络与标准的神经网络类似,为保证其非线性,也需要使用激活函数,即在卷积运算后,把输出值另加偏移量,输入到激活函数,然后作为下一层的输入:
-
常用的激活函数有:tf.sigmoid、tf.nn.relu 、tf.tanh、 tf.nn.dropout 等。
2.6、卷积函数
-
卷积函数是构建神经网络的重要支架,通常 Pytorch 的卷积运算是通过 nn.Conv2d 来完成。
下面先介绍 nn.Conv2d 的参数,及如何计算输出的形状(shape)。
-
nn.Conv2d 函数
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
- in_channels(int):输入信号的通道
- out_channels(int):卷积产生的通道
- kerner_size(int or tuple):卷积核的尺寸
- stride(int or tuple, optional):卷积步长
- padding(int or tuple, optional):输入的每一条边补充 0 的层数
- dilation(int or tuple, optional):卷积核元素之间的间距
- groups(int, optional):控制输入和输出之间的连接: group=1,输出是所有的输入的卷积;group=2,此时相当于有并排的两个卷积层,每个卷积层计算输入通道的一半,并且产生的输出是输出通道的一半,随后将这两个输出连接起来
- bias(bool, optional):如果 bias=True,添加偏置。其中参数 kernel_size,stride,padding,dilation 也可以是一个 int 的数据,此时卷积 height 和 width 值相同;也可以是一个 tuple 数组,tuple 的第一维度表示height 的数值,tuple 的第二维度表示 width 的数值
-
输出形状
-
3、池化层
-
池化(Pooling)又称为下采样。通过卷积层获得图像的特征后,理论上可以直接使用这些特征训练分类器(如 Softmax)。但是,这样做将面临巨大的计算量的挑战,而且容易产生过拟合现象。
为了进一步降低网络训练参数及模型的过拟合程度,就要对卷积层进行池化处理。常用的池化方式通常有 3 种:
- 最大池化(Max Pooling):选择 Pooling 窗口中的最大值作为采样值;
- 均值池化(Mean Pooling):将 Pooling 窗口中的所有值相加取平均,以平均值作为采样值;
- 全局最大(或均值)池化:与平常最大或最小池化相对而言,全局池化是对整个特征图的池化而不是在移动窗口范围内的池化。
-
池化层在 CNN 中可以用来减小尺寸,提高运算速度及减小噪声影响,让各特征更具有健壮性。
池化层比卷积层更简单,它没有卷积运算,只是在滤波器算子滑动区域内取最大值或平均值。
而池化的作用则体现在降采样:保留显著特征、降低特征维度,增大感受野。
3.1、局部池化
-
通常使用的最大或平均池化,是在特征图(Feature Map)上以窗口的形式进行滑动(类似卷积的窗口滑动),操作为取窗口内的平均值作为结果值。经过操作后,特征图降采样,减少了过拟合现象。
其中在移动窗口内的池化被称为局部池化。
-
在 PyTorch 中,最大池化常使用 nn.MaxPool2d,平均池化使用 nn.AvgPool2d。
class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
- kernel_size:池化窗口的大小,取一个 4 维向量,一般是 [height, width],如果两者相等,可以是一个数字;
- stride:窗口在每一个维度上滑动的步长,一般也是 [stride_h, stride_w],如果两者相等,可以是一个数字;
- padding:和卷积类似;
- dilation:卷积对输入数据的空间间隔;
- return_indices:是否返回最大值对应的下标;
- ceil_mode:使用一些方块代替层结构。
假设输入 input 的形状为: ( N , C , H i n , W i n ) (N,C,H_{in},W_{in}) (N,C,Hin,Win)
输出 output 的形状为: ( N , C , H o u t , W o u t ) (N,C,H_{out},W_{out}) (N,C,Hout,Wout),则输出大小与输入大小的计算公式如下:
H o u t = [ H i n + 2 × p a d d i n g [ 0 ] − d i l a t i o n [ 0 ] × ( k e r n e l _ s i z e [ 0 ] − 1 ) − 1 s t r i d e [ 0 ] + 1 ] H_{out}=[\frac{H_{in}+2 \times padding[0] - dilation[0] \times (kernel\_size[0] - 1)-1}{stride[0]}+1] Hout=[stride[0]Hin+2×padding[0]−dilation[0]×(kernel_size[0]−1)−1+1]W o u t = [ W i n + 2 × p a d d i n g [ 1 ] − d i l a t i o n [ 1 ] × ( k e r n e l _ s i z e [ 1 ] − 1 ) − 1 s t r i d e [ 1 ] + 1 ] W_{out}=[\frac{W_{in}+2 \times padding[1] - dilation[1] \times (kernel\_size[1] - 1)-1}{stride[1]}+1] Wout=[stride[1]Win+2×padding[1]−dilation[1]×(kernel_size[1]−1)−1+1]
如果不能整除,则取整数。
3.2、全局池化
-
与局部池化相对的就是全局池化,全局池化也分为最大或平均池化。
所谓的全局池化就是针对常用的平均池化而言,平均池化会有它的 filter_size,而全局平均池化(Global Average Pooling,GAP)就没有 size,它针对的是整张 Feature Map,即一个特征图输出一个值。
-
使用全局平均池化代替 CNN 中传统的全连接层。在使用卷积层的识别任务中,全局平均池化能够为每一个特定的类别生成一个特征图。
GAP 的优势在于:各个类别与 Feature Map 之间的联系更加直观(相比于全连接层的黑箱来说),Feature Map 被转换为分类概率也更加容易,因为在 GAP 中没有参数需要调,所以避免了过拟合现象。GAP 汇总了空间信息,因此对输入的空间转换鲁棒性更强。
-
Keras 中可以使用:全局最大池化层(GlobalMaxPooling2D);
Pytorch 中可以使用:自适应池化层(AdaptiveMaxPool2d(1)) 或 nn.AdaptiveAvgPooling2d(1)
4、现代经典网络
4.1、LeNet-5
-
模型架构
LeNet-5 模型结构为**输入层-卷积层-池化层-卷积层-池化层-全连接层-输出**,为串联模型。
-
模型特点
- 每个卷积层包含 3 个部分:卷积、池化和非线性激活函数;
- 使用卷积提取空间特征;
- 采用降采样(Subsample)的平均池化层(Average Pooling);
- 使用双曲正切(Tanh)的激活函数;
- 最后使用 MLP 作为分类器。
import torch.nn as nn
class LeNet5(nn.Module):
def __init__(self):
super(LeNet5, self).__init__()
# 1 input image channel, 6 output channels, 5x5 square convolution kernel
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 这里论文上写的是conv,官方教程用了线性层
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# Max pooling over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is a square you can only specify a single number
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features
net = LeNet5()
print(net)
LeNet5(
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
4.2、AlexNet
import torchvision
model = torchvision.models.alexnet(pretrained=False) #我们不下载预训练权重
print(model)
AlexNet(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
(1): ReLU(inplace=True)
(2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(4): ReLU(inplace=True)
(5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(7): ReLU(inplace=True)
(8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(9): ReLU(inplace=True)
(10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace=True)
(12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(avgpool): AdaptiveAvgPool2d(output_size=(6, 6))
(classifier): Sequential(
(0): Dropout(p=0.5, inplace=False)
(1): Linear(in_features=9216, out_features=4096, bias=True)
(2): ReLU(inplace=True)
(3): Dropout(p=0.5, inplace=False)
(4): Linear(in_features=4096, out_features=4096, bias=True)
(5): ReLU(inplace=True)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)