Linux深入浅出PyTorch(一)安装及基础知识

PyTorch安装

工具使用

1. 开发工具建议使用pycharm

官网链接: PyCharm: 面向专业开发者的Python IDE
可以下载社区版和专业版,社区版免费,专业版收费。
但是如果是学生或者老师,可以申请JetBrains的免费教育许可证,具体申请条件看官网:
链接: 免费教育许可证
我选择的是社区版(一般使用足够)

2. 安装包管理工具建议使用Anaconda

Anaconda集成了常用于科学分析(机器学习, 深度学习)的大量package,并且借助于conda我们可以实现对虚拟Python环境的管理,可以非常方便的用于机器学习及深度学习。
链接: Anaconda官网
我选择的是默认安装

3. 安装结果检查

安装好之后打开终端,分别输入以下命令可以看到conda的版本、conda默认的python的版本和已经创建的虚拟环境。

在这里插入图片描述

Pytorch安装

也可以在默认base环境中安装,但是为了防止太乱,Anaconda可以创建一个个不同的环境把这些包分开,比如需要一个tensorflow的环境,那就创建一个环境在这个环境内下载安装tensorflow,然后另一个环境装pytorch。

退出某一环境命令如下:

conda deactivate

虚拟环境创建命令如下:

conda create -n pytorch

注:1)输入conda deactivate后,退出base环境,终端行前面没有"(base)"字样即为退出
进入某一环境的命令为:

conda activate env_name # env_name就是你的环境名

如图:
在这里插入图片描述

2)如果想每次打开终端,不自动进入任何环境(现在打开终端自动进入base环境)可以设置如下操作:

conda config --set auto_activate_base false

设置好之后重新进入终端即可。

2. 配置pytorch 虚拟环境

首先终端进入pytorch环境:

conda activate pytorch

1)查看现在环境中所有已安装的包

conda list

从图一可以看到我的环境里面已经创建了一个专门用于pytorch的虚拟环境:pytorchenv,我主要是为运行MADDPG算法创建的环境,环境配置如下:

  • python(3.6.13)
  • OpenAI gym(0.10.5)
  • tensorflow(2.6.2)
  • numpy(1.19.5)
  • pytorch(1.10.2)

2)安装python 3.6版本

conda install python==3.6

在弹出的询问y/n中,选择 y

3)安装pytorch
首先进入pytorch官网查看自己电脑适合哪个版本:
链接: PyTorch官网
可以看到,官网会根据电脑配置给出安装pytorch的命令:
在这里插入图片描述
复制命令到终端下载即可。

测试如下:
在pytorchenv环境下打开python,import torch没有出错:
在这里插入图片描述
4)其他命令
如果想删除某个虚拟环境,则在终端中输入:

conda remove -n env_name --all # env_name用要删除的虚拟环境的名字替换即可

如果想卸载某个包,则在对应的虚拟环境终端中输入:

conda remove package_name # package_name用要删除的包的名字替换

其他不常用命令见Anaconda官网:
链接: Command Reference

3. 在PyCharm中配置PyTorch虚拟环境

  1. 打开PyCharm,新创建一个project: pytorch_learning

  2. 进入这个project,进入File->Settings->Project:pytorch_learning->Python Interpreter:
    在这里插入图片描述
    在这里插入图片描述

  3. 用Add Interpreter选择pytorchenv虚拟环境中的python3.6:
    在这里插入图片描述

至此,配置完成,可以在PyCharm里愉快的使用pytorchenv虚拟环境了,如果还想安装其他包,比如matplotlib,再次打开终端进入pytorch虚拟环境,用安装命令安装即可。

丰富的PyTorch学习资源

  1. Awesome-pytorch-list:目前已获12K Star,包含了NLP,CV,常见库,论文实现以及Pytorch的其他项目。
  2. PyTorch官方文档:官方发布的文档,十分丰富。
  3. PyTorch中文文档
  4. Pytorch-handbook:GitHub上已经收获14.8K,pytorch手中书。
  5. PyTorch官方社区:PyTorch拥有一个活跃的社区,在这里你可以和开发pytorch的人们进行交流。
  6. PyTorch官方tutorials:官方编写的tutorials,可以结合colab边动手边学习
  7. 动手学深度学习:动手学深度学习是由李沐老师主讲的一门深度学习入门课,拥有成熟的书籍资源和课程资源,在B站,Youtube均有回放。
  8. Awesome-PyTorch-Chinese:常见的中文优质PyTorch资源

