第二节:Pytorch基础Tensor,Autograd和nn

第二节:Pytorch基础Tensor,Autograd和nn

上一节主要讲解了选择Pytorch的原因,以及本笔记的开发环境,从本节开始就要正式开始讲解Pytorch的内容

本节将讲解Pytorch中的三个基本概念,分别是Tensor,Autograd和nn
这三个概念分别是:作为网络传播时底层计算对象的张量;在构建网络/计算图时自动追踪需要的张量(即参数矩阵)并在反向传播时自动计算参数矩阵梯度的自动求导机制;将池化层、卷积层等不同的层抽象为对象的nn模块,搭建网络的时候直接实例化不同的层并且有机有机结合

就像我们学习任何一个库首先需要了解的就是这个库中的一些基本概念,我们学习Pytorch时的第一步就是要搞懂Pytorch中的基本概念

学习的过程就像前面我们学习Pandas库的用法的时候一样(详见我的Pandas学习专栏),我们对Pandas的学习其实学的就是Series,DataFrame,Index这三个基本概念,而这三个基本概念在Pandas中的实现就是通过对象

而这从这三个对象出发,有延伸出了MultiIndex,Timestamp,TimeDateIndex等等内容

所以本节将讲解Pytorch中的三大基础概念: Tensor, Autograd和torch.nn

Tensor

通常的数据科学处理的对象都是矩阵,我们既可以理解矩阵是用于存储数据的对象,也可以将矩阵理解为一种量

对于神经网络而言,我们每次训练都是使用的Mini-batch,即给定一批数据,例如有N个

而每个数据都有D个维度,那么我们输入的数据实际上就是一个N×D维的矩阵

我们用计算机中的表示的话,每个维度的起点都是0,那么输入数据的直观表示如下
X 输 入 = X N × D = [ x 0 , 0 x 0 , 1 ⋯ x 0 , D − 1 x 1 , 0 x 1 , 1 ⋯ x 1 , D − 1 ⋮ ⋮ ⋱ ⋮ x N − 1 , 0 x N − 1 , 1 ⋯ x N − 1 , D − 1 ] } N X_{输入}=X_{N\times D}=\left. \begin{bmatrix} x_{0,0}&x_{0,1}&\cdots&x_{0,D-1}\\ x_{1,0}&x_{1,1}&\cdots&x_{1,D-1}\\ \vdots&\vdots&\ddots&\vdots\\ x_{N-1,0}&x_{N-1,1}&\cdots&x_{N-1,D-1}\\ \end{bmatrix} \right \}N X=XN×D=x0,0x1,0xN1,0x0,1x1,1xN1,1x0,D1x1,D1xN1,D1N
所以对于神经网络来说,其本质就是矩阵的乘法

对于卷积神经网络来说,我们可以通过im2col的方法来将卷积操作转换为矩阵的乘法,而在时序上具有变化的递归神经网络直接就是矩阵的乘法

Pytorch中提供了Tensor对象来用于完成矩阵的创建,计算等等内容

即Tensor概念的实现就是通过Tensor对象

下面所有的代码都将基于

import torch
import numpy as np

Tensor的创建

类似于Numpy中的ndarray对象创建,Pytorch中的Tensor具有多种创建方式

torch.tensor函数

torch.tensor函数能够将多种非tensor对象转换为tensor对象

tensor_1=torch.tensor([1,2,3,4])
tensor_2=torch.tensor(np.arange(start=0,stop=4,step=1))

print(tensor_1)
print(tensor_2)
print(type(tensor_1))
print(type(tensor_2))
>>>
tensor([1, 2, 3, 4])
tensor([0, 1, 2, 3])
<class 'torch.Tensor'>
<class 'torch.Tensor'>

torch.Tensor函数

torch.Tensor既可以像前面的torch.tensor函数一样根据Python自带的列或者Numpy的ndarray创建

还可以按照指定的大小来创建Tensor对象

tensor_1=torch.Tensor([1,2,3,4])
tensor_2=torch.Tensor(np.arange(start=0,stop=5,step=1))
tensor_3=torch.Tensor(2,3)
tensor_4=torch.Tensor(2,3,4)

print(tensor_1)
print(tensor_2)
print(tensor_3)
print(tensor_4)
print(type(tensor_1))
print(type(tensor_2))
print(type(tensor_3))
print(type(tensor_4))
>>>
tensor([1., 2., 3., 4.])
tensor([0., 1., 2., 3., 4.])
tensor([[5.8748e+29, 4.5755e-41, 5.8751e+29],
        [4.5755e-41, 8.9030e+29, 4.5755e-41]])
tensor([[[-2.2576e-15,  4.5755e-41, -4.8319e-32,  3.0875e-41],
         [ 4.4842e-44,  0.0000e+00,  1.5695e-43,  0.0000e+00],
         [-4.8319e-32,  3.0875e-41,  7.4269e-44,  0.0000e+00]],

        [[ 0.0000e+00,  0.0000e+00,  0.0000e+00,  0.0000e+00],
         [ 0.0000e+00,  0.0000e+00,  7.7052e+31,  1.9447e+31],
         [ 5.0207e+28,  2.3329e-18,  2.8175e+20,  2.0871e+37]]])
<class 'torch.Tensor'>
<class 'torch.Tensor'>
<class 'torch.Tensor'>
<class 'torch.Tensor'>

torch.arange函数

torch.arange函数用于生成指定区间按照指定步长生成的Tensor

tensor_1=torch.arange(start=0,end=4,step=1)
print(tensor_1)
>>>
tensor([0, 1, 2, 3])

torch.linspace函数

torch.linspace函数用于生成均匀 / 线性分布在指定区间内的指定个数的Tensor

tensor_1=torch.linspace(start=0,end=4,steps=6)
print(tensor_1)
>>>
tensor([0.0000, 0.8000, 1.6000, 2.4000, 3.2000, 4.0000])

torch.logspace函数

torch.linspace函数用于生成以指定数字为底的指数分布在指定区间内的指定个数的Tensor

tensor_1=torch.logspace(start=0.1,end=1,base=2,steps=5)
tensor_2=2**torch.linspace(start=0.1,end=1,steps=5)
print(tensor_1)
print(tensor_2)
>>>
tensor([1.0718, 1.2527, 1.4641, 1.7112, 2.0000])
tensor([1.0718, 1.2527, 1.4641, 1.7112, 2.0000])

torch.empty函数

torch.empty函数用于生成指定大小的空Tensor,便于我们后面用指定out来将输出重定向为空Tensor

tensor_1=torch.empty((2,3))
print(tensor_1)

torch.ones((2,3),out=tensor_1)
print(tensor_1)
>>>
tensor([[-2.0401e-14,  3.0910e-41, -2.0424e-14],
        [ 3.0910e-41,  8.9683e-44,  0.0000e+00]])
tensor([[1., 1., 1.],
        [1., 1., 1.]])

torch.zeros函数

torch.zeros函数用于可以生成指定形状的全为0的Tensor

tensor_1=torch.zeros((5,6))
print(tensor_1)
>>>
tensor([[0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0.]])

torch.ones函数

torch.ones函数可以生成指定形状的全为1的Tensor

tensor_1=torch.ones((2,3))
print(tensor_1)
>>>
tensor([[1., 1., 1.],
        [1., 1., 1.]])

torch.full函数

torch.full函数用于生成指定形状的全为指定数值的Tensor

