文章目录

  • 张量
  • 简介
  • 创建Tensor
  • 查看属性
  • 常用操作
  • 加法操作
  • 变形操作
  • 索引操作(类似于numpy)
  • 广播机制
  • 自动求导——`autograd `包
  • 追踪计算历史
  • 反向传播
  • 预备知识
  • .backward()
  • 使用GPU
  • 查看GPU
  • GPU与张量
  • GPU与模型
  • 并行计算
  • CUDA
  • 并行计算的方法


张量

简介

  1. torch.Tensor 是存储和变换数据的主要工具。 Tensor 和NumPy的多维数组非常类似。然而,Tensor 提供GPU计算和自动求梯度等更多功能,这些使 Tensor 这一数据类型更加适合深度学习。
  2. 存储在各种类型张量的公用数据集类型:
  • 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

预备知识

  1. autograd包为张量上的所有操作提供了自动求导机制。torch.Tensor是这个包的核心类。其中如果属性.requires_gradTrue,那么它将会追踪对于该张量的所有操作。
  2. 当完成计算后可以通过调用.backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性。在 y.backward() 时,如果 y 是标量,则不需要为 backward() 传入任何参数;否则,需要传入一个与 y 同形的Tensor。解释:只允许标量对张量求导。
  3. 阻止一个张量被跟踪历史:假设模型具有 requires_grad = True 的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。这时可以调用.detach()方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad():中。
  4. Function类:TensorFunction 互相连接生成了一个无环图 (acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn属性,该属性引用了创建 Tensor自身的Function(除非这个张量是用户手动创建的,即这个张量的grad_fnNone )。

追踪计算历史

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>

反向传播

预备知识

数学上,若有向量函数pytorch 模型推理 GPU一直增加_广播机制,那么 pytorch 模型推理 GPU一直增加_正态分布_02 关于 pytorch 模型推理 GPU一直增加_随机数_03 的梯度就是一个雅可比矩阵:
pytorch 模型推理 GPU一直增加_广播机制_04
torch.autograd 这个包就是用来计算一些雅可比矩阵的乘积的。例如,如果 pytorch 模型推理 GPU一直增加_正态分布_05 是一个标量函数 pytorch 模型推理 GPU一直增加_随机数_06 的梯度:
pytorch 模型推理 GPU一直增加_正态分布_07
由链式法则,我们可以得到:
pytorch 模型推理 GPU一直增加_随机数_08

注意: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中训练一部分数据,然后再分别计算一部分数据之后,只需要将输出的数据做一个汇总,然后再反传。