张量
- 创建sensor, torch.rand 构建随机初始化矩阵,(0,1)之间
import torch
x = torch.rand(4, 3)
print(x)
# 输出:
tensor([[0.5929, 0.9524, 0.2840],
[0.2746, 0.3503, 0.3990],
[0.1227, 0.8759, 0.6011],
[0.9042, 0.1564, 0.3587]])
torch.zeros() 建立全0矩阵,torch.zeros_like()和torch.zero_() 将现有矩阵转为全0矩阵
y = torch.zeros(4, 3, dtype=torch.long)
z = torch.zeros_like(x)
print(y)
print(z)
# 输出
tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
- 张量的构建
torch.tensor()创建张量,类似于np.array
import torch
a = torch.tensor([5.5, 3])
print(a)
# 输出
tensor([5.5000, 3.0000])
- 基于已经存在的tensor,创建一个新的tensor
import torch
# torch.new_ones 创建一个新的全1矩阵tensor,返回的tensor默认具有相同的torch.dtype和torch.device
y = torch.zeros(4, 3, dtype=torch.long)
b = y.new_ones(4, 3, dtype=torch.double)
print(b)
# torch.rand_like()基于原有tensor,创建一个均值为0,方差为1的正则化矩阵,和原有tensor维度相同
b = torch.rand_like(b, dtype=torch.float64)
print(b)
# 返回的torch.size是一个tuple,支持tuple的所有操作
print(b.size())
# 输出
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
tensor([[0.0526, 0.6942, 0.4970],
[0.6648, 0.4227, 0.6787],
[0.2695, 0.3599, 0.8400],
[0.9022, 0.6533, 0.6454]], dtype=torch.float64)
torch.Size([4, 3])
- 常见的构建tensor方法
- torch.ones() 创建全1张量
- torch.zeros() 创建全0 张量
- torch.eye() 创建对角线为1的张量
- torch.arange(s, e, step) 创建从s开始,e结束,步长为step的的一维张量,类似,
- np.arange()
- torch.linspace(s, e, steps) 创建从s开始,e结束,均匀分为steps的一维张量,类似np.linspace()
- torch.rand() 创建[0,1)均匀分布,torch.randn()服从N(0,1)正态分布
- torch.normal(mean, std) 创建指定均值,指定方差的tensor
- torch.randperm(m) 随机排列
import torch
print(torch.ones(4, 3))
print(torch.zeros(4,3))
print(torch.eye(4,3))
print(torch.arange(1, 10, 2))
print(torch.linspace(1, 10, 6))
print(torch.rand(3,3))
print(torch.randn(3,3))
print(torch.normal(1, 2, [3,4]))
print(torch.randperm(5))
# 输出
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.],
[0., 0., 0.]])
tensor([1, 3, 5, 7, 9])
tensor([ 1.0000, 2.8000, 4.6000, 6.4000, 8.2000, 10.0000])
tensor([[0.3827, 0.1842, 0.1163],
[0.7383, 0.8150, 0.8816],
[0.6863, 0.4619, 0.9042]])
tensor([[-0.0745, -0.5493, -0.3604],
[ 1.4117, 0.4550, 0.6626],
[ 0.8836, 0.3959, -0.1812]])
tensor([[-1.2770, 1.7557, 4.5646, -2.3573],
[ 0.3190, -1.3570, 0.8338, 6.5721],
[ 2.4897, 1.0439, -1.7107, 1.3649]])
tensor([0, 4, 2, 1, 3])
- 张量操作
方式一直接相加
方式二使用torch.add() 相加
方式三 y.add_,原值修改
import torch
x = torch.rand(4, 3)
y = torch.ones(4, 3)
print(x)
print(y)
print(x+y)
print(torch.add(x, y))
y.add_(x)
print(y)
# 输出
tensor([[0.1498, 0.7496, 0.0949],
[0.9943, 0.2537, 0.7342],
[0.9411, 0.3955, 0.2247],
[0.3054, 0.3009, 0.4307]])
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
tensor([[1.1498, 1.7496, 1.0949],
[1.9943, 1.2537, 1.7342],
[1.9411, 1.3955, 1.2247],
[1.3054, 1.3009, 1.4307]])
tensor([[1.1498, 1.7496, 1.0949],
[1.9943, 1.2537, 1.7342],
[1.9411, 1.3955, 1.2247],
[1.3054, 1.3009, 1.4307]])
tensor([[1.1498, 1.7496, 1.0949],
[1.9943, 1.2537, 1.7342],
[1.9411, 1.3955, 1.2247],
[1.3054, 1.3009, 1.4307]])
- 索引操作,类似numpy
需要注意的是:索引出来的结果与原数据共享内存,修改一个,另一个会跟着修改。如果不想修改,可以考虑使用copy()等方法
import torch
x = torch.rand(4, 3)
print(x)
print(x[:, 1])
y = x[:, 1]
y += 1
print(y)
print(x[:, 1])
# 输出
tensor([[0.7755, 0.7065, 0.2731],
[0.3742, 0.3654, 0.2566],
[0.3552, 0.5135, 0.2361],
[0.5460, 0.5074, 0.1195]])
tensor([0.7065, 0.3654, 0.5135, 0.5074])
tensor([1.7065, 1.3654, 1.5135, 1.5074])
tensor([1.7065, 1.3654, 1.5135, 1.5074])
- 维度张量变换,torch.view() 和torch.reshape()
注: torch.view() 返回的新tensor与源tensor共享内存(其实是同一个tensor),更改其中的一个,另外一个也会跟着改变。(顾名思义,view()仅仅是改变了对这个张量的观察角度)
import torch
x = torch.rand(4,4)
y = x.view(2, 8)
# -1表示这一维的数由其他维度决定
z = x.view(-1, 8)
print(y)
print(z)
# 输出
tensor([[0.3544, 0.3086, 0.0605, 0.8646, 0.3699, 0.9313, 0.5907, 0.8219],
[0.8304, 0.6211, 0.2969, 0.9637, 0.6833, 0.5921, 0.3213, 0.3536]])
tensor([[0.3544, 0.3086, 0.0605, 0.8646, 0.3699, 0.9313, 0.5907, 0.8219],
[0.8304, 0.6211, 0.2969, 0.9637, 0.6833, 0.5921, 0.3213, 0.3536]])
上面我们说过torch.view()会改变原始张量,但是很多情况下,我们希望原始张量和变换后的张量互相不影响。为了使创建的张量和原始张量不共享内存,我们需要使用第二种方torch.reshape(), 同样可以改变张量的形状,但是此函数并不能保证返回的是其拷贝值,所以官方不推荐使用。推荐的方法是我们先用 clone() 创造一个张量副本然后再使用 torch.view()进行函数维度变换 。
注:使用 clone() 还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor
- 取值操作
如果我们有一个元素 tensor ,我们可以使用 .item() 来获得这个 value,而不获得其他性质
import torch
x = torch.randn(1)
print(x)
print(x.item())
print(type(x))
print(type(x.item()))
# 输出
tensor([-0.0448])
-0.044772230088710785
<class 'torch.Tensor'>
<class 'float'>
- 广播机制
当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个 Tensor 形状相同后再按元素运算。只有两个tensor有一个维度为1时可以广播
import torch
x = torch.rand(1, 2)
y = torch.rand(3, 1)
print(x)
print(y)
print(x+y)
# 输出
tensor([[0.5110, 0.0495]])
tensor([[0.8719],
[0.5785],
[0.2149]])
tensor([[1.3829, 0.9214],
[1.0895, 0.6279],
[0.7259, 0.2644]])
梯度
- Autograd简介
- torch.Tensor 是这个包的核心类。如果设置它的属性 .requires_grad 为 True,那么它将会追踪对于该张量的所有操作。当完成计算后可以通过调用 .backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性。
- 还有一个类对于autograd的实现非常重要:Function。Tensor 和 Function 互相连接生成了一个无环图 (acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn属性,该属性引用了创建 Tensor 自身的Function(除非这个张量是用户手动创建的,即这个张量的grad_fn是 None )。
from __future__ import print_function
import torch
x = torch.rand(3, 3, requires_grad=True )
# 下面给出的例子中,张量由用户手动创建,因此grad_fn返回结果是None。
print(x.grad_fn)
# 输出
None
反向传播.backward()
因为 out 是一个标量,因此out.backward()和 out.backward(torch.tensor(1.)) 等价。
# 更多举例
x = torch.ones(2, 2, requires_grad=True)
print(x)
y = x ** 2
# y是计算的结果,所以它有grad_fn属性。
# .requires_grad_(...) 原地改变了现有张量的requires_grad标志。如果没有指定的话,默认输入的这个标志是 False。
print(y)
print(y.grad_fn)
z = y * y * 3
out = z.mean()
print(z, out)
print(out.backward())
# 输出
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
tensor([[1., 1.],
[1., 1.]], grad_fn=<PowBackward0>)
<PowBackward0 object at 0x7fce21a33a10>
tensor([[3., 3.],
[3., 3.]], grad_fn=<MulBackward0>) tensor(3., grad_fn=<MeanBackward0>)
None
缺失情况下默认 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 0x7fce22b45690>
注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。
# 因为 out 是一个标量,因此out.backward()和 out.backward(torch.tensor(1.)) 等价
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad)
y = x ** 2
z = y * y * 3
out = z.mean()
# 第一次反向传播
out.backward()
print(x.grad)
# 第二次反向传播,注意grad是累加的
out2 = x.sum()
out2.backward()
print(x.grad)
# 清零后,第三次反向传播
out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)
# 输出
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
[3., 3.]])
tensor([[4., 4.],
[4., 4.]])
tensor([[1., 1.],
[1., 1.]])
备注:如下这种情况下,y 不再是标量。torch.autograd 不能直接计算完整的雅可比矩阵,但是如果我们只想要雅可比向量积,只需将这个向量作为参数传给 backward:
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)
v = torch.tensor([1, 1.0, 1], 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)
# 输出
tensor([-0.2354, -0.2037, -0.4056], requires_grad=True)
tensor([-482.0371, -417.1968, -830.5679], grad_fn=<MulBackward0>)
10
tensor([2048., 2048., 2048.])
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) # 梯度值还是data运算前的值
# 输出
tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])
并行计算
为什么需要cuda
CUDA是我们使用GPU的提供商——NVIDIA提供的GPU并行计算框架。对于GPU本身的编程,使用的是CUDA语言来实现的。但是,在我们使用PyTorch编写深度学习代码时,使用的CUDA又是另一个意思。在PyTorch使用 CUDA表示要开始要求我们的模型或者数据开始使用GPU了。
在编写程序中,当我们使用了 .cuda() 时,其功能是让我们的模型或者数据从CPU迁移到GPU(0)当中,通过GPU开始计算。
注:
我们使用GPU时使用的是.cuda()而不是使用.gpu()。这是因为当前GPU的编程接口采用CUDA,但是市面上的GPU并不是都支持CUDA,只有部分NVIDIA的GPU才支持,AMD的GPU编程接口采用的是OpenCL,在现阶段PyTorch并不支持。
数据在GPU和CPU之间进行传递时会比较耗时,我们应当尽量避免数据的切换。
GPU运算很快,但是在使用简单的操作时,我们应该尽量使用CPU去完成。
当我们的服务器上有多个GPU,我们应该指明我们使用的GPU是哪一块,如果我们不设置的话,tensor.cuda()方法会默认将tensor保存到第一块GPU上,等价于tensor.cuda(0),这将会导致爆出out of memory的错误。我们可以通过以下两种方式继续设置。
数据并行方式:
- 主流的数据并行方式:不同的数据分布到不同的设备中,执行相同的任务
- 它的逻辑是,我不再拆分模型,我训练的时候模型都是一整个模型。但是我将输入的数据拆分。所谓的拆分数据就是,同一个模型在不同GPU中训练一部分数据,然后再分别计算一部分数据之后,只需要将输出的数据做一个汇总,然后再反传
#设置在文件最开始部分
import os
os.environ["CUDA_VISIBLE_DEVICE"] = "2" # 设置默认的显卡
CUDA_VISBLE_DEVICE=0,1 # 使用0,1两块GPU