Вещание
См. также
Термин «вещание» описывает, как NumPy обрабатывает массивы с различными формами во время арифметических операций. При соблюдении определенных ограничений меньший массив «вещательно» распространяется по большему массиву, чтобы они имели совместимые формы. Вещание предоставляет способ векторизации операций с массивами, чтобы циклы выполнялись на C, а не на Python. Это делается без ненужных копий данных и обычно приводит к эффективной реализации алгоритмов. Однако есть случаи, когда вещание является плохой идеей, поскольку оно приводит к неэффективному использованию памяти, что замедляет вычисления.
Операции NumPy обычно выполняются по парам массивов на основе элементарного сравнения. В самом простом случае два массива должны иметь точно такую же форму, как в следующем примере:
>>> a = np.array([1.0, 2.0, 3.0]) >>> b = np.array([2.0, 2.0, 2.0]) >>> a * b array([2., 4., 6.])
Правило вещания NumPy смягчает это ограничение, когда формы массивов удовлетворяют определенным ограничениям. Самый простой пример вещания возникает при объединении массива и скалярного значения в операции:
>>> a = np.array([1.0, 2.0, 3.0]) >>> b = 2.0 >>> a * b array([2., 4., 6.])
Результат эквивалентен предыдущему примеру, где b был массивом. Мы можем представить скаляр b как растягиваемый во время арифметической операции в массив с той же формой, что и a. Новые элементы в b, как показано на рисунке 1, просто являются копиями исходного скаляра. Аналогия с растяжением носит лишь концептуальный характер. NumPy достаточно умен, чтобы использовать исходное скалярное значение без фактического создания копий, чтобы операции вещания были максимально эффективными с точки зрения памяти и вычислений.
Рисунок 1
В самом простом примере вещания скаляр b растягивается, чтобы стать массивом с такой же формой, что и a , так что формы совместимы для элементарного умножения.
Код во втором примере более эффективен, чем в первом, потому что вещание перемещает меньше памяти во время умножения (b является скаляром, а не массивом).
Общие правила вещания
При работе с двумя массивами NumPy сравнивает их формы поэлементно. Он начинает с последнего (т.е. правого) измерения и работает влево. Два измерения совместимы, когда
- они равны, или
- одно из них равно 1.
Если эти условия не выполняются, генерируется исключение ValueError: operands could not be broadcast together, указывающее на то, что массивы имеют несовместимые формы.
Входные массивы не обязательно должны иметь одинаковое количество измерений. Результирующий массив будет иметь такое же количество измерений, как массив с наибольшим количеством измерений среди входных массивов, где размер каждого измерения — это максимальный размер соответствующего измерения среди входных массивов. Обратите внимание, что отсутствующие измерения предполагаются размером один.
Например, если у вас есть 256x256x3 массив значений RGB, и вы хотите масштабировать каждый цвет на изображении на разное значение, вы можете умножить изображение на одномерный массив с 3 значениями. Выравнивание размеров конечных осей этих массивов в соответствии с правилами вещания показывает, что они совместимы:
Image (3d array): 256 x 256 x 3 Scale (1d array): 3 Result (3d array): 256 x 256 x 3
Когда одно из сравниваемых измерений равно единице, используется другое. Другими словами, измерения с размером 1 растягиваются или «копируются», чтобы соответствовать другому.
В следующем примере оба A и B массива имеют оси длиной один, которые расширяются до большего размера во время операции вещания:
A (4d array): 8 x 1 x 6 x 1 B (3d array): 7 x 1 x 5 Result (4d array): 8 x 7 x 6 x 5
Массивы, подлежащие вещанию
Набор массивов называется «подлежащим вещанию» в ту же форму, если вышеуказанные правила дают допустимый результат.
Например, если a.shape — (5,1), b.shape — (1,6), c.shape — (6,) и d.shape — (), так что d — скаляр, то a, b, c и d вещаемы до размерности (5,6); и
-
a действует как массив (5,6), где
a[:,0]вещательно передается в другие столбцы, -
b действует как массив (5,6), где
b[0,:]вещательно передается в другие строки, -
c действует как массив (1,6) и поэтому как массив (5,6), где
c[:]вещательно передается в каждую строку, и, наконец, - d действует как массив (5,6), где единственное значение повторяется.
Вот еще несколько примеров:
A (2d array): 5 x 4 B (1d array): 1 Result (2d array): 5 x 4 A (2d array): 5 x 4 B (1d array): 4 Result (2d array): 5 x 4 A (3d array): 15 x 3 x 5 B (3d array): 15 x 1 x 5 Result (3d array): 15 x 3 x 5 A (3d array): 15 x 3 x 5 B (2d array): 3 x 5 Result (3d array): 15 x 3 x 5 A (3d array): 15 x 3 x 5 B (2d array): 3 x 1 Result (3d array): 15 x 3 x 5
Вот примеры форм, которые не вещаются:
A (1d array): 3 B (1d array): 4 # trailing dimensions do not match A (2d array): 2 x 1 B (3d array): 8 x 4 x 3 # second from last dimensions mismatched
Пример вещания, когда одномерный массив добавляется к двумерному:
>>> a = np.array([[ 0.0, 0.0, 0.0],
... [10.0, 10.0, 10.0],
... [20.0, 20.0, 20.0],
... [30.0, 30.0, 30.0]])
>>> b = np.array([1.0, 2.0, 3.0])
>>> a + b
array([[ 1., 2., 3.],
[11., 12., 13.],
[21., 22., 23.],
[31., 32., 33.]])
>>> b = np.array([1.0, 2.0, 3.0, 4.0])
>>> a + b
Traceback (most recent call last):
ValueError: operands could not be broadcast together with shapes (4,3) (4,)
Как показано на рисунке 2, b добавляется к каждой строке a. На рисунке 3 возникает исключение из-за несовместимых форм.
Рисунок 2
Добавление одномерного массива к двумерному приводит к вещанию, если количество элементов одномерного массива соответствует количеству столбцов двумерного массива.
Рисунок 3
Когда конечные измерения массивов не равны, вещание терпит неудачу, потому что невозможно выровнять значения строк первого массива с элементами второго массива для поэлементного сложения.
Вещание предоставляет удобный способ выполнения внешнего произведения (или любой другой внешней операции) двух массивов. Следующий пример демонстрирует внешнюю операцию сложения двух одномерных массивов:
>>> a = np.array([0.0, 10.0, 20.0, 30.0])
>>> b = np.array([1.0, 2.0, 3.0])
>>> a[:, np.newaxis] + b
array([[ 1., 2., 3.],
[11., 12., 13.],
[21., 22., 23.],
[31., 32., 33.]])
Рисунок 4
В некоторых случаях вещание растягивает оба массива, чтобы сформировать выходной массив, больший, чем любой из исходных массивов.
Здесь оператор newaxis вставляет новую ось в a, превращая его в двумерный массив 4x1. Объединение массива 4x1 с b, имеющим форму (3,), дает массив 4x3.
Практический пример: векторное квантование
Вещание часто встречается в реальных задачах. Типичный пример — алгоритм векторного квантования (VQ), используемый в теории информации, классификации и других смежных областях. Основная операция в VQ находит ближайшую точку в наборе точек, называемых codes в терминологии VQ, к данной точке, называемой observation. В очень простом двумерном случае, показанном ниже, значения в observation описывают вес и рост спортсмена, подлежащего классификации. codes представляют собой различные классы спортсменов. [1] Для поиска ближайшей точки требуется вычисление расстояния между наблюдением и каждой из кодов. Наименьшее расстояние предоставляет наилучшее соответствие. В этом примере codes[0] является ближайшим классом, что указывает на то, что спортсмен, вероятно, баскетболист.
>>> from numpy import array, argmin, sqrt, sum >>> observation = array([111.0, 188.0]) >>> codes = array([[102.0, 203.0], ... [132.0, 193.0], ... [45.0, 155.0], ... [57.0, 173.0]]) >>> diff = codes - observation # the broadcast happens here >>> dist = sqrt(sum(diff**2,axis=-1)) >>> argmin(dist) 0
В этом примере массив observation растягивается, чтобы соответствовать форме массива codes:
Observation (1d array): 2 Codes (2d array): 4 x 2 Diff (2d array): 4 x 2
Рисунок 5
Основная операция векторного квантования рассчитывает расстояние между объектом, подлежащим классификации, темным квадратом, и множеством известных кодов, серыми кружками. В этом простом случае коды представляют отдельные классы. В более сложных случаях используются несколько кодов на класс.
Обычно большое количество observations, возможно, считанных из базы данных, сравнивается с набором codes. Рассмотрим этот сценарий:
Observation (2d array): 10 x 3 Codes (3d array): 5 x 1 x 3 Diff (3d array): 5 x 10 x 3
Трехмерный массив diff является следствием вещания, а не необходимостью для вычисления. Большие наборы данных будут генерировать большой промежуточный массив, что неэффективно с точки зрения вычислений. Вместо этого, если каждое наблюдение рассчитывается индивидуально с помощью цикла Python вокруг кода в двумерном примере выше, используется гораздо меньший массив.
Вещание — мощный инструмент для написания краткого и обычно интуитивно понятного кода, который очень эффективно выполняет вычисления на C. Однако в некоторых случаях вещание использует чрезмерно большие объемы памяти для конкретного алгоритма. В этих случаях лучше написать внешний цикл алгоритма на Python. Это также может привести к более читаемому коду, так как алгоритмы, использующие вещание, как правило, становятся сложнее для интерпретации по мере увеличения числа измерений в вещании.
Примечания
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/user/basics.broadcasting.html