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

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

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

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

卷积

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

import torch
from torch import nn
from d2l import torch as d2ldef 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)**2conv2d.zero_grad()l.sum().backward()conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad # 实际上是在做一个梯度下降,学习率是3e-2if (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_sizeY = 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.]]])

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【DPU系列之】如何通过带外口登录到DPU上的ARM服务器?(Bluefield2举例)

文章目录 1. 背景说明2. 详细操作步骤2.1 目标拓扑结构2.2 连接DPU带外口网线,并获取IP地址2.3 ssh登录到DPU 3. 进一步看看系统的一些信息3.1 CPU信息:8核A723.2 内存信息 16GB3.3 查看ibdev设备 3.4 使用小工具pcie2netdev查看信息3.5 查看PCIe设备信息…

python笔记:gensim进行LDA

理论部分:NLP 笔记:Latent Dirichlet Allocation (介绍篇)-CSDN博客 参考内容:DengYangyong/LDA_gensim: 用gensim训练LDA模型,进行新闻文本主题分析 (github.com) 1 导入库 import jieba,os,re from ge…

【云原生】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.…

Stream流操作

看到Stream流这个概念,我们很容易将其于IO流联系在一起,事实上,两者并没有什么关系,IO流是用于处理数据传输的,而Stream流则是用于操作集合的。 当然,为了方便我们区分,我们依旧在这里复习一下…

长期找 AI 专家,邀请参加线上聊天直播

诚邀 AI 专家参加线上聊天,成为嘉宾。 分享前沿观点、探讨科技和生活 除节假日外,每周举办在线聊天直播 根据话题和自愿形式结合,每期 2~3 位嘉宾 成为嘉宾,见下:

ADS软件(PathWave 先进设计系统软件)分享与安装

ADS软件的简介 ADS软件(Advanced Design System)主要用于射频(RF)、微波(Microwave)和毫米波(Millimeter-wave)电路的设计、仿真和分析。它提供了一套强大的工具和功能,…

Angular进阶-NVM管理Node.js实现不同版本Angular环境切换

一、NVM介绍 1. NVM简介 Node Version Manager(NVM)是一个用于管理多个Node.js版本的工具。它允许用户在同一台机器上安装和使用多个Node.js版本,非常适合需要同时进行多个项目的开发者。NVM是开源的,支持MacOS、Windows和Linux…

【解决】docker一键部署报错

项目场景见:【记录】Springboot项目集成docker实现一键部署-CSDN博客 问题: 1.docker images 有tag为none的镜像存在。 2.有同事反馈,第一次启动docker-compose up -d 项目无法正常启动。后续正常。 原因: 1.服务中指定了镜像m…

Jackson-jr 对比 Jackson

关于Jackson-jr 对比 Jackson 的内容,有人在做了一张下面的图。 简单点来说就 Jackson-jr 是Jackson 的轻量级应用,因为我们在很多时候都用不到 Jackson 的很多复杂功能。 对很多应用来说,我们可能只需要使用简单的 JSON 读写即可。 如我们…

【Linux网络】网络文件共享

目录 一、存储类型 二、FTP文件传输协议 2.1 FTP工作原理 2.2 FTP用户类型 2.3 FTP软件使用 2.3.1 服务端软件vsftpd 2.3.2 客户端软件ftp 2.4 FTP的应用 2.4.1 修改端口号 2.4.2 匿名用户的权限 2.4.3 传输速率 三、NFS 3.1 工作原理 3.2 NFS软件介绍 3.3 NFS配…

企业级数据治理学习总结

1. 水在前面 “数据治理”绝对是吹过的牛里面最高大上的题目了,本来想直接以《企业级数据治理》为题来水的,码字前又跑去图书馆借了几本书,翻了几页才发现自己连半桶水都提不起,撑死只能在小屁孩跟前吹吹牛。 好吧,实在…

怎么把jpg图片变成gif?参考这个方法一键制作

Jpg图片如何变成gif图片?Jpg、gif都是最常用的图片格式,想要将这两种格式的图片互相转化的时候要怎么操作呢?想要将jpg图片变成gif方法很简单,只需要使用gif图片制作(https://www.gif5.net/)工具-GIF5工具网…

华为手机ip地址怎么切换

随着移动互联网的普及,IP地址成为了我们手机上网的重要标识。然而,在某些情况下,我们可能需要切换手机的IP地址,以更好地保护个人隐私、访问特定地区的内容或服务,或者出于其他网络需求。华为手机作为市场上的热门品牌…

用队列实现栈——leetcode刷题

题目的要求是用两个队列实现栈,首先我们要考虑队列的特点:先入先出,栈的特点:后入先出,所以我们的目标就是如何让先入栈的成员后出栈,后入栈的成员先出栈。 因为有两个队列,于是我们可以这样想&…

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

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

Leetcode——面试题02.04.分割链表

面试题 02.04. 分割链表 - 力扣(LeetCode) 对于该链表OJ,我们两种大的方向: 1.在原链表上修改;2.创建新链表,遍历原链表。 在原链上进行修改:如果该节点的val小于x则继续往后走,如…

Ubuntu服务器创建新用户及解决新用户登录Access denied问题

目录 Ubuntu服务器创建新用户及解决新用户登录Access denied问题创建账号步骤创建用户只创建用户添加用户到sudo组 允许账号远程连接重启ssh服务 删除账号要删除用户而不删除用户文件如果要删除并且删除用户的家目录和邮件 查询指令查看所有用户查询特定用户账户信息查看用户组…

【Micropython Pitaya Lite教程】key按键与EXTI中断

文章目录 前言一、按键的使用1.1 按键的简介1.2 读取按键的高低电平 二、EXIT外部中断2.1 EXIT外部中断简介2.2 外部中断基础知识2.3 设置外部中断2.4 示例代码 总结 前言 Micropython Pitaya Lite开发板提供了丰富的功能和灵活的扩展性,其中包括了按键&#xff08…

Python 全栈系列241 GFGo Lite迭代

说明 随着整个算网开发逐渐深入,各个组件、微服务的数量、深度在不断增加。由于算网是个人项目,我一直按照MVP(Minimum Viable Product )的原则在推进。由于最初的时候对架构、算法和业务的理解并没有那么深刻,所以MVP的内容还是在不断变化&…

IoTDB 入门教程 基础篇①——时序数据库为什么选IoTDB ?

文章目录 一、前文二、性能排行第一三、完全开源四、数据文件TsFile五、乱序数据高写入六、其他七、参考 一、前文 IoTDB入门教程——导读 关注博主的同学都知道,博主在物联网领域深耕多年。 时序数据库,博主已经用过很多,从最早的InfluxDB&a…