2021 PyTorch官方实战教程(一)Tensor 详解

共 22799字,需浏览 46分钟

 ·

2021-06-13 16:28

点击上方AI算法与图像处理”,选择加"星标"或“置顶”

重磅干货,第一时间送达

这个系列时pytorch官方实战教程,后续会继续更新。。

一、pytorch Tensor详解

首先,让我们导入PyTorch模块。我们还将添加Python的math模块来简化一些示例。

import torch
import math

Creating Tensors

最简单创建 tensor 的方法是调用torch.empty() :

x = torch.empty(3, 4)
print(type(x))
print(x)

输出:

<class 'torch.Tensor'>
tensor([[0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
[0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
[0.0000e+00, 0.0000e+00, 2.8026e-45, 0.0000e+00]])

让我们把刚才所做的整理一下:

  • 我们创建了一个张量使用众多的工厂方法之一附加到torch模块。

  • 这个张量本身是二维的,有3行4列。

  • 返回的对象类型是 torch.Tensor,是torch.FloatTensor的别名;默认情况下,PyTorch张量由32位浮点数填充(有关数据类型的详细信息,请参见下文。)

  • 打印张量时,可能会看到一些随机的值。torch.empty()调用为张量分配内存,但不使用任何值对其进行初始化-因此您看到的是分配时内存中的内容。

关于张量及其维数和术语的简要说明:

  • 有时你会看到一个称为向量的一维张量。

  • 同样,二维张量通常被称为矩阵。

  • 任何超过两个维度的东西通常被称为张量。

通常情况下,您需要用一些值初始化张量。常见的情况是全0、全1或随机值,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,还有一个是介于0和1之间随机值。

Random Tensors and Seeding

说到随机张量,你注意到它前面的 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]])

上面你应该看到的是random1random3携带相同的值,就像random2random4一样。手动设置RNG的种子会重置它,因此在大多数设置中,基于随机数的相同计算应该提供相同的结果。

更多相关内容,可以参考:https://pytorch.org/docs/stable/notes/randomness.html

Tensor Shapes

通常,在对两个或多个张量执行操作时,它们需要具有相同的形状—也就是说,在每个维度中具有相同数量的维度和相同数量的单元。为此,我们有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([[[ 0.0000e+00, 1.0842e-19, 4.9628e-26],
[-2.5250e-29, 9.8091e-45, 0.0000e+00]],

[[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]]])
torch.Size([2, 2, 3])
tensor([[[ 2.5776e-33, 1.4013e-45, nan],
[ 0.0000e+00, 1.4013e-45, 0.0000e+00]],

[[ 4.9477e-26, -3.6902e+19, 2.6082e-33],
[ 1.4013e-45, 4.9633e-26, -8.5920e+09]]])
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是一个形状为2x2x3的三维张量。

在下面,我们调用.empty_like().zeros_like().one_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()创建数据的副本。

Tensor Data Types

设置张量的数据类型有两种方法:

a = torch.ones((2, 3), dtype=torch.int16)
print(a)

b = torch.rand((2, 3), dtype=torch.float64) * 20.
print(b)

c = b.to(torch.int32)
print(c)

输出:

tensor([[1, 1, 1],
[1, 1, 1]], dtype=torch.int16)
tensor([[ 0.9956, 1.4148, 5.8364],
[11.2406, 11.2083, 11.6692]], dtype=torch.float64)
tensor([[ 0, 1, 5],
[11, 11, 11]], dtype=torch.int32)

设置张量的基础数据类型的最简单方法是在创建时使用可选参数。在上面单元格的第一行中,我们为张量a设置dtype=torch.int16。当我们打印a时,我们可以看到它充满了1而不是1.Python微妙地暗示这是一个整数类型而不是浮点类型。

另一个要注意的是,与将dtype保留为默认值(32位浮点)不同,打印张量还指定了它的dtype

您可能已经注意到,我们已经从将张量的形状指定为一系列整数参数,到将这些参数分组为一个元组。这并不是绝对必要的—Pytorch将把一系列初始的、未标记的整数参数作为张量形状—但是当添加可选参数时,它可以使您的意图更具可读性。

另一种设置数据类型的方法是使用.to()方法。在上面的单元中,我们以通常的方式创建一个随机浮点张量b。接下来,我们通过使用.to()方法将b转换为32位整数来创建c。请注意,c包含与b相同的所有值,但被截断为整数。

