点击上方“AI算法与图像处理”,选择加"星标"或“置顶”
重磅干货,第一时间送达
文 |AI_study
原标题:Stack Vs Concat In PyTorch, TensorFlow & NumPy - Deep Learning Tensor Ops
Existing Vs New Axes
堆叠 和 串联张量之间的差异可以用一个句子描述,所以这里是。
Concatenating joins a sequence of tensors along an existing axis, and stacking joins a sequence of tensors along a new axis.
级联沿着现有轴连接一系列张量,而堆栈则沿着新轴连接一系列张量。
这就是全部!
这是堆叠和串联之间的区别。但是,这里的描述有些棘手,因此让我们看一些示例,以了解如何更好地理解这一点。我们将研究在PyTorch,TensorFlow和NumPy中的堆栈和串联。我们开始做吧。
在大多数情况下,沿着张量的现有轴进行连接非常简单。当我们想沿着新的轴进行连接时,通常会产生混乱。为此,我们堆叠。表示堆叠的另一种方式是,我们创建一个新轴,然后在该轴上连接。
Join Method | Where |
---|---|
Concatenate | Along an existing axis |
Stack | Along a new axis |
因此,请确保我们知道如何为给定的张量创建新轴,然后开始堆叠和连接。
如何在张量中添加或插入轴
为了演示添加轴的想法,我们将使用PyTorch。
import torch
t1 = torch.tensor([1,1,1])
在这里,我们要导入PyTorch并创建一个简单的张量,其单轴长度为3。现在,要在PyTorch中向张量添加轴,我们使用 unsqueeze() 函数。请注意,这与压缩相反。
> t1.unsqueeze(dim=0)
tensor([[1, 1, 1]])
在这里,我们正在添加一个轴,也就是这个张量的索引零处的尺寸。这给我们一个形状为1 x 3的张量。当我们说张量的索引为零时,是指张量形状的第一个索引。
现在,我们还可以在该张量的第二个索引处添加一个轴。
> t1.unsqueeze(dim=1)
tensor([[1],
[1],
[1]])
这就得到了一个形状为 3x1 的张量。添加这样的轴会改变数据在张量内部的组织方式,但不会改变数据本身。基本上,我们只是在重构这个张量。我们可以通过检查每一个的形状看出。
> print(t1.shape)
> print(t1.unsqueeze(dim=0).shape)
> print(t1.unsqueeze(dim=1).shape)
torch.Size([3])
torch.Size([1, 3])
torch.Size([3, 1])
现在,回想一下如何连接 verses 堆栈,当我们进行连接时,我们将沿着现有轴连接一系列张量。这意味着我们正在扩展现有轴的长度。
当我们叠加的时候,我们创建了一个新的轴这是以前不存在的这发生在我们序列中的所有张量上,然后我们沿着这个新的序列。
让我们看看如何在PyTorch中实现这一点。
Stack Vs Cat 在PyTorch
使用PyTorch,我们用于这些操作的两个函数是stack和cat。我们来创建一个张量序列。
import torch
t1 = torch.tensor([1,1,1])
t2 = torch.tensor([2,2,2])
t3 = torch.tensor([3,3,3])
现在,让我们将它们彼此串联在一起。请注意,每个张量都有一个轴。这意味着cat函数的结果也将具有单个轴。这是因为当我们连接时,我们沿现有的轴进行连接。请注意,在此示例中,唯一存在的轴是第一个轴。
> torch.cat(
(t1,t2,t3)
,dim=0
)
tensor([1, 1, 1, 2, 2, 2, 3, 3, 3])
好了,所以我们取了三个单轴张量,每个张量的轴长为3,现在我们有了一个单张量,轴长为9。
现在,让我们沿着将要插入的新轴堆叠这些张量。我们将在第一个索引处插入一个轴。请注意,此插入将通过堆栈函数在后台隐式发生。
> torch.stack(
(t1,t2,t3)
,dim=0
)
tensor([[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
这为我们提供了一个新的张量,其形状为3 x3。请注意,这三个张量是如何沿着该张量的第一个轴连接的。请注意,我们还可以显式插入新轴,然后直接执行串联。
看到这句话是真的。让我们张开所有的张量,向它们添加一个长度为1的新轴,然后沿着第一个轴移动。
> torch.cat(
(
t1.unsqueeze(0)
,t2.unsqueeze(0)
,t3.unsqueeze(0)
)
,dim=0
)
tensor([[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
在这种情况下,我们可以看到我们得到的结果与通过堆叠得到的结果相同。但是,对堆栈的调用更加简洁,因为新的轴插入是由堆栈功能处理的。
Concatenation happens along an existing axis.
请注意,由于当前不存在第二个轴,因此无法沿着第二个轴合并此张量序列,因此在这种情况下,堆叠是我们唯一的选择。
让我们尝试沿第二个轴堆叠。
> torch.stack(
(t1,t2,t3)
,dim=1
)
tensor([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])
好吧,我们相对于第二个轴进行堆叠,这就是结果。
> torch.cat(
(
t1.unsqueeze(1)
,t2.unsqueeze(1)
,t3.unsqueeze(1)
)
,dim=1
)
tensor([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])
要了解此结果,请回想一下在张量末端插入新轴时的外观。现在,我们只需要对所有张量执行此操作,就可以沿着第二个轴对它们进行分类。检查unsqueeze的输出可以帮助使这一点变得可靠。
> t1.unsqueeze(1)
tensor([[1],
[1],
[1]])
> t2.unsqueeze(1)
tensor([[2],
[2],
[2]])
> t3.unsqueeze(1)
tensor([[3],
[3],
[3]])
Stack Vs Concat In TensorFlow
现在让我们使用TensorFlow
import tensorflow as tf
t1 = tf.constant([1,1,1])
t2 = tf.constant([2,2,2])
t3 = tf.constant([3,3,3])
在这里,我们导入了TensorFlow并使用tf.constant()函数创建了三个张量。现在,让我们将这些张量彼此串联。要在TensorFlow中做到这一点,我们使用tf.concat()函数,而不是指定一个dim(如PyTorch),而是指定一个axis。这两个意思相同。
> tf.concat(
(t1,t2,t3)
,axis=0
)
tf.Tensor: id=4, shape=(9,), dtype=int32, numpy=array([1, 1, 1, 2, 2, 2, 3, 3, 3])
在这里,结果与我们使用PyTorch时的结果相同。好吧,让我们现在堆叠它们。
> tf.stack(
(t1,t2,t3)
,axis=0
)
tf.Tensor: id=6, shape=(3, 3), dtype=int32, numpy=
array([[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
同样,结果与PyTorch结果相同。现在,我们将在手动插入新尺寸后将它们连接起来。
> tf.concat(
(
tf.expand_dims(t1, 1)
,tf.expand_dims(t2, 1)
,tf.expand_dims(t3, 1)
)
,axis=1
)
tf.Tensor: id=15, shape=(3, 3), dtype=int32, numpy=
array([[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
与PyTorch调用相对的TensorFlow代码的区别在于,cat()函数现在称为concat()。此外,我们使用expand_dims()函数添加与unsqueeze()函数相对应的轴。
Unsqueezing and expanding dims mean the same thing.
好吧,让我们相对于第二个轴进行堆叠。
> tf.stack(
(t1,t2,t3)
,axis=1
)
tf.Tensor: id=17, shape=(3, 3), dtype=int32, numpy=
array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])
并以手动轴的方式插入。
> tf.concat(
(
tf.expand_dims(t1, 0)
,tf.expand_dims(t2, 0)
,tf.expand_dims(t3, 0)
)
,axis=0
)
tf.Tensor: id=26, shape=(3, 3), dtype=int32, numpy=
array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])
观察到这些结果与PyTorch一致。
Stack Vs Concat In NumPy
让我们现在使用NumPy
import numpy as np
t1 = np.array([1,1,1])
t2 = np.array([2,2,2])
t3 = np.array([3,3,3])
在这里,我们创建了三个张量。现在,让我们将它们彼此串联在一起。
> np.concatenate(
(t1,t2,t3)
,axis=0
)
array([1, 1, 1, 2, 2, 2, 3, 3, 3])
好吧,这给了我们我们期望的结果。请注意,与TensorFlow一样,NumPy也使用了轴参数名称,但是在这里,我们还看到了另一个命名变体。NumPy使用完整单词concatenate
作为函数名称。
Library | Function Name |
---|---|
PyTorch | cat() |
TensorFlow | concat() |
NumPy | concatenate() |
好的,现在开始堆叠
> np.stack(
(t1,t2,t3)
,axis=0
)
array([[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
正如预期的那样,结果是2阶张量,其形状为3 x3。现在,我们将尝试手动方式。
> np.concatenate(
(
np.expand_dims(t1, 0)
,np.expand_dims(t2, 0)
,np.expand_dims(t3, 0)
)
,axis=0
)
array([[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
请注意,结果与我们使用stack()函数时的结果相同。此外,请注意,NumPy还使用术语expand dims作为函数名称。
现在,我们将使用第二个轴进行堆叠以完成此操作。
> np.stack(
(t1,t2,t3)
,axis=1
)
array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])
并且,具有手动插入功能。
> np.concatenate(
(
np.expand_dims(t1, 1)
,np.expand_dims(t2, 1)
,np.expand_dims(t3, 1)
)
,axis=1
)
array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])
Stack或Concat:真实示例
这是我们在现实生活中可能遇到的三个具体示例。让我们决定何时需要堆叠以及何时需要合并。
将图像合并为一个batch
假设我们有三个单独的图像作为张量。每个图像张量具有三个维度,即通道轴,高度轴,宽度轴。请注意,每个张量彼此独立。现在,假设我们的任务是将这些张量连接在一起以形成三个图像的单批张量。
我们是串联还是堆叠?
好吧,请注意,在此示例中,仅存在三个维度,对于一个批次,我们需要四个维度。这意味着答案是沿新轴堆叠张量。该新轴将成为batch 轴。通过为批次添加一个张量,这将为我们提供四个尺寸的张量。
请注意,如果我们沿任何现有尺寸将这三个尺寸结合在一起,则会弄乱通道,高度或宽度。我们不想这样弄乱我们的数据。
import torch
t1 = torch.zeros(3,28,28)
t2 = torch.zeros(3,28,28)
t3 = torch.zeros(3,28,28)
torch.stack(
(t1,t2,t3)
,dim=0
).shape
## output ##
torch.Size([3, 3, 28, 28])
将批次合并为单个批次
现在,假设我们拥有与以前相同的三个图像,但是这次图像已经具有该批次的尺寸。这实际上意味着我们有三批尺寸为1的批次。假设获得单批三个图像是我们的任务。
我们合并还是堆叠?
好吧,请注意我们可以如何结合现有的维度。这意味着我们在批处理维度上将它们合并在一起。在这种情况下,无需堆叠。
这是一个代码示例:
请注意,下面的示例将具有不同的值,因为这两个示例是在不同的时间创建的。
import torch
t1 = torch.zeros(1,3,28,28)
t2 = torch.zeros(1,3,28,28)
t3 = torch.zeros(1,3,28,28)
torch.cat(
(t1,t2,t3)
,dim=0
).shape
## output ##
torch.Size([3, 3, 28, 28])
我们来看第三点。这个很难。或至少更高级。您会明白为什么。
结合现有批次的图像
假设我们有相同的三个单独的图像张量。只是这次,我们已经有了一个batch张量。假设我们的任务是将这三个单独的图像与批次结合在一起。
我们是串联还是堆叠?
好吧,请注意批处理轴中的batch 轴已经存在。但是,对于图像,不存在batch轴。这意味着这些都不起作用。要与stack或cat连接,我们需要张量具有匹配的形状。那么,我们被卡住了吗?这不可能吗?
确实有可能。这实际上是非常常见的任务。答案是先堆叠然后再连接。
我们首先堆叠相对于第一维的三个图像张量。这将创建长度为3的新批次尺寸。然后,我们可以用批处理张量连接这个新的张量。
让我们在代码中看一个例子:
import torch
batch = torch.zeros(3,3,28,28)
t1 = torch.zeros(3,28,28)
t2 = torch.zeros(3,28,28)
t3 = torch.zeros(3,28,28)
torch.cat(
(
batch
,torch.stack(
(t1,t2,t3)
,dim=0
)
)
,dim=0
).shape
## output ##
torch.Size([6, 3, 28, 28])
同样的方法
import torch
batch = torch.zeros(3,3,28,28)
t1 = torch.zeros(3,28,28)
t2 = torch.zeros(3,28,28)
t3 = torch.zeros(3,28,28)
torch.cat(
(
batch
,t1.unsqueeze(0)
,t2.unsqueeze(0)
,t3.unsqueeze(0)
)
,dim=0
).shape
## output ##
torch.Size([6, 3, 28, 28])
文章中内容都是经过仔细研究的,本人水平有限,翻译无法做到完美,但是真的是费了很大功夫,希望小伙伴能动动你性感的小手,分享朋友圈或点个“在看”,支持一下我 ^_^
英文原文链接是:
https://deeplizard.com/learn/video/kF2AlpykJGY
加群交流
欢迎小伙伴加群交流,目前已有交流群的方向包括:AI学习交流群,目标检测,秋招互助,资料下载等等;加群可扫描并回复感兴趣方向即可(注明:地区+学校/企业+研究方向+昵称)
谢谢你看到这里! ????