tensor_1=torch.full((4,2),fill_value=6.0)
print(tensor_1)
>>>
tensor([[6., 6.],
        [6., 6.],
        [6., 6.],
        [6., 6.]])

下面介绍的几个创建Tensor的方法都是创建随机数为元素的Tensor,我们可以使用torch.manual_seed()函数来保证随机的结果可复现

我们下面将以0作为计算机生成随机数的种子值来确保计算随机数的结果可复现

torch.manual_seed(0)

torch.normal函数

torch.normal函数用于生成仅一个元素的Tenso,且该元素服从指定均值和标准差的正态分布

tensor_1=torch.normal(mean=0.0,std=torch.tensor(1.0))
print(tensor_1)
>>>
tensor(-0.2934)

此外,均值和标准差至少一个需要以仅含一个元素的Tensor指定

如果我们指定的mean或者std中以Tensor形式给定的话,那么最后输出将会是多个服从对应分布的随机数

tensor_1=torch.normal(mean=0.0,std=torch.tensor([1.0,2.0,3.0,4.0,5.0]))
print(tensor_1)
>>>
tensor([ 0.8380, -1.4385, -1.2100, -2.3865,  0.9102])

这里我们指定std为多元素Tensor这样会生成五个随机数,分别服从均值为0、标准差为1,均值为0、标准差为2,均值为0标准差为3……

我们如果指定mean为多元素Tensor,那么效果如下

tensor_1=torch.normal(mean=torch.tensor([0.0,1.0,2.0,3.0,4.0,5.0]),std=1.0)
print(tensor_1)
>>>
tensor([-0.8567,  2.1006,  0.9288,  3.1227,  3.4337,  5.3731])

如果mean和std同时都是多元素的Tensor的话,那么两个Tensor形状必须一致,否则会报错

tensor_1=torch.normal(mean=torch.tensor([1.0,2.0,3.0]),std=torch.tensor([4.0,5.0,6.0]))
print(tensor_1)
>>>
tensor([ 1.7675,  8.3190, -4.7426])

torch.randn函数

torch.randn函数用于生成指定大小的、每个元素都服从均值为0,标准差为1的标准正态分布的Tensor

tensor_1=torch.randn((3,4))
print(tensor_1)
>>>
tensor([[-0.7911, -0.0209, -0.7185,  0.5186],
        [-1.3125,  0.1920,  0.5428, -2.2188],
        [ 0.2590, -1.0297, -0.5008,  0.2734]])

torch.randperm函数

torch.randperm函数用于生成0~指定数字之间的整数随机打乱后的Tensor

tensor_1=torch.randperm(10)
print(tensor_1)
>>>
tensor([2, 8, 4, 3, 9, 6, 1, 0, 5, 7])

torch.like系列函数

我们可以用torch.like系列函数创建和指定Tensor具有相同形状的Tensor

只需要对上述的所有函数后加上like即可

tensor_1=torch.Tensor([1,2,3,4])
tensor_2=torch.Tensor(np.arange(start=0,stop=8,step=1).reshape(2,4))

tensor_1_like=torch.ones_like(tensor_1)
tensor_2_like=torch.zeros_like(tensor_2)

print(tensor_1)
print(tensor_1_like)
print()
print(tensor_2)
print(tensor_2_like)
>>>
tensor([1., 2., 3., 4.])
tensor([1., 1., 1., 1.])

tensor([[0., 1., 2., 3.],
        [4., 5., 6., 7.]])
tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.]])

Tensor.new方法

我们可以调用Tensor的nwe系列方法创建与指定Tensor具有相同数据类型的Tensor

类似上面的like系列函数,我们只需要在上述所有函数钱加上new即可

tensor_1=torch.ones(size=(3,4))
print(tensor_1.dtype)
print(tensor_1)
tensor_2=tensor_1.new_zeros(size=(2,3))
print(tensor_2.dtype)
print(tensor_2)
>>>
torch.float32
tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
torch.float32
tensor([[0., 0., 0.],
        [0., 0., 0.]])

Tensor的属性

Tensor概念的靠的是Tensor类,所以Tensor类就会有一些属性,下面就将讲解这些属性

Tensor的存储位置

CPU和GPU都具有计算能力,能够用于编程

过去我们的编程都是在CPU上进行编程,这个时候我们在程序中声明的变量就会存储在RAM或者L1,L2,L3(都是存储设备,但是读取速度快,因此适合和高运行速度的CPU做交互)

而GPU中也存在用于暂时存放数据的存储器,即GPU内存

因此我们使用CUDA编程的话,Tensor就既可以存放在CPU中也可以存放在GPU中,存放在不同的位置主要是为了后面训练时的方便

下面将介绍指定Tensor存放的位置的方法

查询Tensor的存储位置

我们可以调用Tensor的device属性来查看当前Tensor的存储位置

tensor_cpu=torch.tensor(np.arange(start=0,stop=4,step=1),device='cpu')
tensor_gpu=torch.tensor(np.arange(start=0,stop=4,step=1),device='cuda:0')
print(tensor_cpu.device)
print(tensor_gpu.device)
>>>
cpu
cuda:0

需要注意的是,我们安装的CUDA是英伟达为自己的GPU定制的使用GPU进行并行计算的框架,因此在这个框架下可以有很多的GPU,具体看自己的机器,cuda会为他们分别编号,从0开始

创建时指定存储位置

我们可以在任何创建Tensor的语句中指定device参数来指定存储的位置

tensor_cpu=torch.tensor(np.arange(start=0,stop=4,step=1),device='cpu')
tensor_gpu=torch.tensor(np.arange(start=0,stop=4,step=1),device='cuda:0')
print(tensor_cpu.device)
print(tensor_gpu.device)
>>>
cpu
cuda:0
将Tensor转移到GPU上

默认情况下创建的Tensor都保存在CPU上,我们可以调用Tensor类的cuda方法来转移到GPU上,具有多个GPU的时候需要指定GPU的序号,如果只有一个GPU则可以不用指定GPU

tensor_1=torch.tensor(np.arange(start=0,stop=4,step=1))
print(tensor_1.device)
print(tensor_1.cuda('cuda:0').device)
>>>
cpu
cuda:0

此外我们使用这种方法实际上是创建出一个和原Tensor一样的但是存储在GPU上的Tensor,因此原Tensor依旧会存在

tensor_1=torch.tensor(np.arange(start=0,stop=4,step=1))
print(tensor_1.device)
tensor_2=tensor_1.cuda('cuda:0')
print(tensor_1.device)
print(tensor_2.device)
>>>
cpu
cpu
cuda:0
将Tensor转移到CPU上

对于GPU Tensor,我们直接调用cpu方法即可,具体原理和上面一样都是新创建一个一样的CPU Tensor

tensor_1=torch.tensor(np.arange(start=0,stop=4,step=1),device="cuda:0")
print(tensor_1.device)
print(tensor_1.cpu().device)
>>>
cuda:0
cpu
使用to方法在多个设备间进行转移

我们可以使用to方法来将Tensor转移到指定的设备,需要为其传入设备名

tensor_1=torch.tensor(np.arange(start=0,stop=4,step=1))
print(tensor_1.device)
print(tensor_1.to("cuda:0").device)
>>>
cpu
cuda:0

最后需要参与运算的Tensor必须存放在同一个设备上才能够参与运算,否则会报错

数据类型dtype

在Pytorch中由于CPU和GPU在硬件上的不同导致我们使用不同的硬件在计算的时候的不同,所以Tensor存储在CPU和GPU上时候洗不同的

因此还有具有CPU类Tensor和GPU类Tensor