可用的数据类型包括:

  • torch.bool

  • torch.int8

  • torch.uint8

  • torch.int16

  • torch.int32

  • torch.int64

  • torch.half

  • torch.float

  • torch.double

  • torch.bfloat

Math & Logic with PyTorch Tensors

现在你知道了一些创建张量的方法。。。你能用它们做什么?

让我们先看一下基本的算术,以及张量如何与简单的标量相互作用:

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.5

print(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)

输出:

RuntimeError                              Traceback (most recent call last)
<ipython-input-10-fcc83145fe91> in <module>
2 b = torch.rand(3, 2)
3
----> 4 print(a * b)

RuntimeError: The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 1

在一般情况下,不能以这种方式对不同形状的张量进行操作,即使是在类似于上面单元的情况下,其中张量具有相同数量的元素。

In Brief: Tensor Broadcasting

(注意:如果您熟悉NumPy ndarrays中的broadcasting 语义,您会发现这里也适用相同的规则。)

相同形状规则的例外是张量broadcasting。举个例子:

rand = torch.rand(2, 4)
doubled = rand * (torch.ones(1, 4) * 2)

print(rand)
print(doubled)

输出:

tensor([[0.2024, 0.5731, 0.7191, 0.4067],
[0.7301, 0.6276, 0.7357, 0.0381]])
tensor([[0.4049, 1.1461, 1.4382, 0.8134],
[1.4602, 1.2551, 1.4715, 0.0762]])

这里有什么诀窍?我们怎么把2x4张量乘以1x4张量?

广播是在形状相似的张量之间执行操作的一种方式。在上面的例子中,一行四列张量乘以两行四列张量的两行。

这是深度学习的一个重要操作。常见的例子是将一个学习权重的张量乘以一批输入张量,分别对该批中的每个实例应用该操作,并返回一个形状相同的张量,就像上面的(2,4)*(1,4)示例返回的形状张量(2,4)一样。

广播规则如下:

  • 每个张量必须至少有一个维度-没有空张量。

  • 比较两个张量的尺寸,从最后到第一:

    • 每个维度必须相等,或者

    • 其中一个尺寸必须为1,或

    • 维度不存在于其中一个张量中

当然,形状相同的张量是肯定可以的“可广播的”,正如您前面看到的。

以下是一些遵守上述规则并允许广播的情况的示例:

a =     torch.ones(4, 3, 2)

b = a * torch.rand( 3, 2) # 3rd & 2nd dims identical to a, dim 1 absent
print(b)

c = a * torch.rand( 3, 1) # 3rd dim = 1, 2nd dim identical to a
print(c)

d = a * torch.rand( 1, 2) # 3rd dim identical to a, 2nd dim = 1
print(d)

输出:

tensor([[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]],

[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]],

[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]],

[[0.2138, 0.5395],
[0.3686, 0.4007],
[0.7220, 0.8217]]])
tensor([[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]],

[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]],

[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]],

[[0.2612, 0.2612],
[0.7375, 0.7375],
[0.8328, 0.8328]]])
tensor([[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]],

[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]],

[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]],

[[0.8444, 0.2941],
[0.8444, 0.2941],
[0.8444, 0.2941]]])

仔细观察上面每个张量的值:

  • 产生b的乘法运算在a的每个“层”上广播。

  • 对于c来说,操作是在a的每一层和每一行上广播的—每3个元素的列都是相同的。

  • 对于d,我们改变了它-现在每一行都是相同的,跨层和跨列。

有关广播的更多信息,请参阅有关该主题的PyTorch文档。https://pytorch.org/docs/stable/notes/broadcasting.html

以下是一些广播尝试失败的例子:

注意:下面的单元格抛出一个运行时错误。这是故意的。

a =     torch.ones(4, 3, 2)

b = a * torch.rand(4, 3) # dimensions must match last-to-first

c = a * torch.rand( 2, 3) # both 3rd & 2nd dims different

d = a * torch.rand((0, )) # can't broadcast with an empty tensor

输出:

RuntimeError                              Traceback (most recent call last)
<ipython-input-13-591adab00a90> in <module>
1 a = torch.ones(4, 3, 2)
2
----> 3 b = a * torch.rand(4, 3) # dimensions must match last-to-first
4
5 c = a * torch.rand( 2, 3) # both 3rd & 2nd dims different

RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 2

More Math with Tensors

PyTorch张量有三百多个操作可以在它们上面执行。

以下是一些主要业务类别的小案例:

