2.1.2. 运算符
x = torch.tensor([1.0, 2, 4, 8]) y = torch.tensor([2, 2, 2, 2]) x + y, x - y, x * y, x / y, x ** y # **运算符是求幂运算(tensor([ 3., 4., 6., 10.]), tensor([-1., 0., 2., 6.]), tensor([ 2., 4., 8., 16.]), tensor([0.5000, 1.0000, 2.0000, 4.0000]), tensor([ 1., 4., 16., 64.]))
torch.exp(x)
tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
除了按元素计算外,我们还可以执行线性代数运算,包括向量点积和矩阵乘法。 我们将在 2.3节中解释线性代数的重点内容。
我们也可以把多个张量连结(concatenate)在一起, 把它们端对端地叠起来形成一个更大的张量。 我们只需要提供张量列表,并给出沿哪个轴连结。 下面的例子分别演示了当我们沿行(轴-0,形状的第一个元素) 和按列(轴-1,形状的第二个元素)连结两个矩阵时,会发生什么情况。 我们可以看到,第一个输出张量的轴-0长度(6)是两个输入张量轴-0长度的总和(3+3); 第二个输出张量的轴-1长度(8)是两个输入张量轴-1长度的总和(4+4)。
X = torch.arange(12, dtype=torch.float32).reshape((3,4)) Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
(tensor([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [ 2., 1., 4., 3.], [ 1., 2., 3., 4.], [ 4., 3., 2., 1.]]), tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.], [ 4., 5., 6., 7., 1., 2., 3., 4.], [ 8., 9., 10., 11., 4., 3., 2., 1.]]))
有时,我们想通过逻辑运算符构建二元张量。 以X == Y
为例: 对于每个位置,如果X
和Y
在该位置相等,则新张量中相应项的值为1。 这意味着逻辑语句X == Y
在该位置处为真,否则该位置为0。
X == Y
tensor([[False, True, False, True], [False, False, False, False], [False, False, False, False]])
对张量中的所有元素进行求和,会产生一个单元素张量。
X.sum()
tensor(66.)
import torch
# 创建一个3x3的张量
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])# 对第一维度(即按行)求和
row_sum = x.sum(dim=0)print(row_sum) # 输出结果为tensor([12, 15, 18])
# 对第二维度(即按列)求和
col_sum = x.sum(dim=1)print(col_sum) # 输出结果为tensor([ 6, 15, 24])
在上述代码中,通过指定dim参数可以实现按行或按列求和。例如,当dim=0时,将对第一维度(即按行)进行求和操作,并返回每列的求和结果;当dim=1时,将对第二维度(即按列)进行求和操作,并返回每行的求和结果
2.1.3. 广播机制
在上面的部分中,我们看到了如何在相同形状的两个张量上执行按元素操作。 在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制(broadcasting mechanism)来执行按元素操作。 这种机制的工作方式如下:
-
通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
-
对生成的数组执行按元素操作。
在大多数情况下,我们将沿着数组中长度为1的轴进行广播,如下例子:
a = torch.arange(3).reshape((3, 1)) b = torch.arange(2).reshape((1, 2)) a, b
(tensor([[0], [1], [2]]), tensor([[0, 1]]))
由于a
和b
分别是3×1和1×2矩阵,如果让它们相加,它们的形状不匹配。 我们将两个矩阵广播为一个更大的3×2矩阵,如下所示:矩阵a
将复制列, 矩阵b
将复制行,然后再按元素相加。
a + b
tensor([[0, 1], [1, 2], [2, 3]])
广播机制是 PyTorch 中常用的一种计算方式,它可以自动将不同形状张量之间的运算扩展到相同形状上,从而简化复杂的张量运算。
PyTorch 的广播机制要求输入张量在某些维度上的大小能够对应地匹配。具体来说,它遵循以下规则:
-
如果两个张量的维度数不同,则在较少的维度上添加前缀维度 11,直到两个张量的维度数相同。
-
对于两个有相同维度数的张量,如果在每个维度上的大小相同或其中一个张量的大小为 11,则认为它们在该维度上是匹配的。如果两个张量在某个维度上的大小既不相同也不为 11,则会抛出“尺寸不匹配”的异常。
-
在满足以上条件时,可以通过在某些维度上重复张量来使其形状匹配,或者通过在某些维度上将大小为 11 的张量沿着这些维度进行复制来使其形状匹配。
2.1.4. 索引和切片
就像在任何其他Python数组中一样,张量中的元素可以通过索引访问。 与任何Python数组一样:第一个元素的索引是0,最后一个元素索引是-1; 可以指定范围以包含第一个元素和最后一个之前的元素。
如下所示,我们可以用[-1]
选择最后一个元素,可以用[1:3]
选择第二个和第三个元素:
X[-1], X[1:3]
(tensor([ 8., 9., 10., 11.]), tensor([[ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]))
除读取外,我们还可以通过指定索引来将元素写入矩阵。
X[1, 2] = 9 X
tensor([[ 0., 1., 2., 3.], [ 4., 5., 9., 7.], [ 8., 9., 10., 11.]])
如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。 例如,[0:2, :]
访问第1行和第2行,其中“:”代表沿轴1(列)的所有元素。 虽然我们讨论的是矩阵的索引,但这也适用于向量和超过2个维度的张量。
X[0:2, :] = 12 X
tensor([[12., 12., 12., 12.], [12., 12., 12., 12.], [ 8., 9., 10., 11.]])
2.1.5. 节省内存
运行一些操作可能会导致为新结果分配内存。 例如,如果我们用Y = X + Y
,我们将取消引用Y
指向的张量,而是指向新分配的内存处的张量。
在下面的例子中,我们用Python的id()
函数演示了这一点, 它给我们提供了内存中引用对象的确切地址。 运行Y = Y + X
后,我们会发现id(Y)
指向另一个位置。 这是因为Python首先计算Y + X
,为结果分配新的内存,然后使Y
指向内存中的这个新位置。
before = id(Y) Y = Y + X id(Y) == before
False
这可能是不可取的,原因有两个:
-
首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新;
-
如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。
幸运的是,执行原地操作非常简单。 我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如Y[:] = <expression>
。 为了说明这一点,我们首先创建一个新的矩阵Z
,其形状与另一个Y
相同, 使用zeros_like
来分配一个全0的块。
Z = torch.zeros_like(Y) print('id(Z):', id(Z)) Z[:] = X + Y print('id(Z):', id(Z))
id(Z): 139931132035296 id(Z): 139931132035296
如果在后续计算中没有重复使用X
, 我们也可以使用X[:] = X + Y
或X += Y
来减少操作的内存开销。
before = id(X) X += Y id(X) == before
True
2.1.6. 转换为其他Python对象
将深度学习框架定义的张量转换为NumPy张量(ndarray
)很容易,反之也同样容易。 torch张量和numpy数组将共享它们的底层内存,就地操作更改一个张量也会同时更改另一个张量。
A = X.numpy() B = torch.tensor(A) type(A), type(B)
(numpy.ndarray, torch.Tensor)
要将大小为1的张量转换为Python标量,我们可以调用item
函数或Python的内置函数。
a = torch.tensor([3.5]) a, a.item(), float(a), int(a)
(tensor([3.5000]), 3.5, 3.5, 3)
2.1.7. 小结
-
深度学习存储和操作数据的主要接口是张量(�维数组)。它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其他Python对象。