为了提高计算效率,Pytorch和Numpy类似都要求一个Tensor中的数据必须是同类型的

Pytorch中所有的数据类型如下

数据类型dtypeCPU TensorGPU Tensor
32位浮点型torch.float32
torch.float
torch.FloatTensortorch.cuda.FloatTensor
64位浮点型torch.float64
torch.double
torch.DoubleTensortorch.cuda.DoubleTensor
16位浮点型torch.float16
torch.half
torch.HalfTensortorch.cuda.HalfTensor
8位无符号整形torch.uint8torch.ByteTensortorch.cuda.ByteTensor
8位有符号整型torch.int8torch.CharTensortorch.cuda.CharTensor
16位有符号整型torch.int16
torch.short
torch.ShortTensortorch.cuda.ShortTensor
32位有符号整型torch.int32
torch.int
torch.IntTensortorch.cuda.IntTensor
64位有符号整型torch.int64
torch.long
torch.LongTensortorch.cuda.LongTensor
查看数据类型

我们可以直接调用dtype属性查看指定Tensor的数据类型

tensor_1=torch.tensor([0,1,2,3,4])
print(tensor_1.dtype)
>>>
torch.int64

形状shape

我们可以调用size属性来查看指定Tensor的形状

tensor_1=torch.ones((3,4))
print(tensor_1)
print(tensor_1.shape)
>>>
tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
torch.Size([3, 4])

维度ndim

我们可以调用ndim属性来查看指定tensor的维度数量

tensor_1=torch.ones((3,4))
print(tensor_1)
print(tensor_1.ndim)
>>>
tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
2

Tensor的操作

下面我们将讲解Tensor的一些操作

变形

我们在深度学习的过程中经常需要对Tensor进行变形,下面将介绍两种方法

Tensor.reshape方法

我们可以调用Tensor的reshape方法来改变形状

tensor_1=torch.arange(start=0,end=4,step=1)
print(tensor_1)
print(tensor_1.reshape((2,2)))
>>>
tensor([0, 1, 2, 3])
tensor([[0, 1],
        [2, 3]])

但是通过这种方法将会返回视图(具体见Numpy)

tensor_1=torch.arange(start=0,end=4,step=1)
print(tensor_1)
print(tensor_1.reshape((2,2)))
print(tensor_1)
>>>
tensor([0, 1, 2, 3])
tensor([[0, 1],
        [2, 3]])
tensor([0, 1, 2, 3])
Tensor.resize_方法

我们对Tensor使用resize_方法,将直接在原对象的基础上进行操作

tensor_1=torch.arange(start=0,end=4,step=1)
print(tensor_1)
tensor_1.resize_((2,2))
print(tensor_1)
>>>
tensor([0, 1, 2, 3])
tensor([[0, 1],
        [2, 3]])
Tensor.resize_as_方法

Tensor的resize_as_方法能够将Tensor变形为与指定Tensor形状相同的Tensor

tensor_1=torch.ones(size=(3,4))
tensor_2=torch.arange(start=0,end=12,step=1).resize_as_(tensor_1)
print(tensor_1)
print(tensor_2)
>>>
tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])

扩展

我们可以使用Tensor的expand方法来对Tensor的在某个维度上进行扩展

和变形类似,也有as方法

Tensor.expand方法

我们需要为expand方法指定扩展后的Tensor形状

需要注意的是,如果我们给定的是行向量,那么必须沿列方向进行扩展,如果给定列向量,就必须沿行进行扩展

tensor_1=torch.arange(start=0,end=4,step=1)
print(tensor_1)
print(tensor_1.expand(size=(4,4)))
print(tensor_1.expand(size=(3,-1)))
print(tensor_1.reshape(4,1).expand(size=(-1,3)))
>>>
tensor([0, 1, 2, 3])
tensor([[0, 1, 2, 3],
        [0, 1, 2, 3],
        [0, 1, 2, 3],
        [0, 1, 2, 3]])
tensor([[0, 1, 2, 3],
        [0, 1, 2, 3],
        [0, 1, 2, 3]])
tensor([[0, 0, 0],
        [1, 1, 1],
        [2, 2, 2],
        [3, 3, 3]])
Tensor.expand_as方法

和前面类似,使用expand_as_方法将会按照指定张量的形状来扩展当前张量

tensor_1=torch.arange(0,12,1).reshape((3,4))
tensor_2=torch.arange(0,4,1)
print(tensor_1)
print(tensor_2)
print(tensor_2.expand_as(tensor_1))
>>>
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])
tensor([0, 1, 2, 3])
tensor([[0, 1, 2, 3],
        [0, 1, 2, 3],
        [0, 1, 2, 3]])

重复

我们可以使用张良的repeat方法来将张量看做一个整体,然后在指定的方向上进行堆叠

需要为其传入在两个方向上重复的次数

tensor_1=torch.arange(0,12).reshape(2,6)
print(tensor_1.repeat(2,1))
print(tensor_1.repeat(1,2))
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11],
        [ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[ 0,  1,  2,  3,  4,  5,  0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11,  6,  7,  8,  9, 10, 11]])

压缩 / 解压缩

我们可以对指定的Tensor使用unsqueeze方法来在指定的位置插入新的维度来升维,反之可以用squeeze来降维

tensor_1=torch.arange(0,12,1).reshape(2,6)
print(tensor_1.shape)
print(tensor_1)
tensor_2=torch.unsqueeze(tensor_1,dim=0)
print(tensor_2.shape)
print(tensor_2)
>>>
torch.Size([2, 6])
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
torch.Size([1, 2, 6])
tensor([[[ 0,  1,  2,  3,  4,  5],
         [ 6,  7,  8,  9, 10, 11]]])

这里我们指定在第0个维度插入一个维度,那么tensor_1就会自动升维

我们使用squeeze方法就能移除指定的维度,缺省情况下将自动移除所有大小为1的维度

tensor_1=torch.arange(0,12,1).reshape(1,3,4)
print(tensor_1.shape)
print(tensor_1)
tensor_2=torch.squeeze(tensor_1)
print(tensor_2.shape)
print(tensor_2)
>>>
torch.Size([1, 3, 4])
tensor([[[ 0,  1,  2,  3],
         [ 4,  5,  6,  7],
         [ 8,  9, 10, 11]]])
torch.Size([3, 4])
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])

取值

下面我们将讲解从已有的Tensor中取值

索引与切片

PyTorch的索引与切片和Numpy一样,这里就不再赘述

三角与对角取值

我们可以使用torch.tril()函数获取矩阵的下三角内容,使用torch.tilu()函数获取上三角内容,使用torch.diag()获取矩阵的对角内容

但是使用三角与对角取值必须要针对2维的张量

tensor_1=torch.arange(0,25,1).reshape(5,5)
print(tensor_1)
print(torch.tril(tensor_1))
print(torch.triu(tensor_1))
print(torch.diagonal(tensor_1))
>>>
tensor([[ 0,  1,  2,  3,  4],
        [ 5,  6,  7,  8,  9],
        [10, 11, 12, 13, 14],
        [15, 16, 17, 18, 19],
        [20, 21, 22, 23, 24]])
tensor([[ 0,  0,  0,  0,  0],
        [ 5,  6,  0,  0,  0],
        [10, 11, 12,  0,  0],
        [15, 16, 17, 18,  0],
        [20, 21, 22, 23, 24]])
