Сумма скалярных произведений

Как преобразовать 100 из 8 векторов элементов в 10 16 векторов элементов, используя 1000 различных (8,16) весовых матриц? Каждый из 10 выходных векторов представляет собой сумму 100 скалярных произведений:

A = np.random.randn(100,8)
W = np.random.randn(1000,8,16)
B = []

for i in range(10):
  sum = np.zeros((1,16))
  for j in range(100):
    sum += np.dot(A[j], W[i*100+j])   
  B.append(sum)
B = np.asarray(B)     #B.shape=(10,16)

Есть ли для этого функция в Numpy или TensorFlow? Я посмотрел на точку, тензордот, эйнсум и матмул в Numpy, и я до сих пор не уверен, какой из них правильный.

РЕДАКТИРОВАТЬ: я только что понял, что на самом деле хочу получить промежуточный результат перед суммированием точечных произведений: (100,8)x(10 100,8,16) -> (10 100,16).

Я предполагаю, что это можно сделать, изменив форму (100,8) на (1,100,1,8) и (1000,8,16) на (10,100,8,16) и выполнив np.einsum('ijkl,ijlm->ijm', A, B) Но я не уверен, что он будет правильно транслировать от 1 до 10.

Согласно комментарию @Divakar, np.einsum('jk,ijkl->ijl', V, W.reshape(10,100,8,16)) делает свое дело.


person MichaelSB    schedule 15.11.2017    source источник


Ответы (2)


В одной строке это

B1 = np.einsum('ij,ikjl', A, np.reshape(W, (100, 10, 8, 16), order='F'))

Протестируйте это с np.allclose(B.squeeze(), B1) там, где вам нужно .squeeze, потому что у вашего B есть дополнительное измерение размера 1.

Объяснение: ваш W имеет уродливую форму, его первое измерение размером 1000 действительно должно быть разделено на 10 блоков размером 100 (как вы фактически делаете с манипулированием индексом в цикле). Вот для чего нужна перестройка. Порядок в стиле Fortran необходим, потому что мы хотим удалить элементы из W, изменив первый индекс как можно быстрее.

После этого идет простое суммирование по Эйнштейну: по j выполняется умножение матриц, по i добавляется 100 результатов.

person Community    schedule 16.11.2017
comment
Не ожидал такого, но это быстрее, чем tensordot. Кроме того, вы можете избежать фортрана с помощью: np.einsum('jk,ijkl', A, np.reshape(W, (10, 100, 8, 16))). Добавил тайминги в свой пост. - person Divakar; 16.11.2017
comment
@Divakar, почему это не работает: np.einsum('ij,ikjl', A, np.reshape(W, (100, 10, 8, 16)))? Он выдает правильную форму вывода, но неправильные значения... - person MichaelSB; 16.11.2017
comment
@MichaelSB Потому что нам нужно разделить первую ось W так, чтобы последняя ось имела длину 100, что уменьшает сумму по сравнению с первой осью A. - person Divakar; 16.11.2017
comment
@MichaelSB По умолчанию в стиле C последний индекс изменяется быстрее всего. Таким образом, преобразование 1000 элементов в (100, 10) дает вам 100 блоков размера 10 вместо 10 блоков размера 100. Я работал над этим, используя стиль Fortran, Divakar, изменяя порядок осей. Для конкретного примера сравните np.reshape(np.arange(6), (2, 3)), np.reshape(np.arange(6), (2, 3), order='F') и np.reshape(np.arange(6), (3, 2)). - person ; 16.11.2017
comment
@Divakar и 6-футовый белый кобель, спасибо, кажется, я начинаю понимать. Однако я только что понял, что мне нужно сохранить промежуточный расчет. Я отредактировал свой вопрос. Вы не против взглянуть? - person MichaelSB; 16.11.2017
comment
@MichaelSB np.einsum('jk,ijkl->ijl', A, np.reshape(W, (10, 100, 8, 16))) Думаю. - person Divakar; 17.11.2017

Вы можете использовать тензорное умножение, np.tensordot-

def tensordot_app(A, W):
    m,n,r = W.shape
    Wr = W.reshape(-1,A.shape[0],n,r)
    return np.tensordot(A,Wr, axes=((0,1),(1,2)))

Связанный пост для понимания tensordot.

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

In [62]: A = np.random.randn(100,8)
    ...: W = np.random.randn(1000,8,16)

In [63]: %%timeit 
    ...: B = []
    ...: for i in range(10):
    ...:   sum = np.zeros((1,16))
    ...:   for j in range(100):
    ...:     sum += np.dot(A[j], W[i*100+j])   
    ...:   B.append(sum)
    ...: B = np.asarray(B)     #B.shape=(10,16)
1000 loops, best of 3: 1.81 ms per loop

# Other post's einsum soln
In [64]: %timeit np.einsum('ij,ikjl',A,np.reshape(W,(100,10,8,16), order='F'))
10000 loops, best of 3: 83.4 µs per loop

# Other post's einsum soln without fortran re-ordering
In [65]: %timeit np.einsum('jk,ijkl', A, np.reshape(W, (10, 100, 8, 16)))
10000 loops, best of 3: 83.3 µs per loop

In [66]: %timeit tensordot_app(A, W)
10000 loops, best of 3: 193 µs per loop
person Divakar    schedule 16.11.2017