Что такое оператор умножения, который действительно работает с массивами numpy?

Я изучаю NumPy, и я не уверен, что делает оператор *. Это похоже на некоторую форму умножения, но я не уверен, как это определяется. Из ipython:

In [1]: import numpy as np

In [2]: a=np.array([[1,2,3]])

In [3]: b=np.array([[4],[5],[6]])

In [4]: a*b
Out[4]: 
array([[ 4,  8, 12],
       [ 5, 10, 15],
       [ 6, 12, 18]])

In [5]: b*a
Out[5]: 
array([[ 4,  8, 12],
       [ 5, 10, 15],
       [ 6, 12, 18]])

In [6]: b.dot(a)
Out[6]: 
array([[ 4,  8, 12],
       [ 5, 10, 15],
       [ 6, 12, 18]])

In [7]: a.dot(b)
Out[7]: array([[32]])

Кажется, что он выполняет умножение матрицы, но только b, умноженное на a, а не наоборот. Что происходит?

Ответ 1

Это немного сложнее и связано с концепцией broadcasting и тем фактом, что все операции numpy являются элементальными.

  • a - это 2D-массив с 1 строкой и 3 столбцами, а b - это 2D-массив с 1 столбцом и 3 строками.
  • Если вы пытаетесь умножить их на элемент (это то, что делает numpy, если вы делаете a * b, потому что каждая базовая операция, кроме операции dot, является мудрой), она должна транслировать массивы так, чтобы они соответствовали во всех их измерениях.
  • Поскольку первый массив равен 1x3, а второй - 3x1, они могут быть переданы в матрицу 3x3 согласно правилам вещания. Они будут выглядеть так:
a = [[1, 2, 3],
     [1, 2, 3],
     [1, 2, 3]]

b = [[4, 4, 4],
     [5, 5, 5],
     [6, 6, 6]]

И теперь Numpy может размножать их по элементам, давая вам результат:

[[ 4,  8, 12],
 [ 5, 10, 15],
 [ 6, 12, 18]]

Когда вы выполняете операцию .dot, она выполняет стандартное умножение матрицы. Подробнее в docs

Ответ 2

* делает элементное умножение.

Поскольку массивы имеют разные формы, будут применяться правила broadcasting.

In [5]: a.shape
Out[5]: (1, 3)

In [6]: b.shape
Out[6]: (3, 1)

In [7]: (a * b).shape
Out[7]: (3, 3)
  • Все входные массивы с ndim меньше, чем входной массив наибольшего ndim, имеют 1s, добавленные к их формам (здесь не применяется).
  • Размер в каждом измерении выходной формы является максимальным для всех размеров ввода в этом измерении.
  • В вычислениях может использоваться вход, если его размер в определенном измерении либо соответствует размеру вывода в этом измерении, либо имеет значение точно 1.
  • Если вход имеет размер размером 1 в его форме, первая запись данных в этом измерении будет использоваться для всех вычислений вдоль этого измерения. Другими словами, степпинг ufunc просто не будет продвигаться по этому измерению (шаг будет равен 0 для этой размерности).

Таким образом, результирующая фигура должна быть (3, 3) (максимальные размеры размеров a и b), а при выполнении умножения numpy не будет проходить первый размер и b второй размер (их размеры равны 1).

Элемент результата [i][j] равен произведению широковещательного элемента a и b [i][j].

(a * b)[0][0] == a[0][0] * b[0][0]
(a * b)[0][1] == a[0][1] * b[0][0]  # (not stepping through b second dimension)
(a * b)[0][2] == a[0][2] * b[0][0]
(a * b)[1][0] == a[0][0] * b[1][0]  # (not stepping through a first dimension)

etc.