# common functions
a = torch.rand(2, 4) * 2 - 1
print('Common functions:')
print(torch.abs(a))
print(torch.ceil(a))
print(torch.floor(a))
print(torch.clamp(a, -0.5, 0.5))

# trigonometric functions and their inverses
angles = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
sines = torch.sin(angles)
inverses = torch.asin(sines)
print('\nSine and arcsine:')
print(angles)
print(sines)
print(inverses)

# bitwise operations
print('\nBitwise XOR:')
b = torch.tensor([1, 5, 11])
c = torch.tensor([2, 7, 10])
print(torch.bitwise_xor(b, c))

# comparisons:
print('\nBroadcasted, element-wise equality comparison:')
d = torch.tensor([[1., 2.], [3., 4.]])
e = torch.ones(1, 2) # many comparison ops support broadcasting!
print(torch.eq(d, e)) # returns a tensor of type bool

# reductions:
print('\nReduction ops:')
print(torch.max(d)) # returns a single-element tensor
print(torch.max(d).item()) # extracts the value from the returned tensor
print(torch.mean(d)) # average
print(torch.std(d)) # standard deviation
print(torch.prod(d)) # product of all numbers
print(torch.unique(torch.tensor([1, 2, 1, 2, 1, 2]))) # filter unique elements

# vector and linear algebra operations
v1 = torch.tensor([1., 0., 0.]) # x unit vector
v2 = torch.tensor([0., 1., 0.]) # y unit vector
m1 = torch.rand(2, 2) # random matrix
m2 = torch.tensor([[3., 0.], [0., 3.]]) # three times identity matrix

print('\nVectors & Matrices:')
print(torch.cross(v2, v1)) # negative of z unit vector (v1 x v2 == -v2 x v1)
print(m1)
m3 = torch.matmul(m1, m2)
print(m3) # 3 times m1
print(torch.svd(m3)) # singular value decomposition

输出:

Common functions:
tensor([[0.8447, 0.1992, 0.9755, 0.9295],
[0.8190, 0.1029, 0.7480, 0.4949]]
)
tensor([[-0., -0., 1., -0.],
[-0., -0., 1., -0.]]
)
tensor([[-1., -1., 0., -1.],
[-1., -1., 0., -1.]]
)
tensor([[-0.5000, -0.1992, 0.5000, -0.5000],
[-0.5000, -0.1029, 0.5000, -0.4949]]
)

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.6923, 0.7545],
[0.7746, 0.2330]]
)
tensor([[2.0769, 2.2636],
[2.3237, 0.6990]]
)
torch.return_types.svd(
U=tensor([[-0.7959, -0.6054],
[-0.6054, 0.7959]]
),
S=tensor([3.7831, 1.0066]),
V=tensor([[-0.8088, 0.5881],
[-0.5881, -0.8088]]
))

这是一个很小的示例,用于获取更多详细信息和完整的数学函数清单 https://pytorch.org/docs/stable/torch.html#math-operations

Altering Tensors in Place

大多数关于张量的二元运算都会返回第三个新的张量。当我们说c=a*b(其中ab是张量)时,新的张量c将占用不同于其他张量的内存区域。

不过,有时您可能希望在适当的位置更改张量—例如,如果您正在执行元素计算,您可以在其中丢弃中间值。为此,大多数数学函数都有一个附加下划线(_)的版本,它将在适当的位置改变张量。

例如:

a = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print('a:')
print(a)
print(torch.sin(a)) # this operation creates a new tensor in memory
print(a) # a has not changed

b = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print('\nb:')
print(b)
print(torch.sin_(b)) # note the underscore
print(b) # b has changed

输出:

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.8441, 0.9004],
[0.3995, 0.6324]]
)

After adding:
tensor([[1.8441, 1.9004],
[1.3995, 1.6324]]
)
tensor([[1.8441, 1.9004],
[1.3995, 1.6324]]
)
tensor([[0.8441, 0.9004],
[0.3995, 0.6324]]
)

After multiplying
tensor([[0.7125, 0.8107],
[0.1596, 0.3999]]
)
tensor([[0.7125, 0.8107],
[0.1596, 0.3999]]
)

请注意,这些in-place算术函数是torch.Tensor对象上的方法,与许多其他函数(例如,torch.sin())不同,它们没有附加到torch模块。从a.add_(b)可以看出,调用的张量是在适当位置改变的张量。

还有另一种方法可以将计算结果放入现有的分配张量中。到目前为止我们看到的许多方法和函数-包括创建方法有一个out参数,可以指定一个张量来接收输出。如果out张量的形状和数据类型正确,则无需新的内存分配即可实现:

a = torch.rand(2, 2)
b = torch.rand(2, 2)
c = torch.zeros(2, 2)
old_id = id(c)

print(c)
d = torch.matmul(a, b, out=c)
print(c) # contents of c have changed

assert c is d # test c & d are same object, not just containing equal values
assert id(c), old_id # make sure that our new c is the same object as the old one

torch.rand(2, 2, out=c) # works for creation too!
print(c) # c has changed again
assert id(c), old_id # still the same object!

Copying Tensors

与Python中的任何对象一样,将张量赋给变量会使变量成为张量的标签,而不会复制它。例如:

a = torch.ones(2, 2)
b = a

a[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,则克隆也将启用autograd。这将在autograd上的视频中进行更深入的讨论,但如果您想要详细信息的light版本,请继续。

在许多情况下,这将是你想要的。例如,如果您的模型在forward()方法中有多个计算路径,并且原始张量及其克隆都有助于模型的输出,那么要启用模型学习,您需要为这两个张量启用autograd。如果源张量启用了autograd(如果它是一组学习权值或从包含权值的计算中导出,则通常会启用autograd),那么您将得到所需的结果。

另一方面,如果你在做一个原始张量和克隆张量都不需要跟踪梯度的计算,那么只要源张量的autograd已经关闭,你就可以开始了。

不过,还有第三种情况:假设您正在模型的forward()函数中执行一个计算,在这个函数中,默认情况下对所有对象都启用梯度,但是您希望在中间提取一些值来生成一些度量。在这种情况下,您不希望源张量的克隆副本跟踪梯度-关闭autograd的历史跟踪后,性能会得到提高。为此,可以对源张量使用.detach()方法:

a = torch.rand(2, 2, requires_grad=True) # turn on autograd
print(a)

b = a.clone()
print(b)

c = a.detach().clone()
print(c)

print(a)

输出:

tensor([[0.5461, 0.5396],
[0.3053, 0.1973]]
, requires_grad=True)
tensor([[0.5461, 0.5396],
[0.3053, 0.1973]]
, grad_fn=<CloneBackward>)
tensor([[0.5461, 0.5396],
[0.3053, 0.1973]]
)
tensor([[0.5461, 0.5396],
[0.3053, 0.1973]]
, requires_grad=True)

这里发生了什么?

  • 我们创建一个启用requires_grad=True的 a我们还没有讨论这个可选的参数,但将在autograd单元期间讨论。

  • 当我们打印 a时,它会通知我们属性requires_grad=True—这意味着自动加载和计算历史跟踪已打开。

  • 我们克隆了a,并给它贴上了b的标签。当我们打印b时,我们可以看到它正在跟踪它的计算历史-它继承了a的autograd设置,并添加到计算历史中。

  • 我们将a克隆到c中,但首先调用detach()

  • 打印c时,我们看不到任何计算历史,也没有requires_grad=True

detach()方法将张量从其计算历史中分离出来。它说,“做下一步要做的事情,就好像autograd关闭了一样。”它这样做时不改变a-你可以看到,当我们在最后再次打印a时,它保留了它的requires_grad=True属性。

Moving to GPU

PyTorch的一个主要优点是在兼容CUDA的Nvidia GPU上具有强大的加速能力CUDA“代表计算统一设备架构 -Compute Unified Device Architecture,这是Nvidia的并行计算平台。)到目前为止,我们所做的一切都是在CPU上完成的。我们如何转向更快的硬件?

首先,我们应该使用is_available()方法检查GPU是否可用。

注意:如果没有安装与CUDA兼容的GPU和CUDA驱动程序,本节中的可执行单元将不会执行任何与GPU相关的代码。

if torch.cuda.is_available():
print('We have a GPU!')
else:
print('Sorry, CPU only.')

输出:

Sorry, CPU only.

一旦我们确定一个或多个GPU可用,我们就需要把数据放在GPU可以看到的地方。你的CPU对你计算机内存中的数据进行计算。你的GPU有专用内存。每当您想在设备上执行计算时,必须将计算所需的所有数据移动到该设备可访问的内存中(通俗地说,“将数据移动到GPU可访问的内存”简称为“将数据移动到GPU”。)

有多种方法可以将数据传送到目标设备上。您可以在创建时执行此操作:

if torch.cuda.is_available():
gpu_rand = torch.rand(2, 2, device='cuda')
print(gpu_rand)
else:
print('Sorry, CPU only.')

