NumPy: вычисления и устройство массивов

Матрично-векторные произведения через оператор @

Содержание курса

В прошлом уроке мы разбирались с broadcasting — как NumPy согласовывает формы массивов при поэлементных операциях. Оператор @, который появляется в этом уроке, работает по совсем другой логике: он не растягивает размеры и не работает поэлементно. Он вычисляет матричное произведение, и правила совместимости у него свои.

Оператор @ и его эквивалент np.matmul

Оператор @ появился в Python 3.5 специально для матричного умножения. До него приходилось писать np.matmul(A, B) или np.dot(A, B)@ просто делает запись короче и читаемее. Под капотом A @ B и np.matmul(A, B) — одно и то же.

Важно сразу разграничить три оператора, которые легко перепутать:

  • * — поэлементное умножение: каждый элемент A умножается на соответствующий элемент B, форма массивов должна быть совместима по broadcasting.
  • np.dot — для одномерных массивов ведёт себя как скалярное произведение, для двумерных — как матричное умножение, но поведение на трёхмерных и выше отличается от @.
  • @ (np.matmul) — матричное умножение в строгом смысле: строки левого операнда умножаются на столбцы правого и суммируются.

Разница между * и @ хорошо видна на простом примере:

import numpy as np

A = np.array([[1, 2],
              [3, 4]])
B = np.array([[1, 0],
              [0, 1]])

print(A * B)   # поэлементно: [[1, 0], [0, 4]]
print(A @ B)   # матричное:   [[1, 2], [3, 4]]

Для единичной матрицы B разница особенно наглядна: * обнуляет недиагональные элементы, тогда как @ возвращает саму матрицу A — ровно так, как работает умножение на единичную матрицу в линейной алгебре.

Ещё одна деталь: @ не изменяет исходные массивы — результат записывается в новый объект. A и B после операции остаются прежними.