In [60]: N, M = 200, 100
...: Y = np.random.normal(0,1,(N,M))
In [61]: Y1 = Y[:,:,None]
Ваша итерация, 200 шагов для создания (100 100) массивов:
In [62]: timeit [Y1[i,:,:]@Y1[i,:,:].T for i in range(N)]
18.5 ms ± 784 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
einsum
только немного быстрее:
In [64]: timeit np.einsum('ijk,imk->ijm', Y1,Y1)
14.5 ms ± 114 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
, но вы можете применить @
в полном «пакетном» режиме с:
In [65]: timeit Y[:,:,None]@Y[:,None,:]
7.63 ms ± 224 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Но, как отмечает Дивакар, ось суммы имеет размер 1, так что вы можете использовать обычное умноженное вещание. Это внешний продукт, а не матричный.
In [66]: timeit Y[:,:,None]*Y[:,None,:]
8.2 ms ± 64.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
«векторизация» дает большие выгоды при выполнении множества итераций простой операции. Для меньшего количества операций в более сложных операциях усиление не так велико.