я узнал кое-что по пути, пытаясь найти векторизованные и быстрые способы ее решения.
1) Во-первых, существует зависимость итераторов от "for j in range(i)"
. Из моего предыдущего опыта, особенно с целью решения таких проблем на MATLAB
, оказалось, что такую зависимость можно позаботиться с помощью lower triangular matrix
, поэтому np.tril
должен работать там. Таким образом, полностью Векторизованное решение и не столь памяти эффективное решение (так как он создает промежуточный (N,N)
профилированный массив перед окончательным восстановлением в (N,)
формой массива) были бы -
def fully_vectorized(a,b):
return np.tril(np.einsum('ijk,jil->kl',a,b),-1).sum(1)
2) Следующее трик/Идея состояла в том, чтобы держать одну петли для итератора i
в for i in range(N)
, но вставьте эту зависимость с индексированием и используя np.einsum
для выполнения всех этих умножений и суммирования. Преимуществом будет эффективность памяти. Реализация будет выглядеть так:
def einsum_oneloop(a,b):
d = np.zeros(N)
for i in range(N):
d[i] = np.einsum('ij,jik->',a[:,:,i],b[:,:,np.arange(i)])
return d
Есть еще два очевидных способа решить эту проблему. Таким образом, если мы начинаем работать с оригинальным all_loopy
раствора, можно сохранить внешнюю две петли и использовать np.einsum
или np.tensordot
для выполнения этих операций и, таким образом, удалить внутреннюю две петли, как так -
def tensordot_twoloop(a,b):
d = np.zeros(N)
for i in range(N):
for j in range(i):
d[i] += np.tensordot(a[:,:,i],b[:,:,j], axes=([1,0],[0,1]))
return d
def einsum_twoloop(a,b):
d = np.zeros(N)
for i in range(N):
for j in range(i):
d[i] += np.einsum('ij,ji->',a[:,:,i],b[:,:,j])
return d
время выполнения теста
Давайте сравним все пять подходов, опубликованных до сих пор, чтобы решить проблему, в том числе опубликованную в вопросе.
Дело № 1:
In [26]: # Input arrays with random elements
...: m,n,N = 20,20,20
...: a = np.random.rand(m,n,N)
...: b = np.random.rand(n,m,N)
...:
In [27]: %timeit all_loopy(a,b)
...: %timeit tensordot_twoloop(a,b)
...: %timeit einsum_twoloop(a,b)
...: %timeit einsum_oneloop(a,b)
...: %timeit fully_vectorized(a,b)
...:
10 loops, best of 3: 79.6 ms per loop
100 loops, best of 3: 4.97 ms per loop
1000 loops, best of 3: 1.66 ms per loop
1000 loops, best of 3: 585 µs per loop
1000 loops, best of 3: 684 µs per loop
Дело № 2:
In [28]: # Input arrays with random elements
...: m,n,N = 50,50,50
...: a = np.random.rand(m,n,N)
...: b = np.random.rand(n,m,N)
...:
In [29]: %timeit all_loopy(a,b)
...: %timeit tensordot_twoloop(a,b)
...: %timeit einsum_twoloop(a,b)
...: %timeit einsum_oneloop(a,b)
...: %timeit fully_vectorized(a,b)
...:
1 loops, best of 3: 3.1 s per loop
10 loops, best of 3: 54.1 ms per loop
10 loops, best of 3: 26.2 ms per loop
10 loops, best of 3: 27 ms per loop
10 loops, best of 3: 23.3 ms per loop
Дело № 3 (Выход из all_loopy за то, что очень медленно):
In [30]: # Input arrays with random elements
...: m,n,N = 100,100,100
...: a = np.random.rand(m,n,N)
...: b = np.random.rand(n,m,N)
...:
In [31]: %timeit tensordot_twoloop(a,b)
...: %timeit einsum_twoloop(a,b)
...: %timeit einsum_oneloop(a,b)
...: %timeit fully_vectorized(a,b)
...:
1 loops, best of 3: 1.08 s per loop
1 loops, best of 3: 744 ms per loop
1 loops, best of 3: 568 ms per loop
1 loops, best of 3: 866 ms per loop
Идя по номерам , einsum_oneloop
выглядит довольно хорошо для меня, тогда как fully_vectorized
можно использовать при работе с небольшими приличные массивы!
Прекрасные работы там! Я на самом деле не использовал 'numba', и я имел в виду NumPy при публикации вопроса, но это действительно хорошая работа, эти постепенные улучшения и объяснения! Я буду держать вопрос открытым, если все в порядке. – Divakar
Спасибо. И я не против, если вы хотите оставить вопрос открытым. Мне также было бы интересно, если есть более быстрые или другие аналогичные подходы к решению этого вопроса. – MSeifert