Numpy умножение векторов разного размера, избегая циклов for

У меня есть матрица, скажем, P размера (X,Y). Кроме того, у меня есть две матрицы, скажем, Kx и Ky размера (M,N) обе, матрица pk размера (M,N) и два вектора u и v размера X и Y соответственно. Например, их можно определить следующим образом:

import numpy as np
P = np.zeros((X,Y));
pk = np.random.rand(M,N);
Kx = np.random.rand(M,N);
Ky = np.random.rand(M,N);
u = np.random.rand(X);
v = np.random.rand(Y);

В реальном коде они, конечно, не случайны, но для данного примера это не имеет значения. Вопрос в том, существует ли чистый numpy, эквивалентный следующему:

for m in range(0, M):
    for n in range(0, N):
        for i in range(0,X):
            for j in range(0,Y):
               Arg = Kx[m,n]*u[i] + Ky[m,n]*v[j];
               P[i,j] += pk[m,n]*np.cos(Arg);

Все M,N,X,Y разные, но X и Y могут быть одинаковыми, если в противном случае решения не существует.


person Eugene B    schedule 25.04.2015    source источник
comment
два вектора v и u из X и Y соответственно: вы поменяли местами u и v?   -  person DSM    schedule 25.04.2015
comment
конечно, сейчас отредактирую, спасибо!   -  person Eugene B    schedule 25.04.2015
comment
@EugeneB вот как вы можете прояснить свой вопрос: гораздо лучше описать величины P, Kx, Ky и pk несколькими строками кода, чем абзацем текста. И для людей, пытающихся помочь вам, было бы еще лучше, если бы вы могли отредактировать код в своем вопросе, чтобы он действительно работал без необходимости какого-либо редактирования или догадок. См. этот вопрос для примера и этот комментарий для совета.   -  person YXD    schedule 25.04.2015
comment
Спасибо, отредактировал для удобства.   -  person Eugene B    schedule 25.04.2015
comment
@EugeneB вы пытались запустить этот код?   -  person YXD    schedule 25.04.2015
comment
Да. Он отлично работает с интересующими меня значениями, а также со случайными или любыми другими записями.   -  person Eugene B    schedule 25.04.2015


Ответы (1)


Распространенной стратегией устранения for-loop в вычислениях NumPy является работа с многомерными массивами.

Рассмотрим, например, строку

Arg = Kx[m,n]*u[i] + Ky[m,n]*v[j]

Эта строка зависит от индексов m, n, i и j. Значит, Arg зависит от m, n, i и j. Это означает, что Arg можно рассматривать как 4-мерный массив, индексированный m, n, i и j. Таким образом, мы можем устранить 4 for-loops -- насколько это касается Arg -- путем вычисления

Kxu = Kx[:,:,np.newaxis]*u
Kyv = Ky[:,:,np.newaxis]*v
Arg = Kxu[:,:,:,np.newaxis] + Kyv[:,:,np.newaxis,:]

Kx[:,:,np.newaxis] имеет форму (M, N, 1), а u имеет форму (X,). При их перемножении используется трансляция NumPy для создания массива формы (M, N, X) . Таким образом, выше новые оси используются несколько как заполнители, так что Arg заканчивается четырьмя осями, проиндексированными m,n,i,j в указанном порядке.

Точно так же P можно определить как

P = (pk[:,:,np.newaxis,np.newaxis]*np.cos(Arg)).sum(axis=0).sum(axis=0)

sum(axis=0) (вызывается дважды) суммируется по осям m и n, так что P оказывается двумерным массивом, индексированным только i и j.

Работая с этими 4-мерными массивами, мы можем применять операции NumPy ко всем массивам NumPy. Напротив, при использовании 4 for-loops нам приходилось выполнять вычисления скаляров по значениям. Рассмотрим, например, что делает np.cos(Arg), когда Arg является 4-мерным массивом. Это разгружает вычисление всех косинусов в одном вызове функции NumPy, который выполняет базовый цикл в скомпилированном коде C. Это намного быстрее, чем вызов np.cos один раз для каждого скаляра. Это причина, по которой работа с массивами более высокой размерности оказывается намного быстрее, чем код на основе for-loop.


import numpy as np

def orig(Kx, Ky, u, v, pk):
    M, N = Kx.shape
    X = u.size
    Y = v.size
    P = np.empty((X, Y), dtype=pk.dtype)
    for m in range(0, M):
        for n in range(0, N):
            for i in range(0,X):
                for j in range(0,Y):
                   Arg = Kx[m,n]*u[i] + Ky[m,n]*v[j]
                   P[i,j] += pk[m,n]*np.cos(Arg)
    return P

def alt(Kx, Ky, u, v, pk):
    Kxu = Kx[:,:,np.newaxis]*u
    Kyv = Ky[:,:,np.newaxis]*v
    Arg = Kxu[:,:,:,np.newaxis] + Kyv[:,:,np.newaxis,:]
    P = (pk[:,:,np.newaxis,np.newaxis]*np.cos(Arg)).sum(axis=0).sum(axis=0)
    return P

M, N = 10, 20
X, Y = 5, 15
Kx = np.random.random((M, N))
Ky = np.random.random((M, N))
u = np.random.random(X)
v = np.random.random(Y)
pk = np.random.random((M, N))

Проверка работоспособности (показывая, что alt и orig возвращают один и тот же результат):

In [57]: P2 = alt(Kx, Ky, u, v, pk)

In [58]: P1 = orig(Kx, Ky, u, v, pk)

In [59]: np.allclose(P1, P2)
Out[59]: True

Тест, показывающий, что alt значительно быстрее, чем orig:

In [60]: %timeit orig(Kx, Ky, u, v, pk)
10 loops, best of 3: 33.6 ms per loop

In [61]: %timeit alt(Kx, Ky, u, v, pk)
1000 loops, best of 3: 349 µs per loop
person unutbu    schedule 25.04.2015
comment
Большое спасибо за ваш быстрый и подробный ответ! Кажется, это то, что я искал! - person Eugene B; 25.04.2015