web-dev-qa-db-ja.com

pythonの最速のペアワイズ距離メトリック

数値の1D配列があり、すべてのペアワイズユークリッド距離を計算したいと考えています。ブロードキャストでこれを行う方法(SOのおかげ)がありますが、各距離を2回計算するので非効率的です。そして、それはうまくスケーリングしません。

これは、1000個の数値の配列で必要なものを提供する例です。

import numpy as np
import random
r = np.array([random.randrange(1, 1000) for _ in range(0, 1000)])
dists = np.abs(r - r[:, None])

1D配列の値が10kを超える状況に合わせてスケーリングする必要がある場合、これを行うために使用できるscipy/numpy/scikit-learnの最も高速な実装は何ですか。

注:行列は対称なので、これに対処することで少なくとも2倍の高速化を実現できると思いますが、方法はわかりません。

14
roblanf

他の回答はどちらも質問に完全には答えませんでした。1つはCythonにあり、1つはより遅いものでした。しかし、どちらも非常に役立つヒントを提供しました。それらをフォローアップすると、scipy.spatial.distance.pdistは進むべき道です。

ここにいくつかのコードがあります:

import numpy as np
import random
import sklearn.metrics.pairwise
import scipy.spatial.distance

r = np.array([random.randrange(1, 1000) for _ in range(0, 1000)])
c = r[:, None]

def option1(r):
    dists = np.abs(r - r[:, None])

def option2(r):
    dists = scipy.spatial.distance.pdist(r, 'cityblock')

def option3(r):
    dists = sklearn.metrics.pairwise.manhattan_distances(r)

IPythonによるタイミング:

In [36]: timeit option1(r)
100 loops, best of 3: 5.31 ms per loop

In [37]: timeit option2(c)
1000 loops, best of 3: 1.84 ms per loop

In [38]: timeit option3(c)
100 loops, best of 3: 11.5 ms per loop

私はCython実装を試していません(このプロジェクトには使用できません)が、私の結果を他の答えと比較すると、scipy.spatial.distance.pdistは、Cython実装より約3分の1遅いです(np.absソリューションでベンチマークすることにより、さまざまなマシンを考慮に入れます)。

22
roblanf

これは、私のコンピューターでのこの例の速度を3倍以上向上させるCython実装です。 BLASルーチンはおそらくこの単純なコードよりもはるかに優れたスケーリングを行うことができるので、このタイミングはより大きな配列については厳しくレビューする必要があります。

あなたがscipy/numpy/scikit-learnの内部で何かを要求したことを知っていますが、これはあなたに新しい可能性を開くでしょう:

ファイルmy_cython.pyx

import numpy as np
cimport numpy as np
import cython

cdef extern from "math.h":
    double abs(double t)

@cython.wraparound(False)
@cython.boundscheck(False)
def pairwise_distance(np.ndarray[np.double_t, ndim=1] r):
    cdef int i, j, c, size
    cdef np.ndarray[np.double_t, ndim=1] ans
    size = sum(range(1, r.shape[0]+1))
    ans = np.empty(size, dtype=r.dtype)
    c = -1
    for i in range(r.shape[0]):
        for j in range(i, r.shape[0]):
            c += 1
            ans[c] = abs(r[i] - r[j])
    return ans

答えは、すべての繰り返されない評価を含む1次元配列です。

Pythonにインポートするには:

import numpy as np
import random

import pyximport; pyximport.install()
from my_cython import pairwise_distance

r = np.array([random.randrange(1, 1000) for _ in range(0, 1000)], dtype=float)

def solOP(r):
    return np.abs(r - r[:, None])

IPythonによるタイミング:

In [2]: timeit solOP(r)
100 loops, best of 3: 7.38 ms per loop

In [3]: timeit pairwise_distance(r)
1000 loops, best of 3: 1.77 ms per loop
5

メモリの半分を使用しますが、np.abs(r - r[:, None])より6倍遅くなります。

triu = np.triu_indices(r.shape[0],1)
dists2 = abs(r[triu[1]]-r[triu[0]])
3
cyborg