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

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

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

Три случая оператора @: вектор–вектор, матрица–вектор, матрица–матрица

Правило внутренних размеров абстрактно, пока не видишь, что именно происходит с числами и формой результата в каждом из трёх возможных случаев.

Случай 1: вектор @ вектор → скаляр

Когда оба операнда одномерные и имеют одинаковую длину, @ вычисляет скалярное произведение — попарно умножает элементы и суммирует. Результат — NumPy-скаляр с shape ():

import numpy as np

w = np.array([0.5, 0.3, 0.2])
x = np.array([80.0, 90.0, 70.0])

result = w @ x
print(result)        # 81.0
print(result.shape)  # ()

Здесь 0.5*80 + 0.3*90 + 0.2*70 = 40 + 27 + 14 = 81.0. Это и есть взвешенная сумма: w — веса, x — значения. Удобный случай на практике: итоговый балл экзамена, портфельная доходность, взвешенное среднее — всё это один вызов @. Обратите внимание, что result.shape возвращает (), а не (1,): NumPy-скаляр не имеет измерений вообще.

Случай 2: матрица @ вектор → вектор

Если левый операнд — матрица shape (m, k), а правый — вектор shape (k,), результат — вектор shape (m,). Каждая строка матрицы независимо перемножается на вектор и суммируется:

A = np.array([[1, 2, 3],
              [4, 5, 6]])   # shape (2, 3)
v = np.array([1.0, 0.0, -1.0])  # shape (3,)

result = A @ v
print(result)        # [-2. -2.]
print(result.shape)  # (2,)

Первая строка A: 1*1 + 2*0 + 3*(-1) = -2. Вторая: 4*1 + 5*0 + 6*(-1) = -2. Форма (2, 3) @ (3,) даёт (2,) — внутренний размер 3 исчезает, остаётся m=2.

Этот случай — рабочая лошадка линейных моделей: матрица признаков умножается на вектор весов и даёт вектор предсказаний.

Случай 3: матрица @ матрица → матрица

Два двумерных операнда shape (m, k) и (k, n) дают матрицу shape (m, n). Каждый элемент результата — скалярное произведение строки левой матрицы и столбца правой:

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

result = A @ B
print(result)
# [[ 4  5]
#  [10 11]]
print(result.shape)  # (2, 2)

Результат (2, 3) @ (3, 2)(2, 2): внутренний размер 3 «схлопнулся», внешние 2 и 2 образовали новую матрицу.

Сводка по shape результата:

  • (n,) @ (n,)() — скаляр
  • (m, k) @ (k,)(m,) — вектор
  • (m, k) @ (k, n)(m, n) — матрица

Во всех трёх случаях работает одно и то же правило: внутренний размер сокращается, внешние формируют shape результата.