Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 迭代使用自身输出的数组的最佳方法_Python_Arrays_Performance_Loops_Numpy - Fatal编程技术网

Python 迭代使用自身输出的数组的最佳方法

Python 迭代使用自身输出的数组的最佳方法,python,arrays,performance,loops,numpy,Python,Arrays,Performance,Loops,Numpy,首先,我想为这个措辞糟糕的标题道歉——我现在想不出更好的表达方式。基本上,我想知道是否有一种更快的方法可以在Python中实现数组操作,其中每个操作都以迭代方式依赖于以前的输出(例如,前向差分操作、过滤等)。基本上,操作的形式如下: for n in range(1, len(X)): Y[n] = X[n] + X[n - 1] + Y[n-1] 其中,X是一个值数组,Y是输出。在这种情况下,假设在上述循环之前,Y[0]是已知的或单独计算的。我的问题是:是否有一个NumPy功能来加速

首先,我想为这个措辞糟糕的标题道歉——我现在想不出更好的表达方式。基本上,我想知道是否有一种更快的方法可以在Python中实现数组操作,其中每个操作都以迭代方式依赖于以前的输出(例如,前向差分操作、过滤等)。基本上,操作的形式如下:

for n in range(1, len(X)):
    Y[n] = X[n] + X[n - 1] + Y[n-1]
其中,
X
是一个值数组,
Y
是输出。在这种情况下,假设在上述循环之前,
Y[0]
是已知的或单独计算的。我的问题是:是否有一个NumPy功能来加速这种自引用循环?这几乎是我所有脚本的主要瓶颈。我知道NumPy例程从C例程执行中受益,所以我很好奇是否有人知道任何NumPy例程在这里会有所帮助。如果做不到这一点,是否有更好的方法来编程这个循环(在Python中),从而加快它在大型数组中的执行?(>500000个数据点)。

访问单个NumPy数组元素或(elementwise-)在NumPy数组上迭代很慢(就像非常慢一样)。如果你想在一个NumPy数组上进行手动迭代:那就不要这样做

但你有一些选择。最简单的方法是将数组转换为Python列表并在列表上迭代(听起来很傻,但请留下来-我将在答案1的末尾介绍一些基准测试):

如果您还对列表使用直接迭代,则可能会更快:

X = X.tolist()
Y = Y.tolist()
for idx, (Y_n_m1, X_n, X_n_m1) in enumerate(zip(Y, X[1:], X), 1):
    Y[idx] = X_n + X_n_m1 + Y_n_m1
还有一些更复杂的选项需要额外的软件包。最值得注意的是,它们被设计为直接处理数组元素,并尽可能避免Python开销。例如,对于Numba,您可以在jitted(即时编译)函数中使用您的方法:

import numba as nb

@nb.njit
def func(X, Y):
    for n in range(1, len(X)):
        Y[n] = X[n] + X[n - 1] + Y[n-1]
X
Y
可以是NumPy数组,但numba将直接在缓冲区上工作,速度超过了其他方法(可能是数量级)

与Cython相比,Numba的依赖性“更重”,但它可以更快更容易地使用。但是如果没有conda,就很难安装numba。。。YMMV

不过,这里还有一个Cython版本的代码(使用IPython magic编译,如果不使用IPython,则有点不同):


仅举一个关于时间安排的示例(基于):

import numpy as np
import numba as nb
import scipy.signal

def numpy_indexing(X, Y):
    for n in range(1, len(X)):
        Y[n] = X[n] + X[n - 1] + Y[n-1]
    return Y

def list_indexing(X, Y):
    X = X.tolist()
    Y = Y.tolist()
    for n in range(1, len(X)):
        Y[n] = X[n] + X[n - 1] + Y[n-1]
    return Y

def list_direct(X, Y):
    X = X.tolist()
    Y = Y.tolist()
    for idx, (Y_n_m1, X_n, X_n_m1) in enumerate(zip(Y, X[1:], X), 1):
        Y[idx] = X_n + X_n_m1 + Y_n_m1
    return Y

@nb.njit
def numba_indexing(X, Y):
    for n in range(1, len(X)):
        Y[n] = X[n] + X[n - 1] + Y[n-1]
    return Y


def numpy_cumsum(X, Y):
    Y[1:] = X[1:] + X[:-1]
    np.cumsum(Y, out=Y)
    return Y

def scipy_lfilter(X, Y):
    a = [1, -1]
    b = [1, 1]
    return Y[0] - X[0] + scipy.signal.lfilter(b, a, X)

# Make sure the approaches give the same result
X = np.random.random(10000)
Y = np.zeros(10000)
Y[0] = np.random.random()

np.testing.assert_array_equal(numba_indexing(X, Y), numpy_indexing(X, Y))
np.testing.assert_array_equal(numba_indexing(X, Y), numpy_cumsum(X, Y))
np.testing.assert_almost_equal(numba_indexing(X, Y), scipy_lfilter(X, Y))
np.testing.assert_array_equal(numba_indexing(X, Y), cython_indexing(X, Y))

# Timing setup
timings = {numpy_indexing: [], 
           list_indexing: [], 
           list_direct: [],
           numba_indexing: [],
           numpy_cumsum: [],
           scipy_lfilter: [],
           cython_indexing: []}
sizes = [2**i for i in range(1, 20, 2)]

# Timing
for size in sizes:
    X = np.random.random(size=size)
    Y = np.zeros(size)
    Y[0] = np.random.random()
    for func in timings:
        res = %timeit -o func(X, Y)
        timings[func].append(res)

