NumPy Broadcasting для эффективных вычислений

NumPyBeginner
Практиковаться сейчас

Введение

Broadcasting (трансляция) — это фундаментальная концепция в NumPy, которая позволяет выполнять поэлементные арифметические операции над массивами различной формы. Эта мощная функция устраняет необходимость в явных циклах, что приводит к более лаконичному и вычислительно эффективному коду. В этой лабораторной работе вы изучите правила broadcasting и примените их на практических примерах, написав и выполнив скрипты Python.

Broadcasting скаляра к массиву

Простейшая форма broadcasting возникает при выполнении операции между массивом и одним числом (скаляром). NumPy автоматически "растягивает" или "транслирует" скаляр, чтобы он соответствовал форме массива.

Сначала найдите файл broadcasting.py в файловом менеджере слева от экрана. Дважды щелкните по нему, чтобы открыть в редакторе.

Теперь замените содержимое файла broadcasting.py следующим кодом. Этот код создает одномерный массив NumPy и умножает его на скалярное значение.

import numpy as np

## Создаем одномерный массив
a = np.array([1.0, 2.0, 3.0])

## Определяем скаляр
b = 2.0

## Умножаем массив на скаляр
## Скаляр 'b' транслируется к форме массива 'a'
result = a * b

print("Массив 'a':", a)
print("Скаляр 'b':", b)
print("Результат a * b:", result)

Чтобы увидеть результат, вам нужно запустить скрипт. Откройте терминал, нажав на значок + в панели терминала внизу экрана и выбрав Terminal. Затем выполните следующую команду:

python broadcasting.py

Вы должны увидеть следующий вывод, где каждый элемент массива a был умножен на 2.0.

Массив 'a': [1. 2. 3.]
Скаляр 'b': 2.0
Результат a * b: [2. 4. 6.]

Broadcasting двух массивов с совместимыми формами

Broadcasting также работает между двумя массивами, если их формы совместимы. Общее правило совместимости гласит: при сравнении размерностей двух массивов справа налево каждая пара размерностей должна быть либо равной, либо одна из них должна быть равна 1.

Рассмотрим пример сложения одномерного массива с двумерным массивом. Одномерный массив будет транслироваться по каждой строке двумерного массива.

Обновите файл broadcasting.py следующим кодом:

import numpy as np

## Создаем двумерный массив (форма: 2, 3)
a = np.array([[1.0, 2.0, 3.0],
              [4.0, 5.0, 6.0]])

## Создаем одномерный массив (форма: 3,)
b = np.array([10.0, 20.0, 30.0])

## Складываем два массива
## Массив 'b' транслируется к форме (2, 3), чтобы соответствовать массиву 'a'
## Внутренне он становится [[10. 20. 30.], [10. 20. 30.]]
result = a + b

print("Массив 'a' (форма {}):\n{}".format(a.shape, a))
print("Массив 'b' (форма {}): {}".format(b.shape, b))
print("Результат a + b:\n", result)

Снова запустите скрипт в терминале:

python broadcasting.py

Вывод показывает, что одномерный массив b был добавлен к каждой строке двумерного массива a.

Массив 'a' (форма (2, 3)):
[[1. 2. 3.]
 [4. 5. 6.]]
Массив 'b' (форма (3,)): [10. 20. 30.]
Результат a + b:
 [[11. 22. 33.]
 [14. 25. 36.]]

Понимание несовместимых форм

Broadcasting завершится ошибкой, если формы массивов несовместимы согласно правилам. Это приведет к ValueError. Понимание того, когда это происходит, крайне важно для отладки.

Попробуем выполнить операцию с несовместимыми формами. Здесь мы попытаемся сложить массив формы (2, 3) с массивом формы (2,). NumPy сравнивает последние размерности (3 и 2), обнаруживает, что они не равны, и ни одна из них не равна 1. Это вызовет ошибку.

Измените файл broadcasting.py, чтобы он содержал следующий код:

import numpy as np

## Создаем двумерный массив (форма: 2, 3)
a = np.array([[1.0, 2.0, 3.0],
              [4.0, 5.0, 6.0]])

## Создаем несовместимый одномерный массив (форма: 2,)
b = np.array([1.0, 2.0])

print("Массив 'a' (форма {}):\n{}".format(a.shape, a))
print("Массив 'b' (форма {}): {}".format(b.shape, b))

## Это вызовет ValueError
try:
    result = a + b
except ValueError as e:
    print("\nОшибка:", e)

Запустите скрипт из терминала:

python broadcasting.py

Как и ожидалось, программа перехватывает ValueError и выводит сообщение об ошибке, объясняющее, что формы не выровнены. Это правильное и ожидаемое поведение для несовместимых форм.

Массив 'a' (форма (2, 3)):
[[1. 2. 3.]
 [4. 5. 6.]]
Массив 'b' (форма (2,)): [1. 2.]

Ошибка: operands could not be broadcast together with shapes (2,3) (2,)

Практический пример - Векторное квантование

Применим broadcasting к практической задаче. Векторная квантизация (VQ) — это техника, используемая в сжатии данных и классификации. Ключевым шагом является поиск ближайшего "кодового" вектора из "кодовой книги" к заданному "вектору наблюдения". Broadcasting делает этот расчет эффективным.

Представим, что у нас есть наблюдение (например, вес и рост спортсмена) и кодовая книга различных типов спортсменов. Мы хотим найти, к какому типу ближе всего наше наблюдение.

Замените код в файле broadcasting.py следующим:

import numpy as np

## Вектор наблюдения (например, вес, рост)
observation = np.array([111.0, 188.0])

## Кодовая книга векторов
codes = np.array([[102.0, 203.0],
                  [132.0, 193.0],
                  [45.0, 155.0],
                  [57.0, 173.0]])

## Используем broadcasting для одновременного вычитания наблюдения из всех кодов
## observation (2,) транслируется в (4, 2)
diff = codes - observation

## Вычисляем квадрат евклидова расстояния
dist_sq = np.sum(diff**2, axis=-1)

## Находим индекс минимального расстояния
closest_index = np.argmin(dist_sq)

## Получаем ближайший код из кодовой книги
closest_code = codes[closest_index]

print("Наблюдение:", observation)
print("Кодовая книга:\n", codes)
print("Квадраты расстояний:", dist_sq)
print("Индекс ближайшего кода:", closest_index)
print("Ближайший код:", closest_code)

Запустите скрипт в терминале:

python broadcasting.py

Вывод покажет квадраты расстояний от наблюдения до каждого кода и определит ближайший соответствующий кодовый вектор. Весь этот расчет был выполнен без явных циклов Python, благодаря broadcasting.

Наблюдение: [111. 188.]
Кодовая книга:
 [[102. 203.]
 [132. 193.]
 [ 45. 155.]
 [ 57. 173.]]
Квадраты расстояний: [ 306.  466. 5445. 3153.]
Индекс ближайшего кода: 0
Ближайший код: [102. 203.]

Резюме

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