一、张量的创建与类型

1.1 通过列表创建

  1. t = torch.tensor([1, 2])
  2. print(t)
  3. # tensor([1, 2])

1.2 通过元组创建

  1. t = torch.tensor((1, 2))
  2. print(t)
  3. # tensor([1, 2])

1.3 通过Numpy创建

  1. import numpy as np
  2. n = np.array([1, 2])
  3. t = torch.tensor(n)
  4. print(t)
  5. # tensor([1, 2])

1.4 张量的类型

type() 不能识别出Tensor内部的数据类型,只能识别出变量的基本类型是Tensor,而dtype方法可以识别出变量具体为哪种类型的Tensor。

  1. i = torch.tensor([1, 2])
  2. f = torch.tensor([1.0, 2.0])
  3. print(type(i), i.dtype, sep = ' , ')
  4. print(type(f), f.dtype, sep = ' , ')
  5. # <class 'torch.Tensor'> , torch.int64
  6. # <class 'torch.Tensor'> , torch.float32

1.5 张量类型的转化方法

可以使用.float()、.int()等方法对张量类型进行转化。需要注意的是,这里并不会改变原来t的数据类型。

  1. t = torch.tensor([1, 2])
  2. f = t.float()
  3. print(f)
  4. print(t)
  5. # tensor([1., 2.])
  6. # tensor([1, 2])

二、张量的维度

张量的维度中,我们使用的张量如下:

  1. # 一维向量
  2. t1 = torch.tensor((1, 2))
  3. # 二维向量
  4. t2 = torch.tensor([[1, 2, 3], [4, 5, 6]])
  5. # 三维向量
  6. t3 = torch.tensor([[[1, 2], [3, 4]],[[5, 6], [7, 8]]])

2.1 ndim查看张量维度

  1. print(t1.ndim, t2.ndim, t3.ndim, sep = ', ')
  2. # 1, 2, 3
  3. # t1为1维向量
  4. # t2为2维矩阵
  5. # t3为3维张量

2.2 shape&size查看向量的形状

  1. print(t1.shape, t2.shape, t3.shape, sep = ', ')
  2. # torch.Size([2]), torch.Size([2, 3]), torch.Size([2, 2, 2])
  3. print(t1.size(), t2.size(), t3.size(), sep = ', ')
  4. # torch.Size([2]), torch.Size([2, 3]), torch.Size([2, 2, 2])
  5. # t1 向量torch.Size([2])的理解:向量的形状是1行2列。
  6. # t2 矩阵torch.Size([2, 3])的理解:包含两个一维向量,每个一维向量的形状是1行3列。
  7. # t3 矩阵torch.Size([2, 2, 2])的理解:包含两个二维矩阵,每个二维矩阵的形状是2行2列。

2.3 numel查看张量中的元素个数

  1. print(t1.numel(), t2.numel(), t3.numel(), sep = ', ')
  2. # 2, 6, 8
  3. # t1向量中共有2个元素
  4. # t2矩阵中共有6个元素
  5. # t3张量中共有8个元素

2.4 flatten将任意维度张量转为一维张量

  1. t2.flatten()
  2. # tensor([1, 2, 3, 4, 5, 6])
  3. t3.flatten()
  4. # tensor([1, 2, 3, 4, 5, 6, 7, 8])

2.5 reshape任意变形

形变维度的乘积需要等于张量元素的个数。

  1. # 将`t3`变成2×4的矩阵
  2. t3.reshape(2, 4)
  3. # tensor([[1, 2, 3, 4],[5, 6, 7, 8]])
  4. # 将`t3`变成1×4×2的矩阵
  5. t3.reshape(1, 4, 2)
  6. # tensor([[[1, 2], [3, 4], [5, 6], [7, 8]]])

