1.什么是卷积?
以下是数学意义上的卷积
及其数学公式
可能这样难以理解,我们可以举一个例子
假设一个人每天都在不停的吃饭,一天内吃饭的放量与时间是如下关系
横坐标代表进食的时间,纵坐标代表进食的多少
胃里面食物的消化情况如图所示
卷积所要求的就是在某一时刻t 肚子里剩余的饭量
其实我们所求的就是 如图计算对应点的乘积求和
现在看有一点乱,但是我们将g(t)反转过来再一一对应 ,这就是卷积的卷
可以看出,卷积其实就是一个相乘乘积再相加的操作
2.卷积核
2.1什么是卷积核
卷积核就是图像处理时,给定输入图像,输入图像中一个小区域中像素加权平均后成为输出图像中的每个对应像素,其中权值由一个函数定义,这个函数称为卷积核。又称滤波器。
具体计算如图 ,就是一个固定大小的核,以一定的步长去对原图像位置做相乘再求和的操作
在数学上的计算公式(实际上)
根据公式计算实际上是计算卷积核经过中心反转后的乘积,但在实际上可以用对应位置相乘的方法去计算,称为互相关运算。
2.2为什么需要卷积核
2.2.1为什么我们要使用卷积核抽取特征而不采用全连接去抽取特征?
使用全连接是一件很贵的事情,假设我们有一张3*256*256的图片想要得到一个输出时,全连接就需要196608个参数,而卷积只需要768个参数,放在更深的网络差距只会拉的更大
2.2.2卷积核为什么能抽去特征?
卷积核就像我们眼睛,看到一只猫不会先去关注整体,而是先去关注局部的特征,就像我们看一个动物是兔子还是猫,我们会先去关心他们耳朵的特征,尾巴的特征等,最后将所有特征结合起来才能判断出到底是猫还是狗
以下是以边缘检测卷积核为例
代码
import torch
from torch import nn
import torchvision.io as io
from matplotlib import pyplot as plt
net = nn.Conv2d(3, 1, kernel_size=(3, 3), stride=1)
parameter1 = torch.tensor([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]]).float()
net.weight.data.copy_(parameter1)
print(net.weight.data)
# Load and preprocess the image
image_path = "C:\\Users\luca\Desktop\屏幕截图 2024-07-24 165517.jpg"
image = io.read_image(image_path).float()
image /= 255.0
X = image.unsqueeze(0)
X = net(X)
fig,axes=plt.subplots(1,2)
axes[0].imshow(X.squeeze(0).permute(1,2,0).detach().numpy(), cmap="gray")
axes[0].axis("off")
axes[1].imshow(image.permute(1,2,0))
axes[1].axis("off")
plt.show()
2.2.3卷积核的2个特性
1.平移不变性
定义:指卷积操作能够在输入数据的不同位置上识别出相同的特征
重要性:在图像处理任务中,这个特性允许卷积神经网络(CNN)识别图像中的相同对象或模式,无论它们在图像中的位置如何。这对于识别和检测任务非常重要,因为对象的位置可能会有所不同。
2.局部性
局部性是指卷积操作只关注输入数据中的局部区域
重要性:局部性使得卷积网络能够有效地从局部特征中提取信息。通过这种方式,卷积层能够捕捉局部结构,如边缘、纹理和角点,这些局部特征对于构建更复杂的全局特征是基础。局部连接还减少了模型的参数数量,因为卷积核在整个输入数据上共享相同的权重。
2.3卷积的多输入多输出
对于多输入多输出的卷积层来说,输出通道数就是卷积层卷积核的个数,输入通道就是一个卷积核里面所包含的通道数,如图
注意每个卷积核相当于融合了输入的特征,只不过关注重点不一样
2.4 卷积核的步幅与填充(超参数)
2.4.1填充
引入:对于一个的卷积核而言,一个图片经过卷积操作总是会使输出变小的,例3*3的图片经过2*2的卷积核后会变为2*2的特征图
如果不想使输出大小改变,那我们需要怎么做呢
填充(Padding) 是在输入图像的边界周围添加额外像素(0/1)的过程,通过填充后的图片再做卷积,输出的大小会相对于原输出增大
示例:
import torch
from torch import nn
img=torch.rand(1,1,5,5)
conv_unuse_padding=nn.Conv2d(1,1,kernel_size=2)
conv_use_padding=nn.Conv2d(1,1,kernel_size=3,padding=1)
print(f"不使用填充:{conv_unuse_padding(img).shape}")
print(f"使用填充:{conv_use_padding(img).shape}")
结果
不使用填充:torch.Size([1, 1, 4, 4])
使用填充:torch.Size([1, 1, 5, 5])
可以看出使用更大卷积核,但因为使用填充得到了更大的输出
2.4.2步幅
引入:假如我们有一张224*224的图片,在使用5*5卷积核的情况下,我们需要44层才能将输入降低到4*4,需要大量的计算才能得到较小的输出,那有没有简便的方法去计算呢
步幅:指行/列滑动的步长,步幅可以有效减小输出的大小
例子:
一个特征图尺寸为5 ∗ 5 的输入, 使用3 ∗ 3 的卷积核,步幅=2,填充=0,输出的尺寸=(5-3)/2 + 1 = 2。
验证
import torch
from torch import nn
conv_unuse=nn.Conv2d(1,1,kernel_size=3)
conv_use=nn.Conv2d(1,1,kernel_size=3,stride=2)
img=torch.rand(1,1,5,5)
print(f'使用步长为1{conv_unuse(img).shape[2:4]}')
print(f'使用步长为2{conv_use(img).shape[2:4]}')
使用步长为1torch.Size([3, 3])
使用步长为2torch.Size([2, 2])
由此看出,步长减小了输出的大小
2.5池化层(汇聚层)
池化层是卷积神经网络中常用的一个层,用于降低特征图的空间尺寸,从而减少计算量,控制过拟合,并提取特征的高层次抽象
其本质就是对输入的片区域做平均或取最大处理,池化的超参数与卷积核类似常用的有步长,填充,核的大小
有以下两种池化:
avgpooling就是对核区域的元素求平均
maxpooling就是取核区域的最大值
通常使用最大池化较多
池化的作用
-
降低特征图的尺寸:
- 通过池化操作,可以减少特征图的宽度和高度,这样做可以减少计算量和内存使用。
-
减少计算复杂度:
- 池化层通常能显著减少后续层需要处理的数据量,从而降低计算负担。
-
增加特征的鲁棒性:
- 池化操作可以提高网络对小变换(如平移、缩放)的鲁棒性,因为它只关心局部区域的最显著特征。
-
防止过拟合:
- 通过减少特征图的尺寸,池化层有助于降低模型的复杂度,从而减轻过拟合。
2.6 输出大小计算公式 1x1卷积核
2.6.1输出大小计算公式
一张Nh*Nw的图片经过给定卷积核大小k,步长s,填充p的处理后
2.6.2 1x1卷积核
1x1卷积核是一种特殊的全连接,图片的每个像素可以看作每个样本,通道数看作特征。
例:
主要作用:调整通道数,融合特征,增加非线性变换
3.经典的卷积神经网络 lenet
提出:LeNet 是由 Yann LeCun 等人在 1990 年代提出的一个经典卷积神经网络(CNN)架构,它被广泛用于图像分类任务。LeNet 的设计为深度学习领域奠定了基础,并且它的许多概念在现代 CNN 架构中仍然被使用。
架构
代码
我在原有lenet上做了改进
import torch
from torch import nn
from torch.nn import functional as F
import time
net = nn.Sequential(nn.Conv2d(3, 6, kernel_size=5, padding=2),nn.BatchNorm2d(6),
nn.ReLU(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5),nn.BatchNorm2d(16),
nn.ReLU(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
nn.Linear(54*54*16, 120), nn.ReLU(), nn.Linear(120, 84),
nn.ReLU(), nn.Linear(84, 2))
#####################################################################################炼丹
from torchvision import transforms
from torch.utils.data import DataLoader,Dataset
import os
from PIL import Image
def acc(y_hat,y):
y_hat=y_hat.argmax(dim=1)
tf=y_hat==y
return tf.sum()/y_hat.numel()
class catanddong(Dataset):
def __init__(self,rootpath,lable):
self.rootpath=rootpath
self.lable=lable
self.path=os.path.join(rootpath,lable)
self.img_list=os.listdir(self.path)
self.transfrom=transforms.Compose([transforms.Resize((224,224)),transforms.ToTensor()])
def __getitem__(self, item):
with Image.open(os.path.join(self.path,self.img_list[item])) as img:
img=self.transfrom(img)
if self.lable =="Cat":
tai=0
else:
tai=1
if img.shape[0]!=3:
print(self.img_list[item],tai)
os.remove(os.path.join(self.path,self.img_list[item]))
return img,tai
def __len__(self):
return len(self.img_list)
root_path="/data2/nybdr/jjc/PetImages"
lable_cat="Cat"
train_cat=catanddong(root_path,lable_cat)
lable_dong="Dog"
train_dong=catanddong(root_path,lable_dong)
train_dataset=train_dong+train_cat
train_iter=DataLoader(shuffle=True,dataset=train_dataset,num_workers=0,batch_size=128)
def train(net,lr,train_iter,test_iter,epoch,device="cuda:0"):
print(f"训练在{device}")
def init(m):
if type(m)==nn.Linear or type(m)==nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init)
net.to(device)
loss=nn.CrossEntropyLoss()
# updater=torch.optim.Adam(net.parameters(),lr=lr,weight_decay=1e-4)
updater = torch.optim.SGD(net.parameters(), lr=0.003, momentum=0.88)
net=nn.DataParallel(net,device_ids=["cuda:0","cuda:1","cuda:2","cuda:3"])
alltime=time.time()
for i in range(epoch):
net.train()
l_sum=0
ji=0
zql=0
for X,y in train_iter:
ji+=1
X,y=X.to(device),y.to(device)
y_hat=net(X)
zql+=acc(y_hat,y)
l=loss(y_hat,y)
updater.zero_grad()
l.backward()
l_sum+=l
updater.step()
print(zql/ji)
print(f"第{i+1}次训练,平均损失为{l_sum/ji},正确率为{zql/ji:.2f}")
print(time.time()-alltime)
lr=0.03
train(net,lr,train_iter,train_iter,10)
torch.save(net.state_dict(),"/data2/nybdr/jjc/model_data/lenet")
print("存储成功")
测试结果
结语
卷积神经网络(CNN)作为深度学习的一个重要分支,为图像处理和计算机视觉领域提供了强大的工具。在本文中,我们详细探讨了卷积的基本概念、卷积核的作用及其特性,并对卷积操作的超参数如步幅和填充进行了说明。通过这些内容的讲解,我们可以更好地理解卷积神经网络如何高效地从图像中提取特征,并在实际应用中发挥作用。