Frage

Ich habe eine von Pearson Ähnlichkeitsbewertung implementiert für den Vergleich von zwei Wörterbücher von Werten. Es wird mehr Zeit in diesem Verfahren aufgewendet als anderswo (potenziell viele Millionen Anrufe), so ist dies eindeutig die kritische Methode zu optimieren.

Selbst die geringste Optimierung einen großen Einfluss auf meinen Code haben, so bin ich daran interessiert, auch die kleinsten Verbesserungen zu erforschen.

Hier ist, was ich bisher:

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
War es hilfreich?

Lösung

Scipy ist die schnellste!

I don einige Tests mit dem Code oben und auch mit einer Version habe ich auf meinem comp gefunden, siehe unten für die Ergebnisse und der Code:

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)

Andere Tipps

Die echte Geschwindigkeit zu erhöhen würde, indem gewonnen werden, um numpy oder scipy. Kurz davon gibt es microoptimizations: z.B. x*x ist schneller als pow(x,2); Sie können die Werte in der gleichen Zeit wie die Tasten, indem Sie statt extrahieren:

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

so etwas wie

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

und dann

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

und so weiter; ob jeder von ihnen bringt Zeitvorteil microbenchmarking braucht. Je nachdem, wie Sie diese Koeffizienten verwenden den Platz zurückkehren würde sparen Sie sqrt (das ist eine ähnliche Idee zur Verwendung von Quadraten der Abstände zwischen den Punkten, in der Geometrie, eher als die Abstände selbst, und aus dem gleichen Grund - spart Ihnen eine sqrt ; das macht Sinn, weil der Koeffizient ein Abstand ist, ein bisschen ...; -).

Wenn Sie scipy verwenden können, können Sie die pearson-Funktion: http://svn.scipy.org/svn/scipy/trunk/scipy/stats/stats.py (für def pearson() suchen). In dem Code np nur numpy ist (der Code tut import numpy as np).

Ich würde vorschlagen, zu ändern:

[val for val in v1 if val in v2]

set(v1) & set(v2)

do

if not n: return 0.0    # and similar for den

statt

if n == 0: return 0.0

und es lohnt sich zu ersetzen letzten 6 Zeilen mit:

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

Da es sieht aus wie Sie ziemlich viel numerischen Berechnung zu tun, sollten Sie Psyco ein Schuss. Es ist ein JIT-Compiler, die laufenden Code analysiert und optimiert bestimmte Operationen. Installieren Sie es, dann am Anfang der Datei Put:

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

Dies wird Psyco der JIT aktivieren und sollten Sie Ihren Code ein wenig beschleunigen, kostenlos :) (eigentlich nicht, es braucht mehr Speicherplatz)

Wenn die Eingänge auf alle Ihre mathematischen Funktionen ziemlich eingeschränkt sind, können Sie eine Lookup-Tabelle verwenden anstelle der mathematische Funktion. Dies kann man einige Performance (Geschwindigkeit) auf Kosten der zusätzlichen Speicher verdienen, um die Tabelle zu speichern.

Ich bin mir nicht sicher, ob dies in Python hält. Aber die sqrt Berechnung ist eine prozessorintensive Berechnung.

Sie könnten für eine schnelle Annäherung newton

Ich werde schreiben, was ich habe, so weit wie eine Antwort mich von der Frage zu unterscheiden. Dies ist eine Kombination von einigen oben beschriebenen Techniken scheint die beste Verbesserung gegeben zu haben ist weit.

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

Edit:. Es sieht aus wie psyco 15% improvment für diese Version gibt, die nicht massiv ist, aber genug, um seine Verwendung zu rechtfertigen

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top