文章目录
- 张量
- 简介
- 创建Tensor
- 查看属性
- 常用操作
- 加法操作
- 变形操作
- 索引操作(类似于numpy)
- 广播机制
- 自动求导——`autograd `包
- 追踪计算历史
- 反向传播
- 预备知识
- .backward()
- 使用GPU
- 查看GPU
- GPU与张量
- GPU与模型
- 并行计算
- CUDA
- 并行计算的方法
张量
简介
- torch.Tensor 是存储和变换数据的主要工具。 Tensor 和NumPy的多维数组非常类似。然而,Tensor 提供GPU计算和自动求梯度等更多功能,这些使 Tensor 这一数据类型更加适合深度学习。
- 存储在各种类型张量的公用数据集类型:
- 3维=时间序列
- 4维=图像:
#在机器学习工作中,我们经常要处理不止一张图片或一篇文档——我们要处理一个集合。我们可能有10,000张郁金香的图片,这意味着,我们将用到4D张量:
(sample_size, width, height, channel) = 4D
- 5维=视频
创建Tensor
导入
from __future__ import print_function #Python2.x需要
import torch
构造随机矩阵
x = torch.rand(4, 3)
print(x)
构造0矩阵,指定类型long.
x = torch.zeros(4, 3, dtype = torch.long)
还可以直接创建,或者通过numpy创建
x = torch.tensor([1.0, 2, 4, 8])
a = numpy.array([1, 2, 3])
t = torch.from_numpy(a)
这里总结一些常用的Tensor创建函数:
#对角线为1
torch.eye(行数, 列数默认等于行数=None, out默认=None)
#全为1的张量,全为0则使用torch.zeros
torch.ones(*sizes, out=None)
#从区间[0,1)的均匀分布中抽取的一组随机数
torch.rand(*sizes, out=None)
#了从标准正态分布(均值为 0,方差为 1,即高斯白噪声)中抽取一组随机数
torch.randn(*sizes, out=None)
#正态分布与均匀分布
torch.normal(*mean,std*)/uniform(*from,to*)
#给定参数 n,返回一个从 0 到 n-1 的随机整数排列
torch.randperm(n, out=None)
#含从 start 到 end,以 step 为步长的一组序列值(默认步长为 1)
torch.arange(start, end, step=1, out=None)
#返回一个 1 维张量,包含在区间 start 和 end 上均匀间隔的 step 个点。 输出 1 维张量的长度为steps
torch.linspace(start, end, steps=100, out=None)
#返回一个 1 维张量,包含在区间 10^start^ 和 10^end^ 上以对数刻度均匀间隔的 steps 个点。 输出1 维张量的长度为 steps。
torch.logspace(start, end, steps=100, out=None)
#从np创建
torch.from_numpy(nparry)
#从已有的tensor创建,默认具有相同的 torch.dtype和torch.device
x = x.new_ones(4, 3, dtype=torch.double)
x = torch.ones(4, 3, dtype=torch.double)
x = torch.randn_like(x, dtype=torch.float) #仿造形状创建
查看属性
一些常用的判断/查看方法
#判断,如果 obj 是一个 pytorch 张量,则返回 True
torch.is_tensor(obj)
#如何 obj 是一个 pytorch storage 对象,则返回 True
torch.is_storage(obj)
#返回 input 张量中的元素个数int
torch.numel(input)
#获取它的维度信息
print(x.size())
print(x.shape)
常用操作
加法操作
# 方式1
y = torch.rand(4, 3)
print(x + y)
# 方式2
print(torch.add(x, y))
# 方式3 提供一个输出 tensor 作为参数
result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)
# 方式4 in-place
y.add_(x)
print(y)
变形操作
.view():返回具有新形状的张量。返回的张量将与原始张量共享基础数据。
.reshape(): torch.reshape可能返回原始张量的副本或视图->不确定的
区别:.reshape()可以在连续和非连续的张量上运行,而.view()只能在连续的张量上运行。
区别详解
索引操作(类似于numpy)
需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。
索引行列的形式:[a:b; c:d; e:f……],索引元素的形式[a, b, c……]
# 取第二列
print(x[:, 1])
y = x[0,:]
y += 1
print(y)
print(x[0, :]) # 源tensor也被改了了
改变大小:如果你想改变一个 tensor 的大小或者形状,你可以使用 torch.view:
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # -1是指这一维的维数由其他维度决定
print(x.size(), y.size(), z.size())
#以上操作输出同
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
注意 view() 返回的新tensor与源tensor共享内存(其实是同一个tensor),也即更改其中的一个,另 外一个也会跟着改变。(顾名思义,view仅仅是改变了对这个张量的观察⻆度)
x += 1
print(x)
print(y) # 也加了了1
不共享内存,返回一个真正新的副本:用 clone 创造一个副本然后再使用 view 。
注意:使用 clone 还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor 。
如果你有一个标量 tensor ,使用 .item() 来获得这个 value:
x = torch.randn(1)
print(x)
print(x.item())
此外还有转置、索引、切片、数学运算、线性代数、随机数等等,可参考官方文档。
广播机制
当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:先适当(按行/列)复制元素使这两个 Tensor 形状相同后再按元素运算。
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列的矩阵按元素相加。
自动求导——autograd
包
预备知识
-
autograd
包为张量上的所有操作提供了自动求导机制。torch.Tensor
是这个包的核心类。其中如果属性.requires_grad
为True
,那么它将会追踪对于该张量的所有操作。 - 当完成计算后可以通过调用
.backward()
,来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad
属性。在 y.backward() 时,如果 y 是标量,则不需要为 backward() 传入任何参数;否则,需要传入一个与 y 同形的Tensor。解释:只允许标量对张量求导。 - 阻止一个张量被跟踪历史:假设模型具有
requires_grad = True
的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。这时可以调用.detach()
方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad():
中。 -
Function
类:Tensor
和Function
互相连接生成了一个无环图 (acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn
属性,该属性引用了创建Tensor
自身的Function
(除非这个张量是用户手动创建的,即这个张量的grad_fn
是None
)。
追踪计算历史
import torch
创建一个张量并设置requires_grad=True
用来追踪其计算历史
x = torch.ones(2, 2, requires_grad=True)
print(x)
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
对这个张量做一次运算:
y = x**2
print(y)
tensor([[1., 1.],
[1., 1.]], grad_fn=<PowBackward0>)
y
是计算的结果,所以它有grad_fn
属性。
print(y.grad_fn)
<PowBackward0 object at 0x7fb64d56cf70>
对 y 进行更多操作
z = y * y * 3
out = z.mean()
print(z, out)
tensor([[3., 3.],
[3., 3.]], grad_fn=<MulBackward0>) tensor(3., grad_fn=<MeanBackward0>)
.requires_grad_(...)
原地改变了现有张量的requires_grad
标志。如果没有指定的话,默认输入的这个标志是False
。
a = torch.randn(2, 2) # 缺失情况下默认 requires_grad = False
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
False
True
<SumBackward0 object at 0x7f1b24845f98>
反向传播
预备知识
数学上,若有向量函数,那么 关于 的梯度就是一个雅可比矩阵:
而 torch.autograd
这个包就是用来计算一些雅可比矩阵的乘积的。例如,如果 是一个标量函数 的梯度:
由链式法则,我们可以得到:
注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。
.backward()
现在开始进行反向传播,因为out
是一个标量,因此out.backward()
和out.backward(torch.tensor(1.))
等价。
out.backward()
输出导数d(out)/dx
print(x.grad)
grad在反向传播过程中是累加的(accumulated),一般在反向传播之前需把梯度清零。
# 再来反向传播⼀一次,注意grad是累加的 2 out2 = x.sum()
out2.backward()
print(x.grad)
out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)
tensor([[5.5000, 5.5000],
[5.5000, 5.5000]])
tensor([[1., 1.],
[1., 1.]])
现在我们来看一个雅可比向量积的例子:
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)
tensor([-0.3464, -0.1451, 1.6939], requires_grad=True)
tensor([-354.7274, -148.6218, 1734.5309], grad_fn=<MulBackward0>)
9
在这种情况下,y
不再是标量。torch.autograd
不能直接计算完整的雅可比矩阵,但是如果我们只想要雅可比向量积,只需将这个向量作为参数传给 backward:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
也可以通过将代码块包装在with torch.no_grad():
中,来阻止 autograd 跟踪设置了.requires_grad=True
的张量的历史记录。
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
True
True
False
如果我们想要修改 tensor 的数值,但是又不希望被 autograd 记录(即不会影响反向传播), 那么我么可以对 tensor.data 进行操作。
x = torch.ones(1,requires_grad=True)
print(x.data) # 还是一个tensor
print(x.data.requires_grad) # 但是已经是独立于计算图之外
y = 2 * x
x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播
y.backward()
print(x) # 更改data的值也会影响tensor的值
print(x.grad)
tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])
使用GPU
该章节参考动手学深度学习5.5GPU
我们可以指定⽤于存储和计算的设备,如CPU和GPU。默认情况下,张量是在内存中创建的,然后使⽤CPU计算它。深度学习框架要求计算的所有输⼊数据都在同⼀设备上,⽆论是CPU还是GPU。
在PyTorch中,CPU和GPU可以⽤torch.device(‘cpu’)和torch.cuda.device(‘cuda’)表⽰。应该
注意的是,cpu设备意味着所有物理CPU和内存。这意味着PyTorch的计算将尝试使⽤所有CPU核⼼。然而,gpu设备只代表⼀个卡和相应的显存。如果有多个GPU,我们使⽤torch.cuda.device(f’cuda:{i}’)来表⽰第i块GPU(i从0开始)。
查看GPU
#查看显卡信息
!nvidia-smi
#显卡GPU数量
torch.cuda.device_count()
import torch
from torch import nn
torch.device('cpu'), torch.cuda.device('cuda'), torch.cuda.device('cuda:1')
本节让我们来简单地了解一下并行计算的基本概念和主要实现方式,具体的内容会在课程的第二部分详细介绍。
GPU与张量
#现在我们定义了两个⽅便的函数,这两个函数允许我们在请求的GPU不存在的情况下运⾏代码。
def try_gpu(i=0): #@save
"""如果存在,则返回gpu(i),否则返回cpu()。"""
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
def try_all_gpus(): #@save
"""返回所有可⽤的GPU,如果没有GPU,则返回[cpu(),]。"""
devices = [torch.device(f'cuda:{i}')
for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
try_gpu(), try_gpu(10), try_all_gpus()
X = torch.ones(2, 3, device=try_gpu())
#假设你⾄少有两个GPU,下⾯的代码将在第⼆个GPU上创建⼀个随机张量。
Y = torch.rand(2, 3, device=try_gpu(1))
X, Y
复制
如果我们要计算X + Y,X、Y不在同⼀设备上找不到数据会导致失败。由于Y位于第⼆个GPU上,所以我们需要将X移到那⾥,然后才能执⾏相加运算。
Z = X.cuda(1)
print(X)
print(Z)
Y + Z
Z.cuda(1) is Z #测试Z在GPU1上且不分配新内存,True
GPU与模型
类似地,神经⽹络模型可以指定设备。下⾯的代码将模型参数放在GPU上。
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
net(X)
tensor([[0.3596],
[0.3596]], device='cuda:0', grad_fn=<AddmmBackward>)
让我们确认模型参数存储在同⼀个GPU上。
net[0].weight.data.device
device(type='cuda', index=0)
并行计算
在利用PyTorch做深度学习的过程中,可能会遇到数据量较大无法在单块GPU上完成,或者需要提升计算速度的场景,这时就需要用到并行计算。
CUDA
CUDA
是我们使用GPU的提供商——NVIDIA提供的GPU并行计算框架。对于GPU本身的编程,使用的是CUDA
语言来实现的。但是,在我们使用PyTorch编写深度学习代码时,使用的CUDA
又是另一个意思。在PyTorch使用 CUDA
表示要开始要求我们的模型或者数据开始使用GPU了。
在编写程序中,当我们使用了 cuda()
时,其功能是让我们的模型或者数据迁移到GPU当中,通过GPU开始计算。
并行计算的方法
主流方式:不同的数据分布到不同的设备中,执行相同的任务,即数据并行的方式(Data parallelism)
训练的时候模型都是一整个模型。但是我将输入的数据拆分。所谓的拆分数据就是,同一个模型在不同GPU中训练一部分数据,然后再分别计算一部分数据之后,只需要将输出的数据做一个汇总,然后再反传。