Python 如何知道一个算法是否可以更快

Python 如何知道一个算法是否可以更快,python,algorithm,performance,Python,Algorithm,Performance,我正在做一个实验来计算信号的近似熵。详细信息(和实际代码)可在其上找到。不幸的是,虽然算法本身可以工作,但对于大型数据集来说速度非常慢(例如,对于2000长的信号,大约需要25秒)。由于我需要对更长的信号进行计算,以这种速度,我希望我的实验至少持续1个月。我想知道是否有任何方法可以加速算法 import numpy as np def ApEn(U, m, r): def _maxdist(x_i, x_j): return max([abs(ua - va) for

我正在做一个实验来计算信号的近似熵。详细信息(和实际代码)可在其上找到。不幸的是,虽然算法本身可以工作,但对于大型数据集来说速度非常慢(例如,对于2000长的信号,大约需要25秒)。由于我需要对更长的信号进行计算,以这种速度,我希望我的实验至少持续1个月。我想知道是否有任何方法可以加速算法

import numpy as np

def ApEn(U, m, r):

    def _maxdist(x_i, x_j):
        return max([abs(ua - va) for ua, va in zip(x_i, x_j)])

    def _phi(m):
        x = [[U[j] for j in range(i, i + m - 1 + 1)] for i in range(N - m + 1)]
        C = [len([1 for x_j in x if _maxdist(x_i, x_j) <= r]) / (N - m + 1.0) for x_i in x]
        return (N - m + 1.0)**(-1) * sum(np.log(C))

    N = len(U)

    return abs(_phi(m + 1) - _phi(m))
将numpy导入为np
def ApEn(U、m、r):
定义最大值(x_i,x_j):
返回最大值([ua的绝对值(ua-va),拉链中的va(x_i,x_j)])
定义φ(m):
x=[[U[j]表示范围内的j(i,i+m-1+1)]表示范围内的i(N-m+1)]

C=[len([1代表x_j in x if_maxdist(x_i,x_j)我没有看全部内容,但给你一个例子,说明如何使用向量计算优化函数:

def maxdist_opti(x_i,x_j):
    return max(abs(x_i-x_j))
当您的数据存储到numpy数组中时,您可以对它们使用numpy运算符(其中有很多运算符,您可以在此处查看:),而且速度会更快,在上面的例子中,我对numpy数组使用了soutAction和
np.max
函数

这里,使用随机数据:

x_i = np.random.rand(10000)
x_j = np.random.rand(10000)
这里使用的数据不是很长,但是您可以看到非常好的性能增益:

%timeit _maxdist(x_i,x_j)
100 loops, best of 3: 3.01 ms per loop

%timeit maxdist_opti(x_i,x_j)
10000 loops, best of 3: 28 µs per loop
您可以使用以下逻辑仅对整个公式进行向量计算,性能上的增益将是巨大的


请注意,数据越长,使用向量计算的优化程度就越高。

通常优化时,应从降低算法复杂性的算法优化开始,而不仅仅是常数

一个经验法则是查看最里面的循环——它包含执行次数最多的操作

我不确定我是否正确阅读了代码,但它看起来像是一个矩阵,
\U maxdist
对其列进行计算。在这种情况下,确保每列只执行一次计算是有意义的


例如,为每列计算其值,存储在数组中,并在
\u phi

中使用它。如果您愿意将该函数移动到cython并添加一些类型注释,则可以获得显著的性能提升。以下是我的算法版本:

apen.pyx:

cimport cython
from libc.math cimport fabs, log
import numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.initializedcheck(False)
@cython.cdivision(True)
cdef double max_dist(double[:] x_i, double[:] x_j, int m) nogil:
    #Performs the max function described in step 4 of ApEn algorithm
    cdef double out
    cdef double dist
    out = fabs(x_i[0] - x_j[0])
    for k in range(1, m - 1):
        dist = fabs(x_i[k] - x_j[k])
        if dist > out:
            out = dist
    return out

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.initializedcheck(False)
@cython.cdivision(True)
cdef double phi(double[:] Sn, int m, int r):
    cdef int N = len(Sn)
    cdef int i
    cdef int j
    cdef int k

    cdef int c_val
    cdef int counter
    cdef double phi_sum = 0
    cdef double phi
    cdef double m_dist

    #Performs step 3 of the ApEn algorithm
    cdef double[:, :] x = np.empty((N - m  + 1, m), dtype=np.float64)
    with nogil:
        for i in range(N - m + 1):
            for j in range(0, m):
                x[i, j] = Sn[j + i]

        #Performs a combined steps 4 & 5 of the ApEn algorithm
        for i in range(N - m + 1):
            counter = 0
            for j in range(N - m + 1):
                m_dist = max_dist(x[i], x[j], m)
                c_val = 1 if m_dist <= r else 0
                counter += c_val
            phi_sum += log(counter / (N - m + 1.0))
        phi = phi_sum / (N - m + 1.0)
    return phi

cpdef double approx_entropy(double[:] Sn, int m, int r):#Passing in steps 1 & 2 of the ApEn algorithm
    cdef double ApEn = abs(phi(Sn, m, r) - phi(Sn, m + 1, r))#Performs step 6 of the ApEn algorithm
    return ApEn
setup.pxd:

from distutils.core import setup
from Cython.Build import cythonize
from distutils.core import Extension
import numpy as np

extensions = [
    Extension("apen", sources=["apen.pyx"], include_dirs=[np.get_include()], extra_compile_args=["-w"]),
]

setup(
    ext_modules = cythonize(extensions)
)
main.py:

import time
import apen
import numpy as np

start = time.time()
data = np.random.rand(2000)
#data = np.array([85, 80, 89] * 17, dtype=np.float64)
answer = apen.approx_entropy(Sn=data, m=2, r=3)
print(answer)
end = time.time()
print(end - start)

用这个代码在我的笔记本电脑上计算2000个随机数据点,cython代码在0.36秒内计算出ApEn。相比之下,wikipedia代码需要14.75秒。这相当于速度提升了40倍。希望你觉得这很有用!

我会试着研究一下,但这不是向量计算,这可能就是为什么它需要这么做的原因这么长时间,使用运算符for是不省时的。难道不应该在
\U phi
中定义
N=len(U)
吗?你能提供足够长的样本信号来处理吗?@sharatpc一个典型的长信号可以由signal=np.rand.rand(100000)生成明确地说,通常可以证明一个算法是或不是渐近最优的。证明一个算法的特定实现是最优的要困难得多。正如Michael Abrash所说,并多次证明的那样。非常感谢!我将尝试相应地优化我的算法!非常感谢!我已经尝试了所有的方法她建议的解决方案似乎没有任何效果。@DillionEcmark很高兴它起到了作用!不幸的是,如果不添加大量中间数组步骤,有些事情对于numpy就不那么容易矢量化,尽管这在总体上确实是一个合理的建议。ApEn算法似乎是O(N^2),这也没有帮助,因此,请准备好喝杯咖啡,等待更长时间的100000点计算!非常感谢!使用新的实现,现在等待的时间少了很多
import time
import apen
import numpy as np

start = time.time()
data = np.random.rand(2000)
#data = np.array([85, 80, 89] * 17, dtype=np.float64)
answer = apen.approx_entropy(Sn=data, m=2, r=3)
print(answer)
end = time.time()
print(end - start)