2.6 squeeze&unsqueeze

  • squeeze的作用是压缩张量,去掉维数为1位置的维度 ```python

    将t3的维度变为2×1×4

    t_214 = t3.reshape(2, 1, 4) print(t_214)

    tensor([[[1, 2, 3, 4]], [[5, 6, 7, 8]]])

使用squeeze将其变成2×4,去掉维度为1位置的维度

t_24 = t_214.squeeze(1) print(t_24)

tensor([[1, 2, 3, 4], [5, 6, 7, 8]])

  1. - unsqueeze的作用是解压张量,给指定位置加上维数为一的维度。
  2. ```python
  3. # 将2×4的维度再转换成2×1×4,在第二个维度上加一维
  4. # 索引是从0开始的。参数0代表第一维,参数1代表第二维,以此类推
  5. print(t_24.unsqueeze(1))
  6. # tensor([[[1, 2, 3, 4]], [[5, 6, 7, 8]]])

2.7 特殊的零维张量

Tensor 的零维张量只包含一个元素,可以理解为标量,只有大小,没有方向。

  1. # 零维张量的创建只有一个数,不具备一维或多维的概念
  2. t0 = torch.tensor(1)
  3. # 因为它是标量,所以维度是0
  4. print(t0.ndim)
  5. # 0
  6. # 因为它是标量,所以也不具有形状
  7. print(t0.shape)
  8. # torch.Size([])
  9. # 它没有维度,但是有一个数
  10. print(t0.numel())
  11. # 1

2.8 .item():标量转化为数值

在很多情况下,我们需要将最终计算的结果张量转为单独的数值进行输出。

  1. n = torch.tensor(1)
  2. print(n)
  3. # tensor(1)
  4. # 使用.item()方法将标量转为python中的数值
  5. n.item()
  6. # 1

三、张量的索引

3.1 张量的符号索引

张量是有序序列,我们可以根据每个元素在系统内的顺序位置,来找出特定的元素,也就是索引。
一维张量索引与Python中的索引一样是是从左到右,从0开始的,遵循格式为[start: end: step]。

  1. t1 = torch.arange(1, 11)
  2. t1
  3. # tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
  4. # 取出索引位置是0的元素
  5. t1[0]
  6. # tensor(1)

:张量索引出的结果是零维张量,而不是单独的数。要转化成单独的数还需使用上节介绍的item()方法。
可理解为构成一维张量的是零维张量,而不是单独的数。

3.2 张量的step必须大于0

在张量中,step必须大于1,否则就会报错。

  1. t1 = torch.arange(1, 11)
  2. t1[ ::-1]
  3. # ValueError: step must be greater than zero

3.3 二维张量的索引

二维张量的索引逻辑和一维张量的索引逻辑相同,二维张量可以视为两个一维张量组合而成。

  1. t2 = torch.arange(1, 17).reshape(4, 4)
  2. t2
  3. # tensor([[ 1, 2, 3, 4],
  4. # [ 5, 6, 7, 8],
  5. # [ 9, 10, 11, 12],
  6. # [13, 14, 15, 16]])

t2[0,1] 也可用 t2[0][1] 的表示。

  1. # 表示索引第一行、第二个(第二列的)元素
  2. t2[0, 1]
  3. # tensor(2)
  4. t2[0][1]
  5. # tensor(2)

但是 t2[::2, ::2] 与 t2[::2][ ::2] 的索引结果就不同:

  1. t2[::2, ::2]
  2. # tensor([[ 1, 3],
  3. # [ 9, 11]])
  4. t2[::2][::2]
  5. # tensor([[1, 2, 3, 4]])

t2[::2, ::2] 二维索引使用逗号隔开时,可以理解为全局索引,取第一行和第三行的第一列和第三列的元素。
t2[::2][::2] 二维索引在两个中括号中时,可以理解为先取了第一行和第三行,构成一个新的二维张量,然后在此基础上又间隔2并对所有张量进行索引。

  1. tt = t2[::2]
  2. # tensor([[ 1, 2, 3, 4],
  3. # [ 9, 10, 11, 12]])
  4. tt[::2]
  5. # tensor([[1, 2, 3, 4]])

3.4 三维张量的索引

