Эквивалент Python функции MATLAB "ismember"
После многих попыток оптимизации кода, кажется, что одним из последних ресурсов будет попытка выполнить код ниже с использованием нескольких ядер. Я не знаю точно, как преобразовать/переструктурировать мой код, чтобы он мог работать намного быстрее, используя несколько ядер. Буду признателен, если я смогу получить руководство для достижения конечной цели. Конечной целью является возможность быстрого запуска этого кода для массивов 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.
То, что я пытаюсь сделать, это имитировать функцию MATLAB под названием ismember [2] (тот, который отформатирован как: [Lia,Locb] = ismember(A,B)
Я просто пытаюсь получить только часть Locb
.
Из Matlab: Locb, содержащий наименьший индекс в B для каждого значения в A, являющегося членом B. Выходной массив, Locb, содержит 0, где A не является членом B
Одна из основных проблем заключается в том, что мне нужно максимально эффективно выполнять эту операцию. Для тестирования у меня есть два массива из 700 тыс. Элементов. Создание генератора и прохождение значений генератора, похоже, не ускоряет работу.
Ответы
Ответ 1
Прежде чем беспокоиться о нескольких ядрах, я бы устранил линейное сканирование в вашей функции 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
В исходной реализации требуется полное сканирование элементов в B для каждого элемента в A, что делает его O(len(A)*len(B))
. Вышеприведенный код требует одного полного сканирования B для генерации dict Bset. Используя dict, вы эффективно выполняете поиск каждого элемента в константе B для каждого элемента A, делая операцию 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)
на значение, которое вы хотите вернуть.
Ответ 2
sfstewman отличный ответ, скорее всего, решил проблему для вас.
Я просто хотел бы добавить, как вы можете достичь того же исключительно в numpy.
Я использую numpy unique функции 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_sorted
содержит уникальные значения в B
, отсортированные.
-
B_idx
для этих значений содержит индексы в оригинале B
.
-
B_in_A_bool
- это булев массив размером B_unique_sorted
, который
сохраняет ли значение в B_unique_sorted
в A
.
Примечание: Мне нужно искать (уникальные vals из B) в A, потому что мне нужен вывод, который нужно вернуть по отношению к B_idx
Примечание: Я предполагаю, что A
уже уникален.
Теперь вы можете использовать B_in_A_bool
для получения общих vals
B_unique_sorted[B_in_A_bool]
и их соответствующие индексы в оригинале B
B_idx[B_in_A_bool]
Наконец, я предполагаю, что это значительно быстрее, чем чистый Python for-loop, хотя я его не тестировал.
Ответ 3
Попробуйте использовать понимание списка;
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]
Как правило, перечислимые функции намного быстрее, чем для циклов.
Чтобы получить равный список длин,
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()
для ускорения операции.
Ответ 4
Вот точный эквивалент MATLAB, который возвращает оба выходных аргумента [Lia, Locb], которые соответствуют MATLAB, за исключением Python 0, также является допустимым индексом. Таким образом, эта функция не возвращает 0s. Он по существу возвращает 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)