比较数组补丁的最快方法是什么?

时间:2015-06-16 10:32:55

标签: python arrays performance numpy cython

我想比较二维数组$ A $的不同区域与较小尺寸的给定数组$ b $。由于我必须做很多次,因此执行速度非常快是至关重要的。我有一个解决方案,工作正常,但我希望它可以做得更好,更快。

详细说明:

假设我们有一个大数组和一个小数组。我遍历大数组中与小数组大小相同的所有可能“补丁”,并将这些补丁与给定的小数组进行比较。

def get_patch_term(x, y, patch_size, data):
    """
    a patch has the size (patch_size)^^2
    """
    patch = data[(x - (patch_size-1)/2): (x + (patch_size-1)/2 + 1),
                 (y - (patch_size-1)/2): (y + (patch_size-1)/2 + 1)]
    return patch

为了获得补丁,我得到了这种直接阵列访问实现:

def get_patch_term_fast(Py_ssize_t x_i, Py_ssize_t y_i,
                        Py_ssize_t patch_size,
                        np.ndarray[DTYPE_t, ndim=2] big_array):

    assert big_array.dtype == DTYPE
    patch_size = (patch_size - 1)/2

    cdef np.ndarray[DTYPE_t, ndim=2] patch = <np.ndarray[DTYPE_t, ndim=2]>big_array[(x_i - patch_size):(x_i + patch_size + 1), (y_i - patch_size): (y_i + patch_size + 1)]
    return patch

我想这是最关键的任务,可以更快地执行,但我不确定。

我看过Cython,但也许我做错了,我对它并不熟悉。

我的第一次尝试是直接转换为cython:

def get_patch_term_fast_parallel(Py_ssize_t x_i, Py_ssize_t y_i,
                                 Py_ssize_t patch_size,
                                 np.ndarray[DTYPE_t, ndim=2] big_array):

    assert big_array.dtype == DTYPE
    patch_size = (patch_size - 1)/2

    assert big_array.dtype == DTYPE
    cdef Py_ssize_t x
    cdef Py_ssize_t y


    cdef np.ndarray[DTYPE_t, ndim=1] patch = np.empty(np.power((2 * patch_size) + 1, 2))
    with nogil, parallel():
        for x in prange(x_i - patch_size, x_i + patch_size + 1):
            for y in prange(y_i - patch_size, y_i + patch_size + 1):
                patch[((x - (x_i - patch_size)) * (2 * patch_size + 1)) + (y - (y_i - patch_size))] = big_array[x, y]
    #cdef np.ndarray[DTYPE_t, ndim=2] patch = <np.ndarray[DTYPE_t, ndim=2]>big_array[(x_i - patch_size):(x_i + patch_size + 1), (y_i - patch_size): (y_i + patch_size + 1)]
    return patch

这似乎更快(见下文),但我认为并行方法应该更好,所以我提出了这个

A = np.array(range(1200), dtype=np.float).reshape(30, 40)
b = np.array([41, 42, 81, 84]).reshape(2, 2)

x = 7
y = 7
print(timeit.timeit(lambda:get_patch_term_fast(x, y, 3, A), number=300))
print(timeit.timeit(lambda:get_patch_term_fast_parallel(x, y, 3, A).reshape(3,3), number=300))
print(timeit.timeit(lambda:get_patch_term(x, y, 3, A), number=300))

不幸的是,这并不快。对于我使用的测试:

0.0008792859734967351
0.0029909340664744377
0.0029337930027395487

哪个给出了

