Давайте пройдемся по операциям, которые вы пытаетесь, и посмотрим, что мы можем сделать, чтобы упростить их. Для начала вы можете написать
A_t = np.swapaxes(A, 0, 1)
Это эквивалентно
A_t = np.transpose(A, [0, 1, 2])
или
A_t = A.transpose([0, 1, 2])
. Как это ни случается, ни то, ни другое не требуется для вашего текущего приложения. Чтобы понять почему, давайте поработаем с упрощенным примером:
np.random.seed(42)
N = 4
D = 3
K = 2
A = np.random.randint(0, 10, (N, D, K))
В вашем внешнем l oop у вас есть row = A_t[i]
. Но по определению вашего транспонирования это идентично row = A[:, i, :]
, что делает вашу жизнь намного проще, а транспонирование излишним.
Внутренний l oop суммирует некоторые точечные произведения:
val = 0
for n in range(N):
val += np.matmul(row[n], col[n])
Если вы помните определение точечного произведения, вы увидите, что вы делаете эквивалент
np.sum(np.sum(row * col, axis=1), axis=0)
Внутренняя сумма является суммой-произведением в вашем l oop, а внешняя сумма это вычисление val
. Суммирование по обоим измерениям по отдельности аналогично суммированию всего буфера за раз, поэтому мы можем сразу заменить внутренний l oop просто
for i in range(D):
for j in range(D):
S[i][j] = np.sum(A[:, i, :] * A[:, j, :])
Вы можете упростить это с помощью np.dot
, np.tensordot
, np.einsum
или просто эфирное вещание. Первые два излишне сложны, потому что вы действительно умножаете сумму на два измерения одновременно. np.einsum
предлагает самое прямое решение в целом, но это менее простой перевод вашего кода.
Решение 1. Трансляция
Давайте начнем с прямой версии вещания двойного l oop, прежде чем перейти к более идиоматическому c решению:
S = (A[:, None, ...] * A[:, :, None, ...]).sum(axis=(0, -1))
или
S = np.sum(A[:, None, ...] * A[:, :, None, ...], axis=(0, -1))
Это создает вид A
в форме (N, 1, D, K)
и (N, D, 1, K)
соответственно. Умножение транслирует реплицированные оси D
в каждом случае точно на то, что делают циклы for
, поэтому итоговая сумма по осям N
и K
делает именно то, что делала линия S[i][j] = np.sum(A[:, i, :] * A[:, j, :])
ранее.
Решение 2: np.einsum
Это решение позволяет вам применять sum-product непосредственно к любым осям, которые вы хотите:
S = np.einsum('ijk,ihk->jh', A, A)
Обратите внимание, что вы должны использовать другая буква для второй оси второй матрицы (j
и h
), чтобы указать, что вы будете не суммировать по этой оси. S
симметрично, но если бы это было не так, вы могли бы транспонировать его, транспонируя в результат ->hj
.