pytorch基础知识之:张量-自动求导-并行计算

Pytorch学习第一部分:基础知识

  • Let's go !
  • 一、张量
    • 1. 创建张量
    • 2.张量操作
      • 1.tensor与numpy的相互转换
      • 2.获取维度信息
      • 3.加法运算
      • 4.索引操作(获取张量的某一部分)
      • 5.修改张量尺寸(广播机制)
      • 6.扩展&压缩维度
  • 二、自动求导
    • 1.正向计算与反向求导
    • 2.反向求导
      • 1.y是标量
      • 2.y不是标量
      • 3.雅可比矩阵:
      • 4.梯度累加与清零
  • 三、并行计算
    • 1.网络结构分布到不同的设备中(Network partitioning)
    • 2.同一层的任务分布到不同数据中(Layer-wise partitioning)
    • 3.不同的数据分布到不同的设备中,执行相同的任务(Data parallelism)

Let’s go !

这是在Datawhale大家庭学习pytorch的第一部分,主要包括了张量、自动求导、并行计算相关内容,让我们一起看看吧~

一、张量

张量是使用在pytorch种基于向量和矩阵的一种推广,比如我们可以将标量视为零阶张量,向量可以视为一阶张量,矩阵就是二阶张量。

  • Scalar: 标量
  • Vector: 向量
  • Matrix: 矩阵
  • Tensor:张量

在pytorch中,torch.tensor 可以用于表示一个tensor类型的数据。类似的numpy的一些操作,可以发现 tensor 和numpy的非常类似。另外,我们在使用的时候有GPU是最好的(虽然我没有,哭了…)

注:使用pytorch时我应用了pycharm和jupyter notebook两种编译环境进行测试和学习,如果你还没有下载相应的pytorch学习工具可以参考我的另一篇博客:使用anaconda配置pytorch

1. 创建张量

首先导入tensor所需要的

import torch

然后可以使用如下的一些代码进行张量创建

#创建张量
x1 = torch.rand(4, 3)
print(x1)
x2 = torch.zeros(3, 3, dtype=torch.long)
print(x2)
x3 = torch.tensor([5.5, 3])
print(x3)
x1 = x1.new_ones(4, 3, dtype=torch.double)# 创建一个新的tensor,返回的tensor默认具有相同的 torch.dtype和torch.device
print(x1)
x1 = torch.randn_like(x1,dtype=torch.float)# 重置数据类型
print(x1)
print(x1.size())# 获取它的维度信息
print(x2.shape)# 获取它的维度信息,和上一行等价
x4 = torch.arange(1, 10, 2)# 秉承python一致的前闭后开
print(x4)
x5 = torch.rand(0, 1)
print(x5)

常用的一些张量的对照表如下:

函数 功能
tensor(sizes) 基础构造张量函数
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) 随机排列

2.张量操作

在jupyter notebook中输入

?torch.tensor

可以查看tensor的一些相关操作。
pytorch基础知识之:张量-自动求导-并行计算_第1张图片
这里面比较重要的就是第一行的一些参数
data表示放入数据(可以是张量也可以是标量也可以是向量、矩阵)
dtype=None这个地方用于设置数据类型(比如float、int、double等等,但是注意设置的数字类型要与前面data部分放的数字一致不然会报错)
device=None是用于设置是使用cpu还是gpu
requires_grad=False用于表示是否允许求导,后面紧接着的自动求导就要用到这个参数
pin_memory=False表示是否把这个执行放到内存中(放到内存中执行速度会加快但消耗空间会变多,即“用空间换时间”)

1.tensor与numpy的相互转换

import numpy as np

g = np.array([[1,2,3],[4,5,6]])
h = torch.tensor(g)# 将g转换成tensor
print(h)
i = torch.from_numpy(g)# 将g转换成tensor
print(i)
j = h.numpy()# 将tensor转换成numpy
print(j)

注意:torch.tensor创建得到的张量和原数据是不共享内存的,张量对应的变量是独立变量
而torch.from_numpy()和torch.as_tensor()从numpy array创建得到的张量和原数据是共享内存的,张量对应的变量不是独立变量,修改numpy array会导致对应tensor的改变

