张量
内容
张量¶
张量是一种特殊的数据结构,可以将其简单地视为数学中的张量。
先载入一些库:
import torch
import numpy as np
张量可以直接从 Python 原生对象中创建:
data = [[1, 2], [3, 4], [5, 6]]
x_data = torch.tensor(data)
或者,从 Numpy 生成张量:
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
或者,借助一些 torch 函数创建:
x_ones = torch.ones_like(x_data) # 全一张量
x_rand = torch.rand_like(x_data, dtype=torch.float) # 随机张量
可以看看张量的样子:
x_data
tensor([[1, 2],
[3, 4],
[5, 6]])
通过张量的 shape
属性来访问张量的 形状 (沿每个轴的长度):
x_data.shape
torch.Size([3, 2])
如果想知道张量中元素的总数,即形状的所有元素乘积,可以:
x_data.numel() # 即 3 * 2
6
查看张量的数据类型和其所属设备:
x_data.dtype, x_data.device
(torch.int64, device(type='cpu'))
张量运算¶
张量有超过 100 的运算,包括算术,线性代数,矩阵操作(转置,索引,切片),采样等(更加详细的介绍见 torch
)。
默认情况下,张量是在 CPU 上创建的。如果想将其移到在 GPU 上,需要借助 .to
方法。注意:跨设备复制大型张量在时间和内存方面是昂贵的!
# 查看 GPU 是否可用
if torch.cuda.is_available():
# 如果可用,则迁移到 GPU,并打印出来
x_data = x_data.to('cuda')
print(x_data)
tensor([[1, 2],
[3, 4],
[5, 6]], device='cuda:0')
下面简单的列出一些运算:
tensor = torch.ones(4, 3, dtype=torch.float32)
t1 = torch.cat([tensor, tensor], dim=1) # 拼接
print(t1)
y1 = tensor @ tensor.T # 矩阵乘法
print(y1)
tensor([[1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1.]])
tensor([[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.]])
将结果存储到操作张量中的操作称为就地操作(in-place)。它们由 _
后缀表示。例如:x.copy_(y)
,x.t_()
,将会更改 x
。
tensor = torch.tensor(7)
print(tensor, "\n")
tensor.add_(5)
print(tensor)
tensor(7)
tensor(12)
重要
CPU 上的张量和 NumPy 数组上可以共享它们的底层内存位置,改变一个就会改变另一个。
比如,
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy() # 张量转换为 NumPy
print(f"n: {n}")
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
节省内存¶
运行一些操作可能会导致为新结果分配内存。例如,如果我们用 Y = X + Y,我们将取消引用 Y 指向的张量,而是指向新分配的内存处的张量。
在下面的例子中,我们用 Python 的 id()
函数演示了这一点,它给我们提供了内存中引用对象的确切地址。运行 Y = Y + X
后,我们会发现 id(Y)
指向另一个位置。这是因为 Python 首先计算 Y + X
,为结果分配新的内存,然后使 Y
指向内存中的这个新位置。
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]])
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): 2933842197696
id(Z): 2933842197696
如果在后续计算中没有重复使用 X,我们也可以使用 X[:] = X + Y
或 X += Y
来减少操作的内存开销。
before = id(X)
X += Y
id(X) == before
True
当然,就地操作也是符合的。