设三维张量的 shape 是 x、y、z,则可理解为它是由x个二维张量构成,每个二维张量由y个一维张量构成,每个一维张量由z个标量构成。高维张量的思路与低维一样,就是围绕张量的“形状”进行索引。

  1. t3 = torch.arange(1, 28).reshape(3, 3, 3)
  2. t3
  3. # tensor([[[ 1, 2, 3],
  4. # [ 4, 5, 6],
  5. # [ 7, 8, 9]],
  6. # [[10, 11, 12],
  7. # [13, 14, 15],
  8. # [16, 17, 18]],
  9. # [[19, 20, 21],
  10. # [22, 23, 24],
  11. # [25, 26, 27]]])
  12. # 索引第二个矩阵中的第二行、第二个元素
  13. t3[1, 1, 1]
  14. # tensor(14)
  15. # 索引第二个矩阵,行和列都是每隔两个取一个
  16. t3[1, ::2, ::2]
  17. # tensor([[10, 12],
  18. # [16, 18]])

3.5 张量的函数索引

在 PyTorch 中,我们还可以使用 index_select 函数指定index来对张量进行索引,index 的类型必须为 Tensor。

  • index_select(dim, index)表示在张量的哪个维度进行索引,索引的位值是多少。 ```python t1 = torch.arange(1, 11) indices = torch.tensor([1, 2])

    tensor([1, 2])

t1.index_select(0, indices)

tensor([2, 3])

  1. **注**:这里取出的是位置,而不是取出 s[1:2] 区间内左闭右开的元素。
  2. <a name="Ld90H"></a>
  3. #### 3.6 二维张量的函数索引
  4. ```python
  5. t2 = torch.arange(12).reshape(4, 3)
  6. t2
  7. # tensor([[ 0, 1, 2],
  8. # [ 3, 4, 5],
  9. # [ 6, 7, 8],
  10. # [ 9, 10, 11]])
  11. t2.shape
  12. # torch.Size([4, 3])
  13. indices = torch.tensor([1, 2])
  14. t2.index_select(0, indices)
  15. # tensor([[3, 4, 5],
  16. # [6, 7, 8]])

此时 dim 参数取值为0,代表在shape的第一个维度上进行索引,即在向量维度进行索引。
在PyTorch中很多函数都采用的是第几维的思路,后面会介绍给大家,大家还需勤加练习,适应这种思路。同时使用函数式索引,在习惯后对代码可读性会有很大提升。

四、张量的合并与分割

4.1 张量的分割

chunk(tensor, chunks, dim) 能够按照某个维度(dim)对张量进行均匀切分(chunks),并且返回结果是原张量的视图。

  1. # 创建一个4×3的矩阵
  2. t2 = torch.arange(12).reshape(4, 3)
  3. t2
  4. # tensor([[ 0, 1, 2],
  5. # [ 3, 4, 5],
  6. # [ 6, 7, 8],
  7. # [ 9, 10, 11]])

在第0个维度(shape的第一个数字,代表向量维度)上将t2进行4等分:

  1. # 在矩阵中,第一个维度是行,理解为shape的第一个数
  2. tc = torch.chunk(t2, chunks = 4, dim = 0)
  3. tc
  4. # (tensor([[0, 1, 2]]),
  5. # tensor([[3, 4, 5]]),
  6. # tensor([[6, 7, 8]]),
  7. # tensor([[9, 10, 11]]))

根据结果可见:

  • 返回结果是一个元组,不可变

    1. tc[0] = torch.tensor([[1, 1, 1]])
    2. # TypeError: 'tuple' object does not support item assignment
  • 元组中的每个值依然是一个二维张量

    1. tc[0]
    2. # tensor([[0, 1, 2]])
  • 返回的张量tc的一个视图,不是新成了一个对象 ```python

    我们将原张量t2中的数值进行更改

    t2[0] = torch.tensor([6, 6, 6])

再打印分块后tc的结果

tc

(tensor([[6, 6, 6]]),

tensor([[3, 4, 5]]),

tensor([[6, 7, 8]]),

tensor([[ 9, 10, 11]]))

  1. - 若原张量不能均分时,chunk不会报错,会返回次一级均分结果。
  2. ```python
  3. # 创建一个4×3的矩阵
  4. t2 = torch.arange(12).reshape(4, 3)
  5. t2
  6. # tensor([[ 0, 1, 2],
  7. # [ 3, 4, 5],
  8. # [ 6, 7, 8],
  9. # [ 9, 10, 11]])
  10. # 将4行分为3等份,不可分,就会返回2等分的结果:
  11. tc = torch.chunk(t2, chunks = 3, dim = 0)
  12. tc
  13. # (tensor([[0, 2, 2],
  14. # [3, 4, 5]]),
  15. # tensor([[ 6, 7, 8],
  16. # [ 9, 10, 11]]))

