视觉经典神经网络学习01_CNN(1)

一、概述

卷积神经网络(Convolutional Neural Network,CNN)是一种专门用于处理具有网格状结构数据的深度学习模型。最初,CNN主要应用于计算机视觉任务,但它的成功启发了在其他领域应用,如自然语言处理等。

卷积神经网络(Convolutional Neural Network)是含有卷积层的神经网络。卷积层的作用就是用来自动学习、提取图像的特征。

CNN网络主要有三部分构成:卷积层、池化层和全连接层构成,其中卷积层负责提取图像中的局部特征;池化层用来大幅降低运算量并特征增强;全连接层类似神经网络的部分,用来输出想要的结果。

二、卷积思想

1、相关概念

Convolution,输入信息与卷积核(滤波器,Filter)的乘积。

卷:从左往右,从上往下;积:乘积,求和

局部连接

  • 局部连接可以更好地利用图像中的结构信息,空间距离越相近的像素其相互影响越大。

  • 根据局部特征完成目标的可辨识性。

权重共享

  • 图像从一个局部区域学习到的信息应用到其他区域。

  • 减少参数,降低学习难度。

2、卷积层

1.卷积核

在卷积神经网络中,卷积核是非常重要的,它们被用来提取图像中的特征。

  • 卷积核的个数:卷积核(过滤器)的个数决定了其输出特征矩阵的通道数

  • 卷积核的值:卷积核的值是自定义的,根据想要提取的特征来进行设置,后续进行更新。

  • 卷积核的大小:常见的卷积核有1×1、3×3、5×5等,一般都是奇数 × 奇数。

2.卷积计算

卷积的过程是将卷积核在图像上进行滑动计算,每次滑动到一个新的位置时,卷积核和图像进行点对点的计算,并将其求和得到一个新的值,然后将这个新的值加入到特征图中,最终得到一个新的特征图。

  1. input 表示输入的图像

  2. filter 表示卷积核, 也叫做滤波器

  3. input 经过 filter 的得到输出为最右侧的图像,该图叫做特征图

 

卷积的重要性在于它可以将图像中的特征与卷积核进行卷积操作,从而提取出图像中的特征

可以通过不断调整卷积核的大小、卷积核的值和卷积操作的步长,可以提取出不同尺度和位置的特征。

单特征图卷积输出:

import os
import torch
import torch.nn as nn
from matplotlib import pyplot as plt


def showing(img):
    plt.imshow(img)
    plt.show()

def test01():
    # 获取当前脚本文件所在的目录路径
    current_path = os.path.dirname(__file__)
    print(current_path)
    # 找到该路径下的图像
    img_path = os.path.join(current_path,'data','1,png')
    # 转换为相对路径
    img_path = os.path.relpath(img_path)

    # 使用plt读取图片
    img = plt.imread(img_path)
    # print(img.shape) # # torch.Size([808, 576, 4])

    # 创建卷积核
    """
    in_channels 输入通道数
    out_channels 输出通道数
    kernel_size 卷积核大小
    stride 步长
    padding 填充
    bias 布尔值 卷积层是否包含偏置项
    """
    conv = nn.Conv2d(
        in_channels=4,
        out_channels=1,
        kernel_size=3, 
        stride=1,
        padding=0,
        bias=True
    )
    # 注意:卷积层对输入的数据有形状要求 [batch, channel, height, width]
    # 需要进行形状转换  且转换为张量 HWC->CHW->NCHW
    img = torch.tensor(img,dtype=torch.float).permute(2,0,1).unsqueeze(0)
    print(img.shape) # torch.Size([1, 4, 808, 576])

    # 卷积操作
    out = conv(img)
    print(img.shape) # torch.Size([1, 1, 808, 576])

    # 将BCHW->HWC 便于显示
    img = img.squeeze(0).permute(1, 2, 0)
    showimg(img.detach().numpy())

多特征图卷积输出:

# 多特征图输出
def test002():
    dir = os.path.dirname(__file__)
    img = plt.imread(os.path.join(dir, 'data', '1.png'))

    # 定义一个多特征图输出的卷积核
    conv = nn.Conv2d(
        in_channels=4, 
        out_channels=3,  # 三个输出
        kernel_size=3, 
        stride=1, 
        padding=1)

    # 图形要进行变形处理
    img = torch.tensor(img).permute(2, 0, 1).unsqueeze(0)
    print(img.shape) # torch.Size([1, 4, 808, 576])

    # 使用卷积核对图片进行卷积计算
    outimg = conv(img)
    print(outimg.shape) # torch.Size([1, 3, 808, 576])

    # 把图形形状转换回来以方便显示 BCHW->CHW->HWC
    outimg = outimg.squeeze(0).permute(1, 2, 0)
    print(outimg.shape) # torch.Size([808, 576, 3])

    # 显示这三张特征图
    for idx in range(outimg.shape[2]):
        showing(outimg[:,:,idx].squeeze(-1).detach())
3.边缘填充 padding

通过上面的卷积计算,我们发现最终的特征图比原始图像要小,如果想要保持图像大小不变, 可在原图周围添加padding来实现。

更重要的,边缘填充还更好的保护了图像边缘数据的特征

4.步长 stride

按照步长为1来移动卷积核,计算特征图如下所示:

 如果我们把 Stride 增大为2,也是可以提取特征图的,如下图所示:

stride太小:重复计算较多,计算量大,训练效率降低;

stride太大:会造成信息遗漏,无法有效提炼数据背后的特征;

5.多通道卷积计算

