web-dev-qa-db-ja.com

Python MATLABの「ismember」関数に相当

コードの最適化を何度も試みた後、最後のリソースは、複数のコアを使用して以下のコードを実行しようとすることであるように思われます。複数のコアを使用してコードをはるかに高速に実行できるように、コードを変換/再構築する方法が正確にわかりません。最終目標を達成するためのガイダンスをいただければ幸いです。最終的な目標は、各配列が約700,000個の要素を保持する配列AおよびBに対して、このコードを可能な限り高速に実行できるようにすることです。小さな配列を使用したコードは次のとおりです。 700k要素の配列はコメント化されています。

_import numpy as np

def ismember(a,b):
    for i in a:
        index = np.where(b==i)[0]
        if index.size == 0:
            yield 0
        else:
            yield index


def f(A, gen_obj):
    my_array = np.arange(len(A))
    for i in my_array:
        my_array[i] = gen_obj.next()
    return my_array


#A = np.arange(700000)
#B = np.arange(700000)
A = np.array([3,4,4,3,6])
B = np.array([2,5,2,6,3])

gen_obj = ismember(A,B)

f(A, gen_obj)

print 'done'
# if we print f(A, gen_obj) the output will be: [4 0 0 4 3]
# notice that the output array needs to be kept the same size as array A.
_

私がやろうとしているのは、 ismember [2]というMATLAB関数を模倣することです(次のようにフォーマットされています:[Lia,Locb] = ismember(A,B)Locb部分のみ。

Matlabから:Locb、BのメンバーであるAの各値のBの最小インデックスを含みます。出力配列Locbは、AがBのメンバーでない場合は常に0を含みます。

主な問題の1つは、この操作を可能な限り効率的に実行できる必要があることです。テスト用に、700k要素の2つの配列があります。ジェネレーターを作成してジェネレーターの値を調べることは、仕事を早く終わらせるようには見えません。

20
zd5151

複数のコアについて心配する前に、辞書を使用してismember関数の線形スキャンを排除します。

_def ismember(a, b):
    bind = {}
    for i, elt in enumerate(b):
        if elt not in bind:
            bind[elt] = i
    return [bind.get(itm, None) for itm in a]  # None can be replaced by any other "not in b" value
_

元の実装では、Aの要素ごとにBの要素を完全にスキャンして、O(len(A)*len(B))にする必要があります。上記のコードでは、dictBsetを生成するためにBを1回フルスキャンする必要があります。 dictを使用すると、Aの各要素に対してBの各要素のルックアップを効果的に一定にし、操作をO(len(A)+len(B))にします。それでも遅すぎる場合は、上記の関数を複数のコアで実行することを心配してください。

編集:インデックスも少し変更しました。 Matlabは、すべての配列がインデックス1で始まるため、0を使用します。Python/ numpyは配列を0で開始するため、データセットの場合は次のようになります。

_A = [2378, 2378, 2378, 2378]
B = [2378, 2379]
_

要素がない場合は0を返すと、結果はAのすべての要素を除外します。上記のルーチンは、インデックスがない場合は0ではなくNoneを返します。-1を返すことはオプションですが、Pythonは、それが配列の最後の要素であると解釈します。Noneは、配列へのインデックスとして使用される場合、例外を発生させます。別の動作が必要な場合は、Bind.get(item,None)の2番目の引数を変更してください。返される値への式。

17
sfstewman

sfstewmanの優れた回答は、おそらくあなたのために問題を解決しました。

Numpyだけで同じことを達成する方法を追加したいと思います。

私はnumpyの niquein1d 関数を利用します。

B_unique_sorted, B_idx = np.unique(B, return_index=True)
B_in_A_bool = np.in1d(B_unique_sorted, A, assume_unique=True)
  • B_unique_sortedには、ソートされたB内の一意の値が含まれます。
  • B_idxは、これらの値に対して、元のBへのインデックスを保持します。
  • B_in_A_boolは、B_unique_sortedの値がAにあるかどうかを格納するB_unique_sortedのサイズのブール配列です。
    注:出力が必要なため、Aで(Bからの一意の値)を探す必要がありますB_idxに関して返されます
    注:Aはすでに一意であると思います。

これで、B_in_A_boolを使用して共通の値を取得できます

B_unique_sorted[B_in_A_bool]

および元のBのそれぞれのインデックス

B_idx[B_in_A_bool]

最後に、テストはしていませんが、これは純粋なPython forループよりも大幅に高速であると思います。

13

リスト内包表記を使用してみてください。

_In [1]: import numpy as np

In [2]: A = np.array([3,4,4,3,6])

In [3]: B = np.array([2,5,2,6,3])

In [4]: [x for x in A if not x in B]
Out[4]: [4, 4]
_

一般に、リスト内包表記はforループよりもはるかに高速です。

等しい長さのリストを取得するには;

_In [19]: map(lambda x: x if x not in B else False, A)
Out[19]: [False, 4, 4, False, False]
_

これは、小さなデータセットの場合は非常に高速です。

_In [20]: C = np.arange(10000)

In [21]: D = np.arange(15000, 25000)

In [22]: %timeit map(lambda x: x if x not in D else False, C)
1 loops, best of 3: 756 ms per loop
_

大規模なデータセットの場合は、multiprocessing.Pool.map()を使用して操作を高速化してみてください。

1
Roland Smith

これは、MATLABに一致する両方の出力引数[Lia、Locb]を返す正確なMATLABの同等物です。ただし、Python 0も有効なインデックスです。したがって、この関数は0を返しません。基本的にLocb(Locb> 0)を返します。パフォーマンスもMATLABと同等です。

def ismember(a_vec, b_vec):
    """ MATLAB equivalent ismember function """

    bool_ind = np.isin(a_vec,b_vec)
    common = a[bool_ind]
    common_unique, common_inv  = np.unique(common, return_inverse=True)     # common = common_unique[common_inv]
    b_unique, b_ind = np.unique(b_vec, return_index=True)  # b_unique = b_vec[b_ind]
    common_ind = b_ind[np.isin(b_unique, common_unique, assume_unique=True)]
    return bool_ind, common_ind[common_inv]

少し(〜5x)遅いが、固有の関数を使用しない代替実装は次のとおりです。

def ismember(a_vec, b_vec):
    ''' MATLAB equivalent ismember function. Slower than above implementation'''
    b_dict = {b_vec[i]: i for i in range(0, len(b_vec))}
    indices = [b_dict.get(x) for x in a_vec if b_dict.get(x) is not None]
    booleans = np.in1d(a_vec, b_vec)
    return booleans, np.array(indices, dtype=int)