【发布时间】:2013-12-24 01:52:47
【问题描述】:
我有一个 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 个这些成本矩阵。我不确定这个时间是从哪里来的,因为分析表明 200,000 次调用 min 只需要 0.1 秒 - 也许是内存访问?
有没有办法以某种方式并行执行此操作?我认为可能有,但对我来说,似乎每次迭代都是依赖的,除非有更聪明的方法来记忆事物。
与这个问题有相似之处:Can I avoid Python loop overhead on dynamic programming with numpy?
如有必要,我很乐意切换到 C,但我喜欢 Python 用于快速测试的灵活性以及缺乏文件 IO 的繁琐。在我的脑海中,类似以下代码的代码可能会明显更快吗?
#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
新的“麻木”代码:
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
原生:172.70626092s
我对数字和转换的完全简单印象深刻。
【问题讨论】:
-
索引的环绕是故意的还是错误的?当你运行你的代码时,看到的第一个项目是
i = 0、j = 0,所以你得到M[0, 0] = M[0, 0] + min(M[-1, -1], M[-1, 0] + P1, M[0, -1] + P1)。在我看来,您的try试图捕获超出范围的索引(顺便说一下,您应该明确说明您要捕获的内容,即执行except IndexError),但是您的索引中的-1s被认为是“沿着那个维度的最后一个元素”,所以没有任何东西被设置为np.inf。我认为这不是您想要的,但请确认。 -
是的,无意的。在实践中,它似乎并没有影响任何东西,这就是我没有抓住它的原因!
-
请注意,您实际上不需要 需要展开所有循环。在 Numba 中,无论您编写原生 Python for 循环还是编写基于 Numpy 的矢量化操作,Numba JIT 都会自动将任何一种情况转换为完全相同优化的 C 代码。如果您使用常规装饰器而不是
autojit为它提供一些类型信息,那就更好了。 -
我发现这通常很好,但是我在 Numpy 中为一行代码给出的情况只有在我表达循环后才有所改善。
-
+1 用于
numba的出色独立应用程序。正是我想要的。我冒昧地更正了“numba'd”代码中的缩进和其他一些内容。
标签: python numpy dynamic-programming