tensor([[ 0,  1,  2,  3,  4],
        [ 0,  6,  7,  8,  9],
        [ 0,  0, 12, 13, 14],
        [ 0,  0,  0, 18, 19],
        [ 0,  0,  0,  0, 24]])
tensor([ 0,  6, 12, 18, 24])

拼接与拆分

torch.cat函数

我们能够使用torch.cat函数来在指定方向上进行拼接

我们需要指定dim来说明拼接的方向

tensor_1=torch.arange(0,12,1).reshape(2,6)
tensor_2=torch.arange(12,0,-1).reshape(2,6)
print(tensor_1)
print(tensor_2)
print(torch.cat((tensor_1,tensor_2),dim=0))
print(torch.cat((tensor_1,tensor_2),dim=1))
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[12, 11, 10,  9,  8,  7],
        [ 6,  5,  4,  3,  2,  1]])
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11],
        [12, 11, 10,  9,  8,  7],
        [ 6,  5,  4,  3,  2,  1]])
tensor([[ 0,  1,  2,  3,  4,  5, 12, 11, 10,  9,  8,  7],
        [ 6,  7,  8,  9, 10, 11,  6,  5,  4,  3,  2,  1]])
torch.stack函数

我们可以使用Torch.stack函数实现和cat函数一样的效果,但是stack会将单个的Tensor视为一个整体,因此会得到额外的一个维度

tensor_1=torch.arange(0,12,1).reshape(2,6)
tensor_2=torch.arange(12,0,-1).reshape(2,6)
print(tensor_1)
print(tensor_2)
print(torch.stack((tensor_1,tensor_2),dim=0))
print(torch.stack((tensor_1,tensor_2),dim=1))
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[12, 11, 10,  9,  8,  7],
        [ 6,  5,  4,  3,  2,  1]])
tensor([[[ 0,  1,  2,  3,  4,  5],
         [ 6,  7,  8,  9, 10, 11]],

        [[12, 11, 10,  9,  8,  7],
         [ 6,  5,  4,  3,  2,  1]]])
tensor([[[ 0,  1,  2,  3,  4,  5],
         [12, 11, 10,  9,  8,  7]],

        [[ 6,  7,  8,  9, 10, 11],
         [ 6,  5,  4,  3,  2,  1]]])
torch.chunk函数

我们可以使用torch.chunk函数来将张量分割为指定数量的块,我们需要为chunk函数传入分割的对象,分割的块数以及分割的方向

chunk函数最后将会返回子Tensor构成的元祖

tensor_1=torch.arange(0,12,1).reshape(2,6)
print(tensor_1)
for tensor in torch.chunk(tensor_1,2,dim=0):
    print(tensor)
for tensor in torch.chunk(tensor_1,2,dim=1):
    print(tensor)
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[0, 1, 2, 3, 4, 5]])
tensor([[ 6,  7,  8,  9, 10, 11]])
tensor([[0, 1, 2],
        [6, 7, 8]])
tensor([[ 3,  4,  5],
        [ 9, 10, 11]])
torch.split函数

torch.split函数和chunk函数同理,但是能够指定分割的块的大小

我们需要传入分割的对象,各个块的大小和分割的维度

tensor_1=torch.arange(0,12,1).reshape(3,4)
print(tensor_1)
for tensor in torch.split(tensor_1,[1,2,1],dim=1):
    print(tensor)
for tensor in torch.split(tensor_1,[1,2],dim=0):
    print(tensor)
>>>
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])
tensor([[0],
        [4],
        [8]])
tensor([[ 1,  2],
        [ 5,  6],
        [ 9, 10]])
tensor([[ 3],
        [ 7],
        [11]])
tensor([[0, 1, 2, 3]])
tensor([[ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])

裁剪

我们经常会需要对张量进行裁剪,例如超过设定的数字一律变更为设定的数字等等

torch.clamp_max函数

我们可以使用torch.clamp_max函数实现超过最大值进行剪裁

tensor_1=torch.arange(0,12,1).reshape(2,6)
print(tensor_1)
print(torch.clamp_max(tensor_1,max=4))
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[0, 1, 2, 3, 4, 4],
        [4, 4, 4, 4, 4, 4]])
torch.clamp_min函数

我们可以使用torch.clamp_min函数来实现低于最小值进行裁剪

tensor_1=torch.arange(0,12,1).resize(2,6)
print(tensor_1)
print(torch.clamp_min(tensor_1,4))
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[ 4,  4,  4,  4,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
torch.clamp函数

我们可以使用torch.clamp函数来实现在指定范围内进行裁剪

tensor_1=torch.arange(start=0,end=12,step=1).resize(2,6)
print(tensor_1)
print(torch.clamp(tensor_1,min=3,max=8))
>>>

Tensor的计算

下面将讲解Tensor中的计算,主要包括Tensor间大小的比较,Tensor的基本运算(加减乘除,矩阵乘法等),统计相关的运算

大小比较

针对张量间的大小比较,Pytroch提供了一些列函数

函数功能
torch.allclose()比较两个元素是否相近
torch.eq()逐元素比较是否相等
torch.equal()判断两个张量是否具有相同的形状和元素
torch.ge()逐元素判断大于等于
torch.gt()逐元素判断大于
torch.le()逐元素判断小于等于
torch.lt()逐元素判断小于
torch.ne()逐元素判断不等于
torch.isnan()逐元素判断是否为缺失值

此外,我们判断两个元素是否相近,使用的公式是
∣ A − B ∣ ≤ a t o l + r t o l × ∣ B ∣ | A -B| \le {\rm atol} + {\rm rtol }\times |B| ABatol+rtol×B
这里A,B是我们输入的两个Tensor,atol和rtol使我们指定的参数

基本计算

四则运算

Tensor的计算可以分为两种,第一种是逐元素的计算,第二种是矩阵的运算,例如矩阵的相乘,矩阵的转置,矩阵的迹等等

四则计算和Numpy一样,都用封装器装饰起来了,这里不再赘述

此外对于四则计算,PyTorch也支持广播规则,详见我的Numpy学习专栏

科学运算

我们可以使用torch.exp函数,torch.log函数,torch.sqrt函数与torch.rsqrt函数分别计算ex,ln(x), x \sqrt x x , 1 x \frac{1}{\sqrt x} x 1

tensor_1=torch.arange(1,3,1,dtype=torch.float)
print(torch.exp(tensor_1))
print(torch.log(tensor_1))
print(torch.sqrt(tensor_1))
print(torch.rsqrt(tensor_1))
>>>
tensor([2.7183, 7.3891])
tensor([0.0000, 0.6931])
tensor([1.0000, 1.4142])
tensor([1.0000, 0.7071])
矩阵运算
矩阵转置

我们可以使用torch.t函数来计算矩阵的转置

tensor_1=torch.arange(0,12,1).resize_(2,6)
print(tensor_1)
print(torch.t(tensor_1))
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[ 0,  6],
        [ 1,  7],
        [ 2,  8],
        [ 3,  9],
        [ 4, 10],
        [ 5, 11]])
矩阵乘法

我们可以使用torch.matmul函数来计算两个矩阵的乘积

tensor_1=torch.arange(0,12,1).resize_(2,6)
tensor_2=torch.arange(0,12,1).resize_(6,2)
print(tensor_1)
print(tensor_2)
print(torch.matmul(tensor_1,tensor_2))
>>>
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]])
tensor([[ 0,  1],
        [ 2,  3],
        [ 4,  5],
        [ 6,  7],
        [ 8,  9],
        [10, 11]])
tensor([[110, 125],
        [290, 341]])
