最近决定系统学习PyTorch,此前仅在寒假时读了鱼书第一本,现在已经忘得差不多了,写一篇
创建张量的三种方式
直接从数据创建
import torch
data = [[1,2,3],[4,5,6]] # 确定的列表
my_tensor = torch.tensor(data,dtype = torch.float32)
print(my_tensor)tensor([[1., 2., 3.],
[4., 5., 6.]])根据期望的形状创建
shape = (2,3) # 元组
ones = torch.ones(shape)
zeros = torch.zeros(shape)
random = torch.randn(shape)
print("Random Tensor:\n ",random)通过模仿另一个张量来创建
randn_like() 默认继承已知张量的形状和 dtype,数值则重新随机生成;也可以通过 dtype 参数显式修改新张量的 dtype:
template = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
rand_like = torch.randn_like(template) # shape=(2, 2), dtype=torch.float32
rand_like_fp64 = torch.randn_like(template, dtype=torch.float64)张量的几个关键属性
Shape
tensor = torch.randn(2,3) # 下面两节沿用这个 tensor
print(f"Shape:{tensor.shape}")Shape:torch.Size([2, 3])Datatype
print(f"Datatype:{tensor.dtype}")Datatype:torch.float32Device
print(f"Device:{tensor.device}")Device:cpuGrad_fn
# 张量的一个属性,表示该张量如何产生(由哪个运算得到)
# 前向计算一结束就有了,不需要等 backward
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2
print(y.grad_fn)
print(x.grad_fn)<PowBackward0 object at 0x...>
None降维操作
降维操作,指任何将张量压缩为更少元素的操作,比如求和、求平均、求最大值(sum(),mean(),max())
scores = torch.tensor([[10.,20.,30.],[5.,10.,15.]])
average_score = scores.mean()
print(f"Overall Mean:{average_score}")Overall Mean:15.0dim参数
dim参数允许你控制想要压缩的方向
scores = torch.tensor([[10.,20.,30.],[5.,10.,15.]])
avg_per_assignment = scores.mean(dim = 0)
avg_per_student = scores.mean(dim = 1)
print(f"Average per assignment (dim = 0):{avg_per_assignment}")
print(f"Average per student (dim = 1):{avg_per_student}")Average per assignment (dim = 0):tensor([7.5000,15.0000,22.5000])
Average per student (dim= 1):tensor([20.,10.])dim 表示沿哪一个轴执行操作。对二维张量 scores.shape == (2, 3):
dim=0:固定列索引,在每一列中比较或计算,因此得到“每列一个结果”;dim=1:固定行索引,在每一行中比较或计算,因此得到“每行一个结果”。
多数降维操作会消除被计算的维度:
print(scores.mean(dim=1).shape)
print(scores.mean(dim=1, keepdim=True))
print(scores.mean(dim=1, keepdim=True).shape)torch.Size([2])
tensor([[20.],
[10.]])
torch.Size([2, 1])因此 mean(dim=1) 的结果 tensor([20., 10.]) 是一维张量,已经没有“横排”或“竖排”的形状含义;keepdim=True 则保留这一维,得到二维的列向量。
索引
普通索引
b = torch.arange(12).reshape(3,4)
col_2 = b[:,2]
print(b)
print(col_2)tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
tensor([ 2, 6, 10])最大值索引 torch.argmax
scores = torch.tensor([
[10,0,5,20,1], # index 3
[1,30,2,5,0] # index 1
])
best_indices = torch.argmax(scores,dim = 1)
print(best_indices)tensor([3, 1])这个理解应该是:在每一行内部,沿着列索引变化的方向去寻找最大值
注意 argmax 返回的是最大值的索引,不是最大值本身。若还需要最大值,可以使用:
values, indices = torch.max(scores, dim=1)
print(values)
print(indices)tensor([20, 30])
tensor([3, 1])高维张量中的 dim
设 x.shape == (10, 5, 7),维度分别记作 (i, j, k):
best_k = torch.argmax(x, dim=2)
print(best_k.shape)torch.Size([10, 5])这会固定每个 (i, j) 位置,在 k 方向的 7 个元素中寻找最大值。概念上等价于:
for i in range(10):
for j in range(5):
best_k[i, j] = x[i, j, :].argmax()结果有 10 × 5 = 50 个元素;每个元素不是二维索引对,而是一个 k 索引,取值范围为 0 到 6。例如 best_k[3, 2] == 6 表示 x[3, 2, 6] 是 x[3, 2, :] 中的最大值。
通用规律:dim=n 时,固定其他维度、遍历第 n 维,并在普通降维操作中消除第 n 维。
把 dim=2 看成“6 组长度为 4 的数组”
对于按顺序创建的三维张量:
x = torch.arange(24).reshape(2, 3, 4)
# x.shape: (i, j, k) = (2, 3, 4)直接写 argmax(dim=2) 是标准做法:
best_k = torch.argmax(x, dim=2)
print(best_k)tensor([[3, 3, 3],
[3, 3, 3]])也可以先把会保留的 (i, j) 两维合并,显式看出有 6 组长度为 4 的数据:
x_2d = x.reshape(6, 4)
# 每一行正好是一条 x[i, j, :]
best_k_flat = torch.argmax(x_2d, dim=1)
best_k = best_k_flat.reshape(2, 3)
print(best_k_flat)
print(best_k)tensor([3, 3, 3, 3, 3, 3])
tensor([[3, 3, 3],
[3, 3, 3]])对应关系如下:
x_2d[0] ↔ x[0, 0, :] x_2d[3] ↔ x[1, 0, :]
x_2d[1] ↔ x[0, 1, :] x_2d[4] ↔ x[1, 1, :]
x_2d[2] ↔ x[0, 2, :] x_2d[5] ↔ x[1, 2, :]每行都是递增的 4 个数,因此最大值均位于该行的最后一个位置,k 索引都是 3。best_k[i, j] 中的 (i, j) 是输出元素的位置;元素的值 3 才是对应最大值的 k 索引。
这个“先合并未参与计算的维度,再恢复形状”的方法只是在本例中帮助理解;实际代码优先直接写 torch.argmax(x, dim=2),因为它明确保留了原始维度语义。
索引进阶:gather方法
data = torch.tensor([
[10,11,12,13],
[20,21,22,23],
[30,31,32,33]
])
indices = torch.tensor([[2],[0],[3]])
print(torch.gather(data, dim = 1, index = indices))tensor([[12],
[20],
[33]])gather() 同样沿 dim=1 操作,但它不会寻找最大值;indices 明确指定每一行要取哪一列。输出形状与 indices 相同。
本例中:
indices[0, 0] = 2 → 取 data[0, 2] = 12
indices[1, 0] = 0 → 取 data[1, 0] = 20
indices[2, 0] = 3 → 取 data[2, 3] = 33也就是说,对二维张量执行 torch.gather(data, dim=1, index=indices) 时,概念上满足:
output[i, j] = data[i, indices[i, j]]argmax 和 gather 经常配合使用:前者找出最大值的列索引,后者根据这些索引取回最大值。
scores = torch.tensor([
[10, 0, 5, 20, 1],
[1, 30, 2, 5, 0]
])
best_indices = torch.argmax(scores, dim=1)
# tensor([3, 1]),形状为 (2,)
# gather 的 index 必须与输入张量拥有相同的维数,
# 因此用 unsqueeze(1) 将 (2,) 变为 (2, 1)
best_values = torch.gather(scores, dim=1, index=best_indices.unsqueeze(1))
print(best_values)tensor([[20],
[30]])这里 best_indices 给出“最大值在哪一列”,gather 则给出“那一列的实际数值”。