20

在多次尝试优化代码之后,似乎最后一个资源是尝试使用多个内核运行下面的代码。我不确切知道如何转换/重新构造我的代码,以便它可以使用多个内核运行得更快。如果我能得到指导以实现最终目标,我将不胜感激。最终目标是能够尽可能快地为数组 A 和 B 运行此代码,其中每个数组包含大约 700,000 个元素。这是使用小数组的代码。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,对于 A 中属于 B 的每个值,包含 B 中的最低索引。输出数组 Locb 包含 0,只要 A 不是 B 的成员

主要问题之一是我需要能够尽可能高效地执行此操作。为了测试,我有两个 700k 元素的数组。创建一个生成器并检查生成器的值似乎并不能快速完成工作。

4

5 回答 5

18

在担心多核之前,我会通过使用字典来消除您的 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)). 上面的代码需要对 B 进行一次完整扫描才能生成 dict Bset。通过使用 dict,您可以有效地为 A 的每个元素查找 B 中的每个元素,从而使操作O(len(A)+len(B)). 如果这仍然太慢,那么请担心使上述功能在多个内核上运行。

编辑:我还稍微修改了您的索引。Matlab 使用 0,因为它的所有数组都从索引 1 开始。Python/numpy 从 0 开始数组,所以如果你的数据集看起来像这样

A = [2378, 2378, 2378, 2378]
B = [2378, 2379]

如果没有元素返回 0,那么您的结果将排除 A 的所有元素。上述例程返回None无索引而不是 0。返回 -1 是一个选项,但 Python 会将其解释为数组中的最后一个元素。 None如果将其用作数组的索引,则会引发异常。如果您想要不同的行为,请将Bind.get(item,None)表达式中的第二个参数更改为您想要返回的值。

于 2013-04-07T15:59:41.203 回答
15

sfstewman 的出色回答很可能为您解决了这个问题。

我只想补充一下如何在 numpy 中实现相同的功能。

我利用了 numpy独特的 an in1d函数。

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_sortedB包含已排序的唯一值。
  • B_idx为这些值保留原始索引的索引B
  • B_in_A_bool是一个布尔数组,它的大小B_unique_sorted存储一个值B_unique_sorted是否在A
    注意:我需要在 A 中查找(来自 B 的唯一 val),因为我需要返回关于B_idx
    注意的输出:我假设这A已经是唯一的。

现在您可以使用B_in_A_bool来获取常见的 val

B_unique_sorted[B_in_A_bool]

以及它们各自在原文中的索引B

B_idx[B_in_A_bool]

最后,我假设这比纯 Python for 循环要快得多,尽管我没有对其进行测试。

于 2013-04-07T19:30:59.050 回答
2

试试ismember图书馆。

pip install ismember

简单的例子:

# Import library
from ismember import ismember
import numpy as np

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

# Lookup
Iloc,idx = ismember(A, B)
 
# Iloc is boolean defining existence of d in d_unique
print(Iloc)
# [ True False False  True  True]

# indexes of d_unique that exists in d
print(idx)
# [4 4 3]

print(B[idx])
# [3 3 6]

print(A[Iloc])
# [3 3 6]

# These vectors will match
A[Iloc]==B[idx]

速度检查:

from ismember import ismember
from datetime import datetime

t1=[]
t2=[]
# Create some random vectors
ns = np.random.randint(10,10000,1000)

for n in ns:
    a_vec = np.random.randint(0,100,n)
    b_vec = np.random.randint(0,100,n)

    # Run stack version
    start = datetime.now()
    out1=ismember_stack(a_vec, b_vec)
    end = datetime.now()
    t1.append(end - start)

    # Run ismember
    start = datetime.now()
    out2=ismember(a_vec, b_vec)
    end = datetime.now()
    t2.append(end - start)


print(np.sum(t1))
# 0:00:07.778331

print(np.sum(t2))
# 0:00:04.609801

# %%
def ismember_stack(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

pypi的ismember函数几乎快 2 倍。

大向量,例如 700000 个元素:

from ismember import ismember
from datetime import datetime

A = np.random.randint(0,100,700000)
B = np.random.randint(0,100,700000)

# Lookup
start = datetime.now()
Iloc,idx = ismember(A, B)
end = datetime.now()

# Print time
print(end-start)
# 0:00:01.194801
于 2020-06-21T22:07:50.077 回答
1

尝试使用列表理解;

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

对于大型数据集,您可以尝试使用 amultiprocessing.Pool.map()来加速操作。

于 2013-04-07T15:57:50.150 回答
1

这是返回与 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)
于 2017-09-17T20:23:27.163 回答