矩阵的逆

我们可以使用torch.inverse函数计算矩阵的逆

tensor_1=torch.tensor([[1,0,0],[0,1,0],[0,0,1]],dtype=torch.float)
print(tensor_1)
print(torch.inverse(tensor_1))
>>>
tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.]])
tensor([[1., 0., -0.],
        [0., 1., -0.],
        [0., 0., 1.]])

需要注意的是,并不是所有的矩阵都可逆,当计算不可能矩阵的时候就会报错,而且我们需要指定矩阵中的元素的数据类型为浮点型,否则计算的时候就会报错,因为计算的时候会出现除法等

矩阵的迹

我们可使用torch.trace函数来计算矩阵的迹

矩阵的对角线元素的和成为矩阵的迹

tensor_1=torch.arange(0,25).resize_(5,5)
print(tensor_1)
print(torch.trace(tensor_1))
>>>
tensor([[ 0,  1,  2,  3,  4],
        [ 5,  6,  7,  8,  9],
        [10, 11, 12, 13, 14],
        [15, 16, 17, 18, 19],
        [20, 21, 22, 23, 24]])
tensor(60)
统计计算

下面我们将讲解统计相关的计算

取最大值

我们可以使用torch.max函数来获取张量的最大值

tensor_1=torch.randint(low=0,high=10,size=(3,5))
print(tensor_1)
print(torch.max(tensor_1))
>>>
tensor([[0, 8, 2, 8, 2],
        [7, 5, 0, 0, 8],
        [1, 9, 6, 1, 0]])
tensor(9)
取最小值

我们可以用torch.min函数来获取张量的最小值

tensor_1=torch.randint(low=0,high=10,size=(3,5))
print(tensor_1)
print(torch.min(tensor_1))
>>>
tensor([[2, 9, 4, 3, 9],
        [3, 9, 3, 9, 8],
        [5, 3, 2, 8, 5]])
tensor(2)
取最大值位置

我们可以使用torch.argmax函数来获取最大值的位置,使用这种方式将会将数组按行展开为一行,然后从0开始给出最大值的位置,有多个值时取最后一个值的位置

tensor_1=torch.randint(low=0,high=10,size=(3,5))
print(tensor_1)
print(torch.argmax(tensor_1))
>>>
tensor([[0, 9, 9, 3, 1],
        [8, 1, 7, 8, 4],
        [1, 0, 4, 0, 4]])
tensor(2)
取最小值的位置

我们可以使用torch.argmin来获取最小值的位置

tensor_1=torch.randint(low=0,high=10,size=(3,5))
print(tensor_1)
print(torch.argmin(tensor_1))
>>>
tensor([[9, 7, 0, 0, 1],
        [4, 9, 9, 5, 9],
        [2, 2, 2, 1, 4]])
tensor(3)

此外,对于上面两个获取位置的函数,我们可以指定dim函数来获取每一行或每一列的最值

获取前k大的值

我们可以使用torch.topk函数来获取第k大的值与位置,没有指定dim参数时会自动求每行的前k个最大值

tensor_1=torch.randint(low=0,high=10,size=(3,5))
print(tensor_1)
print(torch.topk(tensor_1,3))
>>>
tensor([[9, 1, 1, 0, 3],
        [0, 6, 7, 6, 9],
        [1, 6, 4, 1, 0]])
torch.return_types.topk(
values=tensor([[9, 3, 1],
        [9, 7, 6],
        [6, 4, 1]]),
indices=tensor([[0, 4, 2],
        [4, 2, 3],
        [1, 2, 0]]))
获取第k小的值

我们可以使用torch.kthvalue来获取第k小的值

同理,没有指定dim的值的时候,默认取行的第k小值

tensor_1=torch.randint(low=0,high=10,size=(3,5))
print(tensor_1)
print(torch.kthvalue(tensor_1,2))
>>>
tensor([[5, 2, 5, 8, 1],
        [8, 1, 2, 8, 0],
        [3, 8, 9, 2, 7]])
torch.return_types.kthvalue(
values=tensor([2, 1, 3]),
indices=tensor([1, 1, 0]))
排序

torch.sort函数可以对张量进行排序,输出排序之后的结果,还会输出排序后的结果在原Tensor的位置

同样没有指定dim参数的时候默认会按照行进行排序

tensor_1=torch.randint(low=0,high=10,size=(3,5))
print(tensor_1)
print(torch.sort(tensor_1))
>>>
tensor([[0, 8, 8, 6, 8],
        [5, 8, 6, 5, 2],
        [4, 2, 7, 2, 4]])
torch.return_types.sort(
values=tensor([[0, 6, 8, 8, 8],
        [2, 5, 5, 6, 8],
        [2, 2, 4, 4, 7]]),
indices=tensor([[0, 3, 1, 2, 4],
        [4, 0, 3, 2, 1],
        [1, 3, 0, 4, 2]]))
获取统计值

我们可以使用torch.mean(),torch.sum(),torch.cumsum(),torch.median(),torch.cumprod(),torch.std()函数来获取张量的统计值

tensor_1=torch.randint(low=0,high=10,size=(3,5),dtype=torch.float)
print(tensor_1)
print(torch.mean(tensor_1))
print(torch.sum(tensor_1))
print(torch.cumsum(tensor_1,dim=1))
print(torch.median(tensor_1))
print(torch.cumprod(tensor_1,dim=1))
print(torch.std(tensor_1))
>>>
tensor([[1., 7., 2., 2., 2.],
        [3., 3., 6., 7., 8.],
        [0., 5., 0., 3., 5.]])
tensor(3.6000)
tensor(54.)
tensor([[ 1.,  8., 10., 12., 14.],
        [ 3.,  6., 12., 19., 27.],
        [ 0.,  5.,  5.,  8., 13.]])