实际中的图像都是多个通道组成的:

 就需要:

  1. 当输入有多个通道(Channel), 例如RGB三通道, 此时要求卷积核需要有相同的通道数

  2. 卷积核通道与对应的输入图像通道进行卷积。

  3. 将每个通道的卷积结果按位相加得到最终的特征图。

(图片中的结果应为 -71) 

6.多卷积核卷积计算

实际对图像进行特征提取时, 我们需要使用多个卷积核进行特征提取。这个多个卷积核可以理解为从不同到的视角、不同的角度对图像特征进行提取。

输出特征图的大小计算方式(粗略版,以后会补充):

  1. 输入图像大小: W x W

  2. 卷积核大小: F x F

  3. Stride: S

  4. Padding: P

  5. 输出图像大小: N x N

输出特征图大小计算: 

import torch
import torch.nn as nn


"""卷积输出图大小"""
def test01():
    input = torch.randn(1,1,5,5)
    
    # 创建卷积核
    conv = nn.Conv2(
        in_channels=1,
        out_channels=1,
        kernel_size=3,
        stride=1,
        padding=0,
        bias=True
    )
    # 输出图大小(5-3+2*0)/1+1=3

    out = conv(input)
    print(out.shape) # torch.Size([1, 1, 3, 3])

 多卷积核——神经网络构建:

import torch
import torch.nn as nn
import torch.nn.functional as F

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        # 卷积层 隐藏层
        self.conv1 = nn.Conv2d(
            in_channels=1, # 输入通道数
            out_channels=32, # 输出通道数
            kernel_size=3, # 卷积核大小
            padding=0, 
            stride=1 # 步长
            )
        # 输出特征图大小 30*30
        self.conv2 = nn.Conv2d(
            in_channels=32, # 输入通道数
            out_channels=128, # 输出通道数
            kernel_size=3, # 卷积核大小
            padding=0, 
            stride=1 # 步长
            )
        # 输出特征图大小 28*28
        self.conv3 = nn.Conv2d(
            in_channels=128, # 输入通道数
            out_channels=512, # 输出通道数
            kernel_size=3, # 卷积核大小
            padding=0, 
            stride=1 # 步长
            )
        # 输出特征图大小 26*26
        
        # 线性层 输出层
        # 分类判断 10分类
        self.fc = nn.Linear(512*26*26,10)

    def forward(self,x):
        # 前向传播
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        # 输出 NCHW->ND 线性层需要(batch_size, num_features)的输入形式
        x = x.view(x.size(0),-1)
        x = self.fc(x)
        x = nn.Softmax(dim=1)(x)
        return x


if __name__ == '__main__':
    input = torch.randn(64,1,32,32)
    net = MyNet()
    out = net(input)
    print(out.shape) # torch.Size([64, 10])
    # print(out)
 7.卷积参数共享

数据是 32×32×3 的图像,用 10 个 5×5×3 的filter来进行卷积操作,所需的权重参数:

  • 5×5×3 = 75,表示每个卷积核只需要 75 个参数。

  • 10个不同的卷积核,就需要10*75 = 750 个卷积核参数。

  • 如果还考虑偏置参数$b$,最终需要 750+10=760 个参数。

全连接层的参数量

import torch
import torch.nn as nn


class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        # 隐藏层 线性层
        self.linear1 = nn.Linear(60*60*3, 38*38*3)
        # 输出层
        self.fc = nn.Linear(38*38*3,10)

    def forward(self, x):
        x = self.linear1(x)
        return x
    

if __name__ == '__main__':
    linearmodel = MyNet()
    # 获取网络参数
    params = linearmodel.named_parameters()
    for name,param in params: 
        print(name,param)

卷积层的参数量

import torch
import torch.nn as nn


class MyConv(nn.Module):
    def __init__(self):
        super(MyConv,self).__init__()

        self.conv1 = nn.Conv2d(
            in_channels=3,  # 输入通道数
            out_channels=8,  # 输出通道数
            kernel_size=3,  # 卷积核大小
            stride=1,  # 步长
            padding=0,  # 填充
            bias=True
        )

        # 输出层
        self.fc = nn.Linear(30*30*8,10)

    def forward(self,x):
        x = self.conv1(x)
        # x = x.view(-1,30*30*8)
        x=x.view(x.size(0),-1)
        print(x.shape)
        x = self.fc(x)
        return x
    

if __name__ == "__main__":
    convmodel = MyConv()
    input = torch.randn(1,3,64,64)
    out = convmodel(input)
    print(out.shape)
    # params = convmodel.named_parameters()
    # for name,param in params: 
    #     print(name,param)
8.卷积计算
import torch
import torch.nn as nn


class MyConv(nn.Module):
    def __init__(self):
        super(MyConv,self).__init__()

        self.conv1 = nn.Conv2d(
            in_channels=3,  # 输入通道数
            out_channels=8,  # 输出通道数
            kernel_size=3,  # 卷积核大小
            stride=1,  # 步长
            padding=0,  # 填充
            bias=True
        )

        # 输出层
        self.fc = nn.Linear(30*30*8,10)

    def forward(self,x):
        x = self.conv1(x)
        # x = x.view(-1,30*30*8)
        x=x.view(x.size(0),-1)
        print(x.shape)
        x = self.fc(x)
        return x
    

if __name__ == "__main__":
    convmodel = MyConv()
    input = torch.randn(1,3,64,64)
    out = convmodel(input)
    print(out.shape)
    # params = convmodel.named_parameters()
    # for name,param in params: 
    #     print(name,param)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值