张量

1. 张量介绍

在深度学习中,通常将数据以张量的形式进行表示。如:三维张量表示一个RGB图像,四维张量表示视频。

几何代数中定义的张量,是基于向量和矩阵的推广。

张量维度 代表含义
0维 代表标量(数字)
1维 代表向量
2维 代表矩阵
3维 时间序列数据、股价、文本数据、单张彩色图片(RGB)

张量是现代机器学习的基础,核心是一个容器,可以包含数字和字符串,但是包含字符串的情况比较少。所以,可以将张量想象成一个数字的水桶。
一些存储在各种类型张量的公用数据集类型:

维度 数据集
3维 时间序列
4维 图像
5维 视频
为什么这里图像又是4维的了呢?
这是因为,若是一张图片用这3个字段表示即可:
(width, height, channel) = 3D

但是若是一个数据集,则还需要有图片数量这个字段:

(batch_size, width, height, channel) = 4D

所以对图像数据集来说,是4维张量。

PyTorch中, t o r c h . T e n s o r torch.Tensor torch.Tensor是存储及变换数据的主要工具,虽然与Numpy库的多维数组比较相似,但是 T e n s o r Tensor Tensor提供GPU计算和自动求梯度等更多功能。两者对比如下 [3]:

对比项 Numpy Tensor
相同点 可以定义多维数组,进行切片、改变维度、数学运算等 可以定义多维数组,进行切片、改变维度、数学运算等
不同点 1. 产生的数组类型为numpy.ndarray;
2. 会将ndarray放入CPU中进行运算;
3. 导入方式为 import numpy as np,后续通过np.array([1,2])建立数组;
4. Numpy中没有x.type的用法,只能使用type(x)
1. 产生的数组类型为torch.Tensor;
2. 会将tensor放入GPU中进行加速运算(如果有GPU);
3. 导入方式为import torch,后续通过torch.tensor([1, 2])或torch.Tensor([1,2])建立矩阵;
4. Tensor中查看数组类型使用type(x)和x.type()都可,但是x.type()的输出结果为’torch.LongTensor’或’torch.FloatTensor’,可以看出两个数组的种类区别。而采用type(x),则清一色的输出结果都是torch.Tensor,无法体现类型区别。

2.创建张量

这时,就可以在上一节创建的,interpreter为pytorch虚拟环境的PyCharm项目中运行代码了。
常见的构造Tensor的方法如下:

函数 功能
Tensor(sizes) 基础构造函数
tensor(data) 类似于np.array
ones(sizes) 全1矩阵
zeros(sizes) 全0矩阵
eye(sizes) 对角为1,其余为0的单位矩阵
arange(s, e, step) 从s到e, 步长为step
linspace(s, e, steps) 从s到e,均匀分成step份
rand/randn(sizes) rand是生成数据服从[0, 1)均匀分布的矩阵;randn是生成服从N(0, 1)的正态分布的矩阵
normal(mean, std) 正态分布(均值为mean,标准差是std)
randperm(m) 随机排列

几个例子:

(1)随机初始化矩阵:使用 t o r c h . r a n d ( ∗ s i z e , o u t = N o n e ) torch.rand(*size, out=None) torch.rand(size,out=None)方法

t o r c h . r a n d ( ∗ s i z e , o u t = N o n e ) torch.rand(*size, out=None) torch.rand(size,out=None):

  • *size: 整数序列,定义了输出张量的形状
  • out(Tensor, optional):结果张量

该方法返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数。

注:*size是sizes的意思,表示尺寸可以自由输入

# python
import torch
x = torch.rand(4, 3)
print(x)

因为是随机产生的矩阵,所以代码每次运行结果都不一样:

第一次运行:
在这里插入图片描述
第二次运行:
在这里插入图片描述

(2)全0矩阵的构建:使用 z e r o ( ∗ s i z e ) zero(*size) zero(size)方法

