基于Pytorch深度学习——卷积神经网络(卷积层/池化层/多输入多输出通道/填充和步幅/)

本文章来源于对李沐动手深度学习代码以及原理的理解,并且由于李沐老师的代码能力很强,以及视频中讲解代码的部分较少,所以这里将代码进行尽量逐行详细解释
并且由于pytorch的语法有些小伙伴可能并不熟悉,所以我们会采用逐行解释+小实验的方式来给大家解释代码

在这一块,李沐老师的代码里面其实有很多的测试例子,而这些测试例子往往是劝退初学者的一个很大的因素,为了快速理解并且上手卷积神经网络,我并不会很强调所有的例子,而是根据李沐老师的顺序,将主要框架进行搭建,重要的小例子我会带大家进行浮现,但是具体的一些小例子就让大家自己去试试

本文的动图来自:https://blog.csdn.net/Together_CZ/article/details/115494176
以及一些网络的资料

卷积

相信工科专业的同学对这个词应该不会陌生,尤其是通信专业,我们经常会做信号卷积的操作,但是在其他的使用上面,我们常常使用的是一维卷积,但是在卷积神经网络中,我们用到的是二维卷积,我们可以用下面这张动图来描述二维卷积:
在这里插入图片描述

import torch
from torch import nn
from d2l import torch as d2l

def corr2d(X,K):
    # 计算二维互相关运算
    h,w = K.shape # 核矩阵的行数和列数
    Y = torch.zeros((X.shape[0]-h+1,X.shape[1]-w+1)) # 输出的高度和宽度
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i,j] = (X[i:i+h,j:j+w]*K).sum()
    return Y

我们使用这一段代码来描述这样的一个卷积的过程,这个代码实际上没有很大的难度,但是我们需要弄清楚的是卷积前和卷积后,二维图像尺寸的变化
我认为大家只需要记住一个公式,就可以弄清楚卷积和其他操作之后,我们图像的尺寸了(这里默认二维图像的高和宽是一样的,这里算出的是边长):
O u p u t = ( I n p u t + 2 ∗ p a d d i n g − k e r n e l ) / s t r i d e + 1 结果向下取整 Ouput = (Input+2*padding-kernel)/stride+1\\结果向下取整 Ouput=(Input+2paddingkernel)/stride+1结果向下取整
这个公式的具体推导我们可以看Pytorch官网的推导,这里我也可以把链接放在这里尺寸公式推导

或许初学者小伙伴们就会有疑惑,Output和Input我们都知道,是输入和输出;但是padding,kernel,stride是什么呢?没关系,我们后面会进行解释

卷积层

在上面的代码里面,我们已经实现了一个卷积操作,卷积层实际上就是将这个卷积操作做成一个类

# 实现二维卷积层
class Conv2d(nn.Module):
    def __init__(self,kernel_size):
        super().__init__()
        self.weight = nn.Parameter(torch.rand(kernel_size))
        self.bias = nn.Parameter(nn.zeros(1))
        
    def forward(self,x):
        return corr2d(x,self.weight)+self.bias

如果对类这个概念不是特别了解的同学,可以去看看我之前的文章,有讲解python的类的文章
需要注意的是,由于我们继承了父类nn.Module,所以__call__方法在这里写成forward方法,两者是等价的,我们先来讲解一下卷积层比较重要的一个参数kernel_size

Kernel_size

这个参数表示卷积核的大小,我们根据上面的动图来看,卷积核的大小是3×3,因为映射到图片上的影子是3×3的

或许你会认为这样的实现方式过于麻烦,我们当然也有更简单的实现方法,就是调用torch.nn模型中的Conv2d的函数,我们下面就对这个重要的函数进行讲解

torch.nn.Conv2d

conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

这个函数大家其实很好理解,实际上就是创建一个卷积层,但是可能会让大家疑惑的是,这个函数的一些参数是怎么样的
我们可以找到pytorch官网的参数:

nn.Conv2d(in_channels,out_channels,kernel_size,stride,padding)

我们来一个个参数的理解:
in_channels这个参数代表输入的通道数,通道这个概念我们后面会进行讲解
out_channels这个参数代表输出的通道数
kernel_size这个参数表示卷积核的大小
stride这个参数表示步幅,表示我们每一次卷积挪动的大小
padding这个参数表示扩张,padding为原来图像加宽的程度

stride和padding这两个参数比较简单,我们可以用两个图来描述:

padding

这个动图的虚线部分也就是padding
在这里插入图片描述

stride

在这里插入图片描述

小实验

我们这个小实验就根据我们上面讲的输入输出的尺寸公式以及刚刚讲的函数,对我们的公式进行一个验证:

