web-dev-qa-db-ja.com

numpy配列の最大のN個の要素をすばやく見つける方法

私は次のようにできることを知っています:

import numpy as np
N=10
a=np.arange(1,100,1)
np.argsort()[-N:]

ただし、完全にソートされているため、非常に低速です。

私はnumpyがそれを速くするいくつかのメソッドを提供しているかどうか疑問に思います。

42
Hailiang Zhang

bottleneck モジュールには、Numpy配列で直接機能する高速な部分ソート方法があります: bottleneck.partition()

bottleneck.partition()はソートされた実際の値を返すことに注意してください。ソートされた値のインデックス(numpy.argsort()が返すもの)が必要な場合は bottleneck.argpartition()

私はベンチマークしました:

  • z = -bottleneck.partition(-a, 10)[:10]
  • z = a.argsort()[-10:]
  • z = heapq.nlargest(10, a)

ここで、aはランダムな1,000,000要素の配列です。

タイミングは次のとおりです。

  • bottleneck.partition():ループあたり25.6ミリ秒
  • np.argsort():ループあたり198ミリ秒
  • heapq.nlargest():ループあたり358ミリ秒
42
NPE

numpy 1.8は、部分ソートを実行するpartitionおよびargpartitionを実装します(O(n)時間で、O(n) * log(n))。

import numpy as np

test = np.array([9,1,3,4,8,7,2,5,6,0])

temp = np.argpartition(-test, 4)
result_args = temp[:4]

temp = np.partition(-test, 4)
result = -temp[:4]

結果:

>>> result_args
array([0, 4, 8, 5]) # indices of highest vals
>>> result
array([9, 8, 6, 7]) # highest vals

タイミング:

In [16]: a = np.arange(10000)

In [17]: np.random.shuffle(a)

In [18]: %timeit np.argsort(a)
1000 loops, best of 3: 1.02 ms per loop

In [19]: %timeit np.argpartition(a, 100)
10000 loops, best of 3: 139 us per loop

In [20]: %timeit np.argpartition(a, 1000)
10000 loops, best of 3: 141 us per loop
57
Akavall

提案されたボトルネックソリューションの各マイナス記号

-bottleneck.partsort(-a, 10)[:10]

データのコピーを作成します。コピーを削除することができます

bottleneck.partsort(a, a.size-10)[-10:]

また、提案されたnumpyソリューション

a.argsort()[-10:]

値ではなくインデックスを返します。修正方法は、インデックスを使用して値を見つけることです。

a[a.argsort()[-10:]]

2つのアプローチは異なるポイントでデータを分割するため、2つのボトルネックソリューションの相対速度は、初期配列の要素の順序に依存します。

言い換えれば、特定のランダム配列を使用してタイミングを調整すると、どちらの方法でも速く見えるようになります。

それぞれが1,000,000個の要素を持つ100個のランダム配列全体でタイミングを平均すると、

-bn.partsort(-a, 10)[:10]: 1.76 ms per loop
bn.partsort(a, a.size-10)[-10:]: 0.92 ms per loop
a[a.argsort()[-10:]]: 15.34 ms per loop

タイミングコードは次のとおりです。

import time
import numpy as np
import bottleneck as bn

def bottleneck_1(a):
    return -bn.partsort(-a, 10)[:10]

def bottleneck_2(a):
    return bn.partsort(a, a.size-10)[-10:]

def numpy(a):
    return a[a.argsort()[-10:]]

def do_nothing(a):
    return a

def benchmark(func, size=1000000, ntimes=100):
    t1 = time.time()
    for n in range(ntimes):
        a = np.random.Rand(size)
        func(a)
    t2 = time.time()
    ms_per_loop = 1000000 * (t2 - t1) / size
    return ms_per_loop

t1 = benchmark(bottleneck_1)
t2 = benchmark(bottleneck_2)
t3 = benchmark(numpy)
t4 = benchmark(do_nothing)

print "-bn.partsort(-a, 10)[:10]: %0.2f ms per loop" % (t1 - t4)
print "bn.partsort(a, a.size-10)[-10:]: %0.2f ms per loop" % (t2 - t4)
print "a[a.argsort()[-10:]]: %0.2f ms per loop" % (t3 - t4)
11
kwgoodman

この問題が発生しました。この質問は5歳なので、すべてのベンチマークをやり直してボトルネックの構文を変更する必要がありました(partsortはもうありません。今はpartitionです)。