z e r o ( ∗ s i z e ) zero(*size) zero(size) [1]:

  • *size: 整数序列,定义了输出张量的形状
  • out:指定输出的tensor。
  • dtype:指定返回tensor中数据的类型,如果为None,使用默认值(一般为torch.float32,可以使用 torch.set_default_tensor_type()更改)
  • layout:返回tensor所需要的布局,默认为strided(密集型张量),还有torch.sparse_coo 稀疏性张量,用于存储稀疏矩阵时使用的布局。
  • device:指定返回tensor所处的设备,可以是cpu或者cuda,如果不指定则为默认(一般为cpu,可以使用torch.set_default_tensor_type()进行更改。)
  • requires_grad:指定返回的tensor是否需要梯度,默认为False。
# python
import torch
x = torch.zeros(4, 3, dtype=torch.long)
print(x)
print(x.dtype) # 查看x数据的具体类型 [2]
print("----------------------------") # 分割线

y = torch.zeros(5)
print(y)
print(y.dtype) # 查看y数据的具体类型 [2]

运行结果如下:
在这里插入图片描述

(3)张量的构建:使用 t o r c h . t e n s o r torch.tensor torch.tensor方法

t o r c h . t e n s o r ( ) torch.tensor() torch.tensor():
直接输入一个list数组来得到tensor类型

# python
import torch
x = torch.tensor([5.5, 3])
print(x)

y = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(y)

代码运行结果如下:
在这里插入图片描述

可以看到,输入的数据中有float类型,tensor就自动把整个数组转换成float类型(数组x);若是输入全为整数(数组y),则整个数组数据类型是整数类型。

注:torch.tensor和torch.Tensor的区别:
t o r c h . T e n s o r torch.Tensor torch.Tensor是默认的tensor类型 t o r c h . F l o a t T e n s o r torch.FloatTensor torch.FloatTensor的简称[4],所以用torch.Tensor得到的张量矩阵全是float类型。而torch.tensor是从数据中推断数据类型[3]。例子如下:

# pytorch
import torch
x = torch.tensor([[1, 2, 3], [4, 5, 6]]) # x是根据tensor生成的张量
print(x)
print(type(x))
print(x.type())
print("----------------------------")
y = torch.tensor([[1.1, 2, 3], [4, 5, 6]]) # y也是根据tensor生成的张量
print(y)
print(type(y))
print(y.type())
print("----------------------------")
z = torch.Tensor([[1, 2, 3], [4, 5, 6]]) # z是根据Tensor生成的张量
print(z)
print(type(z))
print(z.type())

运行结果如下:
在这里插入图片描述

分析:

  1. 对于x和y,其中的数据只有1和1.1不同,但是tensor根据x的数据类型把矩阵x归为整型数组,把y归为float型;而对于z,尽管数据都是整数,但是Tensor还是将其归为float型。
  2. 这里也同时看出type(x)与x.type()的不同,前者只会输出整个矩阵的类型是torch.Tensor型数据(而不是Numpy.adarray等类型),后者则会输出具体的矩阵中的数据是整型还是float型。

(4)基于已经存在的tensor,创建一个新的tensor

这里假设x是已经存在的tensor:

# python
![import torch
x = torch.tensor(\[\[1.1, 2, 3\], \[4, 5.6, 6\], \[7, 8, 9.2\], \[10.5, 11, 12\]\])
print(x)
print(x.type())
print("-------------------------------------")
y = x.new_ones(3, 3)  # new_ones创建一个新的全1矩阵,size是3*3,数据类型与x相同
print(y)
print(y.type())
print("-------------------------------------")
z = torch.rand_like(x)  # rand_like创建一个与矩阵x形状相同的矩阵,其中的数据服从[0, 1)分布
print(z)](https://img-blog.csdnimg.cn/5a2080fb96ab46598e2979f3e3cf82cf.png)

运行结果如下:
在这里插入图片描述

可以看到,y是与x数据类型相同,但是形状不同的矩阵;z是与x形状相同且数据类型相同的矩阵(如果x是整型矩阵,即矩阵之包含整数,则z = torch.rand_like(x)会报错,因为利用torch.rand生成的数据不可能是整数)。

(5)总结 [5]:

  1. torch.tensor():输入数据生成张量;
  2. torch.*():用于创建特殊形式的tensor:如torch.ones()生成全1的tensor,torch.zeros()生成全0的tensor;
  3. torch.new_*():用于创建一个与已知tensor数据类型相同的tensor;
  4. torch.*_like():用于创建一个与已知tensor形状相同且数据类型相同的tensor。

3. 张量的操作

1. 加法操作

(1) 方式1:用“+”直接将两个形状相同的tensor直接相加

# python
import torch
x = torch.tensor([[1.1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
# 方式1
y = torch.rand(4, 3)
print("x:", x)
print("y:", y)
print("x+y:", x+y)

运行结果:
在这里插入图片描述

(2)方式2:用add()方法

#python
# 方式2
print("y:\n", y)
print("用add()方法相加:\n", torch.add(x, y)) # x还是上面的tensor

结果如下:
在这里插入图片描述

(3)方式3:in place,原地修改

# python
# 方式3
print("y:\n", y)
y.add_(x) # x还是上面的tensor
print("原地修改y值:\n", y)

结果如下:
在这里插入图片描述

in place character:就地特征,就地操作不占用额外空间,也就是说这里将x直接加到y上,不会占用其他内存。

2. 索引操作

(1)取一列

# python
import torch
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) # 4*3的tensor
print(x)
print(x[:, 1])  # 取第二列并打印出来

结果如下:
在这里插入图片描述

(2)对从x取出来的某一部分进行操作,x也会改变

# python
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) # 4*3的tensor

print("原tensor, x:\n", x)
y = x[0, :]  # 取x的第一行
print("初始y:\n", y)
y += 1
print("操作后的y:\n", y)
print("y操作后,x的值:\n", x)

结果如下:
在这里插入图片描述

这是因为索引出来的结果与原数据共享一个内存,修改一个,另一个也会跟着改变。

3. 维度变换

常见方法有torch.view() 和 torch.reshape()
(1)torch.view()

#python
import torch
x = torch.tensor([[1, 2, 3, 1], [4, 5, 6, 1], [7, 8, 9, 1], [10, 11, 12, 1]])  # 4*3的tensor
y = x.view(16)
z = x.view(-1, 8) # -1是指这个维度的维数由其他维决定
print("x:\n", x)
print(x.storage().data_ptr()) # 查看x的存储位置

print("y:\n", y)
print(y.storage().data_ptr())

print("z:\n", z)
print(z.storage().data_ptr())

运行结果如下:
在这里插入图片描述

可以看到,无论y和z把x变成了什么形状,它们共享同一个内存。可以说,view()仅仅是改变了对这个张量的观察角度。如果更改其中一个的数据,其他的都会跟着改变。

(2)torch.reshape()

# python
import torch
x = torch.arange(9)
print("x:\n", x)
y = x.reshape(3, 3) # 可以看到,调用方式与view是一样的
print("y:\n", y)

结果如下:
在这里插入图片描述

torch.reshape()与torch.view()对比[7]:

  • torch的view()与reshape()方法都可以用来重塑tensor的shape
  • view()方法只适用于满足连续性条件(细节看[7]的讲解)的tensor,并且该操作不会开辟新的内存空间,只是产生了对原存储空间的一个新别称和引用,返回值是视图
  • reshape()方法的返回值既可以是视图,也可以是副本,当满足连续性条件时返回view,否则返回副本(此时等价于先调用contiguous()方法在使用view() )
  • 当不确能否使用view时,可以使用reshape
  • 如果只是想简单地重塑一个tensor的shape,那么就是用reshape,但是如果需要考虑内存的开销而且要确保重塑后的tensor与之前的tensor共享存储空间,那就使用view()

(3)利用torch.view()和clone()结合进行维度变换

当tensor是continuous时,view()和reshape()变换得出的结果所在地址都是tensor原来的地址,tensor在内存中并没有被改变;
当tensor不是continuous时,view()不可使用,reshape()得出的结果是对tensor拷贝后在新的内存地址储存的tensor。

为了防止这种糊里糊涂不知道用了reshape()之后tensor是否被拷贝,对view()不能工作的tensor可以选择先将其用clone()拷贝,再使用view()。
使用clone()的另外一个好处:
会被记录在计算图中,梯度传回到副本时也会传回到源tensor。

# python
import torch
x = torch.arange(9)
y = torch.clone(x)
z = y.view(3,3)
print("x存储位置:", x.storage().data_ptr())
print("y存储位置:", y.storage().data_ptr())
print("z存储位置:", z.storage().data_ptr())

结果如下:
在这里插入图片描述
可以发现,clone()之后,y是占用了一个新的内存;对clone()后的y进行view()操作,tensor内存不变。

4. 取值操作

可以用 .item() 获得tensor中值的值

# python
import torch
x = torch.tensor([1.0])
print(x, type(x))
print(x.item(), type(x.item()))

结果如下:
在这里插入图片描述
可以看到,得出的是tensor中的具体数值。
但是这个方法在tensor包含超过一个数的时候就不能用了,这时就要用tolist()进行取值操作了[8]:

# python
import torch
x = torch.tensor([1.0, 2, 3])
print(x, type(x))
print(x.tolist(), type(x.tolist()))

结果如下:
在这里插入图片描述

PyTorch中的 Tensor 支持超过一百种操作,包括转置、索引、切片、数学运算、线性代数、随机数等等,具体使用方法可参考链接: 官方文档

4. 广播机制

当两个形状不同的tensor按元素进行相加时,会触发广播机制:先复制元素让这两个tensor形状相同,再对其进行相加:

import torch
x = torch.arange(1, 3).view(1, 2) # 只取前两个数
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)

结果如下:
在这里插入图片描述
先将x第一行的元素复制到2,3行,再将y第一列的元素复制到第二列,x、y都变成3*2形状的矩阵,然后进行相加。

自动求导

1. Autograd简介

为张量的所有操作提供了自动求导机制。反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。

(1) t o r c h . T e n s o r torch.Tensor torch.Tensor的属性

  • requires_grad:设置为True:追踪对于该张量的所有操作;完成计算后调用 .backward()来自动计算所有的梯度。
    y.backward():y是标量,不用传入参数;否则,传入一个与y同型的Tensor。
  • .detach() :阻止一个张量被跟踪历史,且阻止未来的计算记录被跟踪。
    可将代码块包装在with torch.no_grad():中。
  • Function类:每个张量都有一个 grad_fn 属性,该属性引用了创建tensor自身的Function;
    tensor与function类互相连接并构建一个计算图,即有向无环图(DAG),用于保存整个完整计算过程的历史信息。
    每当对tensor施加一个运算的时候,就会产生一个function对象,由tensor的.grad_fn属性指向这个function对象,来产生运算结果,记录运算的发生,并记录运算的输入。tensor使用.grad.fn属性记录这个计算图的入口,反向传播中,autograd引擎会按照逆序,通过function的backward依次计算梯度。[12]
    用户手动创建的张量的 grad_fn 属性是None。如:
# python
from __future__ import print_function
import torch
x = torch.randn(3, 3, requires_grad=True)
print(x.grad_fn)

结果:
在这里插入图片描述
注:其中 from future import * 的作用就是将新版本的特性引进当前版本中,也就是说我们可以在当前版本使用新版本的一些特性[9]。其中的print_function就是运用新版本的打印函数的写法。

(2) 创建一个张量并对其进行一些操作

# python
import torch
x = torch.ones(2, 2, requires_grad=True) # 创建一个张量并设置可以进行跟踪历史操作
print("x:\n", x)

# 对这个张量做一次运算:
y = x ** 2
print("y:\n", y)
print("y是计算的结果,所以有grad_fn属性:")
print(y.grad_fn)

# 对y进行更多操作:
z = y * y * 3
out = z.mean() # 均值
print(" z:", z, "\n", "out:", out)

结果如下:
在这里插入图片描述

.requires_grad():原地改变现有张量的 requires_grad 标志,默认值是False,如下:

# python
import torch
a = torch.randn(2, 2)  # 默认情况下 requires_grad 是 False
a = (a * 3) / (a - 1)
print(a.requires_grad)

a.requires_grad_(True) # 更改值为True
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn) # b是a的计算结果,所以有 grad_fn