tensor(3.)
tensor([[1.0000e+00, 7.0000e+00, 1.4000e+01, 2.8000e+01, 5.6000e+01],
        [3.0000e+00, 9.0000e+00, 5.4000e+01, 3.7800e+02, 3.0240e+03],
        [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])
tensor(2.5857)

注意,我们计算cum类累和或者累积的时候需要指定dim参数,即计算的方向

总结

至此,我们终于讲解完了作为PyTorch的基础,即作为计算对象的Tensor

Autograd

PyTorch中的第二个概念就是Autograd,即自动求导机制

在CS231的笔记中,我们讲到网络的本质就是一个函数,接受图片、文本、图片等不同的输入,输出为图片的分类信息等等,我们对网络进行优化的过程就是寻找最好的函数能够最准确的预测我们的目标

更具体的说就是网络中会有诸多参数,我们优化的过程就是求出最佳的参数。而参数的具体存在形式就是矩阵,因此我们的优化对象就是对参数矩阵进行优化,来得到最准确的网络

优化的过程就是每次计算误差,然后根据误差来修改参数,从而不断提高模型的准确率;在实践层面就是根据损失计算参数矩阵的梯度进行更新

而网络本身这个函数难以写出表达式直接给出解析解,因此我们采用数值的方法,进行优化

在采用数值方法计算梯度的时候,我们使用反向传播来减少计算量,因此我们在代码中需要手动计算梯度

手动计算梯度往往会比较复杂

PyTorch中提供了Autograd来帮助我们追踪需要求梯度的对象,自动计算梯度

如何使用Autograd

我们只需要在申明Tensor的时候指定requires_grad参数为True

最后计算完成之后调用backward方法即可

例如我们计算 y = x 2 + x + 1 y=x^2+x+1 y=x2+x+1的y关于x的导数

那么我们申明x的时候指定requires_grad参数为True,然后我们需要求y关于x的导数的时候,我们只需要对y调用backward方法,然后

x=torch.tensor([2],dtype=torch.float,requires_grad=True)
y=x**2+x+1

y.backward()
print(x.grad)
>>>
tensor([5.])

不同维度Tensor的梯度

下面我们来看下不同形状的Tensor的梯度

x1=torch.arange(0,4,1,dtype=torch.float).resize_(2,2)
x2=torch.arange(0,2,1,dtype=torch.float).resize_(2,1)
x1.requires_grad=True
x2.requires_grad=True
y=torch.sum(torch.matmul(x1,x2))
y.backward()

print(y)
print()
print(x1)
print(x1.grad)
print()
print(x2)
print(x2.grad)

>>>
tensor(4., grad_fn=<SumBackward0>)

tensor([[0., 1.],
        [2., 3.]], requires_grad=True)
tensor([[0., 1.],
        [0., 1.]])

tensor([[0.],
        [1.]], requires_grad=True)
tensor([[2.],
        [4.]])

我们能够发现Tensor的梯度的形状和Tensor相同

Torch.nn

Torch.nn是Pytorch的一个模块,其中包含着PyTorch中已经准备好的层,我们可以利用这些层来直接构建我们的时间网络

或者我们利用nn模块来构建我们自己的层

下面我们将介绍卷积层,池化层,激活函数层,循环层,全连接层等相关的用法

Torch.nn模块中将所有的层都用类来实现,而每一个层的类的具体实现中又有forward和backward方法

卷积层

卷积可以看作是输入和卷集合之间的内积运算

关于卷积的好处,如何进行的,工程实践中的im2col技巧等等这里都不再讲述,具体详见我的CS231学习笔记

这里仅讲解如何使用这些卷积层的类

层对应的类功能与作用
torch.nn.Conv1d()对输入信号上运用1D卷积
torch.nn.Conv2d()对输入信号上运用2D卷积
torch.nn.Conv3d()对输入信号上运用3D卷积
torch.nn.ConvTranspose1d()对输入信号进行1D转置卷积
torch.nn.ConvTranspose2d()对输入信号进行2D转置卷积
torch.nn.ConvTranspose3d()对输入信号进行3D转置卷积

我们以最常见的二位卷积为例,其函数签名为

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1,groups=1,bias=True)

其中:

  • in_channels: 输入的图像的通道数
  • out_channels: 输出的FM的数量
  • kernel_size: 卷积核的大小
  • stride: 步长
  • padding: 边框
  • dilation: 卷集合元素的步幅,可用于调整空洞卷积的大小
  • groups: 输入通道到输出通道的阻塞连接数
  • bias: 是否添加偏执项

如何使用卷积层

下面我们将使用lena的图片来讲解如何进行卷积

我们使用卷积层首先需要对卷积层进行初始化,而后传入被卷积的对象

准备工作

首先,导入如下的包

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

我们首先使用PIL查看lena的图片

myImage=Image.open("lena.jpg")
display(myImage)

在这里插入图片描述

接下来我们使用PIL类中的Image对象的convert方法来将其转化为灰度图像,需要注意的是这里转换之后将灰度图像转换为np的ndarray对象,便于后续操作

myImageGray=np.array(myImage.convert('L'),dtype=np.float32)
plt.imshow(myImageGray,cmap=plt.cm.gray)
plt.show()

在这里插入图片描述

接下来我们对这张图片进行卷积,但是首先需要把图片转化为 1 × 1 × 512 × 512 1\times1\times512\times512 1×1×512×512大小的张量

因为我们卷积的输入必须是[batch, channels, height, width]这样的四维张量

imageHeight, imageWidth=myImageGray.shape
myImage_tensor=torch.from_numpy(myImageGray.reshape(1,1,imageHeight,imageWidth))
print(myImage_tensor.shape)
>>>
torch.Size([1, 1, 512, 512])

准备工作的最后一步就是初始化需要用的卷积核,便于在后面初始化卷积层的时候传入,因为我们这个示例是提取图像的边缘

因此需要特定的卷积核,不设置的话默认会使用正态分布随机初始化

#设置一个用于提取边缘的卷积核
kernel=torch.ones(size=(5,5),dtype=torch.float)*-1
kernel[2,2]=24
初始化卷积层

接下来我们首先初始化一个卷积层 / 实例化一个卷积层,传入该层必要的参数,实例化一个卷积层

#初始化卷积层
conv2d=nn.Conv2d(in_channels=1,out_channels=1,kernel_size=(5,5),stride=1,padding=0,bias=False)
#设置卷积核,这里我们设置的输出通道为1,因此只需要传入一个卷积核即可
conv2d.weight.data[0]=kernel
计算卷积

接下来我们为上面实例化的卷积层传入卷积的对象即可

注意我们进行卷积计算在代码角度来说就是定义了自己的类,所以我们

#进行卷积
myImageConvolution=conv2d(myImage_tensor)
可视化结果

上面已经完成了卷积操作,接下来我们可视化卷积的结果

由于输出是 1 × 1 × 508 × 508 1\times1\times508\times508 1×1×508×508的数组,因此我们需要将其降为 508 × 508 508\times508 508×508的图像,然后再进行可视化

卷积之后会将得到卷积之后的图片和原图放在一个数组里,我们直接按照索引调用即可

#可视化结果
image=myImageConvolution.data.squeeze()
plt.figure(figsize=(12,6))
Fig,Axes=plt.subplots(nrows=1,ncols=2)
Axes[0].imshow(image.data,cmap=plt.cm.gray)
Axes[0].axis('off')
Axes[1].imshow(myImageGray,cmap=plt.cm.gray)
Axes[1].axis('off')

在这里插入图片描述

我们使用随机初始化的正态分布卷积核再进行一次同样的过程,那么最后得到的图片如下

在这里插入图片描述

发现几乎没有什么变化,只不过因为是进行标准正态分布的卷积核,因此我们肉眼可察觉的变化就是图像对比度降低

池化层

上面讲解了PyTorch中的卷积层,下面将讲解PyTorch中的池化层

PyTorch的nn模块中提供了多种池化的类,分别是最大值池化(Max Pooling),反最大值池化(UnMax Pooling),平均池化(Average Pooling)和自适应池化(Adaptive Pooling)

具体的nn模块中所有的池化层的类如下

功能
torch.nn.MaxPool1d()针对输入信号上应用1D最大值池化
torch.nn.MaxPool2d()针对输入信号上应用2D最大值池化
torch.nn.MaxPool3d()针对输入信号上应用3D最大值池化
torch.nn.MaxUnPool1d()1D最大值池化的逆运算
torch.nn.MaxUnPool2d()2D最大值池化的逆运算
torch.nn.MaxUnPool3d()3D最大值池化的逆运算
torch.nn.AvgPool1d()针对输入信号上运用1D平均值池化
torch.nn.AvgPool2d()针对输入信号上运用2D平均值池化
torch.nn.AvgPool3d()针对输入信号上运用3D平均值池化
torch.nn.AdaptiveMaxPool1d()针对输入信号上运用1D自适应最大值池化
torch.nn.AdaptiveMaxPool2d()针对输入信号上运用2D自适应最大值池化
torch.nn.AdaptiveMaxPool3d()针对输入信号上运用3D自适应最大值池化
torch.nn.AdaptiveAvgPool1d()针对输入信号上运用1D自适应平均值池化
torch.nn.AdaptiveAvgPool2d()针对输入信号上运用2D自适应平均值池化
torch.nn.AdaptiveAvgPool3d()针对输入信号上运用1D自适应平均值池化

