【问题标题】:np.concatenate a ND tensor/array with a 1D arraynp.concatenate ND 张量/数组与一维数组
【发布时间】:2018-01-02 19:25:21
【问题描述】:

我有两个数组 a & b

a.shape
(5, 4, 3)
array([[[ 0.        ,  0.        ,  0.        ],
        [ 0.        ,  0.        ,  0.        ],
        [ 0.        ,  0.        ,  0.        ],
        [ 0.10772717,  0.604584  ,  0.41664413]],

       [[ 0.        ,  0.        ,  0.        ],
        [ 0.        ,  0.        ,  0.        ],
        [ 0.10772717,  0.604584  ,  0.41664413],
        [ 0.95879616,  0.85575133,  0.46135877]],

       [[ 0.        ,  0.        ,  0.        ],
        [ 0.10772717,  0.604584  ,  0.41664413],
        [ 0.95879616,  0.85575133,  0.46135877],
        [ 0.70442301,  0.74126523,  0.88965603]],

       [[ 0.10772717,  0.604584  ,  0.41664413],
        [ 0.95879616,  0.85575133,  0.46135877],
        [ 0.70442301,  0.74126523,  0.88965603],
        [ 0.8039435 ,  0.62802183,  0.58885027]],

       [[ 0.95879616,  0.85575133,  0.46135877],
        [ 0.70442301,  0.74126523,  0.88965603],
        [ 0.8039435 ,  0.62802183,  0.58885027],
        [ 0.95848603,  0.72429311,  0.71461332]]])

和b

array([ 0.79212707,  0.66629398,  0.58676553], dtype=float32)
b.shape
(3,)

我要获取数组

ab.shape
(5,5,3)

我做如下 首先

b = b.reshape(1,1,3)

然后

b=np.concatenate((b, b,b, b, b), axis = 0)

ab=np.concatenate((a, b), axis = 1)
ab.shape
(5, 5, 3)

我得到了正确的结果,但不是很方便,尤其是在步骤

b=np.concatenate((b, b,b, b, b), axis = 0)

当我必须多次输入时(真实的数据集有很多维度)。有没有更快的方法来得出这个结果?

【问题讨论】:

  • np.concatenate((a, b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)), axis=1)?

标签: python arrays numpy concatenation


【解决方案1】:

以下是一些基于 cᴏʟᴅsᴘᴇᴇᴅ 和 Divakar 解决方案的简单计时:

%timeit np.concatenate((a, b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)), axis=1)

输出: 最慢的运行时间是最快的运行时间的 6.44 倍。这可能意味着正在缓存中间结果。 100000 次循环,3 次中的最佳:每个循环 3.68 µs

%timeit np.concatenate((a, np.broadcast_to(b[None,None], (a.shape[0], 1, len(b)))), axis=1)

输出: 最慢的运行时间是最快的运行时间的 4.12 倍。这可能意味着正在缓存中间结果。 100000 次循环,3 次中的最佳:每个循环 10.7 µs

现在是基于您的原始代码的时间:

%timeit original_func(a, b)

输出: 最慢的运行时间是最快的运行时间的 4.62 倍。这可能意味着正在缓存中间结果。 100000 次循环,最佳 3 次:每个循环 4.69 µs

由于问题要求更快的方法来得出相同的结果,我会根据这些问题计算寻求 cᴏʟᴅsᴘᴇᴇᴅ 的解决方案。

【讨论】:

  • 干杯,但我强烈建议对大小为 a.shape = (500, 499, 3) 的样本数据进行测试。
  • 绝对——需要扩展!
  • “这可能意味着正在缓存中间结果”是 ipython 中 timeit 函数输出的一部分。
  • @RobertMilletich 知道了!
【解决方案2】:

只需将b 广播到3D,然后沿第二个轴连接 -

b3D = np.broadcast_to(b,(a.shape[0],1,len(b)))
out = np.concatenate((a,b3D),axis=1)

带有np.broadcast_tobroadcasting 部分并不实际复制或制作副本,它只是一个复制的视图,然后在下一步中,我们进行连接以进行即时复制。

基准测试

我们正在比较 np.repeat version from @cᴏʟᴅsᴘᴇᴇᴅ's solutionnp.broadcast_to 一个 在本节中,重点是性能。基于广播的版本在第二步中进行复制和连接,可以说是一个合并命令,而np.repeat 版本在两个单独的步骤中进行复制然后连接。

整个方法的时间安排:

案例 #1:a = (500,400,300)b = (300,)

In [321]: a = np.random.rand(500,400,300)

In [322]: b = np.random.rand(300)

In [323]: %%timeit
     ...: b3D = b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)
     ...: r = np.concatenate((a, b3D), axis=1)
10 loops, best of 3: 72.1 ms per loop

In [325]: %%timeit
     ...: b3D = np.broadcast_to(b,(a.shape[0],1,len(b)))
     ...: out = np.concatenate((a,b3D),axis=1)
10 loops, best of 3: 72.5 ms per loop

对于较小的输入形状,调用np.broadcast_to 将比np.repeat 花费更长的时间,因为设置广播所需的工作显然更复杂,如下所示:

In [360]: a = np.random.rand(5,4,3)

In [361]: b = np.random.rand(3)

In [366]: %timeit np.broadcast_to(b,(a.shape[0],1,len(b)))
100000 loops, best of 3: 3.12 µs per loop

In [367]: %timeit b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)
1000000 loops, best of 3: 957 ns per loop

但是,无论输入的形状如何,广播部分都会有一个恒定的时间,即3 u-sec 部分将保持在该标记附近。对应的时间:b.reshape(1, 1, -1).repeat(a.shape[0], axis=0) 将取决于输入形状。因此,让我们更深入地研究,看看这两种方法的连接步骤是如何公平/行为的。

深入挖掘

尝试更深入地了解连接部分的消耗量:

In [353]: a = np.random.rand(500,400,300)

In [354]: b = np.random.rand(300)

In [355]: b3D = np.broadcast_to(b,(a.shape[0],1,len(b)))

In [356]: %timeit np.concatenate((a,b3D),axis=1)
10 loops, best of 3: 72 ms per loop

In [357]: b3D = b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)

In [358]: %timeit np.concatenate((a,b3D),axis=1)
10 loops, best of 3: 72 ms per loop

结论:似乎没有太大的不同。

现在,让我们尝试一个情况,b 所需的复制数量更大,b 的元素数量也非常多。

In [344]: a = np.random.rand(10000, 10, 1000)

In [345]: b = np.random.rand(1000)

In [346]: b3D = np.broadcast_to(b,(a.shape[0],1,len(b)))

In [347]: %timeit np.concatenate((a,b3D),axis=1)
10 loops, best of 3: 130 ms per loop

In [348]: b3D = b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)

In [349]: %timeit np.concatenate((a,b3D),axis=1)
10 loops, best of 3: 141 ms per loop

结论:似乎与np.broadcast_to 合并的连接+复制在这里做得更好。

让我们试试(5,4,3) shape 的原始案例:

In [360]: a = np.random.rand(5,4,3)

In [361]: b = np.random.rand(3)

In [362]: b3D = np.broadcast_to(b,(a.shape[0],1,len(b)))

In [363]: %timeit np.concatenate((a,b3D),axis=1)
1000000 loops, best of 3: 948 ns per loop

In [364]: b3D = b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)

In [365]: %timeit np.concatenate((a,b3D),axis=1)
1000000 loops, best of 3: 950 ns per loop

结论:同样,差别不大。

所以,最后的结论是,如果b 中有很多元素,并且如果a 的第一个轴也是一个大数(因为复制数就是那个),np.broadcast_to 将是一个不错的选择,否则基于np.repeat 的版本可以很好地处理其他情况。

【讨论】:

  • 即使广播更便宜,后续的串联仍然需要创建新的数组,所以也应该定时...
  • 我冒昧地自己添加了时间,因为我似乎得到了比我想要的更多的反对票 ;-)
  • @cᴏʟᴅsᴘᴇᴇᴅ 很抱歉投反对票。希望那些不受我或这篇文章的影响:)
  • 即使他们是,我也不会反对你。加油,希望下期再见!
【解决方案3】:

你可以使用np.repeat:

r = np.concatenate((a, b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)), axis=1)

这样做的目的是首先重塑您的b 数组以匹配a 的尺寸,然后根据a 的第一个轴根据需要多次重复其值:

b3D = b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)

array([[[1, 2, 3]],

       [[1, 2, 3]],

       [[1, 2, 3]],

       [[1, 2, 3]],

       [[1, 2, 3]]])

b3D.shape
(5, 1, 3)

然后将这个中间结果与a 连接起来 -

r = np.concatenate((a, b3d), axis=0)

r.shape
(5, 5, 3)

这与您当前的答案不同,主要在于值的重复不是硬编码的(即,它由重复处理)。

如果您需要处理不同数量的维度(不是 3D 数组),则需要进行一些更改(主要是如何删除 b 的硬编码重塑)。


时间

a = np.random.randn(100, 99, 100)
b = np.random.randn(100)

# Tai's answer
%timeit np.insert(a, 4, b, axis=1)
100 loops, best of 3: 3.7 ms per loop

# Divakar's answer
%%timeit 
b3D = np.broadcast_to(b,(a.shape[0],1,len(b)))
np.concatenate((a,b3D),axis=1)

100 loops, best of 3: 3.67 ms per loop

# solution in this post
%timeit np.concatenate((a, b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)), axis=1)
100 loops, best of 3: 3.62 ms per loop

这些都是极具竞争力的解决方案。但请注意,性能取决于您的实际数据,因此请务必先进行测试!

【讨论】:

  • 投反对票的,请让我知道答案是否错误。如果需要,我会删除它。
  • np.concatenate((a, b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)), axis=1) 这是我能看到的最简单的方法
  • @Edward 很抱歉这个简洁的答案。如果有帮助,请添加说明。
  • @Edward,我还添加了计时。如果您担心性能,请确保测试所有数据解决方案。编码愉快。
  • @COLDSPEED 是的,我注意到了!
【解决方案4】:

您也可以使用np.insert

b_broad = np.expand_dims(b, axis=0) # b_broad.shape = (1, 3)
ab = np.insert(a, 4, b_broad, axis=1)
""" 
Because now we are inserting along axis 1
     a'shape without axis 1 = (5, 3) 
     b_broad's shape          (1, 3)  
can be aligned and broadcast b_broad to (5, 3)
"""

在本例中,我们沿轴 1 插入,并将b_broad 放在给定索引之前,此处为 4。换句话说,b_broad 将在长轴上占据索引 4 并使ab.shape 等于(5, 5, 3)

请注意,在我们进行插入之前,我们会将b 转换为b_broad,以便安全实现您想要的正确广播。 b 的维度较小,插入时会有广播。我们可以使用expand_dims 来实现这个目标。

如果a 的形状为(3, 4, 5),如果沿轴1 插入,您将需要b_broad 的形状为(3, 1) 以匹配尺寸。这可以通过

b_broad = np.expand_dims(b, axis=1)  # shape = (3, 1)

b_broad 设置为正确的形状是一个好习惯,因为您可能有a.shape = (3, 4, 3),在这种情况下您确实需要指定广播的方式!

计时结果

来自 OP 的数据集:COLDSPEED 的答案快 3 倍。

def Divakar():  # Divakar's answer
    b3D = b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)
    r = np.concatenate((a, b3D), axis=1)
# COLDSPEED's result
%timeit np.concatenate((a, b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)), axis=1)
2.95 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# Divakar's result
%timeit Divakar()
3.03 µs ± 173 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# Mine's
%timeit np.insert(a, 4, b, axis=1)
10.1 µs ± 220 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

数据集 2(借用 COLDSPEED 的计时实验):在这种情况下无法得出任何结论,因为它们具有几乎相同的均值和标准差。

a = np.random.randn(100, 99, 100)
b = np.random.randn(100)

# COLDSPEED's result
%timeit np.concatenate((a, b.reshape(1, 1, -1).repeat(a.shape[0], axis=0)), axis=1) 
2.37 ms ± 194 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# Divakar's
%timeit Divakar()
2.31 ms ± 249 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# Mine's
%timeit np.insert(a, 99, b, axis=1) 
2.34 ms ± 154 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

速度取决于数据的大小、形状和数量。如果您关心速度,请在您的数据集上进行测试。

【讨论】:

  • 这个选项看起来很简单。但我担心效率。有趣的是,对于 OP 的样本数据,这是最慢的。但可能会因不同大小的输入而改变。
  • @cᴏʟᴅsᴘᴇᴇᴅ 这个问题不是关于效率,而是关于可读性。我认为这取决于数据的大小。该方法在我的计算机上进行了合理的快速测试:每个循环 10.1 µs ± 220 ns(平均值 ± 标准偏差。7 次运行,每次 100000 次循环)您的方法比我的计算机上的方法快大约 3 倍。但是是的...这取决于数据的大小...
猜你喜欢
  • 1970-01-01
  • 2016-05-16
  • 1970-01-01
  • 2021-02-26
  • 2021-06-03
  • 2015-02-01
  • 1970-01-01
  • 2020-03-17
  • 1970-01-01
相关资源
最近更新 更多