Pearson相似度得分,我该如何进一步优化?

时间:2009-08-20 15:34:27

标签: python optimization similarity pearson

我已经实施了Pearson的相似度分数,用于比较两个值词典。此方法花费的时间比其他任何地方都多(可能有数百万次调用),因此这显然是优化的关键方法。

即使是最轻微的优化也会对我的代码产生重大影响,所以我很想探索最小的改进。

这是我到目前为止所拥有的:

def simple_pearson(v1,v2):

    si = [val for val in v1 if val in v2]

    n = len(si)

    if n==0: return 0.0

    sum1 = 0.0
    sum2 = 0.0
    sum1_sq = 0.0
    sum2_sq = 0.0
    p_sum = 0.0

    for v in si:
        val_1 = v1[v]
        val_2 = v2[v]
        sum1+=val_1
        sum2+=val_2
        sum1_sq+=pow(val_1,2)
        sum2_sq+=pow(val_2,2)
        p_sum+=val_1*val_2

    # Calculate Pearson score
    num = p_sum-(sum1*sum2/n)
    temp = (sum1_sq-pow(sum1,2)/n) * (sum2_sq-pow(sum2,2)/n)
    if temp < 0.0:
        temp = -temp
    den = sqrt(temp)
    if den==0: return 1.0

    r = num/den

    return r

8 个答案:

答案 0 :(得分:4)

真正的速度增加将通过移动到numpy或scipy来获得。除此之外,还有微观优化:例如: x*xpow(x,2)快;您可以通过执行而不是:

来与键同时提取值
si = [val for val in v1 if val in v2]

类似

vs = [ (v1[val],v2[val]) for val in v1 if val in v2]

然后

sum1 = sum(x for x, y in vs)

等等;是否每个都带来时间优势需要微基准测试。根据您使用这些系数的方式返回,方形将为您节省一个sqrt(这与使用几何点之间的距离平方,而不是距离本身类似,并且出于同样的原因 - 为您节省一个sqrt ;这是有道理的,因为系数是一个距离,有点......; - )。

答案 1 :(得分:2)

如果你可以使用scipy,你可以使用pearson函数:http://www.scipy.org/doc/api_docs/SciPy.stats.stats.html#pearsonr

或者您可以从http://svn.scipy.org/svn/scipy/trunk/scipy/stats/stats.py复制/粘贴代码(它有自由许可)(搜索def pearson())。 在代码中np只是numpy(代码执行import numpy as np)。

答案 2 :(得分:2)

Scipy是最快的!

我已经使用上面的代码进行了一些测试,还有我在comp上找到的版本,请参阅下面的结果和代码:

pearson 14.7597990757
sim_pearson 15.6806837987
scipy:pearsonr 0.451986019188

try:
    import psyco
    psyco.full()
except ImportError:
    pass

from math import sqrt

def sim_pearson(set1, set2):
    si={}
    for item in set1:
        if item in set2:
            si[item] = 1

    #number of elements
    n = len(si)

    #if none common, return 0 similarity
    if n == 0: return 0

    #add up all the preferences
    sum1 = sum([set1[item] for item in si])
    sum2 = sum([set2[item] for item in si])

    #sum up the squares
    sum_sq1 = sum([pow(set1[item], 2) for item in si])
    sum_sq2 = sum([pow(set2[item], 2) for item in si])

    #sum up the products
    sum_p = sum([set1[item] * set2[item] for item in si])

    nom = sum_p - ((sum1 * sum2) / n )
    den = sqrt( (sum_sq1 - (sum1)**2 / n) * (sum_sq2 - (sum2)**2 / n) )

    if den==0: return 0
    return nom/den



# from http://stackoverflow.com/questions/1307016/pearson-similarity-score-how-can-i-optimise-this-further
def pearson(v1, v2):
    vs = [(v1[val],v2[val]) for val in v1 if val in v2]

    n = len(vs)

    if n==0: return 0.0

    sum1,sum2,sum1_sq,sum2_sq,p_sum = 0.0, 0.0, 0.0, 0.0, 0.0

    for v1,v2 in vs:
        sum1+=v1
        sum2+=v2
        sum1_sq+=v1*v1
        sum2_sq+=v2*v2
        p_sum+=v1*v2

    # Calculate Pearson score
    num = p_sum-(sum1*sum2/n)
    temp = max((sum1_sq-pow(sum1,2)/n) * (sum2_sq-pow(sum2,2)/n),0)
    if temp:
        return num / sqrt(temp)
    return 1.0






if __name__ == "__main__":
    import timeit

    tsetup = """
from random import randrange
from __main__ import pearson, sim_pearson
from scipy.stats import pearsonr
v1 = [randrange(0,1000) for x in range(1000)]
v2 = [randrange(0,1000) for x in range(1000)]
#gc.enable()
"""
    t1 = timeit.Timer(stmt="pearson(v1,v2)", setup=tsetup)
    t2 = timeit.Timer(stmt="sim_pearson(v1,v2)", setup=tsetup)
    t3 = timeit.Timer(stmt="pearsonr(v1,v2)", setup=tsetup)

    tt = 1000

    print 'pearson', t1.timeit(tt)
    print 'sim_pearson', t2.timeit(tt)
    print 'scipy:pearsonr', t3.timeit(tt)

答案 3 :(得分:1)

我建议改变:

[val for val in v1 if val in v2]

set(v1) & set(v2)

DO

if not n: return 0.0    # and similar for den

而不是

if n == 0: return 0.0

并且值得用以下代码替换最后6行:

try:
    return num / sqrt(abs(temp))
except ZeroDivisionError:
    return 1.0

答案 4 :(得分:1)

因为看起来你正在进行相当多的数值计算,所以你应该给出 Psyco 一个镜头。它是一个JIT编译器,可以分析运行代码并优化某些操作。安装它,然后在文件顶部放置:

try:
    import psyco
    psyco.full()
except ImportError:
    pass

这将启用Psyco的JIT并且应该在某种程度上加速你的代码,免费:)(实际上不是,它会占用更多内存)

答案 5 :(得分:0)

如果您的任何数学函数的输入受到相当大的约束,则可以使用查找表而不是数学函数。这可以以存储表格的额外内存为代价获得一些性能(速度)。

答案 6 :(得分:0)

我不确定这是否适用于Python。但计算sqrt是处理器密集型计算。

您可以选择快速近似newton

答案 7 :(得分:0)

我会发布我迄今为止的答案,以区别于问题。这是上述一些技术的组合,似乎已经给出了最好的改进。

def pearson(v1,v2):
    vs = [(v1[val],v2[val]) for val in v1 if val in v2]

    n = len(vs)

    if n==0: return 0.0

    sum1,sum2,sum1_sq,sum2_sq,p_sum = 0.0, 0.0, 0.0, 0.0, 0.0

    for v1,v2 in vs:
        sum1+=v1
        sum2+=v2
        sum1_sq+=v1*v1
        sum2_sq+=v2*v2
        p_sum+=v1*v2

    # Calculate Pearson score
    num = p_sum-(sum1*sum2/n)
    temp = max((sum1_sq-pow(sum1,2)/n) * (sum2_sq-pow(sum2,2)/n),0)
    if temp:
        return num / sqrt(temp)
    return 1.0

编辑:看起来psyco为这个版本提供了15%的改进,这个版本并不大,但足以证明其使用的合理性。