Урок курса
Broadcasting: правила совместимости форм и исправление несовместимости
NumPy и pandas: практический тренажёрВ предыдущем уроке мы складывали числа по осям и схлопывали массивы до одного значения. Теперь задача противоположная: как выполнять поэлементные операции над массивами, у которых формы не совпадают — и при этом не копировать данные вручную. Именно это и делает 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: два одномерных набора, каждый явно сделан двумерным в разных осях, дают двумерный результат.
Чтобы быстро проверить совместимость любых двух форм в голове, достаточно трёх шагов:
- Выписать оба shape.
- Дополнить более короткий единицами слева.
- Пройтись по осям справа налево: каждая пара должна быть равна или содержать 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) | Нет | — |
Последние два случая — несовместимые формы. Почему именно там возникает сбой и как его прочитать в тексте ошибки — разберём дальше.
Диагностика ошибки несовместимости форм
Когда два массива не проходят проверку совместимости, NumPy бросает ValueError с сообщением вида:
ValueError: operands could not be broadcast together with shapes (3,) (3, 4)
В тексте ошибки всегда написаны обе формы — именно с ними и нужно работать.
Диагностика делается в три шага.
Шаг 1. Выписать оба shape из сообщения об ошибке (или проверить через .shape в коде).
Шаг 2. Выровнять формы справа: к более короткой добавить единицы слева до совпадения числа осей.
Шаг 3. Пройтись по осям слева направо и найти ту, где размеры не равны и ни один из них не равен 1. Это и есть причина ошибки.
Разберём конкретный случай из сообщения выше:
import numpy as np
a = np.array([1, 2, 3]) # shape (3,)
b = np.ones((3, 4)) # shape (3, 4)
# a + b → ValueError
Применяем алгоритм:
- Shape
a:(3,)→ после выравнивания:(1, 3) - Shape
b:(3, 4)→ без изменений - Ось 0:
1и3→ ок, единица растянется - Ось 1:
3и4→ 3 ≠ 4, ни один не равен 1 → несовместимы
Именно ось 1 ломает операцию. Вектор a из трёх элементов выровнялся как строка (1, 3), а у b по оси 1 стоит 4 — растянуть невозможно.
Ещё один пример — чуть менее очевидный:
x = np.ones((2, 3)) # shape (2, 3)
y = np.ones((4,)) # shape (4,)
# x + y → ValueError
Диагностика:
yпосле выравнивания:(1, 4)- Ось 0:
2и1→ ок - Ось 1:
3и4→ 3 ≠ 4, ни один не равен 1 → ошибка
Поверхностно кажется, что 2D-массив и вектор из 4 элементов должны как-то сочетаться. Но после выравнивания вектор встаёт во вторую ось как (1, 4), а там уже стоит 3 — несовместимо.
Главное, что даёт этот разбор: знать, где именно добавить единичную ось, чтобы исправить ошибку. Как только понятно, какая ось проблемная и какому операнду нужна единица в этой позиции — исправление становится механическим. Об этом — в следующем разделе.
Исправление несовместимости через np.newaxis и reshape
Диагностика из предыдущего раздела даёт конкретный ответ: какому операнду нужна единица и в какой позиции. Осталось эту единицу поставить — и операция сработает.
Два инструмента делают это: np.newaxis и reshape.
np.newaxis вставляет единичную ось через индексацию и возвращает view — новый объект-массив, смотрящий на те же данные в памяти.
arr[:, np.newaxis]— добавляет ось после первой:(n,)→(n, 1)arr[np.newaxis, :]— добавляет ось перед первой:(n,)→(1, n)
reshape(-1, 1) и reshape(1, -1) меняют форму явным указанием целевых размеров. -1 означает «вычисли этот размер автоматически». reshape возвращает view, когда это позволяет раскладка данных в памяти, и копию — когда нет (например, для транспонированного массива). Проверить, разделяют ли два массива одну и ту же память, можно через np.shares_memory:
import numpy as np
a = np.array([1, 2, 3]) # обычный contiguous массив
a_col = a.reshape(-1, 1)
print(np.shares_memory(a, a_col)) # True — общая память, это view
a_T = np.ones((3, 4)).T # транспонированный, не contiguous
a_T_flat = a_T.reshape(-1)
print(np.shares_memory(a_T, a_T_flat)) # False — NumPy создал копию
np.shares_memory надёжнее проверки через .base, потому что view может ссылаться на данные через промежуточный базовый объект, и arr.base is source в таком случае даст False даже при реально общей памяти.
Для broadcasting-задач вопрос view-или-копия обычно не критичен: вы вычисляете новый массив, а не изменяете исходные данные через результат. Но знать о np.shares_memory полезно при профилировании памяти.
Какой инструмент выбрать — дело вкуса: np.newaxis читается как намерение («добавить ось»), reshape — как явное указание целевой формы.
Сквозной пример: полный цикл
Возьмём случай из раздела про диагностику: a формы (3,) и b формы (3, 4) — несовместимы.
import numpy as np
a = np.array([1, 2, 3]) # shape (3,)
b = np.ones((3, 4)) # shape (3, 4)
# Диагностика (уже сделана):
# a → после выравнивания (1, 3)
# b → (3, 4)
# Ось 1: 3 ≠ 4, ни один не равен 1 → ValueError
# Что нужно: сделать a формой (3, 1), тогда:
# Ось 0: 3 и 3 → совпадают
# Ось 1: 1 и 4 → единица растянется до 4
# Результат: (3, 4)
a_col = a[:, np.newaxis] # shape (3, 1)
print(a_col.shape) # (3, 1)
result = a_col + b
print(result.shape) # (3, 4)
print(result)
# [[2. 2. 2. 2.]
# [3. 3. 3. 3.]
# [4. 4. 4. 4.]]
Каждый элемент a — 1, 2, 3 — прибавляется ко всей соответствующей строке b. Строка 0 получила +1, строка 1 — +2, строка 2 — +3.
Тот же результат через reshape:
a_col_v2 = a.reshape(-1, 1) # shape (3, 1)
print((a_col_v2 + b).shape) # (3, 4)
Второй пример: операция по столбцам
Теперь обратная задача — прибавить к каждому столбцу матрицы (3, 4) отдельный вектор из 4 элементов. Это тривиальный broadcasting: (4,) выравнивается до (1, 4) и растягивается по строкам. Никакого np.newaxis не нужно:
v = np.array([10, 20, 30, 40]) # shape (4,)
result2 = b + v
print(result2.shape) # (3, 4)
Здесь исправлять нечего — формы уже совместимы. np.newaxis нужен только тогда, когда диагностика показала реальную проблему.
Третий пример: (3, 1) и (1, 4) → полная комбинаторная таблица
col = np.array([10, 20, 30]).reshape(-1, 1) # shape (3, 1)
row = np.array([1, 2, 3, 4]).reshape(1, -1) # shape (1, 4)
table = col + row
print(table.shape) # (3, 4)
print(table)
# [[11 12 13 14]
# [21 22 23 24]
# [31 32 33 34]]
Оба операнда специально сформированы в разных осях: один — столбец, другой — строка. В результате каждый элемент первого складывается с каждым элементом второго. Это стандартный паттерн для построения таблиц попарных операций без вложенных циклов.
Алгоритм исправления в одном месте
- Выписать оба shape.
- Выровнять справа, добавив единицы слева к более короткому.
- Найти ось, где размеры не равны и ни один не равен 1.
- Решить, какому операнду нужна единица в этой позиции.
- Добавить ось через
arr[:, np.newaxis]/arr[np.newaxis, :]илиarr.reshape(-1, 1)/arr.reshape(1, -1). - Проверить
.shapeрезультата — он должен совпасть с ожидаемым.
Шаг 6 стоит делать явно, особенно пока интуиция ещё не отработана: print((a_fixed + b).shape) дешевле, чем разбирать неправильный результат позже.
Broadcasting с оператором @ (матричное умножение) работает по другим правилам — там форма согласуется не поэлементно, а через внутренние размеры матриц. Это отдельная тема следующего урока.
Закрепите тему на задачах
Сравнивайте размерности справа налево: каждая пара должна совпадать либо содержать 1. До вычисления запишите ожидаемую форму результата, особенно для сочетания (n,) и (n, 1).
NumPy broadcasting: какая форма будет у суммы массивов
Broadcasting NumPy: нормализация строк и столбцов
NumPy broadcasting: outer-таблица и центрирование строк
Откройте условие и решите задачу перед чтением следующей темы. В задачах на код проверяется программа, в тестовых заданиях — выбранный ответ. Для последовательного обучения вернитесь в программу бесплатного курса.
Задача 1
Массивы a и b имеют shape (5, 1) и (1, 4) соответственно. Чему равен shape результата a + b?
Задача 2
Массив a имеет shape (5,), массив b имеет shape (3, 5). Запишите одно выражение, которое вычитает a из b через broadcasting без ручного изменения shape. Ответ должен начинаться с b -.
Задача 3
Найди и исправь: несовместимые формы при broadcasting
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.
В редакторе уже есть сломанный код. Он пытается выполнить три операции с broadcasting, но в каждой из трёх допущена ошибка: либо формы несовместимы и нужно добавить ось, либо ось добавлена не туда, либо вместо np.newaxis использован reshape с неверными аргументами.
Что нужно сделать
Найдите и исправьте все три ошибки так, чтобы программа:
- Читала из
stdinодну строку с четырьмя целыми числами:R,C,a_val,b_val. - Создавала матрицу
M = np.ones((R, C)) * a_val— shape(R, C). - Создавала вектор
row = np.arange(C) + b_val— shape(C,). - Создавала вектор
col = np.arange(R) + b_val— shape(R,). - Вычисляла три результата и выводила их shape в три строки:
s1:M + row— прибавитьrowк каждой строкеM; ожидаемый shape(R, C). Ошибка:rowошибочно преобразован вrow.reshape(R, 1)вместоrow.reshape(1, C)или оставлен как(C,).s2:M + col[:, np.newaxis]— прибавитьcolк каждому столбцуM; ожидаемый shape(R, C). Ошибка: написаноcol[np.newaxis, :]вместоcol[:, np.newaxis].s3: outer-произведениеcolиrowбез матрицыM; ожидаемый shape(R, C). Ошибка: написаноcol * rowвместоcol[:, np.newaxis] * row.
Исправьте ровно эти три места. Не меняйте ничего другого.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка с четырьмя целыми числами через пробел: R C a_val b_val.
Гарантируется: R >= 2, C >= 2, R ≠ C.
Формат вывода
Три строки — shape каждого результата в виде кортежа Python, например (3, 4).
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 4
Broadcasting: нормализация строк и столбцов матрицы
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinдва целых числаRиC— число строк и столбцов матрицы. - Читает следующие
Rстрок; каждая содержит ровноCцелых чисел через пробел. Создаёт двумерный массивMчерезnp.array(..., dtype=float)формы(R, C). - Нормализация по строкам. Вычисляет сумму каждой строки через
np.sum(M, axis=1)— результат shape(R,). ДелитMна эти суммы через broadcasting, добавив ось через[:, np.newaxis]илиreshape(-1, 1), чтобы делитель стал формы(R, 1). Сохраняет результат вnorm_rows— shape(R, C). Выводит shapenorm_rowsв одну строку. - Нормализация по столбцам. Вычисляет максимум каждого столбца через
np.max(M, axis=0)— результат shape(C,). ДелитMна эти максимумы через broadcasting (форма(C,)совместима с(R, C)напрямую). Сохраняет результат вnorm_cols— shape(R, C). Выводит shapenorm_colsв одну строку. - Выводит через пробел два значения:
norm_rows[0, 0]иnorm_cols[0, 0], округлённых до одного знака после запятой.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Первая строка: два целых числа R и C через пробел.
Следующие R строк: по C целых чисел через пробел.
Гарантируется: R >= 2, C >= 2, все элементы матрицы положительны.
Формат вывода
Три строки:
- shape
norm_rowsв виде кортежа Python, например(2, 4); - shape
norm_colsв виде кортежа Python, например(2, 4); - два значения через пробел:
norm_rows[0, 0]иnorm_cols[0, 0], каждое с одним знаком после запятой.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Broadcasting: outer-таблица и центрирование матрицы по строкам
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая выполняет два broadcasting-вычисления над одними и теми же данными.
- Читает из
stdinдва целых числаRиC— число строк и столбцов. - Читает следующие
Rстрок; каждая содержит ровноCцелых чисел через пробел. Создаёт двумерный массивMчерезnp.array(..., dtype=float)формы(R, C). - Outer-таблица разностей. Создаёт вектор
row_means— среднее каждой строки черезnp.mean(M, axis=1), shape(R,). Создаёт векторcol_means— среднее каждого столбца черезnp.mean(M, axis=0), shape(C,). Вычисляет outer-таблицуdiff = row_means[:, np.newaxis] - col_meansчерез broadcasting — shape должен быть(R, C). Выводит shapediffв одну строку, затем значение элементаdiff[0, 0]в следующую строку (целое число, если дробная часть нулевая, иначе float с одним знаком после запятой — используйтеg-формат:f"{val:.1g}"не нужен, простоint(val)еслиval == int(val)иначеround(val, 1)). - Центрирование строк. Вычитает из
Mсреднее каждой строки через broadcasting:centered = M - row_means[:, np.newaxis]— shape(R, C). Выводит shapecenteredв одну строку, затем значение элементаcentered[0, 0]в следующую строку (то же правило вывода числа).
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Первая строка: два целых числа R и C через пробел.
Следующие R строк: по C целых чисел через пробел.
Гарантируется: R >= 2, C >= 2.
Формат вывода
Четыре строки:
- shape
diffв виде кортежа Python, например(3, 4); - значение
diff[0, 0]; - shape
centeredв виде кортежа Python, например(3, 4); - значение
centered[0, 0].
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
Broadcasting с тензором (B, R, C): поправки по последней оси и коэффициенты по первой
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinтри целых числаB,R,C— размеры тензора. - Читает следующие
B * Rстрок; каждая содержитCцелых чисел через пробел. Создаёт трёхмерный массивTчерезnp.array(..., dtype=float).reshape(B, R, C)— shape(B, R, C). - Читает одну строку с
Cчислами через пробел — создаёт вектор поправокbiasчерезnp.array(..., dtype=float), shape(C,). - Читает одну строку с
Bчислами через пробел — создаёт вектор коэффициентовcoeffчерезnp.array(..., dtype=float), shape(B,). - Шаг A. Прибавляет
biasкTчерез broadcasting. Векторbiasshape(C,)совместим сTshape(B, R, C)напрямую — выравнивание справа даёт(1, 1, C). Сохраняет результат вT_biased— shape(B, R, C). ВыводитT_biased.shape. - Шаг B. Умножает
T_biasedнаcoeff, добавив кcoeffдве оси черезcoeff[:, np.newaxis, np.newaxis]— shape становится(B, 1, 1), что совместимо с(B, R, C). Сохраняет результат вT_scaled— shape(B, R, C). ВыводитT_scaled.shape. - Вычисляет
total = np.sum(T_scaled)и выводит глобальную сумму элементовT_scaledс ровно двумя знаками после запятой.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Строка 1: три целых числа B R C через пробел.
Следующие B * R строк: по C целых чисел через пробел (данные тензора, строка за строкой по всем батчам).
Следующая строка: C чисел через пробел — вектор bias.
Последняя строка: B чисел через пробел — вектор coeff.
Гарантируется: B >= 2, R >= 2, C >= 2.
Формат вывода
Три строки:
- shape
T_biasedв виде кортежа Python, например(2, 3, 4); - shape
T_scaledв виде кортежа Python, например(2, 3, 4); - глобальная сумма
T_scaledс ровно двумя знаками после запятой, например16.00.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- NumPy broadcasting: какая форма будет у суммы массивов
Сложение массивов NumPy с shape (5, 1) и (1, 4): определите форму результата и потренируйтесь применять правила broadcasting.
- Broadcasting NumPy по строкам и столбцам: формы и пример
Как делить матрицу на суммы строк и значения столбцов без циклов? Правила broadcasting, пример с keepdims и задача NumPy.
- NumPy broadcasting: outer-таблица и центрирование строк
Постройте outer-таблицу и центрируйте матрицу по строкам с помощью broadcasting. Задача на совместимость форм.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
