【问题标题】:Python how to add multiple arrays with different length into onePython如何将多个不同长度的数组添加到一个数组中
【发布时间】:2019-03-29 06:33:10
【问题描述】:

我正在开发一个程序,该程序需要将音频数组与给定的起始索引混合在一起。例如

signal1 = np.array([1,2,3,4])
signal2 = np.array([5,5,5])
signal3 = np.array([7,7,7,7])
sig = np.array([signal1,signal2,signal3])
onset(0, 2, 8)
result = mixing_function(sig,onset)

根据起始,signal2 将从索引 2 添加到 signal1,signal3 将从索引 8 添加到混合,因此混合部分将被零填充。它应该返回:

[1,2,8,9,5,0,0,0,7,7,7,7]

我不确定为此编写代码的有效方法是什么。现在,我创建了一个最大长度为 maxlen 的零数组。然后我将 sig 中的每个元素添加到结果的相应索引范围:

def mixing_function(sig,onset):
    maxlen = np.max([o + len(s) for o, s in zip(onset, sig)])
    result =  np.zeros(maxlen)
    for i in range(len(onset)):
        result[onset[i]:onset[i] + len(sig[i])] += sig[i] 
    return result

但是,这可能会很慢,尤其是当有许多信号混合在一起时,它们都具有不同的起始点。请告知是否有更有效的方法。

非常感谢

J


【问题讨论】:

  • 我看不出有什么明显的方法可以加快速度。只是你可以numba-编译它。并且不要将sig 中的数组列表包装到numpy 数组中。只需将其保留为数组列表即可。
  • "addition" 在数组的上下文中是不明确的。例如,它可能意味着附加。在这种情况下,您似乎是指逐元素添加。另外,正常的英语是“add A and B”或“add A to B”,而不是“B add A”。
  • 定期出现用零(或其他东西)填充。在Convert Python sequence to NumPy array, filling missing values 中有很好的答案,包括mask 的干净版本,还有一个使用itertools.zip_longest 的答案。

标签: python numpy


【解决方案1】:

以下是针对该问题的不同解决方案的一些统计数据。我可以通过对实现进行矢量化以获得 maxlen 来提高性能,但除此之外,我认为您将不得不尝试 cython 或尝试其他编程语言。

import numpy as np
from numba import jit
from time import time
np.random.seed(42)

def mixing_function(sig, onset):
    maxlen = np.max([o + len(s) for o, s in zip(onset, sig)])
    result =  np.zeros(maxlen)
    for i in range(len(onset)):
        result[onset[i]:onset[i] + len(sig[i])] += sig[i] 
    return result

def mix(sig, onset):
    siglengths = np.vectorize(len)(sig)
    maxlen = max(onset + siglengths)
    result = np.zeros(maxlen)
    for i in range(len(sig)):
        result[onset[i]: onset[i]+siglengths[i]] += sig[i]
    return result

@jit(nopython=True)
def mixnumba(sig, onset):
    # maxlen = np.max([onset[i] + len(sig[i]) for i in range(len(sig))])
    maxlen = -1
    for i in range(len(sig)):
        maxlen = max(maxlen, sig[i].size + onset[i])
    result = np.zeros(maxlen)
    for i in range(len(sig)):
        result[onset[i]: onset[i] + sig[i].size] += sig[i]
    return result

def signal_adder_with_onset(data, onset):
    data = np.array(data)
    # Get lengths of each row of data
    lens = np.array([len(i) for i in data])
    #adjust with offset for max possible lengths
    max_size = lens + onset
    # Mask of valid places in each row
    mask = ((np.arange(max_size.max()) >= onset.reshape(-1, 1)) 
            &  (np.arange(max_size.max()) < (lens + onset).reshape(-1, 1)))

    # Setup output array and put elements from data into masked positions
    out = np.zeros(mask.shape, dtype=data.dtype) #could perhaps change dtype here
    out[mask] = np.concatenate(data)
    return out.sum(axis=0)

sigbig = [np.random.randn(np.random.randint(1000, 10000)) for _ in range(10000)]
onsetbig = np.random.randint(0, 10000, size=10000)
sigrepeat = np.repeat(sig, 500000).tolist()
onsetrepeat = np.repeat(onset, 500000)

assert all(mixing_function(sigbig, onsetbig) == mix(sigbig, onsetbig))
assert all(mixing_function(sigbig, onsetbig) == mixnumba(sigbig, onsetbig))
assert all(mixing_function(sigbig, onsetbig) == signal_adder_with_onset(sigbig, onsetbig))

%timeit result = mixing_function(sigbig, onsetbig)
%timeit result = mix(sigbig, onsetbig)
%timeit result = mixnumba(sigbig, onsetbig)
%timeit result = signal_adder_with_onset(sigbig, onsetbig)
# Output
114 ms ± 1.97 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
108 ms ± 2.53 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
368 ms ± 8.22 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
13.4 s ± 211 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit result = mixing_function(sigrepeat, onsetrepeat)
%timeit result = mix(sigrepeat, onsetrepeat)
%timeit result = mixnumba(sigrepeat, onsetrepeat)
%timeit result = signal_adder_with_onset(sigrepeat.tolist(), onsetrepeat)
# Output
933 ms ± 6.43 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
803 ms ± 21.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4.07 s ± 85.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
254 ms ± 11.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

