Pytorch 入门与提高(3)—tensor 的 reshape 操作

7,419 阅读6分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

今天收到掘金的搪瓷杯,算是是自己在过去一个月写作的一个小小鼓励,虽然自以为已经做到不以物喜,不过收到快递拆开看到礼物那一刻,心里还是甜甜的。感谢掘金,感谢自己,为自己加油,同时也感谢你们的鼓励和支持。

有关形状,我们如何用 tensor 来表示图像,我们图像是由 colordepth×hight×widthcolordepth \times hight \times width ,今天来展示一下我绘画功底,用图像来表示是从 Image 转化为 tensor。

005.png

在图上如何将一张图片转换为 tensor,通常用 3 维 tensor 表示一张图片 channel×height×widthchannel \times height \times width

提醒,在 pytorch 中是将颜色通道数维度提前到 0 维度上,这个与其他深度学习框架或者处理计算视觉的库图片表达方式有所不同

006.png

我们将多张图片组合在一起输入到神经网络训练,这也就是我们平时经常提到的批次(batch)。

tensor 的 reshape 操作

今天我们就来聊一聊一些可以改变 tensor 的形状的操作,一个张量主要保存三个属性:名字(name)、维度(shape)和类型(type),其实所谓形状也就是 tensor 的维度。

import numpy as np
import torch
  • view/reshape 改变形状
  • Squeeze/unsqueeze 增加维度/删减维度
  • transpose/permute 变换维度
  • Expand/repeat 维度扩展

高维 tensor

对于高纬 tensor,我们主要理解好后 2 个维度,可以理解为平面,3 维表示立体形状,随着维度增加我们就可以将每一个维度理解为容器或者盒子,更高维可以理解为装着低纬的容器或盒子。

001.png

改变维度或者形状(shape)

在 numpy 中使用 reshape 对 tensor 的形状进行改变,而在 pytorch 我们可以用 view 和 reshape 方法对 tensor 形状进行改变,他们除了名字不同,其他并没有什么区别,所以这里就以 view 为例来说一说如何改变 tensor 形状。(4×1×28×28)(4 \times 1 \times 28 \times 28)如果大家写过几个图片分类简单网络,这个形状 tensor 应该不会陌生,表示 4 张 1 个通道高度和宽度分别为 28 的图片。如果我们要用全连接网络进行识别,需要将高度和宽度拉平再输入到全连接神经网。这是就会用 view ,通过调用 tensor 的 view 然后传入要转换的形状的即可。

img_batch = torch.rand(4,1,28,28)

上面 tensor 的实际意义是 4 张图通道数为 1 大小为 28×2828 \times 28 的图片。

img_batch.view(4,28*28)

002.png

可以通过 view 对 tensor 进行变形(reshape)操作,如上图,用图形方式表现了 4×1×28×284 \times 1 \times 28 \times 28。首先我们需要将 28×2828 \times 28 进行展平,也就是将 28×2828 \times 28 每一行首尾相接,通常这种方式将一个 2 维矩阵展平为一个 1 维向量(如下图)

003.png

    tensor([[0.6365, 0.1717, 0.8811,  ..., 0.8527, 0.1694, 0.4334],
            [0.1407, 0.7842, 0.4685,  ..., 0.5031, 0.9853, 0.4011],
            [0.0238, 0.9538, 0.1811,  ..., 0.4953, 0.8943, 0.2351],
            [0.4644, 0.3313, 0.8963,  ..., 0.7917, 0.7044, 0.1615]])

注意,虽然形状发生变化,但是总的维度数是不变的,当然元素数量也不会发生变化

img_batch.view(4*28,28)

    tensor([[0.6365, 0.1717, 0.8811,  ..., 0.8414, 0.5303, 0.7353],
            [0.7329, 0.5666, 0.9602,  ..., 0.3899, 0.6952, 0.5773],
            [0.7327, 0.8660, 0.0051,  ..., 0.7607, 0.8036, 0.6715],
            ...,
            [0.0486, 0.4234, 0.2400,  ..., 0.9370, 0.8427, 0.0652],
            [0.0779, 0.9036, 0.1388,  ..., 0.3249, 0.8069, 0.8189],
            [0.9946, 0.6009, 0.4542,  ..., 0.7917, 0.7044, 0.1615]])

加下来进行另一种变换,这种变换实际意义不大,在开发过程很少有这种对批次图像变换的操作,具体操作如下图

007.png

img_batch.view(4*28,28).shape
torch.Size([112, 28])

扩充和压缩维度

扩充维度(unsqueeze)

squeeze 和 unsqueeze 分别是对 tensor 进行删除维度和增加维度。

Pos.Idx1234
Neg.Idx-4-3-2-1

在 Pos.Idx(正向)指定维度前插入一个维度,在 Neg.Idx(负向)指定维度之后插入维度,在 0 前插入维度,那么 (4,1,28,28) 0 维前添加 (1,4,1,28,28)

img_batch.unsqueeze(0).shape

在 0 维度扩展一个维度,有点类似我们准备好了一个包装盒,把表示批量图片 tensor 放入到这个包装盒,这样过程可以理解为在 0 维度上进行扩展unsqueeze

008.png

torch.Size([1, 4, 1, 28, 28])

如果指向在一个 tensor 没有维度上进行unsqueeze 就会抛出下面异常。

img_batch.unsqueeze(5)
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-13-fdcc2e08cb60> in <module>
----> 1 img_batch.unsqueeze(5)


IndexError: Dimension out of range (expected to be in range of [-5, 4], but got 5)
# 在 -1(3) 后插入维度,那么 (4,1,28,28) 0 维前添加 (4,1,28,28,1)
# [[1,2,1],[2,3,2]] 2 \times 3 2 \times 3 \times [[[1],[2],[3]]]
a.unsqueeze(-1).shape

这个操作需要怎么理解呢?可以理解将 tensor 里最小单位都用一个小小包装盒包装一层就是如下

torch.Size([4, 1, 28, 28, 1])
# 在 -4(0) 后插入维度,那么 (4,1,28,28) 0 维前添加 (4,1,1,28,28)
# [[[1,2,1],[2,3,2]]] 2 \times 3 2  \times 1 \times 3[[[1],[2],[3]]]
a.unsqueeze(-4).shape
torch.Size([4, 1, 1, 28, 28])
# 在 -5  后插入维度就相当在 0 维度前添加维度,那么 (4,1,28,28) 0 维前添加 (1,4,1,28,28)
a.unsqueeze(-5).shape
torch.Size([1, 4, 1, 28, 28])

压缩维度(squeeze)

# biase 4 batch 32 channel 14 14  $\theta X + bias$
bias = torch.rand(32)
feature_map = torch.rand(4,32,14,14)
# 1,32 -> 
bias = bias.unsqueeze(1).unsqueeze(0).unsqueeze(2)
# .unsqueeze(2).unsqueeze(0)
bias.shape

011.png

torch.Size([1, 32, 1, 1])

如果没有指定具体要 squeeze 哪一个 tensor 维度则对所有可以压缩维度(也就是该维度大小为 1)进行 squeeze操作

bias.squeeze().shape
torch.Size([32])

也可以指定一个要压缩指定的一个维度,例如这里指定了维度 0

bias.squeeze(0).shape
torch.Size([32, 1, 1])

012.png

bias.squeeze(-1).shape
torch.Size([1, 32, 1])

015.png

# 没有报错
bias.squeeze(1).shape
torch.Size([1, 32, 1, 1])
bias.squeeze(-4).shape
torch.Size([32, 1, 1])

维度扩展

Expand 返回当前 tensor 在某维扩展更大后的 tensor expand不会分配新的内存,只是在存在的 tensor 上创建一个新的视图 view. Repeat 沿着特定的维度重复这个 tensor ,和 expand()不同的是,这个函数拷贝 tensor 的数据。

a = torch.rand(4,32,14,14)
# a
temp_tensor = torch.tensor([[1,2],[2,3]])
temp_tensor.shape
torch.Size([2, 2])
temp_tensor.unsqueeze(0).shape
torch.Size([1, 2, 2])
expand_temp_tensor = temp_tensor.expand(4,2,2)
expand_temp_tensor
tensor([[[1, 2],
         [2, 3]],

        [[1, 2],
         [2, 3]],

        [[1, 2],
         [2, 3]],

        [[1, 2],
         [2, 3]]])
b = torch.rand(1,32,1,1)
b.shape
torch.Size([1, 32, 1, 1])

将原有维度进行扩展

# 
b.expand(4,32,14,14).shape

016.png

torch.Size([4, 32, 14, 14])

-1 表示在该维度上并不改变形状

b.expand(-1,32,-1,-1).shape
torch.Size([1, 32, 1, 1])
b.expand(-1,32,-1,-4).shape

如果用一个变量接受 b.expand(-1,32,-1,-4) 尝试去输出个变量,就会抛出异常RuntimeError: Trying to create tensor with negative dimension -4: [1, 32, 1, -4]

torch.Size([1, 32, 1, -4])

对 0 和 1 维进行扩展维

img_batch.repeat(4,64,1,1).shape
torch.Size([16, 64, 28, 28])
bias.repeat(4,1,1,1).shape
torch.Size([4, 32, 1, 1])
bias.repeat(4,1,32,32).shape
torch.Size([4, 32, 32, 32])