寻找改善的乏味循环

时间:2018-10-31 11:48:12

标签: python loops numpy coding-efficiency

在我的代码中,我需要多次计算向量的值,这是另一个数组的不同面片的平均值。 这是我的代码示例,显示了如何执行此操作,但是我发现它的运行效率太低...

import numpy as np
vector_a = np.zeros(10)
array_a = np.random.random((100,100))
for i in range(len(vector_a)):
    vector_a[i] = np.mean(array_a[:,i+20:i+40]

有什么办法可以提高效率?任何意见或建议都非常欢迎!非常感谢!

-是的,20和40是固定的。

3 个答案:

答案 0 :(得分:3)

编辑:

实际上,您可以更快地执行此操作。可以通过对汇总列进行如下操作来改进以前的功能:

def rolling_means_faster1(array_a, n, first, size):
    # Sum each relevant columns
    sum_a = np.sum(array_a[:, first:(first + size + n - 1)], axis=0)
    # Reshape as before
    strides_b = (sum_a.strides[0], sum_a.strides[0])
    array_b = np.lib.stride_tricks.as_strided(sum_a, (n, size), (strides_b))
    # Average
    v = np.sum(array_b, axis=1)
    v /= (len(array_a) * size)
    return v

另一种方法是处理累加的总和,并根据需要为每个输出元素添加和删除。

def rolling_means_faster2(array_a, n, first, size):
    # Sum each relevant columns
    sum_a = np.sum(array_a[:, first:(first + size + n - 1)], axis=0)
    # Add a zero a the beginning so the next operation works fine
    sum_a = np.insert(sum_a, 0, 0)
    # Sum the initial `size` elements and add and remove partial sums as necessary
    v = np.sum(sum_a[:size]) - np.cumsum(sum_a[:n]) + np.cumsum(sum_a[-n:])
    # Average
    v /= (size * len(array_a))
    return v

使用以前的解决方案进行基准测试:

import numpy as np

np.random.seed(100)
array_a = np.random.random((1000, 1000))
n = 100
first = 100
size = 200

%timeit rolling_means_orig(array_a, n, first, size)
# 12.7 ms ± 55.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit rolling_means(array_a, n, first, size)
# 5.49 ms ± 43.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit rolling_means_faster1(array_a, n, first, size)
# 166 µs ± 874 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit rolling_means_faster2(array_a, n, first, size)
# 182 µs ± 2.04 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

所以这最后两个在性能上似乎非常接近。这可能取决于输入的相对大小。


这是一种可能的矢量化解决方案:

import numpy as np

# Data
np.random.seed(100)
array_a = np.random.random((100, 100))

# Take all the relevant columns
slice_a = array_a[:, 20:40 + 10]
# Make a "rolling window" with stride tricks
strides_b = (slice_a.strides[1], slice_a.strides[0], slice_a.strides[1])
array_b = np.lib.stride_tricks.as_strided(slice_a, (10, 100, 20), (strides_b))
# Take mean
result = np.mean(array_b, axis=(1, 2))

# Original method for testing correctness
vector_a = np.zeros(10)
idv1 = np.arange(10) + 20
idv2 = np.arange(10) + 40
for i in range(len(vector_a)):
    vector_a[i] = np.mean(array_a[:,idv1[i]:idv2[i]])
print(np.allclose(vector_a, result))
# True

这是IPython中的快速基准测试(大小增加以供欣赏):

import numpy as np

def rolling_means(array_a, n, first, size):
    slice_a = array_a[:, first:(first + size + n)]
    strides_b = (slice_a.strides[1], slice_a.strides[0], slice_a.strides[1])
    array_b = np.lib.stride_tricks.as_strided(slice_a, (n, len(array_a), size), (strides_b))
    return np.mean(array_b, axis=(1, 2))

def rolling_means_orig(array_a, n, first, size):
    vector_a = np.zeros(n)
    idv1 = np.arange(n) + first
    idv2 = np.arange(n) + (first + size)
    for i in range(len(vector_a)):
        vector_a[i] = np.mean(array_a[:,idv1[i]:idv2[i]])
    return vector_a

np.random.seed(100)
array_a = np.random.random((1000, 1000))
n = 100
first = 100
size = 200

%timeit rolling_means(array_a, n, first, size)
# 5.48 ms ± 26.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit rolling_means_orig(array_a, n, first, size)
# 32.8 ms ± 762 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

答案 1 :(得分:2)

此解决方案在假设您尝试计算列窗口子集的滚动平均值的前提下工作。 例如,忽略行,给定[0, 1, 2, 3, 4]和窗口2,平均值为[0.5, 1.5, 2.5, 3.5],而您可能只希望第二和第三平均值。

您当前的解决方案效率低下,因为它会重新计算vector_a中每个输出的列平均值。有了(a / n) + (b / n) == (a + b) / n,我们可以避免只计算一次每列的平均值,然后根据需要组合列的平均值以产生最终输出。

window_first_start = idv1.min() # or idv1[0]
window_last_end = idv2.max() # or idv2[-1]
window_size = idv2[0] - idv1[0]
assert ((idv2 - idv1) == window_size).all(), "sanity check, not needed if assumption holds true"

# a view of the columns we are interested in, no copying is done here
view = array_a[:,window_first_start:window_last_end]

# calculate the means for each column
col_means = view.mean(axis=0)

# cumsum is used to find the rolling sum of means and so the rolling average
# We use an out variable to make sure we have a 0 in the first element of cum_sum. 
# This makes like a little easier in the next step.
cum_sum = np.empty(len(col_means) + 1, dtype=col_means.dtype)
cum_sum[0] = 0
np.cumsum(col_means, out=cum_sum[1:])

result = (cum_sum[window_size:] - cum_sum[:-window_size]) / window_size 

已经针对您自己的代码对此进行了测试,以上内容明显更快(随输入数组的大小增加),并且比jdehesa提供的解决方案稍快。输入数组为1000x1000时,它比解决方案快两个数量级,比jdehesa的解决方案快一个数量级。

答案 2 :(得分:1)

尝试一下:

import numpy as np     
array_a = np.random.random((100,100))
vector_a = [np.mean(array_a[:,i+20:i+40]) for i in range(10)]
相关问题