CNN
用于视觉处理三大任务:图像分类、目标检测、图像分割
上游:提取特征,CNN
下游:分类、目标、分割等,具体的业
一、概述
卷积神经网络(Convolutional Neural Network,CNN)作为深度学习领域的里程碑式成果,彻底改变了计算机视觉技术的发展轨迹。面对传统全连接网络在处理大尺寸图像时的高计算代价和特征保留不足等挑战,CNN以其独特的结构设计提供了优雅而高效的解决方案。
CNN的核心架构与优势
CNN是一种专门针对网格状数据(如图像)优化的深度学习模型。其核心创新在于通过卷积层自动学习和提取图像的空间层次特征,这种仿生设计灵感来源于人类视觉皮层的工作机制。
三阶段高效处理流程:
卷积层 - 通过滑动窗口方式智能捕捉图像的局部特征,实现特征的自适应学习
池化层 - 显著降低计算复杂度的同时增强关键特征的鲁棒性
全连接层 - 整合高层次特征并输出最终预测结果
超越视觉的广泛应用
虽然CNN最初是为计算机视觉任务设计的,但其卓越的特征提取能力已成功扩展到自然语言处理、医学影像分析、自动驾驶等多个前沿领域。这种跨领域的适应性证明了CNN作为通用特征学习框架的强大潜力。
CNN通过其层次化的特征学习机制,不仅大幅提升了图像识别的准确率,更开创了深度学习处理结构化数据的新范式,为人工智能的发展提供了重要基础。
1.1与传统网络的区别
CNN与传统神经网络的核心区别
连接方式
传统网络:全连接,参数冗余
CNN:局部连接+权值共享,参数高效
特征处理
传统网络:输入展平,丢失空间信息
CNN:保持空间结构,自动提取层次特征
参数量级
传统网络:随输入尺寸平方增长
CNN:固定数量卷积核,适应任意尺寸
性能表现
准确率:CNN提升30%+
参数量:CNN减少90%+
训练效率:CNN快5-10倍
1.2全连接网络的局限性
1.表达能力太有限
全连接神经网络的角色只是一个分类器,如果将整个图片直接输入网络,不仅参数量大,也没有利用好图片中像素的空间特性,增加了学习难度,降低了学习效果。
2.参数量巨大
全连接结构计算量非常大,假设我们有1000×1000的输入,如果隐藏层也是1000×1000大小的神经元,由于神经元和图像每一个像素连接,则参数量会达到惊人的1000×1000×1000×1000
1.3卷积的思想
卷:从左往右,从上往下
积:乘积,求和
Convolution,输入信息与卷积核(滤波器,Filter)的乘积。
局部连接可以更好地利用图像中的结构信息,空间距离越相近的像素其相互影响越大。
根据局部特征完成目标的可辨识性。
二、卷积层
2.1卷积核
卷积核是卷积运算过程中必不可少的一个“工具”,在卷积神经网络中,卷积核是非常重要的,它们被用来提取图像中的特征。
卷积核其实是一个小矩阵,在定义时需要考虑以下几方面的内容:
卷积核的个数:卷积核(过滤器)的个数决定了其输出特征矩阵的通道数。
卷积核的值:卷积核的值是初始化好的,后续进行更新。
卷积核的大小:常见的卷积核有1×1、3×3、5×5等,一般都是奇数 × 奇数。
如下图:
2.2卷积的计算
卷积的过程是将卷积核在图像上进行滑动计算,每次滑动到一个新的位置时,卷积核和图像进行点对点的计算,并将其求和得到一个新的值,然后将这个新的值加入到特征图中,最终得到一个新的特征图。
具体的示例:
import torch.nn as nn
import torch.nn.functional as F
import torch
#输入的图像是32*32*3
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
# 输入的图像是32*32*3
self.c1 = nn.Conv2d(
in_channels=3,
out_channels=16,
kernel_size=3,
stride=1,
bias=True,
)
#输入的图像大小是30*30*16,
self.c2 = nn.Conv2d(
in_channels=16,
out_channels=32,
kernel_size=3,
stride=1,
bias=True,
)
#输出的大小为:(1,32,28,28)
#全连接:全局特征提取,
self.c3 = nn.Linear(32*28*28,10)
def forward(self,x):
x = self.c1(x)
x = F.relu(x)
x = self.c2(x)
x = F.relu(x)
x = x.view(-1, 32*28*28)
out = self.c3(x)
return out
if __name__ == '__main__':
net = Net()
#随机生成大小为32*32*3
input = torch.randn(1,3,32,32)
out = net(input)
print(out.shape)
2.3 边缘填充
通过上面的卷积计算,我们发现最终的特征图比原始图像要小,如果想要保持图像大小不变, 可在原图周围添加padding来实现。
更重要的,边缘填充还更好的保护了图像边缘数据的特征。
使用示例如下:
self.c1 = nn.Conv2d(
in_channels = 3,
out_channels = 64,
kernel_size = 3,
#在图像周围填充一圈
padding = 1,
stride = 1,
)
self.b1 = nn.BatchNorm2d(64)
#输入的图像大小是:
self.c2 = nn.Conv2d(
in_channels = 64,
out_channels = 64,
kernel_size = 3,
#在图像周围填充两圈
padding = 2,
stride = 1,
)
2.4 步长Stride
表示卷积核移动的步长。
stride太小:重复计算较多,计算量大,训练效率降低;
stride太大:会造成信息遗漏,无法有效提炼数据背后的特征;
一般默认值为:1
2.5 池化
池化的特点:
通过降低特征图的尺寸,池化层能够减少计算量,从而提升模型的运行效率。
池化操作可以带来特征的平移、旋转等不变性,这有助于提高模型对输入数据的鲁棒性。
池化层通常是非线性操作,例如最大值池化,这样可以增强网络的表达能力,进一步提升模型的性能。
但是池化也有缺点:
池化操作会丢失一些信息,这是它最大的缺点;
API的使用:
def test01():
inputs = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]]).float()
inputs = inputs.unsqueeze(0).unsqueeze(0)
# 1. 最大池化
# 输入形状: (N, C, H, W)
polling = nn.MaxPool2d(kernel_size=2, stride=1, padding=0)
output = polling(inputs)
print(output)
# 2. 平均池化
polling = nn.AvgPool2d(kernel_size=2, stride=1, padding=0)
output = polling(inputs)
print(output)
三、卷积知识扩展:
3.1膨胀卷积:
为扩大感受野,在卷积核的元素之间插入空格“膨胀”内核,形成空洞卷积,并用膨胀率参数
L
表示要扩大内核的范围,即在内核元素之间插入L-1
个空格。当L=1
时,内核元素之间没有插入空格,变为标准卷积。图下是L=2
的空洞卷积。
API的使用:
import torch
import torch.nn as nn
def test1():
#输入图片大小为3*7*7
input = torch.randn(1,3, 7, 7)
c1 = nn.Conv2d(
in_channels = 3,
out_channels = 1,
kernel_size = 3,
stride = 1,
padding = 0,
dilation = 2,
)
#经过膨胀卷积后为:
#输出图片大小为1*3*3 ((7-(d*(F-1)-1)+2p)/s) +1
out = c1(input)
print(out.shape)
if __name__ == '__main__':
test1()
3.2空间可分离卷积:
对3x3的卷积核,我们同样可以拆分成 3x1 和 1x3 的两个卷积核,对其进行卷积,且采用可分离卷积的计算量比标准卷积要少。
API的使用:
import torch.nn as nn
import torch
#空间分离卷积
class SpatialSeperator(nn.Module):
def __init__(self):
super(SpatialSeperator, self).__init__()
self.c1 = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=(3, 1),
stride=1,
)
self.c2 = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=(1, 3),
)
def forward(self, x):
x = self.c1(x)
x = self.c2(x)
return x
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.c1 = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=3)
def forward(self, x):
x = self.c1(x)
return x
if __name__ == '__main__':
torch.manual_seed(0)
net = SpatialSeperator()
net2 = Net()
input_data = torch.randn(1, 1, 32, 32)
output = net(input_data)
output2 = net2(input_data)
print(output.size())
print(output2.size())
输出结果:
torch.Size([1, 1, 30, 30])
torch.Size([1, 1, 30, 30])
3.2深度可分离卷积
深度可分离卷积由两部组成:
(1) 深度卷积(Depthwise Convolution)
操作:每个输入通道单独使用一个卷积核处理,不跨通道计算。
输入:DF×DF×MDF×DF×M
卷积核:MM 个 DK×DK×1DK×DK×1 的滤波器(每个滤波器负责一个输入通道)。
输出:DF×DF×MDF×DF×M(通道数不变)。
计算量:
DK×DK×M×DF×DFDK×DK×M×DF×DF
(2) 逐点卷积(Pointwise Convolution)
操作:使用 1×11×1 卷积跨通道融合特征。
输入:DF×DF×MDF×DF×M
卷积核:NN 个 1×1×M1×1×M 的滤波器。
输出:DF×DF×NDF×DF×N(改变通道数)。
计算量:
1×1×M×N×DF×DF1×1×M×N×DF×DF
API的使用:
import torch
import torch.nn as nn
class DepthwiseSeparableConv(nn.Module):
def __init__(self):
super(DepthwiseSeparableConv, self).__init__()
self.depthwise = nn.Conv2d(in_channels=8, out_channels=8, kernel_size=3, groups=8, bias=False,stride=1, padding=0)
#
self.pointwise = nn.Conv2d(in_channels=8, out_channels=8, kernel_size=1, bias=False,stride=1, padding=0)
def forward(self, x):
x = self.depthwise(x)
out = self.pointwise(x)
return out
class ConvNet(nn.Module):
def __init__(self):
super(ConvNet, self).__init__()
self.conv1 = nn.Conv2d(in_channels=8, out_channels=8, kernel_size=3, stride=1, padding=0, bias=False)
def forward(self, x):
x = self.conv1(x)
return x
if __name__ == '__main__':
net1 = DepthwiseSeparableConv()
net2 = ConvNet()
x = torch.randn((1, 8, 32, 32))
out1 = net1(x)
out2 = net2(x)
print(out1.size(), out2.size())
输出结果为: torch.Size([1, 8, 30, 30]) torch.Size([1, 8, 30, 30])