我写了一个方法来计算两个数组之间的余弦距离:
def cosine_distance(a, b):
if len(a) != len(b):
return False
numerator = 0
denoma = 0
denomb = 0
for i in range(len(a)):
numerator += a[i]*b[i]
denoma += abs(a[i])**2
denomb += abs(b[i])**2
result = 1 - numerator / (sqrt(denoma)*sqrt(denomb))
return result
在大型阵列上运行它可能会非常慢。这个方法的优化版本会运行得更快吗?
更新:我已经尝试了迄今为止的所有建议,包括scipy。这是打败的版本,结合迈克和史蒂夫的建议:
def cosine_distance(a, b):
if len(a) != len(b):
raise ValueError, "a and b must be same length" #Steve
numerator = 0
denoma = 0
denomb = 0
for i in range(len(a)): #Mike's optimizations:
ai = a[i] #only calculate once
bi = b[i]
numerator += ai*bi #faster than exponent (barely)
denoma += ai*ai #strip abs() since it's squaring
denomb += bi*bi
result = 1 - numerator / (sqrt(denoma)*sqrt(denomb))
return result
答案 0 :(得分:8)
如果您可以使用SciPy,则可以使用cosine
中的spatial.distance
:
http://docs.scipy.org/doc/scipy/reference/spatial.distance.html
如果您不能使用SciPy,您可以尝试通过重写Python来获得一个小的加速(编辑:但它没有像我想象的那样工作,见下文)。
from itertools import izip
from math import sqrt
def cosine_distance(a, b):
if len(a) != len(b):
raise ValueError, "a and b must be same length"
numerator = sum(tup[0] * tup[1] for tup in izip(a,b))
denoma = sum(avalue ** 2 for avalue in a)
denomb = sum(bvalue ** 2 for bvalue in b)
result = 1 - numerator / (sqrt(denoma)*sqrt(denomb))
return result
当a和b的长度不匹配时,最好引发异常。
通过在sum()
调用中使用生成器表达式,您可以计算您的值,其中大部分工作由Python内部的C代码完成。这应该比使用for
循环更快。
我还没有计时,所以我无法猜测它会有多快。但是SciPy代码几乎肯定是用C或C ++编写的,它应该尽可能快。
如果您正在使用Python进行生物信息学,那么您真的应该使用SciPy。
编辑:Darius Bacon计时我的代码并发现它变慢了。所以我计算了我的代码......是的,它更慢了。适合所有人的教训:当你试图加快速度时,不要猜测,测量。令我感到困惑的是,为什么我在Python的C内部进行更多工作的尝试速度较慢。我尝试了1000长度的列表,它仍然较慢。
我不能再花时间试图巧妙地破解Python了。如果你需要更快的速度,我建议你尝试SciPy。
编辑:我只是手工测试,没有时间。我发现,对于简短的a和b,旧代码更快;对于长a和b,新代码更快;在这两种情况下,差异并不大。 (我现在想知道我是否可以信任我的Windows计算机上的timeit;我想在Linux上再次尝试这个测试。)我不会改变工作代码来试图让它更快。还有一次我敦促你去尝试SciPy。 : - )答案 1 :(得分:8)
(我原本以为)如果没有突破C(如numpy或scipy)或改变你的计算,你就不会加速它。但无论如何,这就是我尝试的方式:
from itertools import imap
from math import sqrt
from operator import mul
def cosine_distance(a, b):
assert len(a) == len(b)
return 1 - (sum(imap(mul, a, b))
/ sqrt(sum(imap(mul, a, a))
* sum(imap(mul, b, b))))
在Python 2.6中使用500k元素阵列的速度大约是其两倍。 (在将地图更改为imap之后,请遵循Jarret Hardie。)
以下是原始海报修订代码的调整版本:
from itertools import izip
def cosine_distance(a, b):
assert len(a) == len(b)
ab_sum, a_sum, b_sum = 0, 0, 0
for ai, bi in izip(a, b):
ab_sum += ai * bi
a_sum += ai * ai
b_sum += bi * bi
return 1 - ab_sum / sqrt(a_sum * b_sum)
这很难看,但确实更快。 。
修改并尝试Psyco!它将最终版本的速度提高了4倍。我怎能忘记?
答案 2 :(得分:2)
如果您正在对其进行平衡,则无需abs()
a[i]
和b[i]
。{/ p>
将a[i]
和b[i]
存储在临时变量中,以避免多次执行索引。
也许编译器可以优化它,但可能不是。
检查**2
运算符。是将它简化为乘法,还是使用通用幂函数(log - 乘以2 - 反对数)。
不要两次做sqrt(虽然费用很小)。做sqrt(denoma * denomb)
。
答案 3 :(得分:1)
对于大约1000多个元素的数组,这个速度更快。
from numpy import array
def cosine_distance(a, b):
a=array(a)
b=array(b)
numerator=(a*b).sum()
denoma=(a*a).sum()
denomb=(b*b).sum()
result = 1 - numerator / sqrt(denoma*denomb)
return result
答案 4 :(得分:1)
与Darius Bacon的答案类似,我一直在玩弄操作员和itertools以产生更快的答案。根据timeit,以下似乎在500项阵列上快了1/3:
from math import sqrt
from itertools import imap
from operator import mul
def op_cosine(a, b):
dot_prod = sum(imap(mul, a, b))
a_veclen = sqrt(sum(i ** 2 for i in a))
b_veclen = sqrt(sum(i ** 2 for i in b))
return 1 - dot_prod / (a_veclen * b_veclen)
答案 5 :(得分:1)
使用SciPy内部的C代码对于长输入数组来说非常有用。使用简单直接的Python赢得短输入数组; Darius Bacon基于izip()
的代码最佳基准测试。因此,最终的解决方案是根据输入数组的长度决定在运行时使用哪一个:
from scipy.spatial.distance import cosine as scipy_cos_dist
from itertools import izip
from math import sqrt
def cosine_distance(a, b):
len_a = len(a)
assert len_a == len(b)
if len_a > 200: # 200 is a magic value found by benchmark
return scipy_cos_dist(a, b)
# function below is basically just Darius Bacon's code
ab_sum = a_sum = b_sum = 0
for ai, bi in izip(a, b):
ab_sum += ai * bi
a_sum += ai * ai
b_sum += bi * bi
return 1 - ab_sum / sqrt(a_sum * b_sum)
我制作了一个测试线束,测试了不同长度输入的功能,发现大约200长度的SciPy功能开始获胜。输入数组越大,获胜的越大。对于非常短的长度数组,比如长度3,更简单的代码获胜。这个函数增加了很少的开销来决定采用哪种方式,然后以最好的方式进行。
如果您有兴趣,这是测试工具:
from darius2 import cosine_distance as fn_darius2
fn_darius2.__name__ = "fn_darius2"
from ult import cosine_distance as fn_ult
fn_ult.__name__ = "fn_ult"
from scipy.spatial.distance import cosine as fn_scipy
fn_scipy.__name__ = "fn_scipy"
import random
import time
lst_fn = [fn_darius2, fn_scipy, fn_ult]
def run_test(fn, lst0, lst1, test_len):
start = time.time()
for _ in xrange(test_len):
fn(lst0, lst1)
end = time.time()
return end - start
for data_len in range(50, 500, 10):
a = [random.random() for _ in xrange(data_len)]
b = [random.random() for _ in xrange(data_len)]
print "len(a) ==", len(a)
test_len = 10**3
for fn in lst_fn:
n = fn.__name__
r = fn(a, b)
t = run_test(fn, a, b, test_len)
print "%s:\t%f seconds, result %f" % (n, t, r)
答案 6 :(得分:0)
def cd(a,b):
if(len(a)!=len(b)):
raise ValueError, "a and b must be the same length"
rn = range(len(a))
adb = sum([a[k]*b[k] for k in rn])
nma = sqrt(sum([a[k]*a[k] for k in rn]))
nmb = sqrt(sum([b[k]*b[k] for k in rn]))
result = 1 - adb / (nma*nmb)
return result
答案 7 :(得分:0)
您的更新解决方案仍然有两个平方根。您可以通过将sqrt行替换为:
将其减少为1结果= 1 - 分子/ (SQRT(denoma * denomb))
乘法通常比sqrt快一点。它可能看起来似乎没有多少,因为它只在函数中被调用一次,但听起来你正在计算很多余弦距离,所以这种改进会加起来。
您的代码看起来应该适合矢量优化。因此,如果跨平台支持不是问题,并且您希望进一步加快速度,则可以在C中编码余弦距离代码,并确保您的编译器正在积极地对所得到的代码进行矢量化(即使Pentium II能够进行某些浮点矢量化)