Вещание на Python с перестановками

3

Я понимаю, что transpose в ndarray предназначен для эквивалентности функции matlab permute, но у меня есть конкретная утилита, которая не работает просто. В Matlab у меня есть следующее:

C = @bsxfun(@times, permute(A,[4,2,5,1,3]), permute(B, [1,6,2,7,3,4,5])

где A - трехмерный тензор формы NxNxM, а B - тензор 5D формы NxNxMxPxP. Вышеупомянутая функция предназначена для векторизации петлевых кронекеров. Я предполагаю, что Matlab добавляет 2 синглтонных измерения для A и B, поэтому он может их переупорядочить. Я ищу, чтобы переносить этот код на Python , но я не думаю, что он имеет возможность добавлять эти дополнительные измерения.. Я нашел this, который успешно добавляет дополнительные размеры, однако широковещательная передача не работает с тем же самым matlab bsxfun. Я попытался сделать очевидный перевод (да, я использую numpy для этих ndarray и функций):

A = A[...,None,None]
B = B[...,None,None]
C = transpose(A,[3,1,4,0,2])*transpose(B,[0,5,1,6,2,3,4])

, и я получаю следующую ошибку:

return transpose(axes)
ValueError: axes don't match array

Мое первое предположение заключается в том, чтобы сделать reshape на A и B, чтобы добавить в эти размеры синглтона?

Теперь я получаю следующую ошибку:

mults = transpose(rho_in,[3,1,4,0,2])*transpose(proj,[0,5,1,6,2,3,4])
ValueError: operands could not be broadcast together with shapes (1,9,1,9,8) (9,1,9,1,8,40,40)

EDIT: изменил мой вопрос как минимум о добавлении размеров синглтона, но больше о правильном распространении этого умножения на matlab в python.

Теги:
vectorization
permute
broadcasting

2 ответа

6
Лучший ответ

Огромная разница между MATLAB и numpy заключается в том, что первый использует формат столбца для своих массивов, в то время как последний ряд строк. Следствием этого является то, что неявные размеры одноэлементности обрабатываются по-разному.

В частности, MATLAB явно игнорирует конечные размеры синглтона: rand(3,3,1,1,1,1,1) - фактически матрица 3x3. Вдоль этих строк вы можете использовать bsxfun для работы на двух массивах, если их ведущие измерения совпадают: NxNxM неявно NxNxMx1x1, который совместим с NxNxMxPxP.

Numpy, с другой стороны, позволяет неявные синглтоны впереди. Вам нужно permute ваши массивы таким образом, чтобы их конечные размеры совпадали, например shape (40,40,9,1,9,1,8) с формой (1,9,1,9,8), и результат должен иметь форму (40,40,9,9,9,9,8).

Пример манекена:

>>> import numpy as np
>>> (np.random.rand(40,40,9,1,9,1,8)+np.random.rand(1,9,1,9,8)).shape
(40, 40, 9, 9, 9, 9, 8)

Обратите внимание, что то, что вы пытаетесь сделать, возможно, можно сделать с помощью numpy.einsum. Я предлагаю поближе рассмотреть это. Пример того, что я имею в виду: из вашего вопроса, который я собрал, вы хотите выполнить это: взять элементы A[1:N,1:N,1:M] и B[1:N,1:N,1:M,1:P,1:P] и построить новый массив C[1:N,1:N,1:N,1:N,1:M,1:P,1:P], чтобы

C[i1,i2,i3,i4,i5,i6,i7] = A[i2,i4,i5]*B[i1,i3,i5,i6,i7]

(ваш индексный порядок может отличаться). Если это правильно, вы действительно можете использовать numpy.einsum():

>>> a = np.random.rand(3,3,2)
>>> b = np.random.rand(3,3,2,4,4)
>>> np.einsum('ijk,lmkno->limjkno',a,b).shape
(3, 3, 3, 3, 2, 4, 4)

Следует отметить две вещи. Во-первых, вышеупомянутая операция будет очень интенсивной в памяти, что следует ожидать для случаев векторизации (где вы обычно выигрываете процессорное время за счет потребности в памяти). Во-вторых, вам следует серьезно подумать о перестановке модели данных при переносе кода. Причина, по которой трансляция работает по-разному на двух языках, неразрывно связана с разницей в столбце/главном. Это также означает, что в MATLAB сначала нужно работать с ведущими индексами, поскольку A(:,i2,i3) соответствует непрерывному блоку памяти, а A(i1,i2,:) - нет. И наоборот, в numpy A[i1,i2,:] является смежным, а A[:,i2,i3] - нет.

Эти соображения предполагают, что вы должны настроить логистику своих данных таким образом, чтобы векторизованные операции предпочтительно работали с ведущими индексами в MATLAB и возвращали индексы в numpy. Вы могли бы использовать numpy.einsum для выполнения самой операции, однако ваши измерения должны быть в другом (возможно, обратном) порядке по сравнению с MATLAB, по крайней мере, если предположить, что обе версии кода используют оптимальную настройку.

  • 0
    Можно ли связаться с вами прямым сообщением какого-либо рода, чтобы расширить эту тему (в частности, использование einsum )? Я чувствую, что мои дальнейшие вопросы были бы слишком конкретными, чтобы быть актуальными здесь.
  • 0
    @Mike, к сожалению, переполнение стека явно препятствует сторонним усилиям :) Я вижу вашу озабоченность тем, что вы работаете по теме, но вы должны либо оставить комментарий здесь (если он очень тесно связан и прост), задать новый вопрос (если он кажется большим достаточно, чтобы заслужить полный вопрос), или присоединяйтесь к нам в чате в комнате MATLAB или комнате с питоном .