x = torch.rand(1,2,8,8)
conv2d = nn.Conv2d(2,1,kernel_size=3,padding=1,stride=2)
y = conv2d(x)
print(x.shape)
print(y.shape)
>>> torch.Size([1, 2, 8, 8])
>>> torch.Size([1, 1, 4, 4])

我们先来逐行理解一下我的这个代码
x = torch.rand(1,2,8,8)这个代码表示我们初始化一个尺寸为(1,2,8,8)的一个tensor数据类型,大家可能对这个有一些不解
我们一般交给卷积层处理的数据需要有四个维度,分别是[N,C,H,W],也即是**[批量大小,通道数,高,宽]**
我们在代码中写到
nn.Conv2d(2,1,kernel_size=3,padding=1,stride=2),我们指定输入通道数目为2,输出通道数目为1,结果也很好的显示了我们确实成功的把通道数从2改成了1
接着我们进行运算,套用上面的尺寸变化公式:
o u t p u t = ( 8 − 3 + 2 × 1 ) / 2 + 1 = 4.5 output = (8-3+2×1)/2+1=4.5 output=(83+2×1)/2+1=4.5
向下取整之后得到4,说明我们的公式讲解是正确的

卷积层梯度下降

# 学习由X生成Y的卷积核
conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

X = X.reshape((1,1,6,8))
Y = Y.reshape((1,1,6,7))

for i in range(10):
    Y_hat = conv2d(X)
    l = (Y_hat-Y)**2
    conv2d.zero_grad()
    l.sum().backward()
    conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad # 实际上是在做一个梯度下降,学习率是3e-2
    if (i+1)%2 == 0:
        print(f'batch{i+1},loss{l.sum():.3f}')

在这个代码里面,我们还是仿照前面的梯度下降,设置了损失函数为L,然后设置学习率为3e-2,最后得到结果为

batch2,loss10.328
batch4,loss2.925
batch6,loss0.979
batch8,loss0.364
batch10,loss0.143

池化层

卷积操作对位置是非常的敏感的,所以我们需要一定的平移不变性,实际中会有很多因素导致图像有细微的区别,所以对位置太敏感并不是一件特别好的事情,池化层可以类似于一种激活函数
我们一般常用的是二维最大池化和二维平均池化
在这里插入图片描述
这个图很好的讲解了池化操作是怎么样子的

我们下面来区分一下最大池化和平均池化
最大池化层:每个窗口中最强的模式信号
平均池化层:每个窗口中平均的模式信号

def pool2d(X,pool_size,mode='max'):
    p_h,p_w = pool_size
    Y = torch.zeros((X.shape[0]-p_h+1,X.shape[1]-p_w+1)) # 输出维度
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode == 'max':
                Y[i,j] = X[i:i+p_h,j:j+p_w].max()
            elif mode == 'avg':
                Y[i,j] = X[i:i+p_h,j:j+p_w].mean()
    return Y

这个池化的代码比较好理解,就是将卷积的累加变成找最大值和找平均值

小实验

我们可以来验证一下池化层的输入和输出,并且池化层可以看作是特殊的卷积层,所以它也满足输入输出的尺寸变化*

X = torch.tensor([[0.0,1.0,2.0],[3.0,4.0,5.0],[6.0,7.0,8.0]])
X
>>>tensor([[0., 1., 2.],
        [3., 4., 5.],
        [6., 7., 8.]])

接下来我们来通过一个池化层看看结果:

pool2d(X,(2,2),mode='avg')
>>>tensor([[2., 3.],
        [5., 6.]])

池化层

在这里我们就不像卷积层一样从0开始实现了,我们直接调用torch.nn的函数即可

X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
pool2d = nn.MaxPool2d(3)
>>> tensor([[[[10.]]]])

这里我们需要注意的是,当最大池化层不规定步幅的时候,步幅默认和池化核尺寸一样

多输入多输出通道

多输入通道

在这里插入图片描述
在这里插入图片描述
通过这两个动图

import torch
from d2l import torch as d2l

# 多输入的计算函数
def corr2d_multi_in(X,K):
    return sum(d2l.corr2d(x,k) for x,k in zip(X,K))

上面的这个函数就是计算多输入通道的函数,我们下面跟着李沐老师的思路来测试一下

X=torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
        [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])

K=torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])

corr2d_multi_in(X, K)
>>> tensor([[ 56.,  72.],
        [104., 120.]])

我们从代码看出来,这里的输入X有两个通道,相应的,也会有两个对应的卷积核,下面我们来复刻一下上面的动图:

小实验

