加速python / numpy中的动态编程

时间:2013-12-06 16:12:48

标签: python numpy dynamic-programming

我有一个2D成本矩阵M,可能是400x400,我正在尝试计算通过它的最佳路径。因此,我有一个像:

这样的功能
M[i,j] = M[i,j] + min(M[i-1,j-1],M[i-1,j]+P1,M[i,j-1]+P1)

这显然是递归的。 P1是一些加性常数。我的代码或多或少有效:

def optimalcost(cost, P1=10):
    width1,width2 = cost.shape
    M = array(cost)
    for i in range(0,width1):
       for j in range(0,width2):
          try:
              M[i,j] = M[i,j] + min(M[i-1,j-1],M[i-1,j]+P1,M[i,j-1]+P1)
          except:
              M[i,j] = inf
    return M

现在我知道在Numpy中循环是一个糟糕的想法,对于像初始成本矩阵的计算这样的事情,我已经能够找到缩短时间的捷径。但是,由于我需要对整个矩阵进行评估,因此我不确定如何做到这一点。这在我的机器上每次通话大约需要3秒钟,并且必须应用于这些成本矩阵中的大约300个。我不确定这个时间来自何处,因为分析表明,对min的200,000次调用仅需0.1秒 - 可能是内存访问?

有没有办法以某种方式并行执行此操作?我假设可能有,但对我来说,似乎每次迭代都是依赖的,除非有更聪明的方式来记忆事物。

这个问题有相似之处:Can I avoid Python loop overhead on dynamic programming with numpy?

如果有必要,我很乐意切换到C,但我喜欢Python的灵活性,以便进行快速测试,并且缺乏文件IO的faff。在我的脑海中,类似下面的代码可能会明显更快?

#define P1 10
void optimalcost(double** costin, double** costout){
    /* 
        We assume that costout is initially
        filled with costin's values.
    */
    float a,b,c,prevcost;

    for(i=0;i<400;i++){
        for(j=0;j<400;j++){
            a = prevcost+P1;
            b = costout[i][j-1]+P1;
            c = costout[i-1][j-1];
            costout[i][j] += min(prevcost,min(b,c));
            prevcost = costout[i][j];
        }
    }
}

return;

更新

我在Mac上,我不想安装一个全新的Python工具链,因此我使用了Homebrew

> brew install llvm --rtti
> LLVM_CONFIG_PATH=/usr/local/opt/llvm/bin/llvm-config pip install llvmpy
> pip install numba

新的“numba'd”代码:

from numba import autojit, jit
import time
import numpy as np

@autojit
def cost(left, right):
    height,width = left.shape
    cost = np.zeros((height,width,width))

    for row in range(height):
        for x in range(width):
            for y in range(width):
                cost[row,x,y] = abs(left[row,x]-right[row,y])

    return cost

@autojit
def optimalcosts(initcost):
    costs = zeros_like(initcost)
    for row in range(height):
        costs[row,:,:] = optimalcost(initcost[row])
    return costs

@autojit
def optimalcost(cost):
    width1,width2 = cost.shape
    P1=10
    prevcost = 0.0
    M = np.array(cost)
    for i in range(1,width1):
        for j in range(1,width2):
            M[i,j] += min(M[i-1,j-1],prevcost+P1,M[i,j-1]+P1)
            prevcost = M[i,j]
    return M

prob_size = 400
left = np.random.rand(prob_size,prob_size)
right = np.random.rand(prob_size,prob_size)

print '---------- Numba Time ----------'
t =  time.time()
c = cost(left,right)
optimalcost(c[100])
print time.time()-t

print '---------- Native python Time --'
t =  time.time()
c = cost.py_func(left,right)
optimalcost.py_func(c[100])
print time.time()-t

在Python中编写代码非常有趣,而且非Pythonic。对于有兴趣编写Numba代码的人,请注意,您需要在代码中明确表示循环。之前,我有整齐的Numpy单行,

abs(left[row,:][:,newaxis] - right[row,:])

计算成本。 Numba花了大约7秒钟。正确地写出循​​环可以得到0.5秒。

将它与原生Python代码进行比较是一种不公平的比较,因为Numpy可以很快地做到这一点,但是:

Numba编译:0.509318113327s

Native:172.70626092s

我对这些数字和转换完全简单的印象非常深刻。

2 个答案:

答案 0 :(得分:2)

如果您不难切换到Python的Anaconda发行版,您可以尝试使用Numba,对于这个特殊的简单动态算法,它可能会提供大量的加速而不会让您离开蟒。

答案 1 :(得分:1)

Numpy通常在迭代工作上不是很好(尽管它确实具有一些常用的迭代功能,例如np.cumsumnp.cumprodnp.linalg.*等)。但是对于诸如查找上面最短路径(或最低能量路径)之类的简单任务,您可以通过考虑可以同时计算出什么来矢量化问题(也请避免制作副本:

假设我们在“行”方向(即水平方向)上找到最短路径,我们首先可以创建算法输入:

# The problem, 300 400*400 matrices
# Create infinitely high boundary so that we dont need to handle indexing "-1"
a = np.random.rand(300, 400, 402).astype('f')
a[:,:,::a.shape[2]-1] = np.inf

然后准备一些实用程序数组,我们稍后将使用它们(创建需要固定时间):

# Create self-overlapping view for 3-way minimize
# This is the input in each iteration
# The shape is (400, 300, 400, 3), separately standing for row, batch, column, left-middle-right
A = np.lib.stride_tricks.as_strided(a, (a.shape[1],len(a),a.shape[2]-2,3), (a.strides[1],a.strides[0],a.strides[2],a.strides[2]))

# Create view for output, this is basically for convenience
# The shape is (399, 300, 400). 399 comes from the fact that first row is never modified
B = a[:,1:,1:-1].swapaxes(0, 1)

# Create a temporary array in advance (try to avoid cache miss)
T = np.empty((len(a), a.shape[2]-2), 'f')

最后进行计算和计时:

%%timeit
for i in np.arange(a.shape[1]-1):
    A[i].min(2, T)
    B[i] += T

(超级旧笔记本电脑)计算机上的计时结果为1.78s,已经快了3分钟。我相信您可以通过优化内存布局和对齐方式(以某种方式)来进一步提高(同时坚持使用numpy)。或者,您可以简单地使用multiprocessing.Pool。它易于使用,并且可以轻松地将其分解为较小的问题(通过在批处理轴上划分)。