输出:

Sorry, CPU only.

默认情况下,新的张量是在CPU上创建的,因此我们必须指定何时在GPU上创建带有可选device参数的张量。你可以看到当我们打印新的时态时,PyToch会通知我们它在哪个设备上(如果它不在CPU上)。

您可以使用torch.cuda.device_ucount()查询GPU的数量。如果您有多个GPU,则可以通过索引设置例如:device='cuda:0'device='cuda:1'等等。

作为一种编码实践,用字符串常量指定我们的设备是非常脆弱的。在理想的世界中,无论您是在CPU还是GPU硬件上,您的代码都会执行强劲。您可以通过创建一个设备句柄来完成这一点,该句柄可以传递给张量,而不是字符串:

if torch.cuda.is_available():
my_device = torch.device('cuda')
else:
my_device = torch.device('cpu')
print('Device: {}'.format(my_device))

x = torch.rand(2, 2, device=my_device)
print(x)

输出:

Device: cpu
tensor([[0.3285, 0.5655],
[0.0065, 0.7765]]
)

如果一个设备上存在张量,可以使用to()方法将其移动到另一个设备上。下面的代码行在CPU上创建一个张量,并将其移动到上一个单元中获取的设备句柄。

y = torch.rand(2, 2)
y = y.to(my_device)

重要的是要知道,为了进行涉及两个或更多张量的计算,所有的张量必须在同一个设备上。无论您是否有可用的GPU设备,以下代码都将引发运行时错误:

x = torch.rand(2, 2)
y = torch.rand(2, 2, device='gpu')
z = x + y # exception will be thrown

Manipulating Tensor Shapes

有时,你需要改变张量的形状。下面,我们来看看几个常见的案例,以及如何处理它们。

Changing the Number of Dimensions

可能需要更改维度数的一种情况是将输入的单个实例传递给模型。PyTorch模型通常需要成批(batch)的输入。

例如,假设有一个模型可以处理3 x 226 x 226图像-一个226像素的正方形,有3个颜色通道。当你加载和变换它,你会得到一个张量的形状(3,226,226)。不过,您的模型需要输入shape(N,3,226,226),其中N是批处理中的图像数。那你怎么做一批呢?

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])

unsqueze()方法添加范围1的维度。unsqueze(0)将其添加为新的第零维-现在您有了第零维 batch!

所以如果这是 un*squeezing?squeezing 是什么意思?我们利用了一个事实,范围1*的任何维度都不会改变张量中元素的数量。

c = torch.rand(1, 1, 1, 1, 1)
print(c)

输出:

tensor([[[[[0.8960]]]]])

继续上面的例子,假设模型的输出是每个输入的20个元素的向量。然后您会期望输出具有shape(N,20),其中N是输入批处理中的实例数。这意味着对于单个输入批,我们将得到一个shape(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.4887, 0.8625, 0.6191, 0.9935, 0.1844, 0.6138, 0.6854, 0.0438, 0.0636,
0.2884, 0.4362, 0.2368, 0.1394, 0.1721, 0.1751, 0.3851, 0.0732, 0.3118,
0.9180, 0.7293]])
torch.Size([20])
tensor([0.4887, 0.8625, 0.6191, 0.9935, 0.1844, 0.6138, 0.6854, 0.0438, 0.0636,
0.2884, 0.4362, 0.2368, 0.1394, 0.1721, 0.1751, 0.3851, 0.0732, 0.3118,
0.9180, 0.7293])
torch.Size([2, 2])
torch.Size([2, 2])

你可以从形状上看到,我们的二维张量现在是一维的,如果你仔细观察上面单元格的输出,你会发现,由于有一个额外的维度,打印a会显示一组“额外的”方括号[]。

squeeze()只能处理额外的 1 个维度。请看上面,我们试图在 c中压缩一个尺寸为2的维度,并恢复到我们开始时的形状。对调用squeeze() 和 unsqueeze()只能作用于额外的 1个维度,因为否则会更改张量中的元素数。

另一个可以使用unsqueze()的地方是简化广播。回想一下上面的例子,我们有以下代码:

a =     torch.ones(4, 3, 2)

c = a * torch.rand( 3, 1) # 3rd dim = 1, 2nd dim identical to a
print(c)

这样做的效果是在维度0和维度2上广播操作,使得随机的3×1张量按元素乘以a中的每一个3元素列。