取得した要素の数を50に増やした以外は、kwgoodmanと同じ引数を使用しました(特定の状況に合わせて)。

私はこれらの結果を得ました:

bottleneck 1: 01.12 ms per loop
bottleneck 2: 00.95 ms per loop
pandas      : 01.65 ms per loop
heapq       : 08.61 ms per loop
numpy       : 12.37 ms per loop
numpy 2     : 00.95 ms per loop

そのため、bottleneck_2とnumpy_2(adasのソリューション)は結び付けられました。ただし、np.percentile(numpy_2)これらのtopN要素は既にソートされていますが、他のソリューションの場合はそうではありません。一方、これらの要素のインデックスにも関心がある場合、パーセンタイルは役に立ちません。

pandasも追加しました。これは、可能であれば、その下にボトルネックを使用します( http://pandas.pydata.org/pandas-docs/stable/install.html#recommended-dependencies )。既にpandas SeriesまたはDataFrameを持っている場合は、手始めにnlargestを使用すれば完了です。

ベンチマークに使用されるコードは次のとおりです(python 3をご覧ください)。

import time
import numpy as np
import bottleneck as bn
import pandas as pd
import heapq

def bottleneck_1(a, n):
    return -bn.partition(-a, n)[:n]

def bottleneck_2(a, n):
    return bn.partition(a, a.size-n)[-n:]

def numpy(a, n):
    return a[a.argsort()[-n:]]

def numpy_2(a, n):
    M = a.shape[0]
    perc = (np.arange(M-n,M)+1.0)/M*100
    return np.percentile(a,perc)

def pandas(a, n):
    return pd.Series(a).nlargest(n)

def hpq(a, n):
    return heapq.nlargest(n, a)

def do_nothing(a, n):
    return a[:n]

def benchmark(func, size=1000000, ntimes=100, topn=50):
    t1 = time.time()
    for n in range(ntimes):
        a = np.random.Rand(size)
        func(a, topn)
    t2 = time.time()
    ms_per_loop = 1000000 * (t2 - t1) / size
    return ms_per_loop

t1 = benchmark(bottleneck_1)
t2 = benchmark(bottleneck_2)
t3 = benchmark(pandas)
t4 = benchmark(hpq)
t5 = benchmark(numpy)
t6 = benchmark(numpy_2)
t0 = benchmark(do_nothing)

print("bottleneck 1: {:05.2f} ms per loop".format(t1 - t0))
print("bottleneck 2: {:05.2f} ms per loop".format(t2 - t0))
print("pandas      : {:05.2f} ms per loop".format(t3 - t0))
print("heapq       : {:05.2f} ms per loop".format(t4 - t0))
print("numpy       : {:05.2f} ms per loop".format(t5 - t0))
print("numpy 2     : {:05.2f} ms per loop".format(t6 - t0))
10
Tacio Medeiros

おそらくheapq.nlargest

import numpy as np
import heapq

x = np.array([1,-5,4,6,-3,3])

z = heapq.nlargest(3,x)

結果:

>>> z
[6, 4, 3]

nを使用してbottleneckの最大要素のインデックスを検索する場合は、bottleneck.argpartsort

>>> x = np.array([1,-5,4,6,-3,3])
>>> z = bottleneck.argpartsort(-x, 3)[:3]
>>> z
array([3, 2, 5]
7
Akavall

Numpyのパーセンタイル関数を使用することもできます。私の場合、bottleneck.partsort()よりわずかに高速でした:

import timeit
import bottleneck as bn

N,M,K = 10,1000000,100

start = timeit.default_timer()
for k in range(K):
    a=np.random.uniform(size=M)
    tmp=-bn.partsort(-a, N)[:N]
stop = timeit.default_timer()
print (stop - start)/K

start = timeit.default_timer()
perc = (np.arange(M-N,M)+1.0)/M*100
for k in range(K):
    a=np.random.uniform(size=M)
    tmp=np.percentile(a,perc)
stop = timeit.default_timer()
print (stop - start)/K

ループあたりの平均時間:

  • bottleneck.partsort():59ミリ秒
  • np.percentile():54ミリ秒
2
Aetienne Sardon

配列を数値のリストとして保存しても問題がない場合は、次を使用できます。

import heapq
heapq.nlargest(N, a)

N最大メンバーを取得します。

1
Mike Graham