4.2 split函数

split 既能进行均分,也能进行自定义切分。需要注意的是 split 的返回结果也是视图。

  1. # 第二个参数只输入一个数值时表示均分
  2. # 第三个参数表示切分的维度
  3. torch.split(t2, 2, dim = 0)
  4. # (tensor([[0, 1, 2],
  5. # [3, 4, 5]]),
  6. # tensor([[ 6, 7, 8],
  7. # [ 9, 10, 11]]))

与 chunk 函数不同的是,split 第二个参数可以输入一个序列,表示按照序列数值等分:

  1. torch.split(t2, [1,3], dim = 0)
  2. # (tensor([[0, 1, 2]]),
  3. # tensor([[ 3, 4, 5],
  4. # [ 6, 7, 8],
  5. # [ 9, 10, 11]]))

当第二个参数输入一个序列时,序列的各数值的和必须等于对应维度下形状分量的取值,即 shape 对应的维度。
例如上述代码中,是按照第一个维度进行切分,而t2总共有4行,因此序列的求和必须等于4,也就是1+3=4,而序列中每个分量的取值,则代表切块大小。

4.3 张量的拼接cat

这里一定要将 dim 参数与 shape 返回的结果相对应理解。

  1. a = torch.zeros(2, 3)
  2. a
  3. # tensor([[0., 0., 0.],
  4. # [0., 0., 0.]])
  5. b = torch.ones(2, 3)
  6. b
  7. # tensor([[1., 1., 1.],
  8. # [1., 1., 1.]])

因为在张量a与b中,shape的第一个位置是代表向量维度,所以当dim取0时,就是将向量进行合并,向量中的标量数不变:

  1. torch.cat([a, b], dim = 0)
  2. # tensor([[0., 0., 0.],
  3. # [0., 0., 0.],
  4. # [1., 1., 1.],
  5. # [1., 1., 1.]])

当dim取1时,shape的第二个位置是代表列,即标量数,就是在列上(标量维度)进行拼接,行数(向量数)不变:

  1. torch.cat([a, b], dim = 1)
  2. # tensor([[0., 0., 0., 1., 1., 1.],
  3. # [0., 0., 0., 1., 1., 1.]])

4.4 张量的堆叠 stack

和拼接不同,堆叠不是将元素拆分重装,而是将各参与堆叠的对象分装到一个更高维度的张量里。

  1. a = torch.zeros(2, 3)
  2. a
  3. # tensor([[0., 0., 0.],
  4. # [0., 0., 0.]])
  5. b = torch.ones(2, 3)
  6. b
  7. # tensor([[1., 1., 1.],
  8. # [1., 1., 1.]])

堆叠之后,生成一个三维张量:

  1. torch.stack([a, b], dim = 0)
  2. # tensor([[[0., 0., 0.],
  3. # [0., 0., 0.]],
  4. # [[1., 1., 1.],
  5. # [1., 1., 1.]]])
  6. torch.stack([a, b], dim = 0).shape
  7. # torch.Size([2, 2, 3])

此例中,就是将两个维度为2×3的张量堆叠为一个2×2×3的张量。

4.5 cat与stack的区别

拼接之后维度不变,堆叠之后维度升高。拼接是把一个个元素单独提取出来之后再放到二维张量里,而堆叠则是直接将两个二维向量封装到一个三维张量中。因此,堆叠的要求更高,参与堆叠的张量必须形状完全相同。

4.6 python中的拼接和堆叠

  1. a = [1, 2]
  2. b = [3, 4]
  • cat拼接操作与list的extend相似,不会改变维度,只会在已有框架内增加元素:

    1. a.extend(b)
    2. a
    3. # [1, 2, 3, 4]
  • stack 堆叠操作与 list 的 append 相似,会改变维度:

    1. a = [1, 2]
    2. b = [3, 4]
    3. a.append(b)
    4. a
    5. # [1, 2, [3, 4]]