Как я могу взять точечный продукт по определенному измерению в numpy?

У меня есть два массива. Один n на p, а другой d на p на r. Я хотел бы, чтобы мой вывод был d на n на r, чего я могу легко достичь, построив тензор B ниже. Однако я хотел бы сделать это без этого цикла.

import numpy

X = numpy.array([[1,2,3],[3,4,5],[5,6,7],[7,8,9]]) # n x p
betas = numpy.array([[[1,2],[1,2],[1,2]], [[5,6],[5,6],[5,6]]]) # d x p x r

print X.shape
print betas.shape

B = numpy.zeros((betas.shape[0],X.shape[0],betas.shape[2]))
print B.shape

for i in range(B.shape[0]):
    B[i,:,:] = numpy.dot(X, betas[i])

print "B",B

C = numpy.tensordot(X, betas, axes=([1],[0]))
print C.shape

Я пытался разными способами заставить C соответствовать B, но пока безуспешно. Есть ли способ, который не включает вызов reshape?


person Pavel Komarov    schedule 29.01.2018    source источник


Ответы (3)


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

B = np.tensordot(betas, X, axes=(1,1)).swapaxes(1,2)
# Or np.tensordot(X, betas, axes=(1,1)).swapaxes(0,1)

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

person Divakar    schedule 29.01.2018
comment
И он возвращает представление, а не новый массив! Замечательно. - person Pavel Komarov; 30.01.2018
comment
Для больших массивов tensordot работает сверхбыстро.. но для небольших массивов np.dot в 4 раза быстрее, чем tensordot.. не уверен, почему это так - person kmario23; 30.01.2018
comment
@kmario23 См. здесь соответствующее обсуждение - stackoverflow.com/a/47646944 - person Divakar; 30.01.2018

Поскольку правило dot — это «последний из A со вторым до последнего из B», вы можете выполнить X.dot(betas) и получить массив (n, d, r) (это суммы в общем измерении p). Тогда вам просто нужно транспонировать, чтобы получить (d,n,r)

In [200]: X.dot(betas).transpose(1,0,2)
Out[200]: 
array([[[  6,  12],
        [ 12,  24],
        [ 18,  36],
        [ 24,  48]],

       [[ 30,  36],
        [ 60,  72],
        [ 90, 108],
        [120, 144]]])

Мы также можем записать версию einsum непосредственно из спецификации размеров:

np.einsum('np,dpr->dnr', X,betas)

Так же как и matmul (это делает dot на последних двух осях, в то время как d приходит на помощь).

X@betas
  • Если любой из аргументов имеет значение N-D, N > 2, он обрабатывается как стек матриц, находящихся в двух последних индексах, и передается соответствующим образом.
person hpaulj    schedule 30.01.2018
comment
Посмотрите мои тайминги. np.dot быстрее для небольших массивов, чем tensordot, но наоборот для больших массивов. Не уверен, почему. Любые идеи? - person kmario23; 30.01.2018
comment
Tensordot использует dot, просто массируя входные данные и результаты, чтобы они соответствовали (изменяли форму и транспонировали). - person hpaulj; 30.01.2018

Вот еще один подход с использованием numpy.dot()< /strong>, который также возвращает представление в соответствии с вашим запросом и, что наиболее важно, более чем в 4 раза быстрее, чем tensordot, особенно для массивов небольшого размера. Но np.tensordot намного быстрее, чем обычный np.dot() для достаточно больших массивов. Смотрите тайминги ниже.

In [108]: X.shape
Out[108]: (4, 3)

In [109]: betas.shape
Out[109]: (2, 3, 2)

# use `np.dot` and roll the second axis to first position
In [110]: dot_prod = np.rollaxis(np.dot(X, betas), 1)

In [111]: dot_prod.shape
Out[111]: (2, 4, 2)

# @Divakar's approach
In [113]: B = np.tensordot(betas, X, axes=(1,1)).swapaxes(1,2)

# sanity check :)
In [115]: np.all(np.equal(dot_prod, B))
Out[115]: True

Теперь производительность обоих подходов:

  • Для массивов небольшого размера np.dot() в 4 раза быстрее, чем < a href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.tensordot.html" rel="nofollow noreferrer">np.tensordot()

# @Divakar's approach
In [117]: %timeit B = np.tensordot(betas, X, axes=(1,1)).swapaxes(1,2)
10.6 µs ± 2.1 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

# @hpaulj's approach
In [151]: %timeit esum_dot = np.einsum('np, dpr -> dnr', X, betas)
4.16 µs ± 235 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

# proposed approach: more than 4x faster!!
In [118]: %timeit dot_prod = np.rollaxis(np.dot(X, betas), 1)
2.47 µs ± 11.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

  • Для достаточно больших массивов np.tensordot() намного быстрее, чем np.dot()

In [129]: X = np.random.randint(1, 10, (600, 500))
In [130]: betas = np.random.randint(1, 7, (300, 500, 300))

In [131]: %timeit B = np.tensordot(betas, X, axes=(1,1)).swapaxes(1,2)
18.2 s ± 2.41 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [132]: %timeit dot_prod = np.rollaxis(np.dot(X, betas), 1)
52.8 s ± 14.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
person kmario23    schedule 30.01.2018
comment
После того, как вы преодолеете начальные накладные расходы на настройку с tensordot, она должна быть быстрее, чем np.dot версия - np.rollaxis(np.dot(X, betas), 1). Таким образом, это не должно быть reasonably larger arrays. - person Divakar; 30.01.2018
comment
@Divakar Я вижу. Итак, hpaulj утверждает, что np.tensordot использует np.dot. Думаю, мне придется просмотреть код, чтобы понять, что происходит... Я очень надеюсь, что разработчики NumPy начнут реализовывать поддержку графических процессоров как можно раньше... :) - person kmario23; 30.01.2018