问题 Python中优化的点积


两个n维向量的点积 u=[u1,u2,...un] 和 v=[v1,v2,...,vn] 是由给出的 u1*v1 + u2*v2 + ... + un*vn

一个问题 昨天发布 鼓励我找到使用标准库,没有第三方模块或C / Fortran / C ++调用在Python中计算点积的最快方法。

我计时了四种不同的方法;到目前为止似乎最快 sum(starmap(mul,izip(v1,v2))) (哪里 starmap 和 izip 来自 itertools 模块)。

对于下面给出的代码,这些是经过的时间(以秒为单位,一百万次运行):

d0: 12.01215
d1: 11.76151
d2: 12.54092
d3: 09.58523

你能想到更快的方法吗?

import timeit # module with timing subroutines                                                              
import random # module to generate random numnbers                                                          
from itertools import imap,starmap,izip
from operator import mul

def v(N=50,min=-10,max=10):
    """Generates a random vector (in an array) of dimension N; the                                          
    values are integers in the range [min,max]."""
    out = []
    for k in range(N):
        out.append(random.randint(min,max))
    return out

def check(v1,v2):
    if len(v1)!=len(v2):
        raise ValueError,"the lenght of both arrays must be the same"
    pass

def d0(v1,v2):
    """                                                                                                     
    d0 is Nominal approach:                                                                                 
    multiply/add in a loop                                                                                  
    """
    check(v1,v2)
    out = 0
    for k in range(len(v1)):
        out += v1[k] * v2[k]
    return out

def d1(v1,v2):
    """                                                                                                     
    d1 uses an imap (from itertools)                                                                        
    """
    check(v1,v2)
    return sum(imap(mul,v1,v2))

def d2(v1,v2):
    """                                                                                                     
    d2 uses a conventional map                                                                              
    """
    check(v1,v2)
    return sum(map(mul,v1,v2))

def d3(v1,v2):
    """                                                                                                     
    d3 uses a starmap (itertools) to apply the mul operator on an izipped (v1,v2)                           
    """
    check(v1,v2)
    return sum(starmap(mul,izip(v1,v2)))

# generate the test vectors                                                                                 
v1 = v()
v2 = v()

if __name__ == '__main__':

    # Generate two test vectors of dimension N                                                              

    t0 = timeit.Timer("d0(v1,v2)","from dot_product import d0,v1,v2")
    t1 = timeit.Timer("d1(v1,v2)","from dot_product import d1,v1,v2")
    t2 = timeit.Timer("d2(v1,v2)","from dot_product import d2,v1,v2")
    t3 = timeit.Timer("d3(v1,v2)","from dot_product import d3,v1,v2")

    print "d0 elapsed: ", t0.timeit()
    print "d1 elapsed: ", t1.timeit()
    print "d2 elapsed: ", t2.timeit()
    print "d3 elapsed: ", t3.timeit()

请注意,文件的名称必须是 dot_product.py 让脚本运行;我在Mac OS X版本10.5.8上使用了Python 2.5.1。

编辑:

我运行N = 1000的脚本,这些是结果(以秒为单位,运行一百万次):

d0: 205.35457
d1: 208.13006
d2: 230.07463
d3: 155.29670

我想可以安全地假设,实际上,选项三是最快的,选项二是最慢的(四个中提到的)。


4627
2017-12-01 19:13


起源

@Arrieta:您可以通过将'from dot_product'替换为'from'来删除文件名为dot_product.py的要求 主要”。 - unutbu
@unutbu:当然,我认为用快速运行保存文件比修改脚本更简单。谢谢。 - Escualo
我的结果是:d0已过去:13.4328830242 d1已过去:9.52215504646 d2已过去:10.1050257683 d3已过去:9.16764998436务必检查d1和d3之间的差异是否具有统计显着性。 - liori
@liori:没错。我正在运行N = 1000的问题,并期望看到更大的差异。 - Escualo
如果你反复做一个点积,让其中一个向量保持不变,那么动态编译方法可能值得研究。固定部分为0的所有项都可以完全去除,如果固定部分为1,则可以消除乘法。 - Nick Johnson


答案:


为了好玩,我写了一个使用numpy的“d4”:

from numpy import dot
def d4(v1, v2): 
    check(v1, v2)
    return dot(v1, v2)

我的结果(Python 2.5.1,XP Pro sp3,2GHz Core2 Duo T7200):

d0 elapsed:  12.1977242918
d1 elapsed:  13.885232341
d2 elapsed:  13.7929552499
d3 elapsed:  11.0952246724

    d4逝去了:56.3278584289#去numpy!

而且,为了更有趣,我打开了psyco:

d0 elapsed:  0.965477735299
d1 elapsed:  12.5354792299
d2 elapsed:  12.9748163524
d3 elapsed:  9.78255448667

    d4逝去时间:54.4599059378

