我们将从 reshaping 操作开始。言归正传,我们开始吧。
张量的操作类型
在我们开始具体的张量运算之前,让我们先快速地看一下主要的运算种类,包括我们将要讨论的运算。我们有以下高级操作类型:
-
Reshaping operations
-
Element-wise operations
-
Reduction operations
-
Access operations
有很多单独的运算,以至于当你刚开始的时候,有时会有点吓人,但是根据相似度把类似的运算分组可以帮助你更好地学习张量运算。
展示这些类别的原因是为了让你在本系列的本节结束时能够理解所有这四个类别。
这些关于张量运算的文章的目的不仅是展示常用的具体张量运算,而且是描述运算的情况。掌握现有运算类型的知识比只知道或记住单个运算对以后来说也很有用。
请记住这一点,并在我们探索每一个类别时努力理解这些类别。现在让我们进入 reshaping 操作。
张量的reshape运算
reshape 运算可能是张量操作最重要的类型。这是因为,就像我们在介绍张量的帖子(张量解释——深度学习的数据结构)中提到的那样,张量的形状为我们提供了一些具体的东西,形成对张量的直观感受。
一、张量的类比
假设我们是神经网络程序员,因此,我们通常花时间构建神经网络。为了完成我们的工作,我们使用各种工具。
我们使用数学工具,如微积分和线性代数,计算机科学工具,如Python和PyTorch,physics and engineering tools ,如cpu和gpu,和机器学习工具,如神经网络,层,激活函数等。
我们的任务是建立能够将输入数据转换或映射到正在寻找的正确输出的神经网络。
我们用来“生产产品”的主要成分是数据,它的作用是一个将输入映射到正确输出。
数据在某种程度上是一个抽象的概念,因此当我们想实际使用数据的概念来实现某些东西时,我们使用一种称为张量的特定数据结构,该结构可以在代码中有效地实现。张量具有数学和其他方面的属性,可让我们完成工作。
张量是神经网络程序员用来“生产其产品” (智能)的主要成分。
这与面包师使用面团制作比萨饼的方式非常相似。面团是用于创建输出的输入,但是在生产披萨之前,通常需要对输入进行某种形式的重构(reshaping)。
作为神经网络程序员,我们必须对张量执行相同的操作,通常对张量进行shaping 和 reshaping 是一项常见的任务。
毕竟,我们的网络在张量上运行,这就是为什么了解张量的形状和可用的reshaping 操作非常重要的原因。
我们不是在生产披萨,而是在生产智慧!这可能有点蹩脚,但无所谓。让我们开始进行整形操作。
二、回顾一下张量的形状
假设我们有下面的张量:
> t = torch.tensor([ [1,1,1,1], [2,2,2,2], [3,3,3,3]], dtype=torch.float32)
为了确定这个张量的形状,我们先看其 行数=3,然后 列数=4,所以这个张量是一个3×4的2阶张量。记住,阶是一个常用的词它表示张量中维数。
在PyTorch中,我们有两种方法来获得形状:
> t.size()torch.Size([3, 4])
> t.shapetorch.Size([3, 4])
在PyTorch中,一个张量的 size 和 shape 意味着同样的事情。
通常,在我们知道一个张量的形状之后,我们可以推导出一些东西。首先,我们可以推导出张量的阶。一个张量的阶等于这个张量的形状的长度。
> len(t.shape)2
我们还可以推导出张量中包含的元素的个数。一个张量中元素的数量(在我们的例子中是12个)等于形状的分量值的乘积。
> torch.tensor(t.shape).prod()tensor(12)
在PyTorch中,有一个专门的函数:
> t.numel()12
一个张量中包含的元素数量对于 reshaping 是很重要的,因为 reshaping 必须考虑到当前元素的总数。reshaping 改变了张量的形状,但没有改变底层的数据。我们的张量有12个元素,所以任何 reshaping 都必须恰好包含12个元素。
三、在PyTorch中对张量进行reshaping
现在让我们看看在不改变阶的情况下这个张量t可以被 reshaping 的所有方式:
> t.reshape([1,12])tensor([[1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.]])
> t.reshape([2,6])tensor([[1., 1., 1., 1., 2., 2.], [2., 2., 3., 3., 3., 3.]])
> t.reshape([3,4])tensor([[1., 1., 1., 1.], [2., 2., 2., 2.], [3., 3., 3., 3.]])
> t.reshape([4,3])tensor([[1., 1., 1.], [1., 2., 2.], [2., 2., 3.], [3., 3., 3.]])
> t.reshape(6,2)tensor([[1., 1.], [1., 1.], [2., 2.], [2., 2.], [3., 3.], [3., 3.]])
> t.reshape(12,1)tensor([[1.], [1.], [1.], [1.], [2.], [2.], [2.], [2.], [3.], [3.], [3.], [3.]])
使用reshape() 函数,我们可以指定reshape后 行x列 形状。注意所有的形状都必须考虑到张量中元素的数量。在我们的例子中,这是:
rows * columns = 12 elements
当我们处理一个二阶张量时,我们可以用直观的词 行和列。然而,我们可能无法在高维空间中使用行和列这种描述,但对于高维的张量来说,其基本逻辑是相同的。例如:
> t.reshape(2,2,3)tensor([ [ [1., 1., 1.], [1., 2., 2.] ],
[ [2., 2., 3.], [3., 3., 3.] ]])
在本例中,我们将阶增加到3,因此我们没有了行和列的概念。然而,形状分量(2,2,3)的乘积仍然必须等于原始张量中的元素个数(12)。
请注意,PyTorch还有另一个函数view() ,它的作用与reshape() 函数相同,但是不要让这些名称影响您的使用。无论我们使用哪种深度学习框架,这些概念都是相同的。
通过Squeezing 和Unsqueezing 改变形状
下一种改变张量形状的方法是 squeezing 和 unsqueezing
-
squeezing(压缩)一个张量可以去掉长度为1的维度或轴。
-
Unsqueezing(解压缩)一个张量会增加一个长度为1的维数。
这些函数允许我们扩展或缩小张量的阶(维数)。让我们看看它是如何运作的。
> 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])
> 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])
> 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])
注意当我们squeeze 和unsqueeze张量时,形状是如何变化的。
让我们来看看通过建立一个 flatten 函数来压缩一个张量的一个常见用例。
一、Flatten A Tensor
对一个张量进行flatten(扁平化)操作可以reshape这个张量,使其形状等于张量中包含的元素的数目。这就和一维数组的元素一样。
Flattening a tensor means to remove all of the dimensions except for one.
让我们创建一个名为flatten()的Python函数:
def flatten(t): t = t.reshape(1, -1) t = t.squeeze() return t
flatten()函数接受一个张量 t 作为参数。
由于参数 t 可以是任何张量,我们将 -1作为第二个参数传递给reshape() 函数。在PyTorch中,-1表示reshape()函数根据张量中包含的元素数量计算出该值。请记住,其形状必须等于形状分量的乘积。这就是PyTorch如何在给定第一个参数为1的情况下计算出应该的值。
因为我们的张量 t 有12个元素,所以reshape() 函数能够计算出第二个轴的长度是12。
squeezing 操作之后,删除第一个轴(axis-0),我们就得到了想要的结果,长度为12的一维数组。
下面是一个例子:
> t = torch.ones(4, 3)> ttensor([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.]])
> flatten(t)tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
在以后的文章中,当我们开始构建卷积神经网络时,我们将看到flatten()函数的使用。我们将看到,当把一个输出张量从卷积层传递到线性层时,需要进行 flatten 操作。
在这些例子中,我们已经使整个张量 flatten(展平)了,然而,可以使一个张量的特定部分flatten。例如,假设我们有一个CNN的形状张量[2,1,28,28]。这意味着我们有一批数量为2,其高度和宽度尺寸分别为28 x 28的灰度图像。
在这里,我们可以具体地将这两幅图像 flatten。得到如下形状:[2,1,784]。我们也可以squeeze通道轴,得到如下形状:[2,784]。
二、Concatenating Tensors
我们使用cat() 函数组合张量,得到的张量的形状将取决于两个输入张量的形状。
假设我们有两个张量:
> t1 = torch.tensor([ [1,2], [3,4]])> t2 = torch.tensor([ [5,6], [7,8]])
我们可以将 t1 和 t2 按行(axis-0)组合如下:
> torch.cat((t1, t2), dim=0)tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
我们可以将 t1 和 t2 按列(axis-0)组合如下:
> torch.cat((t1, t2), dim=1)tensor([[1, 2, 5, 6], [3, 4, 7, 8]])
当我们连接张量时,我们增加了结果张量中包含的元素的数量。这将导致形状内的组件值(轴的长度)进行调整,以适应额外的元素。
> torch.cat((t1, t2), dim=0).shapetorch.Size([4, 2])
> torch.cat((t1, t2), dim=1).shapetorch.Size([2, 4])
总结:
我们现在应该很好地理解了reshape 张量的意义。任何时候我们改变一个张量的形状,我们都被认为是在reshapeing 这个张量。
记住这个类比。面包师和面团打交道,神经网络程序员和张量打交道。尽管reshape 的概念是一样的,但我们不是在创造烘焙食品,而是在创造智慧。