html5电影网站模板,app免费下载入口,网站开发的现状研究,柳州网站建设公司这部分是 PyTorch介绍——YouTube系列的内容#xff0c;每一节都对应一个youtube视频。#xff08;可能跟之前的有一定的重复#xff09; 创建张量随机张量和种子张量形状张量数据类型 使用PyTorch张量进行数学与逻辑运算简单介绍——张量广播关于张量更多的数学操作原地修改… 这部分是 PyTorch介绍——YouTube系列的内容每一节都对应一个youtube视频。可能跟之前的有一定的重复 创建张量随机张量和种子张量形状张量数据类型 使用PyTorch张量进行数学与逻辑运算简单介绍——张量广播关于张量更多的数学操作原地修改张量 复制张量迁移到加速器操作张量形状改变维度数量 NumPy 桥接 本节YouTube视频地址点击这里 张量是PyTorch中的核心数据抽象。
首先让我们导入PyTorch模块。我们还将添加Python的数学模块以方便一些示例。
import torch
import math创建张量
创建张量的最简单方法是使用torch.empty()调用
x torch.empty(3, 4)
print(type(x))
print(x)
# 输出
class torch.Tensor
tensor([[ 6.2754e13, 7.0065e-45, 1.4013e-45, 0.0000e00],[-9.3681e11, 3.0634e-41, 0.0000e00, 0.0000e00],[ 1.5457e03, 3.0635e-41, 2.5256e-12, 4.5895e-41]])让我们回顾一下我们刚刚做的
我们使用附加到torch模块的众多工厂方法之一创建了一个张量。张量本身是二维的有3行4列。返回的对象类型为torch.Tensor它是torch.FloatTensor的别名默认情况下PyTorch张量填充有32位浮点数。更多关于下面的数据类型。打印张量时您可能会看到一些随机的值。torch.empty()调用为张量分配内存但不使用任何值初始化它-因此您看到的是分配时内存中的任何内容。
关于张量及其维数和术语的简要说明
您有时会看到一个称为向量的一维张量。同样二维张量通常称为矩阵。任何超过二维的东西通常被称为张量。
通常情况下您需要使用一些值初始化张量。常见情况是全零、全一或随机值torch模块为所有这些提供工厂方法
zeros torch.zeros(2, 3)
print(zeros)ones torch.ones(2, 3)
print(ones)torch.manual_seed(1729)
random torch.rand(2, 3)
print(random)# 输出
tensor([[0., 0., 0.],[0., 0., 0.]])
tensor([[1., 1., 1.],[1., 1., 1.]])
tensor([[0.3126, 0.3791, 0.3087],[0.0736, 0.4216, 0.0691]])功能方法都做了你所期望的——我们有一个充满零的张量另一个张量充满一还有一个张量是随机值在0到1之间。
随机张量和种子
说到随机张量您是否注意到在它前面调用了torch.manual_seed()使用随机值初始化张量例如模型的学习权重很常见但有时——尤其是在研究环境中——您需要一些结果可重复性的保证。手动设置随机数生成器的种子是做到这一点的方法。让我们更仔细地看看
torch.manual_seed(1729)
random1 torch.rand(2, 3)
print(random1)random2 torch.rand(2, 3)
print(random2)torch.manual_seed(1729)
random3 torch.rand(2, 3)
print(random3)random4 torch.rand(2, 3)
print(random4)# 输出
tensor([[0.3126, 0.3791, 0.3087],[0.0736, 0.4216, 0.0691]])
tensor([[0.2332, 0.4047, 0.2162],[0.9927, 0.4128, 0.5938]])
tensor([[0.3126, 0.3791, 0.3087],[0.0736, 0.4216, 0.0691]])
tensor([[0.2332, 0.4047, 0.2162],[0.9927, 0.4128, 0.5938]])您应该在上面看到的是random1和random3携带相同的值random2和random4也是如此。手动设置RNG的种子会重置它因此在大多数设置中取决于随机数的相同计算应该会提供相同的结果。
有关详细信息请参阅PyTorch关于可复现性的文档
张量形状
通常当您对两个或多个张量执行操作时它们需要具有相同的形状-也就是说具有相同数量的维度和每个维度中相同数量的单元格。为此我们有torch.*_like()方法
x torch.empty(2, 2, 3)
print(x.shape)
print(x)empty_like_x torch.empty_like(x)
print(empty_like_x.shape)
print(empty_like_x)zeros_like_x torch.zeros_like(x)
print(zeros_like_x.shape)
print(zeros_like_x)ones_like_x torch.ones_like(x)
print(ones_like_x.shape)
print(ones_like_x)rand_like_x torch.rand_like(x)
print(rand_like_x.shape)
print(rand_like_x)# 输出
torch.Size([2, 2, 3])
tensor([[[5.5989e07, 3.0635e-41, 3.4774e-15],[3.0635e-41, 8.9683e-44, 0.0000e00]],[[1.5695e-43, 0.0000e00, nan],[3.0631e-41, 0.0000e00, 1.4013e-45]]])
torch.Size([2, 2, 3])
tensor([[[ 1.5582e03, 3.0635e-41, 4.0625e04],[ 3.0635e-41, 1.1210e-43, 0.0000e00]],[[ 8.9683e-44, 0.0000e00, 2.2417e24],[ 3.0631e-41, -9.3681e11, 3.0634e-41]]])
torch.Size([2, 2, 3])
tensor([[[0., 0., 0.],[0., 0., 0.]],[[0., 0., 0.],[0., 0., 0.]]])
torch.Size([2, 2, 3])
tensor([[[1., 1., 1.],[1., 1., 1.]],[[1., 1., 1.],[1., 1., 1.]]])
torch.Size([2, 2, 3])
tensor([[[0.6128, 0.1519, 0.0453],[0.5035, 0.9978, 0.3884]],[[0.6929, 0.1703, 0.1384],[0.4759, 0.7481, 0.0361]]])上面代码中的第一个新内容是在张量上使用.shape属性。该属性包含张量每个维度的范围列表——在我们的例子中x是形状为2 x 2 x 3的三维张量。
下面我们调用.empty_like().zeros_like().ones_like()和.rand_like()方法。使用.shape属性我们可以验证这些方法中的每一个都返回一个张量的维数和范围。
最后一种创建张量的方法我们将要介绍的是直接从一个PyTorch集合中指定其数据
some_constants torch.tensor([[3.1415926, 2.71828], [1.61803, 0.0072897]])
print(some_constants)some_integers torch.tensor((2, 3, 5, 7, 11, 13, 17, 19))
print(some_integers)more_integers torch.tensor(((2, 4, 6), [3, 6, 9]))
print(more_integers)# 输出
tensor([[3.1416, 2.7183],[1.6180, 0.0073]])
tensor([ 2, 3, 5, 7, 11, 13, 17, 19])
tensor([[2, 4, 6],[3, 6, 9]])如果您已经在Python元组或列表中有数据使用torch.tensor()是创建一个张量的最直接方法。如上所示嵌套集合将创建多维张量。 注意torch.tensor()是 创建数据的副本。
张量数据类型
有几种方法可以设置张量的数据类型
a torch.ones((2, 3), dtypetorch.int16)
print(a)b torch.rand((2, 3), dtypetorch.float64) * 20.
print(b)c b.to(torch.int32)
print(c)# 输出
tensor([[1, 1, 1],[1, 1, 1]], dtypetorch.int16)
tensor([[ 0.9956, 1.4148, 5.8364],[11.2406, 11.2083, 11.6692]], dtypetorch.float64)
tensor([[ 0, 1, 5],[11, 11, 11]], dtypetorch.int32)设置张量的基础数据类型的最简单方法是在创建时使用可选参数。在上面单元格的第一行我们为张量a设置了dtypetorch.int16。当我们打印a时我们可以看到它充满了1而不是1.Python的微妙之处是这是一个整数类型而不是浮点。
关于打印张量 a 的另一件需要注意的事情是与我们将数据类型dtype设为默认值32位浮点数时的情况不同打印这个张量时也会指明它的数据类型。
你可能还发现我们从将张量的形状指定为一系列整数参数到将这些参数分组。这不是绝对必要的——PyTorch将一系列有限的、未标记的整数参数作为张量形状——但是当添加可选参数时它可以使您的意图更具可读性。
使用PyTorch张量进行数学与逻辑运算
现在你知道了一些创建张量的方法…你能用它们做什么
让我们先看看基本算术以及张量如何与简单标量相互作用
ones torch.zeros(2, 2) 1
twos torch.ones(2, 2) * 2
threes (torch.ones(2, 2) * 7 - 1) / 2
fours twos ** 2
sqrt2s twos ** 0.5print(ones)
print(twos)
print(threes)
print(fours)
print(sqrt2s)# 输出
tensor([[1., 1.],[1., 1.]])
tensor([[2., 2.],[2., 2.]])
tensor([[3., 3.],[3., 3.]])
tensor([[4., 4.],[4., 4.]])
tensor([[1.4142, 1.4142],[1.4142, 1.4142]])正如您在上面看到的张量和标量之间的算术运算例如加法、减法、乘法、除法和指数分布在张量的每个元素上。
因为这些操作的输出将是张量您可以使用通常的运算符优先级规则将它们链接在一起如我们创建张量threes的那一行。
两个张量之间的类似操作也表现得像你直觉上期望的那样
powers2 twos ** torch.tensor([[1, 2], [3, 4]])
print(powers2)fives ones fours
print(fives)dozens threes * fours
print(dozens)# 输出
tensor([[ 2., 4.],[ 8., 16.]])
tensor([[5., 5.],[5., 5.]])
tensor([[12., 12.],[12., 12.]])这里需要注意的是前面编解码器中的所有张量都是相同的形状。如果形状不同当我们试图对张量进行二进制运算时会发生什么
注以下代码运行时会抛出错误。这是故意的。
a torch.rand(2, 3)
b torch.rand(3, 2)print(a * b)在一般情况下您不能以这种方式对不同形状的张量进行操作即使在像上面的单元格这样的情况下张量具有相同数量的元素。
简单介绍——张量广播
如果你熟悉 NumPy 的多维数组ndarray中的广播语义你会发现同样的规则也适用于此处。
同形状规则的一个例外是张量广播。下面是一个例子
rand torch.rand(2, 4)
doubled rand * (torch.ones(1, 4) * 2)print(rand)
print(doubled)# 输出
tensor([[0.6146, 0.5999, 0.5013, 0.9397],[0.8656, 0.5207, 0.6865, 0.3614]])
tensor([[1.2291, 1.1998, 1.0026, 1.8793],[1.7312, 1.0413, 1.3730, 0.7228]])这里有什么诀窍我们是如何可以将2x4张量乘以1x4张量
广播是一种在形状上具有相似性的张量之间执行运算的方式。在上述示例中那个一行四列的张量与那个两行四列张量的两行都进行了乘法运算。
这在深度学习中是一项重要的运算。常见的例子是将一个包含学习权重的张量与一批输入张量相乘将该运算分别应用于这批数据中的每个实例然后返回一个形状相同的张量 —— 就如同我们上面的那个24*14的例子返回了一个形状为24的张量一样。
广播规则是
每个张量必须至少有一个维度-不存在空张量。比较两个张量的维度大小 每个维度的大小必须相等或者其中一个维度的大小必须为 1或者某一个维度在其中一个张量中不存在。
当然形状相同的张量显然是可以进行 “广播” 的正如你之前所看到的那样。 以下是一些符合上述规则并允许进行广播的情况示例
# 创建一个形状为(4, 3, 2)且元素全为1的张量a
a torch.ones(4, 3, 2)# 将张量a与一个形状为(3, 2)的随机张量相乘第三个维度和第二个维度与a的对应维度相同缺少第一个维度
b a * torch.rand( 3, 2) # 3rd 2nd dims identical to a, dim 1 absent
print(b)# 将张量a与一个形状为(3, 1)的随机张量相乘第三个维度大小为1第二个维度与a的第二个维度相同
c a * torch.rand( 3, 1) # 3rd dim 1, 2nd dim identical to a
print(c)# 将张量a与一个形状为(1, 2)的随机张量相乘第三个维度与a的第三个维度相同第二个维度大小为1
d a * torch.rand( 1, 2) # 3rd dim identical to a, 2nd dim 1
print(d)# 输出
tensor([[[0.6493, 0.2633],[0.4762, 0.0548],[0.2024, 0.5731]],[[0.6493, 0.2633],[0.4762, 0.0548],[0.2024, 0.5731]],[[0.6493, 0.2633],[0.4762, 0.0548],[0.2024, 0.5731]],[[0.6493, 0.2633],[0.4762, 0.0548],[0.2024, 0.5731]]])
tensor([[[0.7191, 0.7191],[0.4067, 0.4067],[0.7301, 0.7301]],[[0.7191, 0.7191],[0.4067, 0.4067],[0.7301, 0.7301]],[[0.7191, 0.7191],[0.4067, 0.4067],[0.7301, 0.7301]],[[0.7191, 0.7191],[0.4067, 0.4067],[0.7301, 0.7301]]])
tensor([[[0.6276, 0.7357],[0.6276, 0.7357],[0.6276, 0.7357]],[[0.6276, 0.7357],[0.6276, 0.7357],[0.6276, 0.7357]],[[0.6276, 0.7357],[0.6276, 0.7357],[0.6276, 0.7357]],[[0.6276, 0.7357],[0.6276, 0.7357],[0.6276, 0.7357]]])仔细查看上面每个张量的值
创建b的乘法操作在a的每个“层”上广播注意这里是四层因为是432的矩阵最外层一共有四个。这四个个彼此相同。对于c操作被广播到a的每一层和每一行因为乘的是三行一列所以列之间传播导致结果相同。一共有两列两列相同。所有看上去是一行内两个数是一样的对于d我们切换了它——是一行两列。现在是“行”在四层和两列之间相同的。
以下是一些尝试广播失败的例子
# 这部分代码是错误的运行会报错
a torch.ones(4, 3, 2)b a * torch.rand(4, 3) # dimensions must match last-to-firstc a * torch.rand( 2, 3) # both 3rd 2nd dims differentd a * torch.rand((0, )) # cant broadcast with an empty tensor关于张量更多的数学操作
PyTorch张量有超过300个可以在它们上执行的操作。
以下是从一些主要的运算类别中选取的一小部分示例
import torch
import math# 常见函数
a torch.rand(2, 4) * 2 - 1
print(常见函数:)
print(torch.abs(a)) # 计算张量a中每个元素的绝对值
print(torch.ceil(a)) # 对张量a中每个元素向上取整
print(torch.floor(a)) # 对张量a中每个元素向下取整
print(torch.clamp(a, -0.5, 0.5)) # 将张量a中每个元素限制在-0.5到0.5之间# 三角函数及其反函数
angles torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
sines torch.sin(angles) # 计算角度的正弦值
inverses torch.asin(sines) # 计算正弦值的反正弦值
print(\n正弦值和反正弦值:)
print(angles)
print(sines)
print(inverses)# 按位运算
print(\n按位异或:)
b torch.tensor([1, 5, 11])
c torch.tensor([2, 7, 10])
print(torch.bitwise_xor(b, c)) # 对张量b和c进行按位异或运算# 比较操作:
print(\n广播式的、逐元素的相等比较:)
d torch.tensor([[1., 2.], [3., 4.]])
e torch.ones(1, 2) # 许多比较操作都支持广播
print(torch.eq(d, e)) # 返回一个布尔类型的张量表示d和e对应元素是否相等# 归约操作:
print(\n归约操作:)
print(torch.max(d)) # 返回一个单元素张量表示张量d中的最大值
print(torch.max(d).item()) # 从返回的张量中提取值
print(torch.mean(d)) # 计算张量d的平均值
print(torch.std(d)) # 计算张量d的标准差
print(torch.prod(d)) # 计算张量d中所有元素的乘积
print(torch.unique(torch.tensor([1, 2, 1, 2, 1, 2]))) # 过滤出唯一元素# 向量和线性代数运算
v1 torch.tensor([1., 0., 0.]) # x单位向量
v2 torch.tensor([0., 1., 0.]) # y单位向量
m1 torch.rand(2, 2) # 随机矩阵
m2 torch.tensor([[3., 0.], [0., 3.]]) # 三倍的单位矩阵print(\n向量和矩阵:)
print(torch.linalg.cross(v2, v1)) # y单位向量与x单位向量的叉积结果为负的z单位向量v1 x v2 -v2 x v1
print(m1)
m3 torch.linalg.matmul(m1, m2)
print(m3) # 结果是三倍的m1
print(torch.linalg.svd(m3)) # 对矩阵m3进行奇异值分解# 输出
Common functions:
tensor([[0.9238, 0.5724, 0.0791, 0.2629],[0.1986, 0.4439, 0.6434, 0.4776]])
tensor([[-0., -0., 1., -0.],[-0., 1., 1., -0.]])
tensor([[-1., -1., 0., -1.],[-1., 0., 0., -1.]])
tensor([[-0.5000, -0.5000, 0.0791, -0.2629],[-0.1986, 0.4439, 0.5000, -0.4776]])Sine and arcsine:
tensor([0.0000, 0.7854, 1.5708, 2.3562])
tensor([0.0000, 0.7071, 1.0000, 0.7071])
tensor([0.0000, 0.7854, 1.5708, 0.7854])Bitwise XOR:
tensor([3, 2, 1])Broadcasted, element-wise equality comparison:
tensor([[ True, False],[False, False]])Reduction ops:
tensor(4.)
4.0
tensor(2.5000)
tensor(1.2910)
tensor(24.)
tensor([1, 2])Vectors Matrices:
tensor([ 0., 0., -1.])
tensor([[0.7375, 0.8328],[0.8444, 0.2941]])
tensor([[2.2125, 2.4985],[2.5332, 0.8822]])
torch.return_types.linalg_svd(
Utensor([[-0.7889, -0.6145],[-0.6145, 0.7889]]),
Stensor([4.1498, 1.0548]),
Vhtensor([[-0.7957, -0.6056],[ 0.6056, -0.7957]]))这只是运算的一个小示例。想要了解更多细节以及数学函数的完整列表请查看相关文档。想要了解更多细节以及线性代数运算的完整列表请查看这份文档。
原地修改张量
张量上的大多数二进制操作将返回第三个新张量。当我们说c a * b其中a和b是张量时新张量c将占据与其他张量不同的内存区域。
不过有时候你可能希望原地修改一个张量——例如当你进行逐元素计算并且可以丢弃中间值的时候。为此大多数数学函数都有一个带有下划线_后缀的版本该版本可以原地修改张量。
例如
import torch
import matha torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print(a:)
print(a)
print(torch.sin(a)) # 此操作在内存中创建一个新的张量
print(a) # a 没有改变b torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print(\nb:)
print(b)
print(torch.sin_(b)) # 注意下划线
print(b) # b 已经改变# 输出
a:
tensor([0.0000, 0.7854, 1.5708, 2.3562])
tensor([0.0000, 0.7071, 1.0000, 0.7071])
tensor([0.0000, 0.7854, 1.5708, 2.3562])b:
tensor([0.0000, 0.7854, 1.5708, 2.3562])
tensor([0.0000, 0.7071, 1.0000, 0.7071])
tensor([0.0000, 0.7071, 1.0000, 0.7071])对于算术运算有些函数的行为类似
a torch.ones(2, 2)
b torch.rand(2, 2)print(Before:)
print(a)
print(b)
print(\nAfter adding:)
print(a.add_(b))
print(a)
print(b)
print(\nAfter multiplying)
print(b.mul_(b))
print(b)
# 输出
Before:
tensor([[1., 1.],[1., 1.]])
tensor([[0.3788, 0.4567],[0.0649, 0.6677]])After adding:
tensor([[1.3788, 1.4567],[1.0649, 1.6677]])
tensor([[1.3788, 1.4567],[1.0649, 1.6677]])
tensor([[0.3788, 0.4567],[0.0649, 0.6677]])After multiplying
tensor([[0.1435, 0.2086],[0.0042, 0.4459]])
tensor([[0.1435, 0.2086],[0.0042, 0.4459]])请注意这些原地算术函数是 torch.Tensor 对象的方法不像许多其他函数例如 torch.sin()那样附属于 torch 模块。从 a.add_(b) 可以看出调用该方法的张量会在原地被修改。
还有另一种方法可以将计算结果存储在现有的、已分配好的张量中。到目前为止我们所见到的许多方法和函数——包括创建张量的方法——都有一个 out 参数它允许你指定一个张量来接收输出结果。如果指定的 out 张量具有正确的形状和数据类型那么计算结果就可以存储其中而无需重新分配内存
import torcha torch.rand(2, 2)
b torch.rand(2, 2)
c torch.zeros(2, 2)
old_id id(c)print(c)
# 将a和b矩阵相乘的结果存储到c中
d torch.matmul(a, b, outc)
print(c) # c的内容已改变# 验证c和d是同一个对象而非仅仅值相等
assert c is d
# 确保新的c和旧的c是同一个对象
assert id(c) old_id# 生成一个2x2的随机张量并将结果存储到c中创建操作也适用out参数
torch.rand(2, 2, outc)
print(c) # c的内容再次改变
# 仍然是同一个对象
assert id(c) old_id# 输出
tensor([[0., 0.],[0., 0.]])
tensor([[0.3653, 0.8699],[0.2364, 0.3604]])
tensor([[0.0776, 0.4004],[0.9877, 0.0352]])复制张量
与Python中的任何对象一样将张量分配给变量会使张量的标签可变并且不会复制它。例如
a torch.ones(2, 2)
b aa[0][1] 561 # we change a...
print(b) # ...and b is also altered# 输出
tensor([[ 1., 561.],[ 1., 1.]])但是如果您想要单独的数据副本来处理怎么办clone()方法适合您
a torch.ones(2, 2)
b a.clone()assert b is not a # different objects in memory...
print(torch.eq(a, b)) # ...but still with the same contents!a[0][1] 561 # a changes...
print(b) # ...but b is still all ones# 输出
tensor([[True, True],[True, True]])
tensor([[1., 1.],[1., 1.]])使用 clone() 时有一个重要的事项需要注意。如果源张量启用了自动求导autograd那么克隆后的张量也会启用。关于自动求导我们会在相关视频中深入探讨但如果你想先了解个大概可以接着往下看。
在很多情况下这正是你所期望的。例如若你的模型在其 forward() 方法中有多条计算路径且原始张量及其克隆张量都对模型的输出有贡献那么为了让模型能够学习你需要为这两个张量都开启自动求导。如果源张量启用了自动求导如果它是一组学习权重或者是由涉及这些权重的计算得出的通常就会启用那么你就能得到预期的结果。
另一方面如果你进行的计算中原始张量及其克隆张量都不需要跟踪梯度那么只要源张量关闭了自动求导就没问题了。
不过还有第三种情况假设你在模型的 forward() 函数中进行计算默认情况下所有操作都开启了梯度计算但你想在计算过程中提取一些值来生成某些指标。在这种情况下你不希望源张量的克隆副本跟踪梯度——关闭自动求导的历史跟踪可以提高性能。为此你可以在源张量上使用 .detach() 方法
a torch.rand(2, 2, requires_gradTrue) # turn on autograd
print(a)b a.clone()
print(b)c a.detach().clone()
print(c)print(a)# 输出
tensor([[0.0905, 0.4485],[0.8740, 0.2526]], requires_gradTrue)
tensor([[0.0905, 0.4485],[0.8740, 0.2526]], grad_fnCloneBackward0)
tensor([[0.0905, 0.4485],[0.8740, 0.2526]])
tensor([[0.0905, 0.4485],[0.8740, 0.2526]], requires_gradTrue)这里发生了什么呢 我们创建了一个张量 a并将 requires_gradTrue 开启。我们还未介绍过这个可选参数不过会在自动求导这一单元进行讲解。 当我们打印 a 时它会告知我们 requires_gradTrue 这个属性——这意味着自动求导和计算历史跟踪功能已开启。 我们对 a 进行克隆并将其标记为 b。当我们打印 b 时可以看到它正在跟踪其计算历史——它继承了 a 的自动求导设置并添加到了计算历史中。 我们将 a 克隆到 c但首先调用了 detach() 方法。 打印 c 时我们看不到计算历史也没有 requires_gradTrue。
detach() 方法将张量从其计算历史中分离出来。它的作用是“后续操作就当作自动求导是关闭的”。它在不改变 a 的情况下实现了这一点——你可以看到当我们最后再次打印 a 时它仍然保留着 requires_gradTrue 属性。
迁移到加速器
PyTorch 的一大优势在于它能在诸如 CUDA、MPS、MTIA 或 XPU 等加速器上实现强大的加速功能。到目前为止我们所做的一切操作都是在 CPU 上进行的。那么我们如何迁移到更快的硬件上呢
首先我们应该使用 is_available() 方法来检查加速器是否可用。 注意如果你没有可用的加速器那么本节中可执行的代码单元将不会执行任何与加速器相关的代码。
if torch.accelerator.is_available():print(We have an accelerator!)
else:print(Sorry, CPU only.)
# 输出至少应该是
We have an accelerator!一旦我们确定有一个或多个加速器可用我们就需要把数据放置到加速器能够访问的地方。你的 CPU 对计算机内存RAM中的数据进行计算。而你的加速器有其专用的内存。每当你想在某个设备上执行计算时你都必须将该计算所需的所有数据移动到该设备可访问的内存中。通俗来讲“将数据移动到 GPU 可访问的内存中” 常简称为 “将数据移动到 GPU 上”。
有多种方法可以将数据传输到目标设备上。你可以在创建数据时就进行操作
if torch.accelerator.is_available():gpu_rand torch.rand(2, 2, devicetorch.accelerator.current_accelerator())print(gpu_rand)
else:print(Sorry, CPU only.)
# 输出
tensor([[0.3344, 0.2640],[0.2119, 0.0582]], devicecuda:0)默认情况下新的张量是在 CPU 上创建的所以当我们想要在加速器上创建张量时就必须使用可选的 device 参数进行指定。当我们打印新创建的张量时可以看到PyTorch 会告知我们该张量所在的设备如果不是在 CPU 上的话。
你可以使用 torch.accelerator.device_count() 来查询加速器的数量。如果你有多个加速器你可以通过索引来指定它们以 CUDA 为例devicecuda:0、devicecuda:1 等等。
从编码实践的角度来看在各处都用字符串常量来指定设备是相当不可靠的。在理想情况下无论你的代码是在 CPU 还是在加速器硬件上运行都应该能够稳定地执行。你可以通过创建一个设备句柄来实现这一点这样就可以将这个句柄传递给你的张量而不是使用字符串
my_device torch.accelerator.current_accelerator() if torch.accelerator.is_available() else torch.device(cpu)
print(Device: {}.format(my_device))x torch.rand(2, 2, devicemy_device)
print(x)# 输出
Device: cuda
tensor([[0.0024, 0.6778],[0.2441, 0.6812]], devicecuda:0)如果你已有一个位于某一设备上的张量你可以使用 to() 方法将其移动到另一个设备。下面这行代码会在 CPU 上创建一个张量然后将它移动到你在上一个代码块中获取的设备句柄所对应的设备上。
y torch.rand(2, 2)
y y.to(my_device)要知道若要对两个或更多张量进行计算所有这些张量必须位于同一设备上这一点很重要。不管你是否有可用的加速器设备下面的代码都会抛出运行时错误以 CUDA 为例
x torch.rand(2, 2)
y torch.rand(2, 2, devicecuda)
z x y # 会抛出异常操作张量形状
有时你需要改变张量的形状。下面我们来看看几种常见的情况以及如何处理它们。
改变维度数量
你可能需要改变维度数量的一种情况是向模型传入单个输入实例。PyTorch 模型通常期望输入是批量形式的。
例如假设有一个处理 3 x 226 x 226 图像的模型——这是一个具有 3 个颜色通道、边长为 226 像素的正方形图像。当你加载并转换图像后会得到一个形状为 (3, 226, 226) 的张量。然而你的模型期望的输入形状是 (N, 3, 226, 226)其中 N 是批量中的图像数量。那么如何创建一个批量大小为 1 的输入呢
a torch.rand(3, 226, 226)
b a.unsqueeze(0)print(a.shape)
print(b.shape)输出结果
torch.Size([3, 226, 226])
torch.Size([1, 3, 226, 226])unsqueeze() 方法会添加一个大小为 1 的维度。unsqueeze(0) 会将其作为新的第 0 维度添加进来——现在你就有了一个批量大小为 1 的数据批次
既然这是 “扩充维度unsqueezing”那么 “压缩维度squeezing” 又是什么意思呢我们利用了这样一个事实即任何大小为 1 的维度都不会改变张量中元素的数量。
c torch.rand(1, 1, 1, 1, 1)
print(c)输出
tensor([[[[[0.2347]]]]])继续上面的例子假设模型对于每个输入的输出是一个 20 维的向量。那么你会期望输出的形状是 (N, 20)其中 N 是输入批次中实例的数量。这意味着对于我们的单输入批次我们将得到一个形状为 (1, 20) 的输出。
如果你想用这个输出进行一些非批量的计算——比如只期望一个 20 维向量的计算那该怎么办呢
a torch.rand(1, 20)
print(a.shape)
print(a)b a.squeeze(0)
print(b.shape)
print(b)c torch.rand(2, 2)
print(c.shape)d c.squeeze(0)
print(d.shape)输出
torch.Size([1, 20])
tensor([[0.1899, 0.4067, 0.1519, 0.1506, 0.9585, 0.7756, 0.8973, 0.4929, 0.2367,0.8194, 0.4509, 0.2690, 0.8381, 0.8207, 0.6818, 0.5057, 0.9335, 0.9769,0.2792, 0.3277]])
torch.Size([20])
tensor([0.1899, 0.4067, 0.1519, 0.1506, 0.9585, 0.7756, 0.8973, 0.4929, 0.2367,0.8194, 0.4509, 0.2690, 0.8381, 0.8207, 0.6818, 0.5057, 0.9335, 0.9769,0.2792, 0.3277])
torch.Size([2, 2])
torch.Size([2, 2])从形状可以看出我们的二维张量现在变成了一维张量并且如果你仔细观察上面代码块的输出你会发现打印 a 时由于多了一个维度会显示 “额外” 的一组方括号 []。
你只能压缩大小为 1 的维度。看看上面我们尝试压缩 c 中大小为 2 的维度的情况得到的形状和开始时一样。对 squeeze() 和 unsqueeze() 的调用只能作用于大小为 1 的维度因为否则就会改变张量中元素的数量。
另一个可能用到 unsqueeze() 的地方是为了便于广播。回顾上面的例子我们有如下代码
a torch.ones(4, 3, 2)c a * torch.rand( 3, 1) # 第 3 维度 1第 2 维度与 a 的相同
print(c)其实际效果是在第 0 维和第 2 维上进行广播操作使得随机的 3 x 1 张量与 a 中每个 3 元素的列进行逐元素相乘。
如果随机向量只是一个 3 元素的向量呢我们将失去进行广播的能力因为根据广播规则最后的维度将不匹配。unsqueeze() 可以解决这个问题
a torch.ones(4, 3, 2)
b torch.rand( 3) # 尝试计算 a * b 会引发运行时错误
c b.unsqueeze(1) # 转换为二维张量在末尾添加新维度
print(c.shape)
print(a * c) # 广播又能正常工作了输出
torch.Size([3, 1])
tensor([[[0.1891, 0.1891],[0.3952, 0.3952],[0.9176, 0.9176]],[[0.1891, 0.1891],[0.3952, 0.3952],[0.9176, 0.9176]],[[0.1891, 0.1891],[0.3952, 0.3952],[0.9176, 0.9176]],[[0.1891, 0.1891],[0.3952, 0.3952],[0.9176, 0.9176]]])queeze() 和 unsqueeze() 方法也有原地操作版本即 squeeze_() 和 unsqueeze_()
batch_me torch.rand(3, 226, 226)
print(batch_me.shape)
batch_me.unsqueeze_(0)
print(batch_me.shape)输出
torch.Size([3, 226, 226])
torch.Size([1, 3, 226, 226])有时你可能想要更彻底地改变张量的形状同时仍然保留元素的数量及其内容。其中一种情况出现在模型的卷积层和线性层之间的接口处——这在图像分类模型中很常见。卷积核会产生一个形状为 特征数 x 宽度 x 高度 的输出张量但接下来的线性层期望的是一维输入。reshape() 可以为你完成这个操作前提是你请求的维度所包含的元素数量与输入张量的元素数量相同
output3d torch.rand(6, 20, 20)
print(output3d.shape)input1d output3d.reshape(6 * 20 * 20)
print(input1d.shape)# 也可以将其作为 torch 模块的方法调用
print(torch.reshape(output3d, (6 * 20 * 20,)).shape)输出
torch.Size([6, 20, 20])
torch.Size([2400])
torch.Size([2400])注意
上面代码块最后一行中的 (6 * 20 * 20,) 参数是因为 PyTorch 在指定张量形状时期望是一个元组——但是当形状是方法的第一个参数时它允许我们偷懒只使用一系列整数。在这里我们必须添加括号和逗号以使方法确信这确实是一个单元素元组。
在可能的情况下reshape() 会返回要改变形状的张量的一个视图——也就是说一个单独的张量对象它指向相同的底层内存区域。这一点很重要这意味着对源张量所做的任何更改都将反映在该张量的视图中除非你对其进行 clone() 操作。
在一些超出本介绍范围的情况下reshape() 必须返回一个携带数据副本的张量。有关更多信息请参阅文档。
NumPy 桥接
在上面关于广播的部分中提到过 PyTorch 的广播语义与 NumPy 的是兼容的——但 PyTorch 和 NumPy 之间的关系远不止于此。
如果你有现有的机器学习或科学代码其中数据存储在 NumPy 的 ndarray 中你可能希望将相同的数据表示为 PyTorch 张量无论是为了利用 PyTorch 的 GPU 加速功能还是其用于构建机器学习模型的高效抽象。在 ndarray 和 PyTorch 张量之间进行转换很容易
import numpy as npnumpy_array np.ones((2, 3))
print(numpy_array)pytorch_tensor torch.from_numpy(numpy_array)
print(pytorch_tensor)输出
[[1. 1. 1.][1. 1. 1.]]
tensor([[1., 1., 1.],[1., 1., 1.]], dtypetorch.float64)PyTorch 创建了一个与 NumPy 数组形状相同且包含相同数据的张量甚至保留了 NumPy 默认的 64 位浮点数数据类型。
转换也可以很容易地反向进行
pytorch_rand torch.rand(2, 3)
print(pytorch_rand)numpy_rand pytorch_rand.numpy()
print(numpy_rand)输出
tensor([[0.8716, 0.2459, 0.3499],[0.2853, 0.9091, 0.5695]])
[[0.87163675 0.2458961 0.34993553][0.2853077 0.90905803 0.5695162 ]]重要的是要知道这些转换后的对象与它们的源对象使用相同的底层内存这意味着对其中一个对象的更改会反映在另一个对象上
numpy_array[1, 1] 23
print(pytorch_tensor)pytorch_rand[1, 1] 17
print(numpy_rand)输出
tensor([[ 1., 1., 1.],[ 1., 23., 1.]], dtypetorch.float64)
[[ 0.87163675 0.2458961 0.34993553][ 0.2853077 17. 0.5695162 ]]