代码之家  ›  专栏  ›  技术社区  ›  Edward

np。将ND张量/数组与一维数组串联

  •  3
  • Edward  · 技术社区  · 7 年前

    我有两个阵列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。连接((b,b,b,b,b,b),轴=0)
    

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

    4 回复  |  直到 7 年前
        1
  •  4
  •   cs95 abhishek58g    7 年前

    简单广播 b 3D 然后沿第二个轴连接-

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

    这个 broadcasting 放弃 np.broadcast_to 不实际复制或制作副本,只是一个复制视图,然后在下一步中,我们进行串联,以动态复制。

    标杆管理

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

    整个进场时间:

    案例#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。广播\u到 需要的时间比 np。重复 鉴于设置广播所需的工作显然更为复杂,时间安排如下:

    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。广播\u到 在这里做得更好。

    让我们试试 (5,4,3) 形状:

    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。广播\u到 否则会是个不错的选择 np。重复 基于的版本可以很好地处理其他情况。

        2
  •  3
  •   cs95 abhishek58g    7 年前

    您可以使用 np.repeat :

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

    这样做的目的是首先重塑 b 数组以匹配的维度 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)
    

    然后将此中间结果与 -

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

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

    如果需要为不同数量的维度(而不是三维阵列)处理此问题,则需要进行一些更改(主要是如何删除 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
    

    这些都是极具竞争力的解决方案。但是,请注意,性能取决于您的实际数据,所以请确保您首先进行测试!

        3
  •  1
  •   rmilletich    7 年前

    以下是一些基于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秒

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

    输出: 最慢的跑步比最快的跑长4.12倍。这可能意味着正在缓存中间结果。 100000个回路,最好3个:每个回路10.7秒

    下面是基于原始代码的计时:

    %timeit original_func(a, b)
    

    输出: 最慢的跑步比最快的跑长4.62倍。这可能意味着正在缓存中间结果。 100000个回路,最好3个:每个回路4.69秒

    由于这个问题需要更快的方法来得出相同的结果,我会选择cásááá基于这些问题计算的解决方案。

        4
  •  0
  •   Tai    7 年前

    您还可以使用 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\u宽 将占据长轴上的索引4,并使 ab.shape 相同的 (5, 5, 3) .

    笔记 再次强调,在插入之前 b 进入 b\u宽 对于 安全地 实现您想要的正确广播。的维度 b 较小,插入时将进行广播。我们可以使用 expand_dims 实现这一目标。

    如果 a 形状良好 (3, 4, 5) ,您将需要 b\u宽 有形状 (3, 1) 如果沿轴1插入,则匹配尺寸。这可以通过以下方式实现

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

    这将是一个很好的做法 b\u宽 因为你可能 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)
    

    速度将取决于数据的大小、形状和体积。如果您担心速度问题,请在您的数据集上进行测试。