官网链接: PyCharm: 面向专业开发者的Python IDE
可以下载社区版和专业版,社区版免费,专业版收费。
但是如果是学生或者老师,可以申请JetBrains的免费教育许可证,具体申请条件看官网:
链接: 免费教育许可证
我选择的是社区版(一般使用足够)
Anaconda集成了常用于科学分析(机器学习, 深度学习)的大量package,并且借助于conda我们可以实现对虚拟Python环境的管理,可以非常方便的用于机器学习及深度学习。
链接: Anaconda官网
我选择的是默认安装
安装好之后打开终端,分别输入以下命令可以看到conda的版本、conda默认的python的版本和已经创建的虚拟环境。
也可以在默认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
设置好之后重新进入终端即可。
首先终端进入pytorch环境:
conda activate pytorch
1)查看现在环境中所有已安装的包
conda list
从图一可以看到我的环境里面已经创建了一个专门用于pytorch的虚拟环境:pytorchenv,我主要是为运行MADDPG算法创建的环境,环境配置如下:
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
打开PyCharm,新创建一个project: pytorch_learning
进入这个project,进入File->Settings->Project:pytorch_learning->Python Interpreter:
至此,配置完成,可以在PyCharm里愉快的使用pytorchenv虚拟环境了,如果还想安装其他包,比如matplotlib,再次打开终端进入pytorch虚拟环境,用安装命令安装即可。
在深度学习中,通常将数据以张量的形式进行表示。如:三维张量表示一个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,无法体现类型区别。 |
这时,就可以在上一节创建的,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) | 随机排列 |
几个例子:
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)
因为是随机产生的矩阵,所以代码每次运行结果都不一样:
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]
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())
分析:
这里假设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生成的数据不可能是整数)。
(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上,不会占用其他内存。
(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)
这是因为索引出来的结果与原数据共享一个内存,修改一个,另一个也会跟着改变。
常见方法有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]:
(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内存不变。
可以用 .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 支持超过一百种操作,包括转置、索引、切片、数学运算、线性代数、随机数等等,具体使用方法可参考链接: 官方文档。
当两个形状不同的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形状的矩阵,然后进行相加。
为张量的所有操作提供了自动求导机制。反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
# 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就是运用新版本的打印函数的写法。
# 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
梯度:方向导数,函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。[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=41i∑zizi=3y2y=x2zi=3x4zi∣xi=1=3∂xi∂out=3x3∂xi∂out∣xi=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 J∗v即为所求的雅可比向量积。[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原来的值计算的
并行计算:将复杂问题分解成若干个部分,将每一个部分交给独立的处理器(计算资源)进行计算,以提高效率。[14]
# python
import os
os.environ["CUDA_VISIBLE_DEVICE"] = "2" # 设置默认的显卡
或者:
2)在命令行中执行脚本文件时指定:
# python
CUDA_VISBLE_DEVICE = 0, 1 python train.py # 使用0,1两块GPU
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