def get_best_fit_fast(np.ndarray[DTYPE_t, ndim=2] big_array,
                      np.ndarray[DTYPE_t, ndim=2] small_array):

    # we assume the small array is square
    cdef Py_ssize_t patch_size = small_array.shape[0]

    cdef Py_ssize_t x
    cdef Py_ssize_t y

    cdef Py_ssize_t x_range = big_array.shape[0] - patch_size
    cdef Py_ssize_t y_range = big_array.shape[1] - patch_size

    cdef np.ndarray[DTYPE_t, ndim=2] p
    cdef np.ndarray[DTYPE_t, ndim=2] weights = np.empty((x_range - patch_size)*(y_range - patch_size)).reshape((x_range - patch_size), (y_range - patch_size))

    with nogil, parallel():
        for x in prange(patch_size, x_range):
            for y in prange(patch_size, y_range):
                p = get_patch_term_fast(x, y, patch_size, big_array)
                weights[x - patch_size, y - patch_size] = np.linalg.norm(np.abs(p - small_array))

    return np.min(weights)

所以,我的第一个问题是,是否可以更快地完成它?第二个问题是,为什么并行方法不比原来的numpy实现快?

编辑:

我尝试进一步并行化get_best_fit函数,但遗憾的是我收到了很多错误,指出我不能在没有gil的情况下分配Python对象。

以下是代码:

{{1}}

PS:我省略了返回最小补丁的部分......

3 个答案:

答案 0 :(得分:1)

我认为依赖于Product.PRODUCT_ADD_URL函数的功能,可能已经有一个高度优化的实现。例如,如果它只是一个卷积,那么看看Theano,它甚至可以让你很容易地利用GPU。即使你的函数不像简单的卷积那么简单,你可以在Theano中使用构建块,而不是试图用Cython进行真正的低级别。

答案 1 :(得分:0)

最初根据模式匹配发布另一个答案(由标题带走),发布另一个答案

使用一个循环遍历两个数组(大和小)并应用部分相关度量(或其他),而不会一直切割列表:

作为旁注,观察事实(取决于当然的度量),一旦完成一个补丁,下一个补丁(向下一行或向右一列)与之前的补丁共享很多,只有其初始和最终因此,行(或列)改变,通过减去前一行并添加新行(或反之亦然),可以从先前的相关性更快地计算新的相关性。由于未给出度量函数,因此添加为观察。

def get_best_fit(big_array, small_array):

    # we assume the small array is square
    patch_size = small_array.shape[0]
    x = 0 
    y = 0
    x2 = 0 
    y2 = 0
    W = big_array.shape[0]
    H = big_array.shape[1]
    W2 = patch_size
    H2 = patch_size
    min_value = np.inf
    tmp = 0
    min_patch = None
    start = 0 
    end = H*W
    start2 = 0
    end2 = W2*H2
    while start < end:

        tmp = 0
        start2 = 0
        x2 = 0 
        y2 = 0
        valid = True
        while start2 < end2:
            if x+x2 >= W or y+y2 >= H: 
                valid = False
                break
            # !!compute partial metric!!
            # no need to slice lists all the time
            tmp += some_metric_partial(x, y, x2, y2, big_array, small_array)
            x2 += 1 
            if x2>=W2: 
                x2 = 0 
                y2 += 1
            start2 += 1

        # one patch covered
        # try next patch
        if valid and min_value > tmp:
            min_value = tmp
            min_patch = [x, y, W2, H2]

        x += 1 
        if x>=W: 
            x = 0 
            y += 1

        start += 1

    return min_patch, min_value

答案 2 :(得分:0)

并行函数的时间测量的另一个问题是在运行并行函数后调用数组对象上的reshape。可能是并行功能更快的情况,但随后重塑正在增加额外的时间(虽然reshape应该非常快,但我不确定有多快)。

另一个问题是我们不知道您的some_metric字词是什么,并且它并不表示您并行使用它。我看到您的并行代码工作的方式是您并行获取补丁,然后将它们放入队列中以便由some_metric函数进行分析,从而破坏了代码并行化的目的。

正如John Greenhall所说,使用FFT和卷积可能非常快。但是,要利用并行处理,您仍然需要并行分析补丁和小数组。

阵列有多大?记忆在这里也是一个问题吗?