2.获取维度信息

# 查看tensor的维度信息(两种方式)
k = torch.rand(2, 3) 
print(k.shape)
print(k.size())

3.加法运算

x = torch.ones(4, 2)
y = torch.rand(4, 2)
print(x)
print(y)
print(x+y)
print(torch.add(x, y))

result = torch.empty(5, 3)
torch.add(x, y, out=result)# 这个地方因为引入了不同维数的out这个参数会报错,但是不影响求和。
print(result)

y.add_(x)
print(y)

4.索引操作(获取张量的某一部分)

#获取张量某一部分
x = torch.ones(4, 3)
y = torch.rand(4, 3)
print(y)
print(y[:, 1])# 获取第2列
z = x[0, :]# 获取第1行
#索引实际指向同一区域
z += 1
print(z)
print(x[0, :])#发现x的第一行也发生改变

pytorch基础知识之:张量-自动求导-并行计算_第2张图片

5.修改张量尺寸(广播机制)

#修改张量尺寸
x = torch.randn(4, 4)
print(x)
y = x.view(16)
print(y)
z = x.view(-1, 8)
print(z)
print(x)

如上述代码所示x初始为一个44的张量,我们可以将其变为y那样161的,或者z那样设定为8列行数-1表示自动生成为2的2*8的,等等。

#克隆
x2 = x.clone()
x2 += 1
print(x)
print(x2)

如果不想像上面一种操作产生索引指向同一地址我们可以使用**clone()**进行复制。

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)

由于 x 和 y 分别是1行2列和3行1列的矩阵,如果要计算 x + y ,那么 x 中第一行的2个元素被广播 (复制)到了第二行和第三行,⽽ y 中第⼀列的3个元素被广播(复制)到了第二列。如此,就可以对2 个3行2列的矩阵按元素相加。

6.扩展&压缩维度

在第二维进行压缩:

# 扩展&压缩tensor的维度:squeeze
print(o)
r = o.unsqueeze(1)# 扩展维度
print(r)
print(r.shape)

pytorch基础知识之:张量-自动求导-并行计算_第3张图片

t = r.squeeze(1)# 压缩维度(注意只能在维度为1的地方进行压缩,不然会报错)
print(t)
print(t.shape)

在这里插入图片描述

二、自动求导

1.正向计算与反向求导

pytorch基础知识之:张量-自动求导-并行计算_第4张图片
假设我们有一个如上图所示的神经网络,左边为我们的输入数据,右边为输出数据,当我们从左向右进行运算时就是我们常见的正向计算,正向计算到最后输出结果后,我们可以对结果进行评估分析,然后再反向进行修正即反向求导,通过使用修正后的值作为新的输入数据,再次正向计算依此类推直到最终得到 我们满意的结果。
反向求导的目的就是:得到变量导数,比如一个简单的例子:y = 3x^2 + 2x,当我们正向计算时我们可以得到最终的输出,但是我们得不到变量x,为了得到x我们使用反向求导。

2.反向求导

1.y是标量

首先将参数requires_grad=True设置为True,表示允许求导。

import torch

x1 = torch.tensor(1.0, requires_grad=True)
x2 = torch.tensor(2.0, requires_grad=True)
y = x1 + 2*x2
print(y)

查看是否允许求导

print(x1.requires_grad)
print(x2.requires_grad)
print(y.requires_grad)
# 查看每个变量导数大小。此时因为还没有反向传播,因此导数都不存在
print(x1.grad.data)
print(x2.grad.data)
print(y.grad.data)

pytorch基础知识之:张量-自动求导-并行计算_第5张图片
标量y进行反向求导并查看反向求导后的结果。

## 反向传播后看导数大小
y = x1 + 2*x2
y.backward()
print(x1.grad.data)
print(x2.grad.data)

2.y不是标量

如果y不是标量:我们需要先将y转换成标量,因为只有标量对标量标量对向量求梯度,x可以是标量或者向量,但 y只能是标量,对分别求导没影响的就是求和
pytorch基础知识之:张量-自动求导-并行计算_第6张图片