from torch import nn
A = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K1 = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
B = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
K2 = torch.tensor([[1.0, 2.0], [3.0, 4.0]])

这里我们将两个通道进行拆开,分为A和B两个,将两个通道的卷积核也进行分开,分为K1和K2

Y1 = d2l.corr2d(A,K1)
Y1
>>> tensor([[19., 25.],
        [37., 43.]])
        
Y2 = d2l.corr2d(B,K2)
Y2
>>> tensor([[37., 47.],
        [67., 77.]])
        
Y1+Y2
>>> tensor([[ 56.,  72.],
        [104., 120.]])

我们可以看出来,这个Y1+Y2和之前的结果是一样的,所以我们成功的验证了多通道卷积的过程

多输出通道

# 多输出通道的计算函数
def corr2d_multi_in_out(X,K):
    return torch.stack([corr2d_multi_in(X,K) for k in K],0)
    
K=torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
K = torch.stack((K,K+1,K+2),0)
K.shape
>>> torch.Size([3, 2, 2, 2])

我们这里生成的K是一个批量大小为3,通道数目为2的一个tensor的数据类型

X=torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
        [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])

K=torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])

corr2d_multi_in_out(X,K)
>>> tensor([[[ 56.,  72.],
         [104., 120.]],

        [[ 56.,  72.],
         [104., 120.]]])

可能到这里,很多小伙伴就看不懂了,但是不要怕,我们慢慢的对代码进行拆开讲解:

小实验

for k in K:
    print(corr2d_multi_in(X,K))
>>> tensor([[ 56.,  72.],
        [104., 120.]])
tensor([[ 56.,  72.],
        [104., 120.]])

根据这个代码,我们可以知道,我们输出的通道是根据卷积核的个数来判断的,也就是我们输出的通道数目和我们给定卷积核的个数是一样的

为了验证我们的想法,可以再来试试:

K=torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]],[[2.0,3.0],[4.0,5.0]]])
tmp = torch.stack([corr2d_multi_in(X,K) for k in K],0)
tmp
>>> tensor([[[ 56.,  72.],
         [104., 120.]],

        [[ 56.,  72.],
         [104., 120.]],

        [[ 56.,  72.],
         [104., 120.]]])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/593403.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 笔记 15:Java 数组相关内容补充,多维数组,Arrays 类的常见用法,以及冒泡排序

一、前言 记录时间 [2024-05-05] 系列文章简摘: Java 笔记 01:Java 概述,MarkDown 常用语法整理 Java 笔记 02:Java 开发环境的搭建,IDEA / Notepad / JDK 安装及环境配置,编写第一个 Java 程序 Java 笔记 …

【在线OJ】Vue在线OJ项目

一、主页 二、题库 三、在线编译器 四、比赛 五、搜索 六、个人主页

【区块链】比特币架构

比特币架构 2009年1月,在比特币系统论文发表两个月之后,比特币系统正式运行并开放了源码,标志着比特币网络的正式诞生。通过其构建的一个公开透明、去中心化、防篡改的账本系统,比特币开展了一场规模空前的加密数字货币体验。在区…

vue3(实现上下无限来往滚动)

一、问题描述 一般在大屏项目中,很常见的效果,就是容器中的内容缓慢地向下移动,直到底部停止,然后快速滚动回顶部,然后接着缓慢滚动到底部。并且在特定的情况下,还需要进行一些小交互,那就还得让…

RabbitMQ之生产批量发送

为什么要用生产批量发送? 批量发送消息,可以提高MQ发送性能。但是 RabbitMQ 并没有提供了批量发送消息的 API 接口,使用 spring-amqp 的 BatchingRabbitTemplate 实现批量能力。 SimpleBatchingStrategy 发送策略满足以下规则会进行发送: ba…

FreeRTOS低功耗模式(1-19)

低功耗模式简介(了解) 很多应用场合对于功耗的要求很严格,比如可穿戴低功耗产品、物联网低功耗产品等 一般MCU都有相应的低功耗模式,裸机开发时可以使用MCU的低功耗模式。 FreeRTOS也提供了一个叫Tickless的低功耗模式,方便带FreeRTOS操作系统的应用开发 stm32的低…

C#创建obj三维模型文件

介绍 使用开源库创建obj三维模型文件。 开源库地址:https://github.com/JeremyAnsel/JeremyAnsel.Media.WavefrontObj 相关API地址:https://jeremyansel.github.io/JeremyAnsel.Media.WavefrontObj/api/JeremyAnsel.Media.WavefrontObj.ObjFile.html …

docker desktop实战部署oracle篇

