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

Broadcasting: правила совместимости форм и исправление несовместимости

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

В предыдущем уроке мы складывали числа по осям и схлопывали массивы до одного значения. Теперь задача противоположная: как выполнять поэлементные операции над массивами, у которых формы не совпадают — и при этом не копировать данные вручную. Именно это и делает broadcasting.

Правила broadcasting: выравнивание форм и растяжение размера 1

Broadcasting — это соглашение NumPy о том, как интерпретировать операцию между массивами разных форм. Ничего физически не копируется: NumPy просто договаривается, какой размер считать «растянутым», и вычисляет результат напрямую.

Всё поведение описывается двумя правилами, которые применяются последовательно.

Правило 1: выравнивание форм справа. Если у массивов разное число осей, к форме с меньшим числом осей добавляются единицы слева — до тех пор, пока число осей не совпадёт.

Например, массив с формой (4,) и массив с формой (3, 4): первый становится (1, 4). Выравнивание всегда идёт справа — именно поэтому вектор-строка из 4 элементов совместим с матрицей из 3 строк по 4 столбца, а не наоборот.

Правило 2: размер 1 растягивается. После выравнивания NumPy смотрит на каждую ось по очереди. Если размеры совпадают — всё хорошо. Если один из них равен 1 — он «растягивается» до размера другого. Результирующий размер по этой оси равен большему из двух.

Операция возможна, если после выравнивания для каждой пары размеров выполняется хотя бы одно условие:

  • размеры равны,
  • или один из них равен 1.

Если ни то ни другое — NumPy выбросит ошибку. Но об этом в следующем разделе.

Покажем, как работает логика на практике:

import numpy as np

# Случай 1: (3, 4) и (4,)
# Шаг 1 — выравнивание: (3, 4) и (1, 4)
# Шаг 2 — растяжение: ось 0 у второго операнда — размер 1, растягиваем до 3
# Результат: (3, 4)
A = np.ones((3, 4))
v = np.array([1, 2, 3, 4])  # shape (4,)
result = A + v
print(result.shape)  # (3, 4)

Вектор v прибавляется к каждой строке матрицы A — без явного копирования, просто по правилу растяжения.

# Случай 2: (3, 1) и (1, 4)
# Оба операнда уже двумерны, выравнивание не нужно
# Ось 0: 3 и 1 → растягиваем 1 до 3
# Ось 1: 1 и 4 → растягиваем 1 до 4
# Результат: (3, 4)
col = np.array([[10], [20], [30]])  # shape (3, 1)
row = np.array([[1, 2, 3, 4]])      # shape (1, 4)
result2 = col + row
print(result2.shape)  # (3, 4)
print(result2)
# [[11 12 13 14]
#  [21 22 23 24]
#  [31 32 33 34]]

Здесь каждый элемент col складывается с каждым элементом row — получается полная комбинаторная таблица из 12 значений. Это классический паттерн broadcasting: два одномерных набора, каждый явно сделан двумерным в разных осях, дают двумерный результат.

Чтобы быстро проверить совместимость любых двух форм в голове, достаточно трёх шагов:

  1. Выписать оба shape.
  2. Дополнить более короткий единицами слева.
  3. Пройтись по осям справа налево: каждая пара должна быть равна или содержать 1.

Если все пары прошли проверку — операция сработает, а результирующая форма — это максимум по каждой оси.

Вот несколько пар для быстрой проверки интуиции:

| Форма A | Форма B | После выравнивания | Совместимы? | Результат | |---|---|---|---|---| | (5,) | (3, 5) | (1, 5) и (3, 5) | ✅ | (3, 5) | | (3, 1) | (3, 4) | без изменений | ✅ | (3, 4) | | (3, 4) | (3, 4) | без изменений | ✅ | (3, 4) | | (3, 4) | (3, 5) | без изменений | ❌ | — | | (2, 3) | (4,) | (2, 3) и (1, 4) | ❌ | — |

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