x = torch.tensor([1., 2.]).requires_grad_()
y = x * x

y.sum().backward()
print(x.grad)
>>>tensor([2., 4.])

pytorch基础知识之:张量-自动求导-并行计算_第7张图片
上面和这里的torch.ones_like(y) 位置指的就是雅可比矩阵左乘的那个向量[1, 1]。
一些其他等价形式:

x = torch.tensor([1., 2.]).requires_grad_()
y = x * x

grad_x = torch.autograd.grad(outputs=y, inputs=x, grad_outputs=torch.ones_like(y))
print(grad_x[0])
>>>tensor([2., 4.])
x = torch.tensor([1., 2.]).requires_grad_()
y = x * x

grad_x = torch.autograd.grad(outputs=y.sum(), inputs=x)
print(grad_x[0])
>>>tensor([2., 4.])

3.雅可比矩阵:

pytorch基础知识之:张量-自动求导-并行计算_第8张图片

4.梯度累加与清零

grad在反向传播过程中是累加的,这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零

# 导数是会累积的,重复运行相同命令,grad会增加
y = x1 + 2*x2
y.backward()
print(x1.grad.data)
print(x2.grad.data)

第二遍执行的结果:
在这里插入图片描述
第三遍执行的结果:
在这里插入图片描述
第四遍执行的结果:
在这里插入图片描述
梯度清零

x1.grad.data.zero_()
x2.grad.data.zero_()

清零后执行结果:
在这里插入图片描述

三、并行计算

1.网络结构分布到不同的设备中(Network partitioning)

在刚开始做模型并行的时候,这个方案使用的比较多。其中主要的思路是,将一个模型的各个部分拆分,然后将不同的部分放入到GPU来做不同任务的计算。其架构如下:
pytorch基础知识之:张量-自动求导-并行计算_第9张图片
这里遇到的问题就是,不同模型组件在不同的GPU上时,GPU之间的传输就很重要,对于GPU之间的通信是一个考验。但是GPU的通信在这种密集任务中很难办到。所有这个方式慢慢淡出了视野。

2.同一层的任务分布到不同数据中(Layer-wise partitioning)

第二种方式就是,同一层的模型做一个拆分,让不同的GPU去训练同一层模型的部分任务。其架构如下:
pytorch基础知识之:张量-自动求导-并行计算_第10张图片
这样可以保证在不同组件之间传输的问题,但是在我们需要大量的训练同步任务加重的情况下,会出现和第一种方式一样的问题。

3.不同的数据分布到不同的设备中,执行相同的任务(Data parallelism)

第三种方式有点不一样,它的逻辑是,我不再拆分模型,我训练的时候模型都是一整个模型。但是我将输入的数据拆分。所谓的拆分数据就是,同一个模型不同GPU训练一部分数据,然后再分别计算一部分数据之后,只需要将输出的数据做一个汇总,然后再反传。其架构如下:
pytorch基础知识之:张量-自动求导-并行计算_第11张图片
这种方式可以解决之前模式遇到的通讯问题。
注意:现在的主流方式是数据并行的方式(Data parallelism)
数据并行的简单理解就是对不同的处理器或者核心上分发数据,随后这些处理器和核心再对得到的数据进行处理。对于Pytorch模型中的DataParallel和DistributedDataParallel来说都属于是数据并行策略,模型运行在多个GPU上,每个GPU保留一份模型的拷贝,通过对数据进行分割,模型训练过程中每个GPU接收到自己独立的数据批处理切片,对这些数据切片独立计算梯度更新,比如同时运行在4张卡上的一个数据并行的模型,每张卡上的batch_size为4,那么0卡上的数据就为0-3,1卡上的为4-7等,以此类推,在4张卡上独立的计算梯度,最后通过同步或者异步更新策略将这些梯度更新在各个卡之间进行同步来保证权重的一致性。
现在的主流方式是数据并行的方式(Data parallelism)

你可能感兴趣的:(pytorch,python,深度学习)