1、前言 oracle数据库官方已提供现成的镜像,可以直接拿来部署了。 由于项目中需要使用oracle数据库的分表功能,之前安装的是standard版本,无奈只能重新安装。网上查了一番,使用的方法都比较传统老旧:下载安装包手动安…

多线程局部存储技术

问题 多线程上下文中,每个线程需要使用一个专属的全局变量,该如何实现? 代码示例 一种可能的解决方案 test1.c #define _GNU_SOURCE /* To get pthread_getattr_np() declaration */ #define _XOPEN_SOURCE > 500 || _POSIX_C_SOURC…

谷歌上架,为什么会触发填表单,可以避免吗?怎么填表单可以提高通过率?

在谷歌上架过程中,相信大部分开发者都有收到过谷歌发来表单填写的邮件通知,要求开发者们在14天内根据表单要求回复关于应用部分情况。邮件如图: 根据触发填表单的开发者分享的经验来看,填完表之后出现的情况不尽相同,且…

【华为】路由综合实验(OSPF+BGP基础)

【华为】路由综合实验 实验需求拓扑配置AR1AR2AR3AR4AR5PC1PC2 查看通信OSPF邻居OSPF路由表 BGPBGP邻居BGP 路由表 配置文档 实验需求 ① 自行规划IP地址 ② 在区域1里面 启用OSPF ③ 在区域1和区域2 启用BGP,使AR4和AR3成为eBGP,AR4和AR5成为iBGP对等体…

【JVM】class文件格式,JVM加载class文件流程,JVM运行时内存区域,对象分配内存流程

这篇文章本来只是想讲一下class文件格式,讲着讲着越讲越多。JVM这一块吧,知识比较散比较多,如果深研究下去如死扣《深入理解Java虚拟机》,这本书很深很细,全记住是不可能的,其实也没必要。趁这个机会直接把…

RK3568平台(基础篇)linux错误码

一.概述 linux应用程序开发过程中,经常会遇到一些错误信息的返回,存在的可能性有,参数有误、非法访问、系统资源限制、设备/文件不存在、访问权限限制等等。对于这类错误,可以通过perror函数输出具体描述,或者通过str…

nacos-server-1.2.1启动

1、双击startup.cmd 2、启动日志 3、访问http://192.168.26.210:8848/nacos/index.html 4、登录 用户名:nacos 密码:nacos

掌握JavaScript面向对象编程核心密码:深入解析JavaScript面向对象机制对象概念、原型模式与继承策略全面指南,高效创建高质量、可维护代码

ECMAScript(简称ES,是JavaScript的标准规范)支持面向对象编程,通过构造函数模拟类,原型链实现继承,以及ES6引入的class语法糖简化面向对象开发。对象可通过构造函数创建,使用原型链共享方法和属…

【云原生】Docker 的网络通信

Docker 的网络通信 1.Docker 容器网络通信的基本原理1.1 查看 Docker 容器网络1.2 宿主机与 Docker 容器建立网络通信的过程 2.使用命令查看 Docker 的网络配置信息3.Docker 的 4 种网络通信模式3.1 bridge 模式3.2 host 模式3.3 container 模式3.4 none 模式 4.容器间的通信4.…

小白如何搭建git

1、安装git 在Windows上安装git: 关注微信公众号“机器人学”回复 “搭建git” 利用百度云网盘下载安装包,建议下载如下版本的否则可能会出现错误。 安装完成后,在开始菜单里Git->git bash,弹出命令窗说明git安装成功。 鼠标右…

【Python项目】基于DJANGO的【基于语音识别的智能垃圾分类系统】

技术简介:使用Python技术、DJANGO框架、MYSQL数据库等实现。 系统简介:用户们可以在系统上面录入自己的个人信息,录入后还可以对信息进行修改,网站可以对用户上传的音频文件进行识别,然后进行垃圾分类。 背景&#xf…

【学习AI-相关路程-工具使用-自我学习-NVIDIA-cuda-工具安装 (1)】

【学习AI-相关路程-工具使用-自我学习-NVIDIA-cuda (1)】 1、前言2、环境配置1、对于jetson orin nx 的cuda环境2、对于Ubuntu 20.04下cuda环境 3、自我总结-安装流程1、在ubuntu下,如果想使用cuda平台,应该注意什么 和 都安装什么…

BGE向量模型架构和训练细节

模型论文:https://arxiv.org/pdf/2309.07597 模型数据:https://data.baai.ac.cn/details/BAAI-MTP 训练数据 由无标签数据和有标签数据组成。 无标签数据使用了悟道等数据集,有标签数据使用了dureader等数据集。 都是文本对,对于…