结果如下:
在这里插入图片描述

2. 梯度

梯度:方向导数,函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。[10]

对上面得出的 out 进行反向传播:因为 out 是一个标量,所以 out.backward() 和 out.backward(torch.tensor(1.))等价。

# python
out.backward()
print("导数 d(out)/dx: ", x.grad) # 输出导数 d(out)/dx

结果如下:

在这里插入图片描述
计算步骤如下:
o u t = 1 4 ∑ i z i z i = 3 y 2 y = x 2 z i = 3 x 4 z i ∣ x i = 1 = 3 ∂ o u t ∂ x i = 3 x 3 ∂ o u t ∂ x i ∣ x i = 1 = 3 out = \frac{1}{4}\sum_{i}z_i \quad z_i = 3y^2 \quad y = x^2 \\ z_i = 3x^4 \quad z_i|_{x_i=1} = 3 \\ \frac{\partial out}{\partial x_i} = 3x^3 \quad \frac{\partial out}{\partial x_i}|_{x_i=1}=3 out=41izizi=3y2y=x2zi=3x4zixi=1=3xiout=3x3xioutxi=1=3
也就是代码结果得到的全为3的tensor。

数学上,若有向量值函数 y = f ( x ) y=f(x) y=f(x),那么y相对于x的梯度是一个雅可比矩阵[10]:
在这里插入图片描述
注意:grad在反向传播过程中是累加的,每次反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前把梯度清零。如下:

# python
out2 = x.sum()
out2.backward()
print("第二次反向传播:", x.grad)

out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print("第三次反向传播:", x.grad)

得到:
在这里插入图片描述
一个雅可比向量积的例子:

# python
import torch
x = torch.randn(3, requires_grad=True)
print(x)

y = x * 2
i = 0
while y.data.norm() < 1000:
    y = y * 2
    i = i + 1
print(y)
print(i)

代码结果:
在这里插入图片描述
注:data.norm():先对张量y每个元素进行平方,然后对它们求和,最后取平方根。 这些操作计算就是所谓的L2或欧几里德范数 [11]。

这种情况下,y不再是标量。torch.autograde()不能直接计算完整的雅可比矩阵,若进行反向传播的根节点为一个向量,则需要传入与该节点同等size的向量[12]:

# python
import torch
x = torch.ones(1, 2, dtype=torch.float, requires_grad=True)
y = torch.add(x, 2) # 对x进行加法操作
z = torch.zeros(1, 2)
z[0, 0] = y[0, 1]**3 + 3 * y[0, 0]
z[0, 1] = y[0, 0]**2 + 3 * y[0, 1]
z.backward(torch.FloatTensor([[1, 1]]), retain_graph=True)
# 一次反向传播后会销毁当前计算图,设置retain_graph为true可以在当前运算中保留运算图
print(x.grad)
x.grad.data.zero_() # grad更新时,每一次运算后都需要将上一次的梯度记录清空
z.backward(torch.FloatTensor([[0, 1]]), retain_graph=True)
print(x.grad)
x.grad.data.zero_()
z.backward(torch.FloatTensor([[1, 0]]), retain_graph=True)
print(x.grad)
# 通过设置参数[0, 1]和[1, 0]可以得到该计算的雅可比矩阵
x.grad.data.zero_()
z.backward(torch.FloatTensor([[2, 1]]), retain_graph=True)
print(x.grad)
x.grad.data.zero_()
z.backward(torch.FloatTensor([[2, 2]]))
print(x.grad)

代码结果:
在这里插入图片描述
可见这里传入的参数是对原本正常求出的Jacobian matrix进行了线性操作。torch.autograd不能直接计算整个雅克比,因此需要我们给backward()传递向量作为参数从而得到雅可比向量积。
雅可比向量积是说,对于函数 y = f ( x ) y = f(x) y=f(x)定义雅可比矩阵为 J J J,则对于给定的向量 v = ( v 1 , v 2 , . . . , v m ) T v = (v_1, v_2, ..., v_m)^T v=(v1,v2,...,vm)T,计算 J ∗ v J*v Jv即为所求的雅可比向量积。[12]
关于backward()的具体输入参数的含义看这里:PyTorch中的backward()函数详解

可以通过将代码块包装在with torch.no_grad:中来阻止autograd自动设置了 .requires_grad=True的张量的历史记录:

# python
import torch
x = torch.randn(3, requires_grad=True)
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)

结果如下:
在这里插入图片描述
也就是说,不想被track的计算部分可以通过这么一个上下文管理器包裹起来。这样可以执行计算,但该计算不会在反向传播中被记录。[13]

如果想修改tensor的数值,但是又不希望被autograde记录(也不影响反向传播),可以对tensor.data进行操作:

# python
import torch
x = torch.ones(1, requires_grad=True)

print(x.data) # 还是一个tensor
print(x.data.requires_grad) # 但独立于反向传播计算图之外

y = 2 * x
x.data *= 100 # 只是改变了x值,但是不会记录在计算图中,所以不会影响原来的梯度传播