和卷积层一样,下面以torch.nn.MaxPool2d()为例讲解池化操作相关参数的应用

torch.nn.MaxPool2d(kernel_size, stride=None,Padding=0,dilation=1, return_indices=False,ceil_mode=False)

其中:

  • kernel_size: 池化窗口的大小
  • stride: 池化窗口移动的步长,默认为kernel_size
  • padding: 输入每一条边补充的0的层数
  • dilation: 控制窗口中元素的步幅的参数
  • return_indices: 是否返回每个窗口中最大值的索引,如果返回将会有助于UnPooling
  • ceil_mode: 是否进行向上取整

同样,我们为池化层的输入也是一个四维的数组: [batch_size, channel_number, Height, Width]

下面我们将在上面卷积的基础上继续进行池化来讲解如何使用池化

如何使用池化

接下来我们在上面得到的卷积后的边缘图上继续进行池化

进行池化需要像上面进行卷积一样首先初始化池化层,然后为池化层传入需要进行池化的图像即可

准备工作

我们首先准备需要被池化的对象,注意必须是Tensor类型的,因此我们不能使用前面的已经squeeze过得image

#准备部分
myImageEdged=myImageConvolution.data
maxPool2d=nn.MaxPool2d(kernel_size=(2,2),stride=2)
进行池化

我们下面直接传入我们需要被卷积的对象即可

#进行池化
myImageEdgedPooled=maxPool2d(myImageEdged)
可视化结果

我们下面可视化结果,来查看池化的效果

#进行可视化
plt.figure(figsize=(12,6))
Fig,Axes=plt.subplots(nrows=1,ncols=2)
Axes[0].imshow(myImageEdgedPooled.squeeze(),cmap=plt.cm.gray)
Axes[1].imshow(image,cmap=plt.cm.gray)
plt.show()

在这里插入图片描述

我们发现左侧经过最大值池化的图片的边缘信息相比于右侧输入前的图片有了很大的提升

最后,对于自适应池化的时候,我们只需要指定输出的图像的尺寸即可

激活层

下面我们将讲解PyTorch中的激活层

PyTorch中提供了十几种激活函数,我们这里之讲解最常见的四种激活函数层

作用
torch.nn.SigmoidSigmoid激活函数
torch.nn.tanhTanh激活函数
torch.nn.ReLuRelu激活函数
torch.nn.SoftplusRelu激活函数的平滑近似

和前面的层类似,激活层同样首先需要初始化,接下来传入需要被激活的对象即可

如何使用激活层

下面我们将讲解如何使用激活层函数

准备工作

我们首先准备需要激活的数据已经初始化各层

#准备工作
myImageEdgedPooled=myImageEdgedPooled.squeeze()
Sigmoid=nn.Sigmoid()
Tanh=nn.Tanh()
ReLu=nn.ReLU()
Softplus=nn.Softplus()
进行激活
myImageSigmoid=Sigmoid(myImageEdgedPooled).squeeze()
myImageTanh=Tanh(myImageEdgedPooled).squeeze()
myImageReLu=ReLu(myImageEdgedPooled).squeeze()
myImSoftplus=Softplus(myImageEdgedPooled).squeeze()
可视化结果

接下来我们将可视化激活的结果

Fig,Axes=plt.subplots(nrows=2,ncols=2,figsize=(12,12))
for i in range(2):
    for j in range(2):
        Axes[i][j].plot(label=labels[i])
        Axes[i][j].imshow(myImageActivated[i],cmap=plt.cm.gray)
        Axes[i][j].axis('off')
plt.show()

在这里插入图片描述

基本上看不出来效果,因为我们只经过了一层卷积,实际上经过了更多层之后我们再可视化结果出来将会非常明显

全连接层

下面将讲解PyTorch中如何使用全连接层

全连接层可以视为线性层加上一个激活层,因此这里将主要讲解线性层

线性层主要使用torch.nn.Linear()函数

其函数签名如下

torch.nn.Linear(in_features, out_features, bias=True)

其中:

  • in_features: 输入样本的特征数量
  • out_features: 输出样本的特征数量
  • bias: 是否学习偏执项,如果设置为True,就会学习偏执项

循环层

PyTorch中提供了三种循环层的实现,其中根据循环的次数,每种实现又分为多层和单层

功能
torch.nn.RNN多层RNN单元
torch.nn.LSTM多层长短期记忆LSTM单元
torch.nn.GRU多层门限循环GRU单元
torch.nn.RNNCell一个RNN循环层单元
torch.nn.LSTMCell一个长短期记忆LSTM单元
torch.nn.GRUCell一个门限循环GRU单元

下面我们将以torch.nn.RNN来讲解各个参数

torch.nn.RNN(input_size, hidden_size, num_layers, nonlinearity, bias, batch_first, dropout, bidrectional)

其中:

  • input_size: 输入的x的特征数量
  • hidden_size: 隐藏层的特征数量
  • num_layers: RNN的中间网络层数
  • nonlinearity: 指定激活函数,默认是tanh
  • bias: 是否使用偏执项,默认为True
  • batch_first: 是否将batch_num作为输入的第一个维度,如果为True,则输入为[batch_size,time_step,feature]
  • dropout: 是否使用dropout,默认为0,不使用,如果使用则指定0~1之间的数字即可
  • bidrectional: 如果为True,则变成双向RNN

关于RNN在后面的实例讲解中会详细讲解

定义自己的层:继承Module

前面讲过,torch.nn模块中每一个层都是一个类,因此我们想要定义自己的层或者模块的话,也需要写一个类

我们首先讲解通过继承Module的方法来定义自己的层

我们下面将自己定义一个完整的全连接层出来,因为torch.nn中的模块只有Linear和Activation,我们自己定义的层中将会将这两个层组合起来形成层一个完整的全连接层

如何定义自己的层

我们下面将通过继承nn.Module这个父类,但是为了避免我们给自己定义的层初始化的时候赋值的init和父类的init冲突

因此我们会使用super钻石继承确保在初始化的时候不会和父类的init冲突

其次,上面所有的层都具有前向传播和反向传播的功能,因此我们自己定义的层将会重写nn.Module类中的forward方法

此外,如果我们自己定义的层不是由PyTorch中可以反向传播的层,那么我们还要自己手写backward方法

初始化

我们在初始化的时候使用super钻石继承,并且初始化所有需要的层

class FullFullyConnected(nn.Module):
    def __init__(self):
        #使用钻石继承,确保不会冲突
        super(FullFullyConnected,self).__init__()
        #定义第一个隐藏层
        self.hidden1=nn.Linear(in_features=10,out_features=10,bias=True)
        #定义第一个激活层
        self.active1=nn.ReLU()
定义前向传播

接下来我们要在类中重写forward方法

    def forward(self,x):
        x=self.hidden1(x)
        x=self.active1(x)
        return x

最后,我们使用继承nn.Module的方式定义自己的层如下

class FullFullyConnected(nn.Module):
    def __init__(self):
        #使用钻石继承,确保不会冲突
        super(FullFullyConnected,self).__init__()
        #定义第一个隐藏层
        self.hidden1=nn.Linear(in_features=10,out_features=10,bias=True)
        #定义第一个激活层
        self.active1=nn.ReLU()
        
    
    def forward(self,x):
        x=self.hidden1(x)
        x=self.active1(x)
        return x   