Показать ещё 3 комментария
2

Глядя на ваш код MATLAB, у вас есть -

C = bsxfun(@times, permute(A,[4,2,5,1,3]), permute(B, [1,6,2,7,3,4,5])

Итак, по существу -

B : 1 , 6 , 2 , 7 , 3 , 4 , 5 
A : 4 , 2 , 5 , 1 , 3

Теперь, в MATLAB нам пришлось брать размеры синглтона из более высоких, поэтому вся эта проблема приносит тусклые 6, 7 для B и dims 4 5 для A.

В NumPy мы вносим их явно с помощью np.newaxis/None. Таким образом, для NumPy мы могли бы сделать это так:

B : 1 , N , 2 , N , 3 , 4 , 5 
A : N , 2 , N , 1 , 3 , N , N

где N представляет новую ось. Обратите внимание, что нам нужно было поставить новые топоры в конце для A, чтобы продвинуть другое измерение для выравнивания. В режиме constrast это происходит в MATLAB по умолчанию.

Создание B выглядит достаточно просто, поскольку размеры кажутся в порядке, и нам просто нужно добавить новые оси в соответствующих местах - B[:,None,:,None,:,:,:].

Создание такого A не выглядит прямым. Игнорируя N's в A, мы имели бы - A : 2 , 1 , 3. Таким образом, отправной точкой будут перестановки размеров, а затем добавить в эти две новые оси, которые были проигнорированы - A.transpose(1,0,2)[None,:,None,:,:,None,None].

До сих пор мы имеем -

B (new): B[:,None,:,None,:,:,:]
A (new): A.transpose(1,0,2)[None,:,None,:,:,None,None]

В NumPy мы можем пропустить ведущие новые оси и конечные не-синглетонные dims. Таким образом, мы могли бы упростить так:

B (new): B[:,None,:,None]
A (new): A.transpose(1,0,2)[:,None,...,None,None]

Конечным результатом будет умножение между этими двумя расширенными версиями -

C = A.transpose(1,0,2)[:,None,...,None,None]*B[:,None,:,None]

Тест времени выполнения

Я полагаю, что сообщение @Andras означало, что эквивалентная реализация np.einsum должна выглядеть примерно так: np.einsum('ijk,lmkno->ljmikno',A,B).

In [24]: A = np.random.randint(0,9,(10,10,10))
    ...: B = np.random.randint(0,9,(10,10,10,10,10))
    ...: 

In [25]: C1 = np.einsum('ijk,lmkno->ljmikno',A,B)

In [26]: C2 = A.transpose(1,0,2)[:,None,...,None,None]*B[:,None,:,None]

In [27]: np.allclose(C1,C2)
Out[27]: True

In [28]: %timeit np.einsum('ijk,lmkno->ljmikno',A,B)
10 loops, best of 3: 102 ms per loop

In [29]: %timeit A.transpose(1,0,2)[:,None,...,None,None]*B[:,None,:,None]
10 loops, best of 3: 78.4 ms per loop

In [30]: A = np.random.randint(0,9,(15,15,15))
    ...: B = np.random.randint(0,9,(15,15,15,15,15))
    ...: 

In [31]: %timeit np.einsum('ijk,lmkno->ljmikno',A,B)
1 loop, best of 3: 1.76 s per loop

In [32]: %timeit A.transpose(1,0,2)[:,None,...,None,None]*B[:,None,:,None]
1 loop, best of 3: 1.36 s per loop
  • 0
    Я могу подтвердить, что это действительно то, что я имел в виду с einsum :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню