将Matlab转换为Python-加速循环

时间:2019-12-28 11:31:31

标签: python matlab loops

我一直在将一些代码从Matlab转换为Python,用于分析实验室中的数据。我们有两个时间戳列表,我们想用一个来预示另一个时间戳:对于第一个列表中的每个元素,我们在第二个列表中寻找具有精确时间间隔的时间戳。如果有的话,我们将它们放在单独的列表中。

这是我使用的带有随机数据的Matlab代码的可运行示例。这可能是非常粗糙的,因为我不熟悉Matlab。在下面的 Ctrigger 是触发列表,而 Csignal 是我们要预示的信号列表。对于 Ctrigger 的每个元素,我们查看 Csignal 中是否存在位于以 offset 为中心且宽度为 gate的窗口内的元素。所选事件将放置在 Hsignal 中。

% Matlab code

Ctrigger = linspace(0, 3000000, (3000000-1)/3);
length_t = length(Ctrigger);

Bsignal = linspace(0, 3000000, (3000000-1)/10);
length_s = length(Bsignal);
noise = reshape(20*rand(length_s,1)-10,[1,length_s]);
Csignal = Bsignal + noise;

offset = 3;
gate = 1;

Hsignal=zeros(length_s,1);
marker = 1;

tic
for j=1:length_t-1
    m = marker;
    tstart=Ctrigger(j)+offset-gate/2;
    tstop=Ctrigger(j)+offset+gate/2;
    while(m <= length_s-1)
        if(Csignal(m)<tstart)
            marker=m;
            m=m+1;
        end
        if(Csignal(m)>=tstart && Csignal(m)<=tstop)
            Hsignal(m)=Csignal(m);
            m = m+1;
        end
        if(Csignal(m)>tstop)
            break;
        end
    end
end

toc

Hsignal=Hsignal(Hsignal~=0);
Hsignal = unique(Hsignal);

大约有90'000个事件被选择放置在 Hsignal 中,而Matlab大约需要0.05秒来运行它。我已经介绍了 marker 计数器,因为两个列表 Csignal Ctrigger 区域已经按时排序了。 marker 设置在一个先驱窗口的开始处:当我移至下一个触发器时,我不会再次查看所有 Csignal ,而只会从该窗口的开始。为避免重复计算,我在最后删除了重复项。

如果您想对代码有所了解,下面是输入和输出的简化版本:

Ctrigger = [1, 10, 11, 20, 30, 40, 50, 60]
Csignal = [4, 11, 13, 17, 25, 34, 41, 42, 50, 57, 65]
print(Hsignal)
# [4, 11, 13, 41, 42]

现在,我已经从Matlab复制了此代码,只是对其进行了少许调整以适合python(再次,可能非常粗糙):

# Python code

Ctrigger = range(0, 3000000, 3)
length_t = len(Ctrigger)

Bsignal = range(0, 3000000, 10)
length_s = len(Bsignal)
noise = 1e-05*np.asarray(random.sample(range(-1000000,1000000), int(length_s)))
Csignal = list(np.sort(np.asarray(Bsignal) + noise))

offset = 3
gate = 1

Hsignal = list()
marker = 0

length_t = len(Ctrigger)
length_s = len(Csignal)

start = time.time()

for j in range(length_t):
    m = marker
    t_start = Ctrigger[j] + offset - gate/2
    t_stop = Ctrigger[j] + offset + gate/2   
    while m < length_s:   
        if (Csignal[m] < t_start):
            marker = m
            m = m + 1
        elif (Csignal[m] >= t_start and Csignal[m] <= t_stop):
            Hsignal.append(Csignal[m])
            m = m + 1
        elif (Csignal[m] > t_stop):
            break

end = time.time()
Hsignal = np.sort(np.asarray(list(set(Hsignal))))

print(end-start)

类似地,在 Hsignal 中放置了大约90'000个元素。关键问题是python大约需要1.7秒来运行它!我什至尝试了这种替代方法,它消除了一些循环(这里我仍然使用数组,因为我必须将元素添加到整个列表中):

start = time.time()
result = list()
for event in Ctrigger:
    c = Csignal - event - offset
    d = Csignal[abs(c) <= gate/2]
    result.append(list(d))


flat = [item for sublist in result for item in sublist]
flat = np.sort(np.asarray(list(set(flat))))

end = time.time()
print(end-start)

但更糟的是,将近10分钟。

我真的不明白问题出在哪里。对于我的应用程序, Ctrigger 的长度为100e06,而 Csignal 的长度为20e06。在matlab中,相同的代码需要1.06秒,而在python中则需要10分钟以上。似乎同时删除循环并加快处理速度并非易事。

2 个答案:

答案 0 :(得分:3)

可能会使您的算法变慢的原因是在{p>中使用np.append

Hsignal = np.append(Hsignal, Csignal[m])

您应该使用列表,而不是NumPy数组:

Ctrigger = [1, 10, 11, 20, 30, 40, 50, 60]
Csignal = [4, 11, 13, 17, 25, 34, 41, 42, 50, 57, 65]

offset = 2
gate = 2

Hsignal = []
marker = 0

for j in range(len(Ctrigger)):
    m = marker
    t_start = Ctrigger[j] + offset - gate/2
    t_stop = Ctrigger[j] + offset + gate/2   
    while m < len(Csignal):   
        if Csignal[m] < t_start:
            marker = m
            m = m + 1
        elif Csignal[m] <= t_stop:
            Hsignal.append(Csignal[m])
            m = m + 1
        else:
            break

Hsignal = sorted(set(Hsignal))

列表建立后,您可以将其转换为数组:

Hsignal = np.array(Hsignal)

答案 1 :(得分:3)

如何将运行时间降至6ms

您已经看到Python循环非常慢。默认情况下,没有像Matlab一样的jit-Compiler可以加快循环速度。因此,您有以下可能性:

  • 如有可能,请在Numpy中向量化代码。
  • 使用Cython编译函数
  • 使用Numba编译函数

在下面的示例中,我使用Numba,因为在这种情况下使用起来非常简单。

示例

import numpy as np
import numba as nb

@nb.njit()
def main_nb(Ctrigger, Csignal, offset, gate):
    Hsignal = np.zeros(Ctrigger.shape[0])

    marker = 1
    for j in range(Ctrigger.shape[0]):
        m = marker
        t_star = Ctrigger[j] + offset - gate/2
        t_sto = Ctrigger[j] + offset + gate/2   
        while m < Csignal.shape[0]:   
            if (Csignal[m] < t_star):
                marker = m
                m = m + 1
            elif (Csignal[m] >= t_star and Csignal[m] <= t_sto):
                Hsignal[m] = Csignal[m]
                m = m + 1
            elif (Csignal[m] > t_sto):
                break
    return Hsignal

还请注意避免使用列表。像在Matlab中一样使用简单的数组。

时间

import time

#Use simple numpy arrays if possible, not lists
Ctrigger = np.arange(0, 3000000, 3)
length_t = Ctrigger.shape[0]

Bsignal = np.arange(0, 3000000, 10)
noise = 1e-05*np.random.rand(Bsignal.shape[0])
Csignal = np.sort(np.asarray(Bsignal) + noise)

offset = 3
gate = 1

start = time.time()
Hsignal=main(Ctrigger, Csignal, offset, gate)
print("Pure Python takes:" +str(time.time()-start))
#Pure Python takes:6.049151659011841

#First call takes longer (compilation overhead)
#The same may be the case in matlab
start = time.time()
Hsignal=main_nb(Ctrigger, Csignal, offset, gate)
print("First Numba run takes:" +str(time.time()-start))
#First Numba run takes:0.16272664070129395

start = time.time()
Hsignal=main_nb(Ctrigger, Csignal, offset, gate)
print("All further Numba calls run takes:" +str(time.time()-start))
#All further Numba calls run takes:0.006016731262207031

Hsignal = np.unique(Hsignal)