基于此,我宣布d0为胜利者:)


更新

@ kaiser.se:我可能应该首先提到我确实将所有内容转换为numpy数组:

from numpy import array
v3 = [array(vec) for vec in v1]
v4 = [array(vec) for vec in v2]

# then
t4 = timeit.Timer("d4(v3,v4)","from dot_product import d4,v3,v4")

我包括在内 check(v1, v2) 因为它包含在其他测试中。离开它会给numpy一个不公平的优势(虽然看起来它可以使用一个)。数组转换削减了大约一秒钟(比我想象的要少得多)。

我的所有测试均以N = 50运行。

@nikow:我正在使用numpy 1.0.4,这无疑是旧的,它确实有可能在我安装它之后的一年半里提高了性能。


更新#2

@ kaiser.se哇,你完全正确。我一定是在想这些是列表或者其他东西(我真的不知道我在想什么......对于编程而言+1)。

这看起来如何:

v3 = array(v1)
v4 = array(v2)

新结果:

d4 elapsed:  3.22535741274

使用Psyco:

d4 elapsed:  2.09182619579

d0仍然赢得Psyco,但numpy整体可能更好,特别是对于更大的数据集。

昨天我有点困扰我缓慢的numpy结果,因为可能是numpy用于 很多 计算并进行了大量优化。显然,不要打扰我的结果:)


8
2017-12-01 20:12



伟大的发现,塞思!首先,令人难以置信的是Numpy太慢了!我希望它会快得多。另外,我对Psyco一无所知(我认为自己是一个Python迷!) - 感谢你指出它,我将明确地检查出来。最后,有趣的是,基本上,Psyco的东西为d0制作了纯粹优化的C代码,并且不知道如何优化d3。我想消息是,如果你想使用Psyco,你应该布置算法,以便它可以被优化(而不是“隐藏”其构造背后的逻辑)。再次,伟大的发现! - Escualo
你的numpy安装可能有问题。在我的机器上,numpy比其他选项快得多(我没有尝试过psyco)。并且N = 50对于显示其强度的numpy来说有点小。 - nikow
你这样做是错的。使numpy数组一次(而不是传递将由numpy转换的列表 每一次),numpy会快得多。也放弃支票。 - u0b34a0f6ae
你在做 非常 错误。您正在将列表传递给numpy。事实上,单元素numpy数组的列表。 - u0b34a0f6ae
感谢更新!只是另一个例子,很难正确使用numpy。 - u0b34a0f6ae


我不知道更快,但我建议:

sum(i*j for i, j in zip(v1, v2))

它更容易阅读,甚至不需要标准库模块。


4
2017-12-01 19:53



@SilentGhost:你的方法需要更长的时间。对于N = 10,它花了18.0258秒(一百万次运行)。我要找的是速度;确实可读性不是问题,因为点积是从函数调用的(udotv = dot(u,v)),我可以在dot的定义中尽可能多地注释代码。你的方法真的不合适。 - Escualo
@SilentGhost,快速删除:将zip更改为itertools.izip可将时间减少到15.84879。也许值得知道? - Escualo
如果表现如此重要,请用C语言写出来 - SilentGhost
对不起,SilentGhost。我想你错过了这一点。 - Escualo
这绝对是我要做的。如果这是一个问题,我会在Windows上输入psyco以获得性能。 - hughdbrown


这是与numpy的比较。我们将快速星图方法与 numpy.dot

首先,迭代普通的Python列表:

$ python -mtimeit "import numpy as np; r = range(100)" "np.dot(r,r)"
10 loops, best of 3: 316 usec per loop

$ python -mtimeit "import operator; r = range(100); from itertools import izip, starmap" "sum(starmap(operator.mul, izip(r,r)))"
10000 loops, best of 3: 81.5 usec per loop

numpy ndarray:

$ python -mtimeit "import numpy as np; r = np.arange(100)" "np.dot(r,r)"
10 loops, best of 3: 20.2 usec per loop

$ python -mtimeit "import operator; import numpy as np; r = np.arange(100); from itertools import izip, starmap;" "sum(starmap(operator.mul, izip(r,r)))"
10 loops, best of 3: 405 usec per loop

看到这一点,numpy数组上的numpy似乎最快,其次是使用list的python函数结构。


4
2017-12-02 15:25





请对此“d2a”功能进行基准测试,并将其与“d3”功能进行比较。

from itertools import imap, starmap
from operator import mul

def d2a(v1,v2):
    """
    d2a uses itertools.imap
    """
    check(v1,v2)
    return sum(imap(mul, v1, v2))

map(在Python 2.x上,这是我假设您使用的)在计算之前不必要地创建一个虚拟列表。


0
2017-12-23 15:20