深度学习——卷积神经网络(CNN,conv,maxpooling)

1,587 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。这是一篇介绍卷积神经网络入门到搭建的文章,可以学习到卷积网络其中的Conv卷积层,pooling池化层重要结构的原理和代码实现。文章较长感兴趣请耐心观看哦!谢谢🙏

截屏2022-04-11 16.11.57.png 这是一个全连接的线性结构的网络模型,像这样的模型网络全是由线性层串行连接起来,叫做全连接层,上一层的输出参与到下一层的输入里。可以看出这样的网络只适合处理一下线性的问题,不适合处理图像问题

如果用全连接神经网络处理大尺寸图像具有三个明显的缺点

1、首先将图像展开为向量会丢失空间信息;

2、其次参数过多效率低下,训练困难;

3、同时大量的参数也很快会导致网络过拟合

使用卷积神经网络可以很好的解决这三个问题

CNN卷积神经

与常规神经网络不同,卷积神经网络的各层中的神经元是3维排列的:宽度、高度和深度(维度)。其中的宽度和高度是很好理解的,因为本身卷积就是一个二维模板,但是在卷积神经网络中的深度指的是激活数据体的第三个维度,而不是整个网络的深度,整个网络的深度指的是网络的层数。举个例子来理解什么是宽度,高度和深度,假如做一个猫狗图像识别,使用这个小猫图像是作为卷积神经网络的输入

图片22.png 一张栅格化的RGB图片是有红,绿,蓝三个底色组成的

未命名图片.png 需要保存红,绿,蓝三个颜色通道,所以一张RGB图片由三个序列组成,如果图片尺寸为64 * 64,那么就会有3个规模为64*64的矩阵,该输入数据体的维度就会是64 * 64 * 3(宽度,高度和深度(维度))。我们将看到,层中的神经元将只与前一层中的一小块区域连接,而不是采取全连接方式。对于用来分类猫狗数据集中的图像的卷积网络,其最后的输出层的维度是1 * 1 * 2,因为在卷积神经网络结构的最后部分将会把全尺寸的图像压缩为包含分类评分的一个向量,向量是在深度方向排列的。

构建卷积神经网络的各种层

卷积神经网络主要由这几种类层构成:输入层、卷积层、激活层、池化层和最后的全连接层。通过这些层叠加起来,就可以构建一个完整的卷神经网络,所以卷积层经过卷积操作也是要经过激活函数的。在我看来卷积层和池化层操作就是一种特征提取器,浓缩特征,放大有用特征,减少数据杂质,减少数据的数据量,减少运算的需求。

卷积层CONV

卷积层的作用:通过卷积层后仍然保留图像的空间特征,空间结构,但是数据量会减少,通过卷积层数据仍然会得到三维的张量,但是深度(维度)、高和宽会发生改变。

构建卷积层需要注意的是:

第一点要明确输入的数据的维度是多少,输出的矩阵维度是多少,卷积核的维度要和输入矩阵的维度一致

第二点想要卷积神经网络正常跑起来,要注意每层之间张量变化的选择

卷积核

卷积核一般是一个N*N的方阵组成的,它的作用是和矩阵做数乘,得到一个新的矩阵

卷积实现过程

输入的数据是5 * 5 * 1,卷积和是3*3的数据,stride(移动的步长为1)=1卷积核在从左上角开始做数乘,依次往右移动做数乘,达到边界自动往下降一层,直至最后一个数,下面是过程演示:

第一次移动:

1.png 第二次移动:

2.png 第三次移动:

3.png 第四次移动:

截屏2022-04-12 09.23.59.png 。。。

第九次移动后的到结果:

截屏2022-04-12 09.24.25.png 移动的次数要看卷积核在图像中的占比 上面演示的只是个一维的矩阵,如果是多维(多通道)的矩阵,那么每一个维度的矩阵都要和一个卷积核做数乘,然后把每个维度的卷积结果矩阵按位相加

多维的计算演示:

图片23.png 按位相加 因为要保持它的空间结构

截屏2022-04-12 09.54.57.png

图片24.png 输入矩阵的维度为n,卷积核的维度也是n,但是最后输出的矩阵是一维的。如果想输出m维的矩阵,那就要和m的卷积核和做运算,最后输出的结果按顺序排列起来就得到m维的矩阵

图片222.png 总结:卷积层的权重是四个维度,也就是说构建卷积层需要四个维度的参数,第一输入的矩阵维度,第二输出矩阵的维度,第三卷积核的宽,第四卷积核的高

图片25.png 代码实现:

import torch
in_channels,out_channels = 5,10 #定义输出输入的数据的通道数
width,height = 100,100\
kernel_size = 3  #定义卷积核的大小
bath_size = 1
input = torch.randn(bath_size,in_channels,width,height)#用torch随机定义一个输入矩阵
conv_layer = torch.nn.Conv2d(in_channels,out_channels,kernel_size =kernel_size)
#构建卷积层需要四个数据(输入的通道数,输出的通道数,卷积核的宽,卷积核的高)# 构建卷积层的输入的通道数一定要和需要卷积的矩阵的输入通道数一致
output = conv_layer(input)

