Numpy 다차원 배열에서 shape이 다른 배열끼리의 연산이 가능하게 하는 기능을 브로드캐스트 Broadcast라고 합니다.
예를 들어 2×2 행렬과 scala 2를 곱할 때 scala 2가 2×2로 확장되면서 두 개의 element wise 곱셈이 가능해지는 것입니다. 여기서 element wise란 각각의 개별 원소 by 원소 라고 보시면 됩니다.
a = np.array([[1,2],[3,4]])
b = np.array([10, 20])
a * b
위에 예시처럼 element wise 곱을 하면 결과는 아래처럼 b라는 1차원 배열이 2차원으로 확장되어서 곱해집니다. 이렇게 자동으로 곱셈이 가능하도록 확장해주는 것이 브로드캐스트의 기능입니다.
array([[10, 40],
[30, 80]])
브로드캐스트의 경우 같은 shape의 배열끼리 곱하는 것보다 더 적은 메모리를 차지하기 때문에 좀 더 효율적이라고 볼 수 있습니다.
브로드캐스트를 적용이 가능한 경우와 불가능한 경우를 알아보기 위해 numpy 공식 문서를 번역하여 공유드려보도록 하겠습니다.
출처: https://numpy.org/doc/stable/user/basics.broadcasting.html
두 배열에 대해 처리할 때, numpy는 두 배열의 shape을 element-wise 하게 비교합니다. 이때 차원을 살펴보는데, 두 배열이 호환 가능할 때는 다음과 같습니다.
1. 두 축이 똑같거나 또는
2. 둘 중 하나의 축이 1일때 입니다
이 조건이 충족되지 않으면, ValueError: operands could not be broadcast together
라는 에러가 발생하면서 배열이 서로 호환되지 않는 shape임을 나타냅니다.
배열은 반드시 같은 차원의 수를 가질 필요는 없습니다. 예를 들어, RGB 값인 256x256x3 배열의 경우, 이미지에 있는 각 컬러를 다른 값에 의해서 확장하고 싶을 때, 그 이미지를 3개의 값으로 이루어진 1차원 배열과 곱할 수 있습니다. 브로드캐스트 규칙에 따라 이러한 배열의 크기에 대해서 다음과 같이 호환가능함을 알 수 있습니다:
Image (3d array): 256 x 256 x 3
Scale (1d array): 3
Result (3d array): 256 x 256 x 3
둘 중 하나의 차원이 1차원인데, 이 1차원이 확장되어서 다른 배열의 차원과 호환이 가능해집니다.
다음 예시를 보면, A와 B 배열 모두 브로드캐스트를 적용할 때 확장이 가능한 길이인 1을 축으로 가지고 있습니다:
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 (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 # 축의 길이가 다릅니다
A (2d array): 2 x 1
B (3d array): 8 x 4 x 3 # A의 축 길이 2와 B의 축 길이 4가 맞지 않습니다
실제 브로드캐스트 예시입니다:
>>> x = np.arange(4)
>>> xx = x.reshape(4,1)
>>> y = np.ones(5)
>>> z = np.ones((3,4))
>>> x.shape
(4,)
>>> y.shape
(5,)
>>> x + y
ValueError: operands could not be broadcast together with shapes (4,) (5,)
>>> xx.shape
(4, 1)
>>> y.shape
(5,)
>>> (xx + y).shape
(4, 5)
>>> xx + y
array([[ 1., 1., 1., 1., 1.],
[ 2., 2., 2., 2., 2.],
[ 3., 3., 3., 3., 3.],
[ 4., 4., 4., 4., 4.]])
>>> x.shape
(4,)
>>> z.shape
(3, 4)
>>> (x + z).shape
(3, 4)
>>> x + z
array([[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.]])
브로드캐스트는 두 배열의 간단한 outer product(외적) 또는 outer operation을 제공합니다.
다음의 예시는 두 개의 1차원 배열의 outer addition operation을 보여줍니다.
>>> a = np.array([0.0, 10.0, 20.0, 30.0]) #a.shape = (4,)
>>> b = np.array([1.0, 2.0, 3.0]) #b.shape = (3,)
>>> a[:, np.newaxis] + b #a[:, np.newaxis].shape = (4,1) & b.shape = (3,)
array([[ 1., 2., 3.],
[ 11., 12., 13.],
[ 21., 22., 23.],
[ 31., 32., 33.]])
여기서 newaxis index operator는 a에 새로운 축을 삽입하여, 2차원의 4×1 배열을 만들어냅니다. (3,) shape을 가진 b와 4×1 배열을 결합하면 4×3 배열이 됩니다.