y.backward()
print(x) # 更改data的值也会影响原来x的值
print(x.grad) # 但是反向传播结果还是哟你用x原来的值计算的

结果如下:
在这里插入图片描述

3. 并行计算简介

并行计算:将复杂问题分解成若干个部分,将每一个部分交给独立的处理器(计算资源)进行计算,以提高效率。[14]

(1)CUDA

  • 全称是 Compute Unified Device Architecture,是一个用于并行计算的平台和 API,允许开发人员使用支持 CUDA 的 GPU 进行并行编程;[14]
  • GPU 并不能独立进行运算,它需要与 CPU 通过 PCIe 总线连接到一起协同进行工作,使用 GPU 进行的并行计算可以被视为是 CPU 和 GPU 的异构计算架构,CPU 负责处理逻辑复杂的串行部分,GPU 负责处理数据密集的并行部分,其中 CPU 通常被称为 host 主机端,GPU 通常被称为 device 设备端; [14]
  • 数据在GPU和CPU之间进行传递时会比较耗时,我们应当尽量避免数据的切换;
  • GPU运算很快,但是在使用简单的操作时,我们应该尽量使用CPU去完成;
  • 当我们的服务器上有多个GPU,我们应该指明我们使用的GPU是哪一块,如果我们不设置的话,tensor.cuda()方法会默认将tensor保存到第一块GPU上,等价于tensor.cuda(0),这将会导致爆出out of memory的错误。我们可以通过以下两种方式继续设置:
    1)在代码中直接指定:
# python
import os
os.environ["CUDA_VISIBLE_DEVICE"] = "2" # 设置默认的显卡

或者:
2)在命令行中执行脚本文件时指定:

# python
CUDA_VISBLE_DEVICE = 0, 1 python train.py  # 使用0,1两块GPU

(2)常见的并行方法

1)网络结构分布到不同的设备中(Network partitioning)
将一个模型的各个部分拆分,然后将不同的部分放入到GPU来做不同任务的计算。其架构如下:
在这里插入图片描述
缺点是这种方法对GPU之间数据的传输要求很高。

2)同一层的任务分布到不同数据中(Layer-wise partitioning)
把同一个卷积层的任务拆分放到不同GPU中计算,但是可能出现的缺陷跟模型1)一样:数据传输速率太慢,成为瓶颈。
在这里插入图片描述

3)不同的数据分布到不同的设备中,执行相同的任务(Data parallelism)
不再拆分模型,而是拆分数据,每个GPU进行一个单独的训练,然后将输出的数据进行一个汇总,得到的模型进行一个综合,再反传到不同GPU。结构如下:
在这里插入图片描述
这种模式不会出现前面的问题,所以是现在的主流方式。

参考:
[1] torch.zeros方法: https://blog.csdn.net/Fluid_ray/article/details/109704614
[2] 查看张量数据的具体类型:https://blog.csdn.net/m0_37586991/article/details/87878632
[3] Numpy与Tensor两者的对比:https://cloud.tencent.com/developer/article/1737690
[4] torch.Tensor: https://pytorch-cn.readthedocs.io/zh/latest/package_references/Tensor/
[5] torch创建tensor方式总结:https://www.dounaite.com/article/625422047cc4ff68e645f524.html
[6] Datawhale深入浅出PyTorch项目 (强推)
[7] PyTorch:view() 与 reshape() 区别详解:https://blog.csdn.net/Flag_ing/article/details/109129752
[8] tensor.item()、tensor.tolist()方法使用举例:https://blog.csdn.net/weixin_47725177/article/details/124116914
[9] python中 from future import * 的作用:https://blog.csdn.net/zzc15806/article/details/81133045
[10] PyTorch自动求导:Autograd包案例详解: https://zhuanlan.zhihu.com/p/136454725
[11] PyTorch中data.norm()的含义:https://blog.csdn.net/jnbfknasf113/article/details/110141537
[12] tensor与自动微分: https://www.jianshu.com/p/aa7e9f65fa3e
[13] with torch.no_grad() 详解: https://blog.csdn.net/weixin_46559271/article/details/105658654
[14] 并行计算入门:https://zhuanlan.zhihu.com/p/181669611

猜你喜欢

转载自blog.csdn.net/weixin_41794514/article/details/126850887