> import torch
> t = torch.tensor([
[1,1,1,1],
[2,2,2,2],
[3,3,3,3]
], dtype=torch.float32)
张量 t 的 shape 或者 size 为:
>t.size()
torch.Size([3, 4])
> t.shape
torch.Size([3, 4])
reshape前后元素总量不变,查看一个张量中的元素总量可以用以下两种方法:
> torch.tensor(t.shape).prod()
tensor(12)
>t.numel()
12
二阶张量 t 中一共有12个元素,那么对t进行 reshape 可能的结果是将 3x4 改成 1x12、2x6、3x4、4x3、6x2、12x1,当然也可以用 t.reshape(2,2,3) 这样的方式 reshape 为 2x2x3 的三阶张量。
把一个n阶的张量变形为只有一行的一阶张量,即变为 array 类型。这个操作通常在CNN中由卷积层传递到全连接层的时候发生。卷积层的输出是很多的 feature map,每一个 feature map 都是一个二阶张量,但是全连接层的输入只能是数组,因此需要把卷积层输出的高阶张量压平变为一维数组,才能输入全连接层。
对二阶张量进行 flatten 的操作,是第一行数据不动,将第二行数据拼接至第一行数据末尾,再将第三行数据拼接到第二行数据的末尾,以此类推。
采用 reshape() 函数对张量 t 进行变换:
>print(t.reshape([1,12]))
> print(t.reshape([1,12]).shape)
tensor([[1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]])
torch.Size([1, 12])
输出的张量有两层中括号,而且 shape 为“[1, 12]”,表明其在计算机中存储的形式仍然为二阶张量,只不过有一个 axis 的长度为 1 而已。要进一步将其转为一阶张量,需要用到 squeeze() 函数:
> print(t.reshape([1,12]).squeeze())
>print(t.reshape([1,12]).squeeze().shape)
tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])
torch.Size([12])
squeeze() 函数的作用就是去掉高阶张量中所有长度为 1 的 axes,使高阶张量降低阶数为低阶张量。
类似的还有 unsqueeze() 函数,为张量添加一个长度为1的 axis,相当于 squeeze() 的逆操作:
> print(t.reshape([1,12]).squeeze().unsqueeze(dim=0))
>print(t.reshape([1,12]).squeeze().unsqueeze(dim=0).shape)
tensor([[1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]])
torch.Size([1, 12])
定义一个 flatten() 函数包含上述 reshape 和 squeeze 两步:
def flatten(t):
t = t.reshape(1, -1)
t = t.squeeze()
return t
reshape() 函数中的第二个参数填写了“-1”,是让计算机自己根据张量中的元素数量决定一维数组的长度。可以验证:
> flatten(t)
tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])
两个二阶张量:
>t1 = torch.tensor([
[1,2],
[3,4] ])
> t2 = torch.tensor([
[5,6],
[7,8] ])
使用 cat() 函数同时指定拼接的方向(维数),可以得到一个拼接后的新张量。
沿第一个维度(行)拼接为4行2列:
> torch.cat((t1, t2), dim=0)
tensor([
[1, 2],
[3, 4],
[5, 6],
[7, 8]])
沿第二个维度(列)拼接为2行4列:
> torch.cat((t1, t2), dim=1)
tensor([
[1, 2, 5, 6],
[3, 4, 7, 8]])
一次输入CNN的是不是一张而是一批图像,输入的是一个具有 [B, C, H, W] 形状的四阶张量。如果直接对这个张量进行展平,会把所有图像的所有颜色通道(或者 feature map)全都混在一起。
我们要做的其实只是把 [B, C, H, W] 这个四阶张量的 H 和 W 两个维度展平。
创建3个 4x4 的二阶张量代表3张 4x4 的图片,数字 i 只属于第 i 张图片:
>t1 = torch.tensor([
[1,1,1,1],
[1,1,1,1],
[1,1,1,1],
[1,1,1,1] ])
>t2 = torch.tensor([
[2,2,2,2],
[2,2,2,2],
[2,2,2,2],
[2,2,2,2]
])
>t3 = torch.tensor([
[3,3,3,3],
[3,3,3,3],
[3,3,3,3],
[3,3,3,3]
])
将这三个二阶张量堆栈起来组成一个 batch,可以用 torch.stack() 函数:
>t = torch.stack((t1, t2, t3))
>t.shape
torch.Size([3, 4, 4])
此时还缺一个中间的颜色通道维度,采用 reshape() 函数来添加:
> t = t.reshape(3,1,4,4)
>t
创建的四阶张量 t 为(注释标明了每一个维度的含义):
tensor(
# Batch
[ # Channel
[ # Height
[ # Width
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]
]
],
[
[
[2, 2, 2, 2],
[2, 2, 2, 2],
[2, 2, 2, 2],
[2, 2, 2, 2]
]
],
[
[
[3, 3, 3, 3],
[3, 3, 3, 3],
[3, 3, 3, 3],
[3, 3, 3, 3]
]
]
])
因为灰度图片只有一个颜色通道,所以要把 [B, C, H, W] 中从第二个维度起的后三个维度 C、H、W 都展平,采用PyTorch自带的 torch.flatten() 添加一个 start_dim=1 的参数即可:
> t.flatten(start_dim=1).shape
torch.Size([3, 16])
>t.flatten(start_dim=1)
tensor([
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
])
输出结果保留了 batch 的维度,而把后面的维度都展平了,这样同一 batch 中的不同图片数据就不会混在一起了。
对于有三个颜色通道的 RGB 图片,不同的颜色通道(或者 feature map)也不应该混淆在一起,这时候把 torch.flatten() 函数的参数改为 start_dim=2 即可。
corresponding elements 是指在张量中的位置相同,就是在各个 axis 上的索引编号相同的元素。
> t1 = torch.tensor([
[1,2],
[3,4]
], dtype=torch.float32)
>t2 = torch.tensor([
[9,8],
[7,6]
], dtype=torch.float32)
1 和 9 就是 corresponding elements,因为索引的 indexes 是一样的:
> t1[0][0]
tensor(1.)
> t2[0][0]
tensor(9.)
所有的 element-wise operations 都只能在具有相同 shape 的张量之间进行
> t1 + t2
tensor([[10., 10.],
[10., 10.]])
> t1 - t2
tensor([[-8., -6.],
[-4., -2.]])
> t1 * t2
tensor([[ 9., 16.],
[21., 24.]])
> t1 / t2
tensor([[0.1111, 0.2500],
[0.4286, 0.6667]])
PyTorch中张量间的加减乘除四则运算都是 element-wise 的。
> print(t1 + 2)
tensor([[3., 4.],
[5., 6.]])
> print(t1 - 2)
tensor([[-1., 0.],
[ 1., 2.]])
> print(t1 * 2)
tensor([[2., 4.],
[6., 8.]])
> print(t1 / 2)
tensor([[0.5000, 1.0000],
[1.5000, 2.0000]])
用函数命令来运算,结果是一样的:
> print(t1.add(2))
> print(t1.sub(2))
> print(t1.mul(2))
> print(t1.div(2))
但 t1 是一个 2x2 的二阶张量,而数 2 是一个零阶的张量,没有 shape,和之前说的 element-wise operations 需要在 shape 一样的张量之间进行不相符。
Broadcasting tensor 就是把两个 shape 不一样的 tensor 匹配成 shape 一样的 sensor 的过程,匹配的方法是将 shape 小的 tensor 进行复制和拼接,使小的 tensor 变成和大的 tensor 具有一样的 shape。这是PyTorch在 shape 不同的张量之间进行操作之前会进行的一个步骤,即:
>np.broadcast_to(2, t1.shape)
array([[2, 2],
[2, 2]])
对于不同 shape 的 tensor 之间的操作:
> t1 = torch.tensor([
[1,1],
[1,1]
], dtype=torch.float32)
> t1.shape
torch.Size([2, 2])
> t2 = torch.tensor([2,4], dtype=torch.float32)
> t2.shape
torch.Size([2])
t1 与 t2 相加时,PyTorch对 t2 进行了 broadcast 以匹配 t1 的 shape:
> np.broadcast_to(t2.numpy(), t1.shape)
array([[2., 4.],
[2., 4.]], dtype=float32)
> t1 + t2
tensor([[3., 5.],
[3., 5.]])
比较操作是两个张量之间按元素进行比较,返回值的数据类型是 torch.bool(布尔运算值),即 True 和 False。
> t = torch.tensor([
[0,5,0],
[6,0,7],
[0,8,0]
], dtype=torch.float32)
各项比较操作分别为:
> t.eq(0) # equal to 判断是否相等
tensor([[True, False, True],
[False, True, False],
[True, False, True]])
> t.ge(0) # greater than or equal to 大于等于
tensor([[True, True, True],
[True, True, True],
[True, True, True]])
> t.gt(0) # greater than 大于
tensor([[False, True, False],
[True, False, True],
[False, True, False]])
> t.lt(0) # less than 小于
tensor([[False, False, False],
[False, False, False],
[False, False, False]])
> t.le(7) # less than or equal to 小于等于
tensor([[True, True, True],
[True, True, True],
[True, False, True]])
PyTorch也是先对 number 类型的零维张量进行了 broadcasting 之后才进行比较操作。
> t.abs() # absolute 取绝对值
tensor([[0., 5., 0.],
[6., 0., 7.],
[0., 8., 0.]])
> t.sqrt() # squrt root 求平方根
tensor([[0.0000, 2.2361, 0.0000],
[2.4495, 0.0000, 2.6458],
[0.0000, 2.8284, 0.0000]])
> t.neg() # negative 取负
tensor([[-0., -5., -0.],
[-6., -0., -7.],
[-0., -8., -0.]])
> t.neg().abs() # 先取负再取绝对值
tensor([[0., 5., 0.],
[6., 0., 7.],
[0., 8., 0.]])
reduction operations 让我们能够对单个张量内的元素执行运算。
以一个 3x3 二阶张量举例:
> t = torch.tensor([
[0,1,0],
[2,0,2],
[0,3,0]
], dtype=torch.float32)
reduction operations:
> t.sum() # 求元素之和
tensor(8.)
> t.prod() # 求元素乘积
tensor(0.)
> t.mean() # 求元素平均值
tensor(.8889)
> t.std() # 求元素标准差
tensor(1.1667)
之所以称之为 reduction operations 是因为他们的输出从原来输入的二阶张量变为了零阶张量,张量中总的元素数量减少了:
> t.numel()
9
> t.sum().numel()
1
> t.sum().numel() < t.numel()
True
可以指定 reduction operations 作用的 axis:
> t = torch.tensor([
[1,1,1,1],
[2,2,2,2],
[3,3,3,3]
], dtype=torch.float32)
> t.sum(dim=0)
tensor([6., 6., 6., 6.])
> t.sum(dim=0).shape
torch.Size([4])
> t.sum(dim=1)
tensor([ 4., 8., 12.])
> t.sum(dim=1).shape
torch.Size([3])
t = torch.tensor([
[1,0,0,2],
[0,3,3,0],
[4,0,0,5]
], dtype=torch.float32)
> t.max()
tensor(5.)
> t.argmax()
tensor(11)
argmax() 函数输出的只有一个零阶张量,是因为PyTorch先对高阶张量进行了 flatten() 操作,然后才去比较大小:
> t.flatten()
tensor([1., 0., 0., 2., 0., 3., 3., 0., 4., 0., 0., 5.])
> t.flatten().argmax()
tensor(11)
argmax() 函数也可以按指定的 axis 方向进行操作:
> t.max(dim=0)
(tensor([4., 3., 3., 5.]), tensor([2, 1, 1, 2]))
> t.argmax(dim=0)
tensor([2, 1, 1, 2])
> t.max(dim=1)
(tensor([2., 3., 5.]), tensor([3, 1, 3]))
> t.argmax(dim=1)
tensor([3, 1, 3])
max() 函数会返回两个张量,第一个是按指定的 axis 方向找出的最大元素值,第二个是这些最大元素在指定的 axis 方向上的索引号.
argmax() 在神经网络中的用处在于:图像分类神经网络的输出层是一个包含 k 个元素的一阶张量(其中 k 是图片的类别数),张量中的元素是对应各个类别的 prediction values(翻译成置信概率?),所有的 prediction values 之和为 1。我们会取置信概率最大的那一种作为最终预测的类别(比如 a 类预测值 0.05,b 类 0.95,那就认为是 b 类)。这样就需要使用 argmax() 函数来找出最大的 prediction value 对应的是哪一类。
求张量元素的平均值:
> t = torch.tensor([
[1,2,3],
[4,5,6],
[7,8,9]
], dtype=torch.float32)
> t.mean()
tensor(5.)
> type(t.mean())
torch.Tensor
输出的数据类型仍然是一个张量(虽然是零阶张量),可以通过 item() 函数提取出零阶张量中的数据:
> t.mean().item()
5.0
> type(t.mean().item())
float
对于输出为含有多个元素的张量时,可以采用 tolist() 将其转为 Python list:
> t.mean(dim=0).tolist()
[4.0, 5.0, 6.0]
或者用 numpy() 将其装换为 numpy 数组:
> t.mean(dim=0).numpy()
array([4., 5., 6.], dtype=float32)