将图像输入批次扁平化为CNN
将图像输入批次扁平化为CNN
欢迎回到这个神经网络编程系列。在这篇文章中,我们将可视化一个单一灰度图像的张量扁平化操作,并且我们将展示如何扁平化特定的张量轴,这在使用CNN时通常是必需的,因为我们处理的是输入批次,而不是单个输入。
让我们开始吧。
扁平化整个张量
张量的扁平化操作是卷积神经网络中的常见操作。这是因为卷积层的输出在传递到全连接层之前必须被扁平化。
在之前的文章中,我们学习了张量的形状以及重塑操作。扁平化操作是一种特定类型的重塑操作,其中所有的轴都被_压缩_或_合并_在一起。
为了扁平化一个张量,我们至少需要两个轴。这使得我们从尚未扁平的东西开始。现在让我们看看一个手写的来自MNIST数据集的数字8图像。这个图像具有2
个不同的维度,高度_和_宽度。
高度和宽度分别是18 x 18
。这些维度告诉我们这是一个裁剪后的图像,因为MNIST数据集包含28 x 28
的图像。现在让我们看看这两个轴的高度和宽度如何被扁平化为长度为324
的单一轴。
上面的图像展示了我们扁平化的输出,具有长度为324
的单一轴。边缘的白色对应于图像顶部和底部的白色。
在这个例子中,我们扁平化了整个张量图像,但如果我们只想扁平化张量内的特定轴呢?这在使用CNN时通常是必需的。
让我们看看我们如何在代码中扁平化张量的特定轴,使用PyTorch。
扁平化张量的特定轴
在关于CNN输入张量形状的文章中,我们学习了卷积神经网络的张量输入通常具有4
个轴,一个用于批次大小,一个用于颜色通道,每个用于高度和宽度。
(批次大小,通道,高度,宽度)
让我们从这里开始,构建一个符合这些规格的张量。首先,假设我们有以下三个张量。
构建图像批次的张量表示
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]
])
这些张量每个的形状都是4 x 4
,所以我们有三个秩为2的张量。在这里,我们将这三个4 x 4
图像视为可以通过CNN处理的批次。
记住,批次使用单个张量表示,所以我们需要将这三个张量合并为一个具有三个轴而不是2
的更大的张量。
> t = torch.stack((t1, t2, t3))
> t.shape
torch.Size([3, 4, 4])
在这里,我们使用了stack()
方法沿着一个新的轴连接我们的三个张量序列。由于我们在一个新的轴上有三个张量,我们知道这个轴的长度应该是3
,实际上,我们可以看到在形状中我们有3
个张量,它们的高度和宽度都是4
。
想知道stack()
方法如何工作吗?stack()
方法的解释将在本系列后面介绍。
长度为3
的轴代表批次大小,而长度为4
的轴分别代表高度和宽度。这就是这个张量批次表示的输出看起来的样子。
> 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]]])
此时,我们有一个包含三个4 x 4
图像的秩为3的张量。我们现在需要做的就是为这个张量添加一个颜色通道轴,以使其成为CNN所期望的形式。我们基本上为这些图像张量中的每一个都有一个隐含的单一颜色通道,所以实际上,这些将是灰度图像。
CNN期望看到一个明确的颜色通道轴,因此让我们通过重塑这个张量来添加一个。
> t = t.reshape(3,1,4,4)
> 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]
]
]
))
注意到我们在批次大小轴之后指定了一个长度为1
的轴。然后,我们跟随高度和宽度轴,长度为4
。同时,注意到额外的长度为1
的轴并没有改变张量中元素的数量。这是因为当我们乘以一时,组成部分值的乘积不会改变。
第一个轴有3
个元素。第一个轴的每个元素代表一个图像。对于每个图像,我们在通道轴上有一个单一的颜色通道。这些通道中的每一个都包含4
个数组,包含4
个数字或标量组件。
让我们通过索引这个张量来查看。
我们有第一张图像。
> 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的单个张量,所以我们不想扁平化整个东西。我们只想扁平化批次张量中的图像张量。
让我们先扁平化整个东西,只是为了看看它会变成什么样子。另外,我想对那些在我们上一篇文章中提供了flatten()
函数替代实现的每个人大声说出来。看看。
> t.reshape(1,-1)[0]
tensor([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, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
> t.reshape(-1)
tensor([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, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
> t.view(t.numel())
tensor([1, 1, 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])
> t.flatten()
tensor([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])
在底部,你会注意到另一种内置于张量对象的方法,叫做flatten()
。这种方法产生的输出与其他替代方案完全相同。
我想让你注意到这个输出,我们已经扁平化了整个批次,这将所有图像合并到了一个单一轴上。记住,_1_代表第一张图像的像素,_2_代表第二张图像,而_3_代表第三张图像。
这个扁平化的批次在我们的CNN内部不会很好地工作,因为我们需要对批次张量中的每张图像进行单独预测,现在我们有一个扁平化的混乱。
这里的解决方案是,在保持批次轴的同时扁平化每张图像。这意味着我们只想扁平化张量的一部分。我们想扁平化颜色通道轴以及高度和宽度轴。
这些轴需要被扁平化:(C,H,W)
这可以使用PyTorch的内置flatten()
方法完成。
扁平化张量的特定轴
让我们运行以下代码:
> t.flatten(start_dim=1).shape
torch.Size([3, 16])
> t.flatten(start_dim=1)
tensor(
[
[1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
[2, 2, 2, 2, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
[3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]
]
)
注意在调用中我们如何指定了start_dim
参数。这告诉flatten()
方法应该从哪个轴开始扁平化操作。这里的1是一个索引,所以它是第二个轴,即颜色通道轴。我们跳过了批次轴,保持它不变。
检查形状,我们可以看到我们有一个秩为2的张量,其中包含三个单通道图像,这些图像已经被扁平化为16
个像素。
扁平化RGB图像
如果我们扁平化一个RGB图像,颜色通道会发生什么?
每个颜色通道将首先被扁平化。然后,扁平化的通道将被排列在张量的单个轴上并排成一行。让我们看一个代码示例。
我们将构建一个高度为两像素,宽度为两像素的示例RGB图像张量。
r = torch.ones(1,2,2)
g = torch.ones(1,2,2) + 1
b = torch.ones(1,2,2) + 2
img = torch.cat(
(r,g,b)
,dim=0
)
这给了我们想要的张量。我们可以通过检查形状来验证这一点:
> img.shape
torch.Size([3, 2, 2])
我们有三个颜色通道,高度和宽度都是两像素。我们也可以这样检查这个张量的数据:
> img
tensor([
[
[1., 1.]
,[1., 1.]
]
,[
[2., 2.]
, [2., 2.]
],
[
[3., 3.]
,[3., 3.]
]
])
现在,我们可以通过扁平化图像张量来看看这是什么样子。
> img.flatten(start_dim=0)
tensor([1., 1., 1., 1., 1., 2., 2., 2., 2., 2., 3., 3., 3., 3.])
注意,这里的start_dim
参数告诉flatten()
方法从哪里开始扁平化。在这个例子中,我们扁平化了整个图像。然而,我们也只扁平化通道,像这样:
> img.flatten(start_dim=1)
tensor([
[1., 1., 1., 1.],
[2., 2., 2., 2.],
[3., 3., 3., 3.]
])
总结
我们现在应该对张量的扁平化操作有了很好的理解。我们知道如何扁平化整个张量,我们知道如何扁平化张量的特定维度/轴。我们将看到这在我们构建CNN时如何被利用。在那之前,我们下一篇见!