print(input.shape)
print(output.shape)
print(conv_layer.weight.shape)

输出:

结果.png 可以看出输入的矩阵高和宽是100 * 100,做卷积后输出的高和宽变成 98 * 98。
如果想要输出的数据的高和宽保持和原始高和宽怎么做?

padding

conv内提供的一个参数,加上这个参数会临时改变输入的矩阵的高和宽,前面的得知一个5 * 5的矩阵和一个3*3的卷积核做数乘会得到一个3 * 3的矩阵,如果padding=1,原本5 * 5的矩阵变成6 * 6,再和3 * 3的卷积核做数乘得到的矩阵会是5 * 5的。

截屏2022-04-12 11.03.53.png 向外一层的填充默认是0

import torch

input = [3,4,6,5,7,2,4,6,8,2,1,6,7,8,4,9,7,4,6,2,3,7,5,4,1]#一维1*25的数据
input = torch.Tensor(input).view(1,1,5,5)#用Tensor.view函数来转换数据的形状(B,C,W,H)

copy_layer = torch.nn.Conv2d(1,1,kernel_size=3,bias=**False**)
conv_layer_p = torch.nn.Conv2d(1,1,kernel_size=4,padding=1,bias=False)

kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1,1,3,3)#自己定义个卷积核矩阵
conv_layer_p.weight.data =  kernel.data #对卷积层的权重做一个初始化
copy_layer.weight.data = kernel.data

output = copy_layer(input)
output_p = conv_layer_p(input)

#对比
print(output)
print( '形状=' ,output.shape)
print(output_p)
print( '形状=' ,output_p.shape)

结果:

截屏2022-04-12 11.21.50.png

池化层MaxPooling

最大池化层,作用是减少数据的数据量,降低运算的需求,按照卷积核的形状在输入的矩阵范围内找最大值,用这些最大值组成新的矩阵,默认strdie(步长)为2,MaxPooling对维度没有要求,只会改变原始矩阵的高和宽维度不会发生改变。

截屏2022-04-12 11.26.02.png

import torch

input_M =[3,4,6,5,2,4,6,8,2,6,7,8,9,7,4,6]
input_M = torch.Tensor(input_M).view(1,1,4,4)

maxpooling = torch.nn.MaxPool2d(kernel_size = 2)

output = maxpooling(input_M)
print(output)

结果:

截屏2022-04-12 11.43.19.png

实现构建一个简单的卷积神经网络

构建先大概规划一下模型如图:

我们需要预测MNIST的10个分类,所以最后我们需要输出10个数据,操作很简单就是在设计模型的时候堆上卷积层和池化层就可以了,只要注意第一点就是层与层之间的维度是能对上的,就能保证卷积神经的正常运行。

数据经过卷积神经网络后,也会需要进行全连接的处理的,进行全连接有一点很重要就是需要的是线性的数据,需要知道要接收的数据量是多少,也就是说数据经过卷积层后得到的矩阵我们要把数据先进行线性的变化在计算数据量,然后把数据量告诉全连接层,才能正常运行全连接。

怎么解决这个问题

第一、设计好模型手算出来

第二、同样设计好模型,定义一个和MNIST图片大一样的矩阵,拿这个矩阵放进设计好的卷积层里跑一跑,让电脑帮我们算。

实例:

import torch\
import torch.nn.functional as f
input_MO = torch.randn(1,1,28,28)

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1,10,kernel_size=5)
        self.conv2 = torch.nn.Conv2d(10,20,kernel_size=5)
        self.pooling = torch.nn.MaxPool2d(2)

    def forward(self, x):
        batch_size = x.size(0)
        x = self.pooling(f.relu(self.conv1(x)))
        x = self.pooling(f.relu(self.conv2(x)))
        return x
model= Net()
output=model(input_MO)
print(output.shape)

结果:

截屏2022-04-12 15.53.40.png 算出的是通道20高4宽4的矩阵,总共2044=320个数据点所以,全连接层需要接收320个数据量。

完整的模型:

数据➡️卷积层➡️ReLU激活层➡️池化层➡️卷积层➡️ReLU激活层➡️池化层➡️全连接

图片26.png


'''模型里加入特征提取层(卷积层,下采样层),提高精确度'''
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1,10,kernel_size=5) #卷积
        self.conv2 = torch.nn.Conv2d(10,20,kernel_size=5)
        self.pooling = torch.nn.MaxPool2d(2) #最大池化
        self.linear1 = torch.nn.Linear(320,160)
        self.linear2 = torch.nn.Linear(160,10)

    def forward(self, x):
        batch_size = x.size(0)
        x = self.pooling(f.relu(self.conv1(x)))
        x = self.pooling(f.relu(self.conv2(x)))
        x = x.view(batch_size,-1) #把数据张开变成向量
        x = self.linear1(x)
        return self.linear2(x)

model = Net()

用此模型训练MNIST数据集,预测精度可以达到98%,(这里只放了模型代码,其他代码可以参考juejin.cn/post/708590… )对比上一篇文章训练的结果提升了1%,已经很棒了。

图片27.png

181649815898_.pic.jpg