# Plottig absolute times

%matplotlib notebook
import matplotlib.pyplot as plt

fig = plt.figure(1)
ax = plt.subplot(111)

for func in timings:
    ax.plot(sizes, 
            [time.best for time in timings[func]], 
            label=str(func.__name__))
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('size')
ax.set_ylabel('time [seconds]')
ax.grid(which='both')
ax.legend()
plt.tight_layout()

# Plotting relative times

fig = plt.figure(1)
ax = plt.subplot(111)

baseline = numba_indexing # choose one function as baseline
for func in timings:
    ax.plot(sizes, 
            [time.best / ref.best for time, ref in zip(timings[func], timings[baseline])], 
            label=str(func.__name__))
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_xlabel('size')
ax.set_ylabel('time relative to "{}"'.format(baseline.__name__))
ax.grid(which='both')
ax.legend()

plt.tight_layout()
结果如下:

绝对运行时

相对运行时(与numba函数相比)

所以,只要把它转换成一个列表,你的速度大约会快3倍!通过直接迭代这些列表,您可以获得另一个(更小的)加速,在这个基准测试中只有20%,但是我们现在比原始解决方案快了近4倍。与列表操作相比,使用numba您可以将其速度提高100倍以上!Cython只比numba慢一点(~40-50%),可能是因为我没有挤出所有可能的优化(通常不会慢10-20%)。但是,对于大型阵列,差异会变小

1我确实在中详细介绍了。Q+A是关于转换成
set
的,但是因为
set
使用(隐藏的)“手动迭代”,所以它也适用于这里


我包括NumPy
cumsum
和Scipy
lfilter
方法的计时。与numba函数相比,小型阵列的速度大约慢20倍,大型阵列的速度慢4倍。然而,如果我正确地解释了这个问题,您将寻找一般的方法,而不仅仅是示例中应用的方法。并非每个自引用循环都可以使用NumPy或SciPys过滤器中的
cum*
函数实现。但即便如此,他们似乎也无法与Cython和/或numba竞争。

使用
np.cumsum非常简单:

#!/usr/bin/env python3
import numpy as np
import random

def r():
    return random.randint(100, 1000)
X = np.array([r() for _ in range(10)])
fast_Y = np.ndarray(X.shape, dtype=X.dtype)
slow_Y = np.ndarray(X.shape, dtype=X.dtype)
slow_Y[0] = fast_Y[0] = r()

# fast method
fast_Y[1:] = X[1:] + X[:-1]
np.cumsum(fast_Y, out=fast_Y)

# original method
for n in range(1, len(X)):
    slow_Y[n] = X[n] + X[n - 1] + slow_Y[n-1]


assert (fast_Y == slow_Y).all()

您描述的情况基本上是一个离散滤波器操作。这是在中实现的。您描述的特定条件对应于
a=[1,-1]
b=[1,1]

import numpy as np
import scipy.signal

a = [1, -1]
b = [1, 1]

X = np.random.random(10000)
Y = np.zeros(10000)

newY = scipy.signal.lfilter(b, a, X) + (Y[0] - X[0])
在我的电脑上,计时结果如下:

%timeit func4(X, Y.copy())
# 100000 loops, best of 3: 14.6 µs per loop

% timeit newY = scipy.signal.lfilter(b, a, X) - (Y[0] - X[0])
# 10000 loops, best of 3: 68.1 µs per loop

我想我击败了你-你能用我的答案为你的机器添加计时吗?我将很快修改我的代码以尝试这些解决方案中的每一个,但是我的印象是,对于超大阵列,使用列表要慢一些?我将经常使用数百万点的数据结构。一旦达到这个程度,python列表的性能折衷是什么?@o11c I包括了计时。但我试着让它更一般化<代码>np.cum*
功能可能不适用于所有情况。它在这里工作,但是很多自引用循环不能用numpy函数重写。到目前为止,这是一个非常好的答案。我经常希望有一个很好的简单的库来进行这样的计时研究(根据不同的大小比较不同的算法),它可以干净地处理所有事情。回来阅读你答案的最新版本后,我真的很欣赏它的深度!事实上,Numba已经成为我最喜欢的新模块,我目前正在努力了解如何最好地使用它。由于我为物理研究创建计算模型,这种易于使用的性能模块是我遇到的最好的东西,而不是用FORTRAN或C++编写代码。还有,因为我是物理学家而不是计算机工程师!啊,为什么我的电脑这么慢。。。。似乎一个人不使用我的电脑就很容易得到4倍的进步。请注意,为了完成问题的要求,您需要使用
scipy.signal.lfilter(b,a,X)-X[0]+Y[0]
。在我的测试中正好是
0
。)@MSeifert感谢对初始值的修复。我太习惯于零均值信号了。你是对的,在这个问题的背景下,我问了一些改进滤波器的方法(在时域中应用了四阶巴特沃斯滤波器),但是我想用这样一个w
import numpy as np
import scipy.signal

a = [1, -1]
b = [1, 1]

X = np.random.random(10000)
Y = np.zeros(10000)

newY = scipy.signal.lfilter(b, a, X) + (Y[0] - X[0])
%timeit func4(X, Y.copy())
# 100000 loops, best of 3: 14.6 µs per loop

% timeit newY = scipy.signal.lfilter(b, a, X) - (Y[0] - X[0])
# 10000 loops, best of 3: 68.1 µs per loop