如果随机向量是三元素向量呢?我们将失去做广播的能力,因为根据广播规则,最终尺寸将不匹配。unsqueze()来解决:

a = torch.ones(4, 3, 2)
b = torch.rand( 3) # trying to multiply a * b will give a runtime error
c = b.unsqueeze(1) # change to a 2-dimensional tensor, adding new dim at the end
print(c.shape)
print(a * c) # broadcasting works again!

输出:

torch.Size([3, 1])
tensor([[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]],

[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]],

[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]],

[[0.3142, 0.3142],
[0.8068, 0.8068],
[0.6503, 0.6503]]])

squeeze() 和 unsqueeze() 方法也有 in-place 版本, 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])

有时,您可能希望更彻底地更改张量的形状,同时仍保留元素的数量及其内容。发生这种情况的一种情况是在模型的卷积层和模型的线性层之间的接口处-这在图像分类模型中很常见。卷积核将产生形状 features x width x height 的输出张量,但下面的线性层需要一维输入。如果您请求的尺寸产生与输入张量相同数量的元素,则reshape()将为您执行此操作:

output3d = torch.rand(6, 20, 20)
print(output3d.shape)

input1d = output3d.reshape(6 * 20 * 20)
print(input1d.shape)

# can also call it as a method on the torch module:
print(torch.reshape(output3d, (6 * 20 * 20,)).shape)

输出:

torch.Size([6, 20, 20])
torch.Size([2400])
torch.Size([2400])

(注意:上面单元格的最后一行中的(6 * 20 * 20,) 参数是因为PyTorch在指定张量形状时需要一个元组(tuple)-但是当形状是方法的第一个参数时,它允许我们作弊并只使用一系列整数。在这里,我们必须添加括号和逗号,以使方法相信这实际上是一个单元素元组。)

如果可以的话,reshape()将返回一个关于要更改的张量的视图,即一个单独的张量对象查看相同的内存底层区域。这一点很重要:这意味着对源张量所做的任何更改都将反映在该张量的视图中,除非您克隆它。

有些条件超出了本文的介绍范围,在这些条件下,reshape()必须返回一个包含数据副本的张量。有关更多信息,请参阅文档。https://pytorch.org/docs/stable/torch.html#torch.reshape

NumPy Bridge

在上面关于广播的部分中,提到了PyTorch的广播语义与NumPy的兼容,但是PyTorch和NumPy之间的亲缘关系甚至更深。

如果您有存储在NumPy ndarrays中的数据的现有ML或科学代码,您可能希望将这些数据表示为PyTorch张量,无论是利用PyTorch的GPU加速,还是利用其高效的抽象来构建ML模型。很容易在Ndarray和Pytork张量之间切换:

import numpy as np

numpy_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.]], dtype=torch.float64)

PyTorch创建了一个与NumPy数组相同形状和包含相同数据的张量,甚至保留了NumPy默认的64位浮点数据类型。

转换可以很容易地转到另一个方向:

pytorch_rand = torch.rand(2, 3)
print(pytorch_rand)

numpy_rand = pytorch_rand.numpy()
print(numpy_rand)

输出:

tensor([[0.5647, 0.9160, 0.7783],
[0.8277, 0.4579, 0.6382]])
[[0.5646949 0.91600937 0.77828014]
[0.82769746 0.45785618 0.6381657 ]]

重要的是要知道,这些转换后的对象使用的底层内存与其源对象相同,这意味着对其中一个对象的更改将反映在另一个对象中:

print(pytorch_tensor)

pytorch_rand[1, 1] = 17
print(numpy_rand)

输出:

tensor([[ 1.,  1.,  1.],
[ 1., 23., 1.]], dtype=torch.float64)
[[ 0.5646949 0.91600937 0.77828014]
[ 0.82769746 17. 0.6381657 ]]


     
个人微信(如果没有备注不拉群!
请注明:地区+学校/企业+研究方向+昵称



下载1:何恺明顶会分享


AI算法与图像处理」公众号后台回复:何恺明,即可下载。总共有6份PDF,涉及 ResNet、Mask RCNN等经典工作的总结分析


下载2:终身受益的编程指南:Google编程风格指南


AI算法与图像处理」公众号后台回复:c++,即可下载。历经十年考验,最权威的编程规范!



   
下载3 CVPR2021

AI算法与图像处公众号后台回复:CVPR即可下载1467篇CVPR 2020论文 和 CVPR 2021 最新论文

点亮 ,告诉大家你也在看


    

浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报