返回文章索引

PyTorch 深度学习(一):Tensor基础

最近决定系统学习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.float32

Device

print(f"Device:{tensor.device}")
Device:cpu

Grad_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.0

dim参数

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 索引,取值范围为 06。例如 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 索引都是 3best_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]]

argmaxgather 经常配合使用:前者找出最大值的列索引,后者根据这些索引取回最大值。

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 则给出“那一列的实际数值”。

留下回应

评论方式

署名评论填写昵称和邮箱后直接发布。