PyTorch - 28 - PyTorch,TensorFlow和NumPy中的Stack vs Concat - 深度学习Tensor Ops

Existing Vs New Axes

张量和级联张量之间的差异可以用一个句子描述,所以这里是。

级联沿着现有轴连接一系列张量,而堆叠则沿着新轴连接一系列张量。

这就是全部!

这是堆叠和串联之间的区别。但是,这里的描述有些棘手,因此让我们看一些示例,以了解如何更好地理解这一点。我们将介绍在PyTorch,TensorFlow和NumPy中的堆叠和连接。我们开始做吧。

在大多数情况下,沿着张量的现有轴进行连接非常简单。当我们想沿着新的轴进行连接时,通常会产生混乱。为此,我们堆叠。表示堆叠的另一种方式是说我们创建一个新轴,然后在该轴上连接。

Join Method Where
Concatenate 沿现有轴连接
Stack 沿新轴堆叠

因此,请确保我们知道如何为给定张量创建新轴,然后开始堆叠和连接。

How To Add Or Insert An Axis Into A Tensor

为了演示添加轴的想法,我们将使用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]])

这为我们提供了一个3 x 1形状的张量。添加这样的轴会更改数据在张量内部的组织方式,但不会更改数据本身。基本上,我们只是在重塑张量。通过检查每个形状,我们可以看到这一点。

> 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])

现在,回想一下级联堆叠的问题,当我们进行合并时,我们将沿着现有轴连接一系列张量。这意味着我们正在扩展现有轴的长度。

当我们堆叠时,我们正在创建一个以前不存在的新轴,并且该轴遍历了序列中的所有张量,然后沿着这个新序列进行合并。

让我们看看如何在PyTorch中完成此操作。

Stack Vs Cat In 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。

现在,让我们沿着将要插入的新轴堆叠这些张量。我们将在第一个索引处插入一个轴。请注意,此插入将由stack函数在后台隐式发生。

> 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]])

在这种情况下,我们可以看到我们得到的结果与通过堆叠得到的结果相同。但是,对堆栈的调用更加简洁,因为新的轴插入是由堆栈功能处理的。

串联沿着现有轴发生。

请注意,由于当前不存在第二个轴,因此无法沿着第二个轴合并此张量序列,因此在这种情况下,堆叠是我们唯一的选择。

让我们尝试沿第二个轴堆叠。

> 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]])

要了解此结果,请回想一下在张量末端插入新轴时的外观。现在,我们只需要对所有张量执行此操作,就可以沿着第二个轴对它们进行分类。检查未压缩的输出可以帮助使这一点变得可靠。

> 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),而是指定一个轴。这两个意思相同。

> 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, 0)
        ,tf.expand_dims(t2, 0)
        ,tf.expand_dims(t3, 0)
    )    
    ,axis=0
)
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, 1)
        ,tf.expand_dims(t2, 1)
        ,tf.expand_dims(t3, 1)
    )
    ,axis=1
)
tf.Tensor: id=26, shape=(3, 3), dtype=int32, numpy=
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])

观察到这些结果与PyTorch一致。

Stack Vs Concatenate 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使用完整单词串联作为函数名称。

功能名称
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 Or Concat: Real-Life Examples

这是我们在现实生活中可能遇到的三个具体示例。让我们决定何时需要堆叠以及何时需要合并。

Joining Images Into A Single 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])

Joining Batches Into A Single Batch

现在,假设我们具有与以前相同的三个图像,但是这次图像已经具有该批次的尺寸。这实际上意味着我们有三批尺寸为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])

我们来看第三点。这个很难。或至少更高级。您会明白为什么。

Joining Images With An Existing 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])

希望对您有所帮助,您现在就可以掌握。

猜你喜欢

转载自blog.csdn.net/weixin_48367136/article/details/112557657