在这里,我们将用重塑操作(reshape)来启动它,在开始进行重塑操作之前,首先对神经网络和深度学习的张量做一个全面的概述:
我们使用的主要操作通常分为四大类:Reshaping operations / Element-wise operations / Reduction operations / Access operations
对于一个二维张量来说,对第一个轴的操作默认按列操作,第二个轴按行操作
我们有一个张量:
import torch
t = torch.tensor([
[1,1,1,1],
[2,2,2,2],
[3,3,3,3]
], dtype=torch.float32)
张量的形状是3x4,这是一个有两个轴的二阶张量:第一个轴长度是3,元素是数组;第二个轴长度是4,元素是数字。
在pytorch中我们有2种编程方式来访问这个形状:用size的方法 / 用shape这个属性
t.size() # torch.Size([3, 4]
t.shape # torch.Size([3, 4]
我们可以通过检查它的形状长度来获得张量的秩
len(t.shape) # 2
张量形状给出的另一个重要特征是张量种包含的元素个数:
torch.tensor(t.shape).prod() # tensor(12)
# 我们把这个形状转换成一个张量,然后使用.prod看到这个张量包含12个分量
t.numel() # 12 函数,计算元素数量
我们在重塑时要考虑到元素的个数,前文提到,重塑不会改变基础数据,改变的只是形状
现在我们来看一下t在不改变秩的情况下被重塑(reshaping)的所有方式:
t.reshape(1,12)
t.reshape(2, 6)
t.reshape(3, 4)
t.reshape(4, 3)
t.reshape(6, 2)
t.reshape(12,1)
# 形状中的分量值是12的因数,它们的乘积是12,在重塑后,我们有12个位置代表原来12个值
所有这些例子我们使用了两个因子使秩保持在2,如果我们使用多个因子,就可以改变秩
t.reshape(2,2,3) # 因子乘积是12
下一步我们可以改变张量的形状,通过压缩(squeezing)和解缩(unsqueezing)它们
print(t.reshape(1, 12)) # tensor([[1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]])
print(t.reshape(1, 12).shape) # torch.Size([1, 12])
print(t.reshape(1, 12).squeeze()) # tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.])
print(t.reshape(1, 12).squeeze().shape) # torch.Size([12])
* 压缩一个张量可以移除所有长度为1的轴
print(t.reshape(1, 12).squeeze().unsqueeze(dim=0)) # tensor([[1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]])
print(t.reshape(1, 12).squeeze().unsqueeze(dim=0).shape) # torch.Size([1, 12])
* 解缩一个张量会增加一个长度为1的维度,这些函数允许我们扩大或缩小张量的秩
一个非常常见的用例:通过构建一个flatten函数来压缩一个张量
压扁一个张量意味着去除所有的轴只保留一个,把多维的输入一维化,它创造了一个单轴的张量,包含了张量的元素,本质上就是把一个张量拉平;我们创建了一个一维数组,它包含张量的所有标量分量
一个flatten操作是当我们从一个卷积层过渡到一个全连接层时必须在神经网络中发生的操作,我们从卷积层获得输出,它以输出通道的形式给我们,我们把这些通道变平成一个单一的一维数组
def flatten(t): # 这个flatten函数用一个叫做t的张量作为参数
t = t.reshape(1, -1)
# 因为输入张量可以是任何形状的,我们通过一个-1来表示参数函数的第二个参数(在pytorch中,-1告诉reshape函数:根据一个张量中包含的其他值和元素的个数来求出值应该是多少)
t = t.squeeze()
return t
print(flatten(t)) # tensor([1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]) 在压缩第一个轴后,我们得到了想要的结果——一个长度为12的一维数组
拼接操作(可以测试我们对于形状概念的理解):
我们可以使用ptorch的cat函数来组合2个张量。就2个张量形状而言,我们计算他们的方式会影响输出张量结果的形状
torch.cat((A,B),axis) # 是对A, B两个tensor进行拼接,参数axis指定拼接的方式:axis=0为按行拼接;axis=1为按列拼接
当我们改变一个张量形状的时候,我们就认为是在重塑张量
张量的flatten操作是卷积神经网络中一个常见操作,因为传递给全连接层的卷积层输出必须在进入全连接层之前进行flatten,flatten是一种特殊的reshaping,即所有轴都被挤压在一起
如果我们想对一个张量进行flatten操作,那么它至少要有2个轴
把一个轴的张量变平:
# 前文提到,CNN输入的形状通常长度为4,即有四个轴——批量大小、颜色通道、高度、宽度
import torch
t1 = torch.tensor([ # 我们有3个张量,每一个的形状都是4x4,所以我们有3个秩为2的张量 我们把它们看作是3张4x4的图片,他们用来创建一个批次传入CNN
[1, 1, 1, 1], # 这里是用一个张量来表示一个完整的批次,所以我们要把这三个张量合并成一个更大的张量,它有3个轴
[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]
])
t = torch.stack((t1, t2, t3)) # 使用一个堆栈函数,将3个张量的序列连接到一个新的轴上
t.shape # torch.Size([3, 4, 4]) 形状是3x4x4 新轴上有3个张量,新轴的长度为3 长度为3的轴表示批的大小
t
''' 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]]])
# 现在我们要做的是把这个张量变成一个CNN期望的形式,为彩色通道增加一个轴
'''
对于每个图像张量,我们基本上都有一个隐式的单色通道,CNN需要一个显式的彩色通道轴,让我们通过重塑来为它添加一个轴
t = t.reshape(3, 1, 4, 4)
t '''tensor([[[[1, 1, 1, 1], # 长度为1的额外轴不会改变张量中元素的数量(乘以1数值不会变)
[1, 1, 1, 1], # 第一个轴有3个元素,每个元素代表一个图像,对于每个图像,我们有一个包含单个通道的彩色通道轴
[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]]]]) '''
t[0] '''tensor([[[1, 1, 1, 1], 我们获得一个图像
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]]])'''
t[0][0] ''' tensor([[1, 1, 1, 1], 在第一个图像中有第一个彩色通道
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])'''
t[0][0][0] # tensor([1, 1, 1, 1]) 在第一个图像的第一个彩色通道中,有第一行像素
t[0][0][0][0] # tensor(1) 在第一行像素中,有第一个像素值
* 记住:整批是一个单独的张量,它将被传递给CNN;现在让我们看看如何使这批图像变平:
t.reshape(1, -1)[0]
t.reshape(-1)
t.view(t.numel())
t.flatten()
''' 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]'''
我们把整个批次都压平了,把所有的图像都映射到一个单独的轴上,1代表第一张图像的像素,2来自第二张图…这个被压扁的批处理效果不太好,因为所有的图像都被压缩成一个,因此我们需要对每一个图像进行单独预测
解决方案是:保持批轴的同时使没个图像变平,意味着我们只想把张量的一部分变平,我们想用高度和宽度轴来压平颜色通道轴,这里使用pytorch的flatten来法完成
t.flatten(start_dim=1).shape # torch.Size([3, 16]) # 我们得到了一个秩为2的张量,有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]])'''
# 注意:我们在调用中指定start_dim参数,这告诉了flatten函数,当它开始flatten操作时应该从哪个轴开始,这里的1是一个索引,所以应该是第二个轴,即我们跳过了批轴