如何对这个大型数组计算进行矢量化和加速?

时间:2016-05-18 07:51:38

标签: python arrays algorithm numpy vectorization

我目前正在尝试计算10.000 x 10.000数组值中所有子方格总和的总和。例如,如果我的数组是:

<activity android:name="com.iphonik.AppPreferenceActivity"
            android:label="Preferences">
        </activity>

我希望结果是:

1 1 1 
2 2 2
3 3 3 

所以,作为第一次尝试,我写了一个非常简单的python代码来做到这一点。因为它在O(k ^ 2.n ^ 2)中(n是大数组的大小,k是我们得到的子方形的大小),处理非常长。我在O(n ^ 2)中编写了另一个算法来加速它:

1+1+1+2+2+2+3+3+3                        [sum of squares of size 1]
+(1+1+2+2)+(1+1+2+2)+(2+2+3+3)+(2+2+3+3) [sum of squares of size 2]
+(1+1+1+2+2+2+3+3+3)                     [sum of squares of size 3]
________________________________________
68

所以这段代码工作正常。给定一个数组和子方格的大小,它将返回所有这些子方格中的值的总和。我基本上迭代了子方块的大小来获得所有可能的值。

问题是对于大型阵列来说这又是一件好事(对于10.000 x 10.000阵列,超过20天)。我用Google搜索并了解到我可以使用numpy对迭代进行矢量化。但是,在我的情况下,我无法弄清楚如何制作它......

如果有人可以帮助我加快算法速度,或者给我一些关于这个主题的好文档,我会很高兴的!

谢谢!

3 个答案:

答案 0 :(得分:6)

根据@Divakar的优秀想法,我建议使用integral images来加速回旋。如果矩阵非常大,则必须多次卷积(每个内核大小一次)。使用积分图像(也称为求和面积表)可以非常有效地计算几个卷积(或正方形内的和的计算)。

计算完整的图像M后,区域(x0, y0) - (x1, y1)内所有值的总和可以使用 4个aritmetic计算来计算,无论是什么窗口大小(来自维基百科的图片):

M[x1, y1] - M[x1, y0] - M[x0, y1] + M[x0, y0]

Link from wikipedia

这可以很容易地在numpy中进行矢量化。可以使用cumsum计算积分图像。以下示例:

tab = np.array([[1, 1, 1], [2, 2, 2], [3, 3, 3]])
M = tab.cumsum(0).cumsum(1) # Create integral images
M = np.pad(M, ((1,0), (1,0)), mode='constant') # pad it with a row and column of zeros

M用一行和一列零填充来处理第一行(x0 = 0y0 = 0)。

然后,给定窗口大小W,可以有效地计算大小W的每个窗口的总和,并使用numpy完全向量化:

all_sums = M[W:, W:] - M[:-W, W:] - M[W:, :-W] + M[:-W, :-W]

注意上面的矢量化操作,计算每个窗口的总和,即矩阵的每个A,B,C和D.然后将所有窗口的总和计算为

total = all_sums.sum()

请注意,对于与N不同的大小,与卷积不同,整数图像只需计算一次,因此,代码可以非常有效地编写为:

def get_all_sums(A):
    M = A.cumsum(0).cumsum(1)
    M = np.pad(M, ((1,0), (1,0)), mode='constant')

    total = 0
    for W in range(1, A.shape[0] + 1):
        tmp = M[W:, W:] + M[:-W, :-W] - M[:-W, W:] - M[W:, :-W]
        total += tmp.sum()

    return total

示例的输出:

>>> get_all_sums(tab)
68

将卷积与具有不同大小矩阵的积分图像进行比较的一些时序。 getAllSums使用Divakar的卷积方法,get_all_sums使用上述基于积分图像的方法:

>>> R1 = np.random.randn(10, 10)
>>> R2 = np.random.randn(100, 100)

1)使用R1 10x10矩阵:

>>> %time getAllSums(R1)
CPU times: user 353 µs, sys: 9 µs, total: 362 µs
Wall time: 335 µs
2393.5912717342017

>>> %time get_all_sums(R1)
CPU times: user 243 µs, sys: 0 ns, total: 243 µs
Wall time: 248 µs
2393.5912717342012

2)使用R2 100x100矩阵:

>>> %time getAllSums(R2)
CPU times: user 698 ms, sys: 0 ns, total: 698 ms
Wall time: 701 ms
176299803.29826894

>>> %time get_all_sums(R2)
CPU times: user 2.51 ms, sys: 0 ns, total: 2.51 ms
Wall time: 2.47 ms
176299803.29826882

请注意,对于足够大的矩阵,使用积分图像比卷积快300倍。

答案 1 :(得分:2)

这些滑动求和最适合计算为2D卷积求和,可以使用scipy's convolve2d有效计算。因此,对于特定大小,您可以获得总结,如此 -

def getSum(tab,size):
    # Define kernel and perform convolution to get such sliding windowed summations
    kernel = np.ones((size,size),dtype=tab.dtype)
    return convolve2d(tab, kernel, mode='valid').sum()

为了获得所有大小的总结,我认为在内存和性能效率方面最好的方法是使用循环来遍历所有可能的大小。因此,为了得到最终的总和,你会有 -

def getAllSums(tab):
    finalSum = 0
    for i in range(tab.shape[0]):
        finalSum += getSum(tab,i+1)
    return finalSum

示例运行 -

In [51]: tab
Out[51]: 
array([[1, 1, 1],
       [2, 2, 2],
       [3, 3, 3]])

In [52]: getSum(tab,1) # sum of squares of size 1
Out[52]: 18

In [53]: getSum(tab,2) # sum of squares of size 2
Out[53]: 32

In [54]: getSum(tab,3) # sum of squares of size 3
Out[54]: 18

In [55]: getAllSums(tab) # sum of squares of all sizes
Out[55]: 68

答案 2 :(得分:2)

根据计算每个数字计算的次数的想法,我来到这个简单的代码:

def get_sum(matrix, n):
    ret = 0
    for i in range(n):
        for j in range(n):
            for k in range(1, n + 1):
                # k is the square size. count is times of the number counted.
                count = min(k, n - k + 1, i + 1, n - i) * min(k, n - k + 1, j + 1, n - j)
                ret += count * matrix[i][j]
    return ret

a = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]

print get_sum(a, 3) # 68
Divakar的解决方案非常棒,但我认为我的效率更高,至少在渐近的时间复杂度方面(O(n ^ 3)与Divakar的O(n ^ 3logn)相比)。

我现在得到一个O(n ^ 2)解决方案......

基本上,我们可以做到:

def get_sum2(matrix, n):
    ret = 0
    for i in range(n):
        for j in range(n):
            x = min(i + 1, n - i)
            y = min(j + 1, n - j)
            # k < half
            half = (n + 1) / 2
            for k in range(1, half + 1):
                count = min(k, x) * min(k, y)
                ret += count * matrix[i][j]
            # k >= half
            for k in range(half + 1, n + 1):
                count = min(n + 1 - k, x) * min(n + 1 - k, y)
                ret += count * matrix[i][j]
    return ret

当1&lt; = k&lt; = n / 2

时,您可以看到sum(min(k, x) * min(k, y))可以在O(1)中计算

所以我们来到O(n ^ 2)代码:

def get_square_sum(n):
    return n * (n + 1) * (2 * n + 1) / 6


def get_linear_sum(a, b):
    return (b - a + 1) * (a + b) / 2


def get_count(x, y, k_end):
    # k <= min(x, y), count is k*k
    sum1 = get_square_sum(min(x, y))

    # k > min(x, y) and k <= max(x, y), count is k * min(x, y)
    sum2 = get_linear_sum(min(x, y) + 1, max(x, y)) * min(x, y)

    # k > max(x, y), count is x * y
    sum3 = x * y * (k_end - max(x, y))

    return sum1 + sum2 + sum3


def get_sum3(matrix, n):
    ret = 0
    for i in range(n):
        for j in range(n):
            x = min(i + 1, n - i)
            y = min(j + 1, n - j)
            half = n / 2

            # k < half
            ret += get_count(x, y, half) * matrix[i][j]
            # k >= half
            ret += get_count(x, y, half + half % 2) * matrix[i][j]

    return ret 

测试:

a = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
n = 1000
b = [[1] * n] * n
print get_sum3(a, 3) # 68
print get_sum3(b, n) # 33500333666800

你可以将我的O(n ^ 2)Python代码重写为C,我相信它会产生一个非常有效的解决方案......