如何使用自己的层

我们使用自己定义的层的方法和前面使用其他已有的层一样,都需要先初始化,而后再传入参数

由于我们这里定义的层不接受参数,因此直接初始化即可

tensor_1=torch.randn(size=(10,10))
FC1=FullFullyConnected()
tensor_fc=FC1(tensor_1)
print(tensor_1)
print(tensor_fc)
>>>
tensor([[ 0.4629,  0.0837, -1.2839,  0.4527, -0.6590,  2.0947,  0.1450,  1.8047,
         -0.6084, -1.4890],
        [-0.8538,  0.6132, -0.3397, -0.8609, -1.8449,  1.3515, -0.1282, -0.6317,
          0.1421, -2.1947],
        [-1.6875,  0.0892,  1.2954,  0.5101, -0.0839, -0.2016,  0.7128,  1.1543,
         -0.8651,  1.2375],
        [-1.7278, -0.0663, -0.4331, -0.1501,  2.7511, -1.0149, -0.7330, -0.0480,
          1.4899, -0.8567],
        [-0.7402,  0.5390, -2.6320, -0.6609,  0.4869, -1.7148, -1.0466,  0.0344,
          0.6116, -0.1393],
        [ 0.2615,  1.1694,  0.2150, -1.1122,  1.4637,  0.2205, -1.3508,  1.6452,
          1.1957, -0.4391],
        [-0.9802, -1.0784, -3.0049,  0.1965, -0.3353,  0.9292, -0.4944, -0.1350,
         -1.0272, -0.5549],
        [ 1.1813, -0.1011, -1.0880,  1.1272,  0.5243, -0.8886,  2.7688, -0.8650,
          0.3541, -0.5068],
        [-0.3272, -0.9878,  0.8594, -1.3090, -0.7113,  0.7571, -0.0180,  0.0554,
         -0.7975, -0.0180],
        [-0.9507, -0.2011, -0.3700,  0.1737, -1.3402,  0.2870, -0.2274, -1.6156,
          0.3843,  0.2390]])
tensor([[0.0000, 0.0000, 0.4476, 0.0000, 0.8943, 0.0000, 0.2417, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.7750, 0.0000, 0.0358, 0.1904, 0.7234, 0.0000, 0.0000,
         0.0000],
        [1.2237, 1.1890, 0.0000, 0.1504, 0.8442, 0.2634, 0.3370, 0.0000, 0.4271,
         0.5127],
        [0.4093, 0.0000, 0.0000, 0.1421, 0.0000, 0.0000, 0.0000, 0.4103, 0.0000,
         0.0000],
        [0.4802, 0.0000, 0.0000, 0.1308, 0.0000, 1.2301, 0.0376, 0.5475, 0.0000,
         0.0000],
        [0.0000, 0.2749, 0.4855, 0.8600, 0.0514, 0.0000, 0.3226, 0.0000, 0.2008,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.3299, 0.8485, 0.0000, 0.0307, 0.0000,
         0.0000],
        [0.5818, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.6146, 0.1734, 0.8528,
         0.2822],
        [0.0000, 0.0749, 0.1786, 0.0797, 0.0000, 0.0000, 0.2563, 0.0000, 0.3372,
         0.3674],
        [0.3993, 0.1307, 0.0000, 0.0000, 0.0000, 0.9444, 0.3101, 0.0000, 0.0000,
         0.2424]], grad_fn=<ReluBackward0>)

定义自己的层:nn.Sequential函数

上面我们通过继承nn.Module的方式定义了自己的层,而我们自己定义类的时候实际上可以将多个层聚合在一起,最终实现编写模块

实际上我们还可以通过nn.Sequential函数来实现更加简洁的定义自己的层

而且我们能够在使用继承Module编写类的时候运用Sequential函数

如何定义自己的层

nn.Sequential函数接受定义好的层(实际上是类)作为参数,因此我们自己定义的类也是可以的

然后默认就会按照传入的次序进行计算

所以我们只需要按顺序传入即可

FullFullyConnected_sq=nn.Sequential(nn.Linear(10,10,bias=True),nn.ReLU())

如何使用自己定义的层

我们直接使用即可

tensor_fc=FullFullyConnected_sq(tensor_1)

print(tensor_1)
print(tensor_fc)
>>>
tensor([[ 0.4629,  0.0837, -1.2839,  0.4527, -0.6590,  2.0947,  0.1450,  1.8047,
         -0.6084, -1.4890],
        [-0.8538,  0.6132, -0.3397, -0.8609, -1.8449,  1.3515, -0.1282, -0.6317,
          0.1421, -2.1947],
        [-1.6875,  0.0892,  1.2954,  0.5101, -0.0839, -0.2016,  0.7128,  1.1543,
         -0.8651,  1.2375],
        [-1.7278, -0.0663, -0.4331, -0.1501,  2.7511, -1.0149, -0.7330, -0.0480,
          1.4899, -0.8567],
        [-0.7402,  0.5390, -2.6320, -0.6609,  0.4869, -1.7148, -1.0466,  0.0344,
          0.6116, -0.1393],
        [ 0.2615,  1.1694,  0.2150, -1.1122,  1.4637,  0.2205, -1.3508,  1.6452,
          1.1957, -0.4391],
        [-0.9802, -1.0784, -3.0049,  0.1965, -0.3353,  0.9292, -0.4944, -0.1350,
         -1.0272, -0.5549],
        [ 1.1813, -0.1011, -1.0880,  1.1272,  0.5243, -0.8886,  2.7688, -0.8650,
          0.3541, -0.5068],
        [-0.3272, -0.9878,  0.8594, -1.3090, -0.7113,  0.7571, -0.0180,  0.0554,
         -0.7975, -0.0180],
        [-0.9507, -0.2011, -0.3700,  0.1737, -1.3402,  0.2870, -0.2274, -1.6156,
          0.3843,  0.2390]])
tensor([[0.0000, 0.1729, 0.0000, 0.0000, 0.0000, 0.0000, 0.1613, 1.4986, 0.0000,
         0.1095],
        [0.0413, 0.9777, 0.1765, 0.0000, 0.0000, 0.0000, 1.1156, 0.1326, 0.3105,
         0.2201],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.3235, 0.0000, 0.0000, 0.0000,
         0.6702],
        [1.3102, 0.8750, 0.0000, 1.1329, 0.6050, 0.2187, 0.0000, 0.0000, 0.6006,
         0.0000],
        [1.2826, 0.0000, 0.0000, 0.3812, 0.7152, 0.0000, 0.0000, 0.0000, 0.6444,
         0.0000],
        [0.3345, 0.0000, 0.6984, 0.6998, 0.0000, 0.5939, 0.3767, 0.2992, 0.0000,
         0.2814],
        [0.2024, 0.5153, 0.0000, 0.0000, 0.5810, 0.0000, 0.0000, 0.8145, 0.8486,
         0.0000],
        [0.1304, 0.6713, 0.0000, 0.9815, 0.8314, 0.0000, 0.0000, 0.0000, 0.8190,
         0.0000],
        [0.0000, 0.5890, 0.1878, 0.0000, 0.0000, 0.0000, 0.3286, 0.1269, 0.1274,
         0.2809],
        [0.0000, 0.4306, 0.0000, 0.0000, 0.0931, 0.0000, 0.3725, 0.0000, 1.2228,
         0.0000]], grad_fn=<ReluBackward0>)

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值