PyTorch Tensor
概述
torch.Tensor
与 NumPy 的多维数组非常类似,区别在于:
- GPU 计算。
- 自动求梯度。
Tensor 的创建
创建未初始化的 Tensor:
torch.empty(*sizes)
创建随机初始化的 Tensor:
- 均匀分布(从区间 [0, 1) 中均匀取样):
torch.rand(*sizes)
- 标准正态分布(均值为 0,方差为 1):
torch.randn(*sizes)
- 离散正态分布(可以分别为每个量指定 mean 和 std):
torch.normal(mean, std, sizes)
torch.normal(means, stds)
torch.normal(means, std)
torch.normal(mean, stds)
- 均匀分布(从区间 [0, 1) 中均匀取样):
创建指定值的 Tensor:
- 全 0:
torch.zeros(x, y)
- 全 1:
torch.ones(x, y)
- 全 n:
torch.ones(x, y) * n
- 对角线为 1,其余为 0:
torch.eye(x, y)
- 等宽数列:
- 定步长:
torch.arange(s, e, step)
- 定个数:
torch.linspace(s, e, steps)
- 定步长:
- 全 0:
从数据创建:
- 从 Python 列表或 Numpy 数组:
torch.Tensor([a, b, c])
- 从已有的 Tensor:
x = x.new_ones()
- 从 Python 列表或 Numpy 数组:
创建时可指定:
- 数据类型:
dtype=torch.float
,具体参见官网上的类型列表。 - 是否需要梯度更新:
requires_grad=True
- 设备:
device='cpu'
或者device=torch.device('cuda')
。
也可之后在设备间移动 Tensor:x = x.to(device)
注意,这种直接创建的 tensor 为叶子节点:x.is_leaf = True
。
Tensor 的读取与存储
与模型的读取与存储一样:
- 读取:
x = torch.load('x.pt')
- 存储:
torch.save(x, 'x.pt')
可以存储一个字典:
torch.save({'x': x, 'y': y}, 'xy_dict.pt')
看到这里是不是发现和模型的保存(torch.save(model.state_dict())
)是一回事。
Tensor 的操作
获取 Tensor 的属性
获取形状:
x.size()
x.shape
返回值为形状元组。
获取维度:x.dim()
,返回值为 int。
可直接操作的属性:
x.data
:该 Tensor 的数据,对其的操作不会被自动求导机制记录。x.grad
:梯度信息。
算数操作
一般而言,有以下形式:
- 用 Python 的运算符:
y = x + y
(开辟了新内存,并将 y 指向新内存)y[:] = x + y
(没有开辟新内存)
- 用 PyTorch 提供的函数:
y = torch.add(x, y)
(开辟了新内存,并将 y 指向新内存)torch.add(x, y, out=y)
(没有开辟新内存)y.add_(x)
(没有开辟新内存)
注意所有后面带有下划线的函数均表示该操作直接作用于当前矩阵(inplace)。
索引操作
类似 NumPy。
例子:
取 x 的第一行的元素:y = x[0, :]
。
注意: x 和 y 是共享内存的。
改变形状
y = x.view(sizes*)
注意:x 和 y 同样是共享内存的,如果不想共享内存,则 y = x.clone().view(sizes*)
。
可以有一个值为 -1,这样 PyTorch 会自动推断该值。
例子:
- 将图片输入全连接层:
linear(x.view(x.shape[0], -1))
,shape 的第一个值是 batch size。
禁用梯度计算
可以节省大量内存空间,因为省去了没有用的中间变量的保存。
可以使用 with 方式:
with torch.no_grad():
y = x * 2
也可以使用装饰器的方式:
@torch.no_grad()
def foo():
pass
其他操作
操作 | 描述 |
---|---|
x.clone() 或 torch.clone(x) |
拷贝 x 的一个副本,注意该操作会被记录在计算图中,梯度回传到副本时也会传到源 Tensor |
x.detach() |
返回一个新的 Tensor,其从当前计算图中剥离,但是注意内存是共享的,如果对其进行修改将出发错误 |
x.detach_() |
将当前 Tensor 从计算图中剥离,使其成为叶子节点,注意不能对 view 使用该函数 |
x.requires_grad_(requires_grad=True) |
设置 x 为需要梯度信息 |
x.item() |
x 必须为 0 维度,返回其值,类型为 Python 内置类型 |
x.float() |
返回一个新的指定类型的张量 |
x.mean() |
返回一个张量,其为 x 的均值,注意 x 必须是浮点类型 |
x.gather(dim, index) |
|
x.argmax(dim) |
返回指定维度上最大值的索引 |
x.numpy() |
返回张量 x 的 numpy 数据形式,注意内存是共享的 |
x.sum(dim) |
在指定维度求和,如果不给定维度,则返回所有元素的值的和 |
操作 | 描述 |
---|---|
torch.cat((a, b, c), dim=d) |
在维度 d 上把这三个张量拼接起来 |
重点详解:
x.scatter_(dim, index, src)
根据索引 index,在维度 dim 上把 x 的值更新为 src 上的对应值。 例子:生成 One Hot 向量
res = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device)
res.scatter_(1, x.view(-1, 1), 1)
梯度的计算
- Tensor 的属性
requires_grad
若为True
,则 PyTorch 将追踪其上的操作。 - 通过 inpalce 函数
x.requires_grad_(boolean)
可设置该属性。 - 使用
with torch.no_grad()
可禁用一整块的代码中的 Tensor 中的梯度计算。model.eval()
will notify all your layers that you are in eval mode, that way, batchnorm or dropout layers will work in eval mode instead of training mode.torch.no_grad()
impacts the autograd engine and deactivate it. It will reduce memory usage and speed up computations but you won’t be able to backprop.
- Tensor 的
grad_fn
属性记录了该 Tensor 是由什么操作产生的。 - 之后调用
x.backward()
进行反向传播,梯度将记录在 Tensor 的grad
属性中。
一般流程:
optimizer.zero_grad()
:清空梯度,或者遍历所有参数,通过param.grad.data.zero_()
手动清空。loss.backward()
:反向传播,计算新的梯度。optimizer.step()
:根据梯度对 Tensor(参数)进行更新。
注意,对于标量,直接调用 x.backward()
即可,否则需要传入一个同形状的权重:x.backward(w)
。
这样做的目的是避免 Tensor 对 Tensor 求导,实际上可以理解为 l = torch.sum(x*w)
,具体参见这里的解释。
一般情况下最终的 loss 都是标量。
Tensor 的维度
我们以求和为例,首先创建一个形状为 (2, 3, 4, 5)
的张量:
a = torch.randn(2,3,4,5)
tensor([[[[-1.2746e+00, 9.4579e-01, 1.6964e+00, 1.7108e+00, -5.2078e-01],
[ 1.2146e+00, -9.6105e-01, 5.4967e-01, 7.6222e-02, -1.3477e+00],
[-9.2304e-02, 1.1924e+00, 9.0743e-01, -9.2746e-02, -1.2672e-01],
[-3.8137e-01, -5.0026e-01, -9.6745e-02, 2.2675e+00, 9.3933e-01]],
[[ 2.1660e+00, -7.8956e-01, 3.1534e-01, -1.3994e+00, -6.9351e-01],
[ 1.2564e+00, 3.8183e-01, 1.4704e+00, -1.9463e+00, 1.8061e-01],
[ 1.1230e+00, 5.9794e-01, -1.9380e+00, -1.5386e-01, 6.1002e-01],
[ 8.6600e-01, -5.4200e-01, 5.3109e-01, -3.3880e-01, 1.0513e+00]],
[[ 2.2685e+00, 1.5996e-01, 2.1415e-01, -8.0140e-01, 1.5485e-01],
[-1.2627e+00, 3.3279e-01, -7.0145e-01, -7.9849e-01, -1.1547e+00],
[-6.2403e-01, 1.1128e+00, -8.4175e-01, 7.5590e-01, 1.3277e+00],
[ 1.3062e+00, -2.9533e-01, 1.8625e+00, -2.4466e+00, -1.0042e-01]]],
[[[ 6.3783e-01, -4.0830e-01, 5.2721e-01, -1.6377e+00, 1.8268e+00],
[-5.5396e-02, 2.8299e+00, -2.5214e+00, -2.9159e-01, -5.6524e-01],
[ 6.3573e-01, 2.2495e+00, -4.1573e-01, -4.1093e-01, 1.5515e+00],
[-6.7659e-01, -1.4087e-01, 7.2113e-01, 1.2728e-01, -1.5104e+00]],
[[ 1.4417e+00, -1.0893e+00, 9.4346e-01, 8.9341e-01, -1.3597e-01],
[ 1.2500e+00, -2.6341e-03, 2.1313e-01, 9.0191e-01, -7.2716e-01],
[ 5.9710e-01, -3.9374e-02, -3.7236e-02, -1.3946e+00, 1.4514e+00],
[-1.7948e+00, 3.8287e-01, 1.5575e-01, -1.1133e+00, 6.8300e-01]],
[[-7.0522e-01, -2.7154e+00, 3.0693e-01, 3.3656e-01, 8.4110e-01],
[-9.5167e-01, -1.6524e+00, -1.4965e+00, -9.7646e-01, 4.4489e-01],
[ 1.1324e+00, -1.1026e+00, -7.0591e-01, 7.0250e-01, 2.5926e-01],
[-6.3822e-02, -1.9037e+00, 9.6132e-01, -1.0421e+00, -2.9982e-01]]]])
观察输出结果,我们发现最内侧的基本单元为类似 [-6.3822e-02, -1.9037e+00, 9.6132e-01, -1.0421e+00, -2.9982e-01]
的含有 5 个元素的向量。
再往上进一层,发现由 4 个这样的 5 元素向量组成更高维度的基本单元。
依次类推。
对于形状为 (2, 3, 4, 5)
的张量,我们可以将其理解为一个 (2, 3)
的张量,但是其中每一个元素都是一个 (4, 5)
的张量。
分别在不同维度求和:
a.sum(0)
tensor([[[-0.6368, 0.5375, 2.2236, 0.0731, 1.3060],
[ 1.1592, 1.8688, -1.9718, -0.2154, -1.9129],
[ 0.5434, 3.4420, 0.4917, -0.5037, 1.4248],
[-1.0580, -0.6411, 0.6244, 2.3948, -0.5711]],
[[ 3.6077, -1.8789, 1.2588, -0.5060, -0.8295],
[ 2.5064, 0.3792, 1.6835, -1.0444, -0.5465],
[ 1.7201, 0.5586, -1.9752, -1.5484, 2.0614],
[-0.9288, -0.1591, 0.6868, -1.4521, 1.7343]],
[[ 1.5633, -2.5555, 0.5211, -0.4648, 0.9959],
[-2.2144, -1.3196, -2.1980, -1.7749, -0.7098],
[ 0.5084, 0.0102, -1.5477, 1.4584, 1.5870],
[ 1.2424, -2.1990, 2.8239, -3.4888, -0.4002]]])
a.sum(1)
tensor([[[ 3.1599, 0.3162, 2.2259, -0.4900, -1.0594],
[ 1.2083, -0.2464, 1.3186, -2.6686, -2.3217],
[ 0.4066, 2.9031, -1.8723, 0.5093, 1.8110],
[ 1.7908, -1.3376, 2.2969, -0.5179, 1.8902]],
[[ 1.3743, -4.2130, 1.7776, -0.4077, 2.5319],
[ 0.2429, 1.1748, -3.8048, -0.3661, -0.8475],
[ 2.3652, 1.1075, -1.1589, -1.1030, 3.2622],
[-2.5352, -1.6617, 1.8382, -2.0282, -1.1272]]])
a.sum(2)
tensor([[[-0.5337, 0.6769, 3.0568, 3.9618, -1.0558],
[ 5.4113, -0.3518, 0.3788, -3.8384, 1.1484],
[ 1.6879, 1.3102, 0.5335, -3.2906, 0.2275]],
[[ 0.5416, 4.5302, -1.6888, -2.2130, 1.3026],
[ 1.4941, -0.7485, 1.2751, -0.7126, 1.2713],
[-0.5883, -7.3742, -0.9342, -0.9795, 1.2454]]])
a.sum(3)
tensor([[[ 2.5576, -0.4682, 1.7881, 2.2285],
[-0.4012, 1.3429, 0.2391, 1.5675],
[ 1.9961, -3.5845, 1.7306, 0.3263]],
[[ 0.9458, -0.6038, 3.6101, -1.4795],
[ 2.0533, 1.6353, 0.5773, -1.6865],
[-1.9360, -4.6322, 0.2856, -2.3482]]])
参考
- https://tangshusen.me/Dive-into-DL-PyTorch/#/chapter02_prerequisite/2.2_Tensor
- PyTorch 的 backward 为什么有一个 grad_variables 参数 ?
- ‘model.eval()’ vs ‘with torch.no_grad()’
- What step(), backward(), and zero_grad() do
Links: pytorch-tensor