TL.DR。 通过使用np.vectorize 来获得随机长度的长信号maxlen,边际性能提高(大约快10%)。请注意,对于许多小信号,@Paritosh Singh 答案的执行速度比其他信号快。

【讨论】:

    【解决方案2】:

    如果您偏移信号,然后将它们放入数据框中,NaN 将被添加到列中以使所有行的长度相同。然后你可以做df.sum()。然而,这将返回一个浮点数而不是整数。

    【讨论】:

    • 这听起来很有趣。你能给出一个偏移量的代码示例吗?
    【解决方案3】:

    尝试使用适当插入的信号并简单地执行 3 个 numpy 数组加法的长度相等的 numpy 零数组。应该会大大加快速度。

    def mixing_function(sig,onset):
        maxlen = np.max([o + len(s) for o, s in zip(onset, sig)])
        sig1 = np.zeros(maxlen)
        sig2 = np.zeros(maxlen)
        sig3 = np.zeros(maxlen)
        sig1[onset[0]:onset[0] + len(sig[0])] = sig[0]
        sig2[onset[1]:onset[1] + len(sig[1])] = sig[1]
        sig3[onset[2]:onset[2] + len(sig[2])] = sig[2]
        result = sig1+sig2+sig3
        print(sig1)
        print(sig2)
        print(sig3)
        print(result)
    

    【讨论】:

    • 上面的代码只是一个例子,实际上,sig 可能包含几十到几百个项目。所以仍然无法摆脱 for 循环,这将基本相同。
    • 啊。是的。可能不会很好地扩展。但是,如果 numpy 的添加不适合你,我不确定会怎样。
    【解决方案4】:

    这是一个应该可以解决问题的尝试。

    def signal_adder_with_onset(data, onset):
        # Get lengths of each row of data
        lens = np.array([len(i) for i in data])
        #adjust with offset for max possible lengths
        max_size = lens + onset
        # Mask of valid places in each row
        mask = ((np.arange(max_size.max()) >= onset.reshape(-1, 1)) 
                &  (np.arange(max_size.max()) < (lens + onset).reshape(-1, 1)))
    
        # Setup output array and put elements from data into masked positions
        out = np.zeros(mask.shape, dtype=data.dtype) #could perhaps change dtype here
        out[mask] = np.concatenate(data)
        return out.sum(axis=0)
    
    import numpy as np
    signal1 = np.array([1,2,3,4])
    signal2 = np.array([5,5,5])
    signal3 = np.array([7,7,7,7])
    sig = np.array([signal1,signal2,signal3])
    onset = np.array((0, 2, 8))
    result = signal_adder_with_onset(sig, onset)
    print(result)
    #[1 2 8 9 5 0 0 0 7 7 7 7]
    

    编辑:矢量化操作只在数据量大时启动,在数据量较少时速度较慢。

    添加用于比较

    import time
    
    def signal_adder_with_onset(data, onset):
        # Get lengths of each row of data
        lens = np.array([len(i) for i in data])
        #adjust with offset for max possible lengths
        max_size = lens + onset
        # Mask of valid places in each row
        mask = ((np.arange(max_size.max()) >= onset.reshape(-1, 1)) 
                &  (np.arange(max_size.max()) < (lens + onset).reshape(-1, 1)))
    
        # Setup output array and put elements from data into masked positions
        out = np.zeros(mask.shape, dtype=data.dtype) #could perhaps change dtype here
        out[mask] = np.concatenate(data)
        return out.sum(axis=0)
    
    def mixing_function(sig,onset):
        maxlen = np.max([o + len(s) for o, s in zip(onset, sig)])
        result =  np.zeros(maxlen)
        for i in range(len(onset)):
            result[onset[i]:onset[i] + len(sig[i])] += sig[i] 
        return result
    
    import numpy as np
    signal1 = np.array([1,2,3,4])
    signal2 = np.array([5,5,5])
    signal3 = np.array([7,7,7,7])
    sig = np.array([signal1,signal2,signal3])
    sig = np.repeat(sig, 1000000)
    onset = np.array((0, 2, 8))
    onset = np.repeat(onset, 1000000)
    start1 = time.time()
    result = signal_adder_with_onset(sig, onset)
    end1 = time.time()
    start2 = time.time()
    result2 = mixing_function(sig,onset)
    end2 = time.time()
    print(f"Original function: {end2 - start2} \n Vectorized function: {end1 - start1}")
    print(result)
    #Output:
    Original function: 9.28258752822876 
     Vectorized function: 2.5798118114471436
    [1000000 2000000 8000000 9000000 5000000 0 0 0 7000000 7000000 7000000
     7000000]
    

    【讨论】:

    • 这段代码实际上比操作中已经提出的代码要慢得多。
    • 其实用这种方法恐怕要慢 5 倍左右。
    • 它成功了,但它真的更快吗?我检查了一下,对我来说这种方法工作得更慢。
    • 嗯,使用不同的数据集我得到了非常不同的结果:sig = np.array([np.random.randn(np.random.randint(1000, 10000)) for _ in range(10000)])onset = np.random.randint(0, 10000, size=10000) 给出结果:Original function: 0.156998872756958 Vectorized function: 14.857199907302856 我认为不同长度的长信号比一百万个微小的信号更现实信号,但我想只有 op 可以确定他期望什么样的数据。
    猜你喜欢
    • 2014-01-07
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-22
    • 2020-05-02
    • 1970-01-01
    相关资源
    最近更新 更多