第四章 PyTorch深度学习基础
4.1 Tensor对象及其运算
Tensor对象是一个任意维度的矩阵,但是一个Tensor中所有元素的数据类型必须一致。torch包含的数据类型和普遍编程语言的数据类型类似,包含浮点型、有符号整型和无符号整型。这些类型既可以定义在CPU上,也可以定义在GPU上。在使用Tensor 数据类型时,可以通过dtype属性指定它的数据类型,device 指定它的设备(CPU或者GPU)。
4.1.1 代码
import torch
import numpy as np
#torch.tensor
print('torch.Tensor默认为:{}'.format(torch.Tensor(1).dtype))
print('torch.tensor默认为:{}'.format(torch.tensor(1).dtype))
#可以用list构建
a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float64)
# 也可以用ndarry构建
b = torch.tensor(np.array([[1, 2], [3, 4]]), dtype=torch.uint8)
print(a)
print(b)
# 通过device指定设备
cuda0 = torch.device('cuda:0')
c = torch.ones((2, 2), device=cuda0)
print(c)
通过device在GPU上定义变量后,可以在终端上通过nvidia-smi命令查看显存(显卡内存)占用。torch 还支持在 CPU和GPU之间复制变量。
4.1.2 代码
c = c.to('cpu', torch.double)
print(c.device)
b = b.to(cuda0, torch.float)
print(b.device)
对Tensor执行算数运算符的运算,是两个矩阵对应元素的运算。torch.mm执行矩阵乘法的运算。
4.1.3 代码
a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[1, 2], [3, 4]])
c = a * b
print("逐元素相乘:", c)
c = torch.mm(a, b)
print("矩阵乘法:", c)
torch.clamp起到分段函数的作用,可用于去掉矩阵中过小或者过大的元素;torch.round将小数转为整数;torch.tanh计算双曲正切函数,该函数将数值映射到(0,1)。
4.1.4 代码
a = torch.tensor([[1, 2], [3, 4]])
torch.clamp(a, min=2, max=3)
a = torch.tensor([-1.1, 0.5, 0.501, 0.99])
torch.round(a)
a = torch.Tensor([-3, -2, -1, -0.5, 0, 0.5, 1, 2, 3])
torch.tanh(a)
除了直接从 ndarray 或 list类型的数据中创建Tensor,PyTorch 还提供了一些可直接创建数据的函数,这些函数往往需要提供矩阵的维度。torch.arange 和Python内置的range的使用方法基本相同,其中第3个参数是步长。torch.limspace 的第3个参数指定返回的个数。torch.ones返回全0,torch.zeros返回全0矩阵。
torch.rand 返回范围为[0,1]的均匀分布采样的元素所组成的矩阵,torch.randn返回从正态分布采样的元素所组成的矩阵,torch.randint 返回指定区间的均匀分布采样的随机整数所生成的矩阵。
4.1.5 代码
print(torch.arange(5))
print(torch.arange(1, 5, 2))
print(torch.linspace(0, 5, 10))
print(torch.ones(3, 3))
print(torch.zeros(3, 3))
print(torch.rand(3, 3))
print(torch.randn(3, 3))
print(torch.randint(0, 9, (3, 3)))
4.2 Tensor的索引和切片
Tensor支持基本索引和切片操作,还支持ndarray中的高级索引(整数索引和布尔索引)操作。
4.2.1 代码
# Tensor的索引和切片
a = torch.arange(9).view(3, 3)
# 基本索引
print(a[2, 2])
# 切片
print(a[1:, :-1])
# row:1,2 col:1,2 -1:取开区间
# 带步长的切片(Python现在不支持负步长)
print(a[::2])
print(a[::2, ::2])
# 整数索引
rows = [0, 1]
cols = [2, 2]
print(a[rows, cols])
# 布尔索引
index = a > 4
print(index)
print(a[index])
torch.nonzero用于返回非零值的索引矩阵。
4.2.2 代码
a = torch.arange(9).view(3, 3)
index = torch.nonzero(a >= 8)
print(index)
a = torch.randint(0, 2, (3, 3))
print(a)
index = torch.nonzero(a)
print(index)
torch.where(condition,x,y)判断condition 的条件是否满足。当某个元素满足条件时,则返回对应矩阵x相同位置的元素,否则返回矩阵y的元素。
4.2.3 代码
x = torch.randn(3, 2)
y = torch.ones(3, 2)
print(x)
print(torch.where(x > 0, x, y))
4.3 Tensor的变换、拼接和拆分
Tensor.nelement、Tensor.ndimension、ndimension.size可分别用来查看矩阵元素的个数、轴的个数以及维度,属性Tensor.shape 也可以用来查看 Tensor的维度。
4.3.1 代码
a = torch.rand(1, 2, 3, 4, 5)
print("元素个数", a.nelement())
print("轴的个数", a.ndimension())
print("矩阵维度", a.size(), a.shape)
在PyTorch中,Tensor.view 和 Tensor.reshape 都能被用来更改 Tensor 的维度。它们的区别在于,Tensor.view 要求 Tensor 的物理存储必须是连续的,否则将报错;而Tensor.reshape 则没有这种要求。但是,Tensor.view 返回的一定是一个索引,更改返回值,则原始值同样被更改;Tensor.reshape 返回的是引用还是复制是不确定的。它们的相同之处是都接收要输出的维度作为参数,且输出的矩阵元素个数不能改变,可以在维度中输入一1,PyTorch 会自动推断它的数值。
4.3.2 代码
b = a.view(2*3, 4*5)
print(b.shape)
c = a.reshape(-1)
print(c.shape)
d = a.reshape(2*3, -1)
print(d.shape)
torch.squeeze 和 torch.unsqueeze 用于为Tensor 去掉和添加轴。其中 torch.squeeze用于去掉维度为1的轴,而torch.unsqueeze用于给Tensor 的指定位置添加一个维度为1的轴。
4.3.3 代码
b = torch.squeeze(a)
print(b.shape)
c = torch.unsqueeze(b, 0)
print(c.shape)
torch.t和torch.transpose 用于转置二维矩阵。这两个函数只接收二维Tensor,torch.t是torch.transpose.的简化版。
4.3.4 代码
a = torch.tensor([[2]])
b = torch.tensor([[2, 3]])
print(torch.transpose(a, 1, 0,))
print(torch.t(a))
print(torch.transpose(b, 1, 0,))
print(torch.t(b))
对于高纬度Tensor,可以使用permute方法来交换维度。
4.3.5 代码
a = torch.rand((1, 224, 224, 3))
print(a.shape)
b = a.permute(0, 3, 1, 2)
print(b.shape)
PyTorch 提供了 torch.cat 和 torch.stack 用于拼接矩阵。不同之处是,torch.cat 在已有的轴dim 上拼接矩阵,给定轴的维度可以不同,而其他轴的维度必须相同。torch.stack在新的轴上拼接,它要求被拼接的矩阵的所有维度都相同。下面的例子可以很清楚地表明它们的使用方式和区别。
4.3.6 代码
a = torch.randn(2, 3)
b = torch.randn(3, 3)
# 默认维度为dim=0
c = torch.cat((a, b))
d = torch.cat((b, b, b), dim=1)
print(c.shape)
print(d.shape)
c = torch.stack((b, b), dim=1)
d = torch.stack((b, b), dim=0)
print(c.shape)
print(d.shape)
除了拼接矩阵,PyTorch还提供了torch.split和torch.chunk用于拆分矩阵。它们的不同之处在于,torch.split传入的是拆分后每个矩阵的大小,可以传入list,也可以传入整数,而torch.chunk传入的是拆分的矩阵个数。
4.3.7 代码
a = torch.randn(10, 3)
# dim 0:10, 分为1, 2, 3,4 10=1+2+3+4
for x in torch.split(a, [1, 2, 3, 4], dim=0):
print(x.shape)
# dim 0:10 均匀分割为4块
for x in torch.split(a, 4, dim=0):
print(x.shape)
# 其中的4是拆分后矩阵个数
for x in torch.chunk(a, 4, dim=0):
print(x.shape)
4.4 PyTorch的Reduction操作
Reduction操作的特点是它往往对一个Tensor内的元素执行归约操作,比如torch.max找极大值、torch.cumsum计算累加,它还提供了dim参数来指定沿矩阵的哪个维度执行操作。
代码
import torch
# 默认求取全局最大值
a = torch.tensor([[1, 2], [3, 4]])
print("全局最大值:", torch.max(a))
# 指定维度 dim 后, 返回最大值其索引
print(torch.max(a, dim=0))
a = torch.tensor([[1, 2], [3, 4]])
print("沿着横轴计算每一列的累加:")
print(torch.cumsum(a, dim=0))
print("沿着纵轴计算每一行的乘积:")
print(torch.cumprod(a, dim=1))
# 计算矩阵的均值、中值、协方差
a = torch.Tensor([[1, 2], [3, 4]])
a.mean(),a.median(), a.std()
# torch.unique用来找出矩阵中出现了哪些元素
a = torch.randint(0, 3, (3, 3))
print(a)
print(torch.unique(a))
4.5 PyTorch的自动微分
当将 Tensor 的 requires_grad 属性设置为True时,PyTorch 的torch.autograd会自动追踪它的计算轨迹。当需要计算微分的时候,只需要对最终计算结果的Tensor 调用backward方法,所有计算节点的微分就会被保存在grad属性。
4.5.1 代码
x = torch.arange(9).view(3, 3)
x.requires_grad
x = torch.rand(3, 3, requires_grad=True)
print(x)
w = torch.ones(3, 3, requires_grad=True)
y = torch.sum(torch.mm(w, x))
print(y)
y.backward()
print(y.grad)
print(x.grad)
print(w.grad)
Tensor.detach会将Tensor从计算图剥离出去,不再计算它的微分。
4.5.2 代码
x = torch.rand(3, 3, requires_grad=True)
w = torch.rand(3, 3, requires_grad=True)
print(x)
print(y)
yy = torch.mm(w, x)
detached_yy = yy.detach()
y = torch.mean(yy)
y.backward()
print(yy.grad)
print(detached_yy)
print(w.grad)
print(x.grad)
with torch.no_grad():包括的代码段不会计算微分。
4.5.3 代码
with torch.no_grad():
y = torch.sum(torch.mm(w, x))
print(y.requires_grad)