Оптимизированный метод для вычисления расстояния косинуса в Python

В C # с Linq вы можете сделать это:

smallPortion = largeBytes.Take(4).ToArray();
largeBytes = largeBytes.Skip(4).Take(5).ToArray();

;)

9
задан Dan 1 December 2009 в 02:00
поделиться

7 ответов

Если вы можете использовать SciPy, вы можете использовать косинус из пространственного расстояния :

http: // docs .scipy.org / doc / scipy / reference / space.distance.html

Если вы не можете использовать SciPy, вы можете попытаться получить небольшое ускорение, переписав свой Python (EDIT: но это не сработало, как Я так и думал, см. Ниже).

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 () , вы можете вычислять свои значения, при этом большая часть работы выполняется кодом C внутри Python. Это должно быть быстрее, чем использование цикла для .

Я не рассчитал время, поэтому не могу предположить, насколько это может быть быстрее. Но код SciPy почти наверняка написан на C или C ++, и он должен быть настолько быстрым, насколько это возможно.

Если вы занимаетесь биоинформатикой на Python, вам все равно стоит использовать SciPy.

РЕДАКТИРОВАТЬ: Дариус Бэкон рассчитал свой код и обнаружил, что он медленнее. Я рассчитал свой код и ... да, он работает медленнее. Урок для всех: когда вы пытаетесь ускорить процесс, не гадайте, а измеряйте.

Я сбит с толку, почему моя попытка приложить больше усилий к внутренним компонентам C Python медленнее. Я пробовал его для списков длиной 1000, но он все еще был медленнее.

Я не могу больше тратить время на попытки хитроумно взломать Python. Если вам нужно больше скорости, я предлагаю вам попробовать SciPy.

РЕДАКТИРОВАТЬ: Я просто тестировал вручную, без тайм-аута. Я считаю, что для краткости a и b старый код работает быстрее; для длинных a и b новый код быстрее; в обоих случаях разница не велика. (Теперь мне интересно, могу ли я доверять timeit на моем компьютере с Windows; я хочу снова попробовать этот тест на Linux.) Я бы не стал менять рабочий код, чтобы попытаться получить его быстрее. И еще раз призываю вас попробовать SciPy. : -)

(Теперь мне интересно, могу ли я доверять timeit на моем компьютере с Windows; я хочу снова попробовать этот тест на Linux.) Я бы не стал менять рабочий код, чтобы попытаться получить его быстрее. И еще раз призываю вас попробовать SciPy. : -)

(Теперь мне интересно, могу ли я доверять timeit на моем компьютере с Windows; я хочу снова попробовать этот тест на Linux.) Я бы не стал менять рабочий код, чтобы попытаться получить его быстрее. И еще раз призываю вас попробовать SciPy. : -)

8
ответ дан 4 December 2019 в 10:04
поделиться

(изначально я думал) вы не собираетесь сильно его ускорять, не переходя на 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 с массивами из 500 тыс. Элементов. (После изменения карты на imap, вслед за Джарретом Харди.)

Вот измененная версия измененного кода исходного плаката:

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 раза. Как я мог забыть?

8
ответ дан 4 December 2019 в 10:04
поделиться

Подобно ответу Дариуса Бэкона, я играл с оператором и itertools, чтобы получить более быстрый ответ. Следующее выглядит на 1/3 быстрее в массиве из 500 элементов согласно timeit:

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)
1
ответ дан 4 December 2019 в 10:04
поделиться

Нет необходимости брать abs () из a [i] и b [i] , если вы возводите его в квадрат.

Сохраните a [i] и b [i] в временные переменные, чтобы не выполнять индексацию более одного раза. Может быть, компилятор сможет это оптимизировать, а может и нет.

Проверьте оператор ** 2 . Это упрощает его до умножения или используется общая степенная функция (log - умножить на 2 - antilog).

Не выполняйте sqrt дважды (хотя стоимость этого небольшая). Сделайте sqrt (denoma * denomb) .

2
ответ дан 4 December 2019 в 10:04
поделиться

Это быстрее для массивов размером около 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
1
ответ дан 4 December 2019 в 10:04
поделиться

Использование кода C внутри SciPy дает большие преимущества для длинных входных массивов. Использование простого и прямого Python дает преимущество для коротких входных массивов; Код Дариуса Бэкона 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)
1
ответ дан 4 December 2019 в 10:04
поделиться
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
0
ответ дан 4 December 2019 в 10:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: