본문 바로가기
Data Science/파이썬으로 데이터분석 하기

Python 기초 - NumPy Broadcasting 이해하기 (2)

by 싸코 2017. 10. 9.

지난 포스팅에서는 NumPy를 활용해서 간단하게 벡터 연산과 행렬 연산을 해보았다. 이번 포스팅에서 다룰 내용은 기초라고는 할 수 없지만 앞으로 NumPy를 활용해서 다른 모양의 배열 간의 연산을 수행할 때 꼭 이해해야 하는 개념인 브로드캐스팅을 알아야 하기 때문에 이를 소개하고자 한다.


2017/10/09 - [Data Science/Python] - Python 기초 - NumPy로 선형대수 표현하기 (1)



선형대수 Linear Algebra



1. 벡터 Vectors
2. 행렬 Matrix

3. 브로드캐스팅 Broadcasting


3. 브로드캐스팅 Broadcasting

브로드캐스팅이라고 하면 우리는 "방송"이라는 단어가 생각나 브로드캐스팅이 왜 파이썬에서 나온거냐고 그 개념이 잘 와닿지 않을 수 있다. Dictionary.com에서 broadcast로 검색을 해봤더니


verb (used with object)broadcast or broadcasted,broadcasting.
1.
to transmit (programs) from a radio or television station.
2.
to speak, perform, sponsor, or present on a radio or televisionprogram:
The president will broadcast his message on all stations tonight.
3.
to cast or scatter abroad over an area, as seed in sowing.
4.
to spread widely; disseminate:
She broadcast the good news all over town.
5.
to indicate unwittingly to another (one's next action); telegraph:

He broadcast his punch and the other man was able to parry it.



위와 같은 결과가 나왔다. 1번과 2번은 우리가 잘아는 방송과 관련된 뜻이었지만, 그 밑을 보면 broadcast란 단어는 무언가를 '흩뿌리고 퍼뜨리고 전파'할 때 사용하는 것이라는 것을 알 수 있다. 아래 그림을 보면서 전파한다는 의미를 더 설명해보도록 하겠다.



일반적으로 NumPy에서 모양이 다른 배열끼리는 연산이 불가능하다.


In [167]: a = np.array([1,2,3])


In [168]: b = np.array([1,2])

In [169]: a + b
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-169-f96fb8f649b6> in <module>()
----> 1 a + b

ValueError: operands could not be broadcast together with shapes (3,) (2,)

에러 문구를 보면 broadcast가 되지 못했다는 의미가 있다. 특정한 어떤 조건이 맞아진다면 모양이 다른 배열끼리도 연산을 수행할 수 있다는 의미를 가진다.

Image result for numpy broadcasting
출처: http://www.astroml.org/book_figures/appendix/fig_broadcast_visual.html

브로드캐스팅은 어떤 조건만 만족한다면 모양이 다른 배열끼리의 연산도 가능하게 해주며 모양이 부족한 부분은 확장하여 연산을 수행할 수 있도록 한다는 것이라고 생각할 수 있다. 확장 또는 전파한다는 의미로 Broadcasting을 설명하는 가장 간단한 예는 배열과 스칼라 값을 계산하는 것이다. 스칼라는 일반 상수를 생각하면 된다.

위의 그림에서는 첫 번째 그림에 해당하는 것으로 0, 1, 2라는 NumPy로 생성한 배열에 스칼라 5를 합한 결과가 5, 6, 7이라는 것을 알 수 있다. 일반적인 파이썬 리스트를 사용하면 for문을 이용해야 같은 결과를 얻을 수 있지만, NumPy에서는 브로드캐스팅의 개념 덕분에 5가 0이외에 1과 2의 원소 부분에도 전파(broadcast)되어 계산되어 간단하게 합 연산을 수행하는 것만으로 같은 결과를 얻을 수 있었다.

두 번째 그림은 배열 간의 계산으로 배열의 차원이 확대된 케이스이다. 두 번째 그림은 3x3 배열에 1x3 배열을 합 연산한 경우이다. 각 행에 동일한 계산을 전파한 것을 볼 수 있다.

세번째 그림은 브로드캐스팅의 확장성 측면을 극명하게 보여주는 케이스이다. 3x1 배열과 1x3 배열의 합을 했는데 두 번째에서는 한쪽의 더 낮은 차원의 배열에서만 아래(0번 축) 방향으로 broadcast한 것에 반해 양 쪽 배열에서 broadcast한 것을 확인할 수 있다.


정리하면,
브로드캐스팅(Broadcasting)은 모양이 다른 배열들 간의 연산이 어떤 조건을 만족했을 때 가능해지도록 배열을 자동적으로 변환하는 것이라고 정의할 수 있다. 그리고 누락되었거나 길이가 1인 차원에 대해 브로드캐스팅이 수행된다.

위의 세 그림은 배열 연산에서 항상 1차원 배열이 포함되어 있다. 그리고 행/열의 어떤 축이든(;0번/1번 어떤 축이든) 차원의 길이가 같은 것을 알 수 있다. 3x3 + 1x3,  3x1 + 1x3

브로드캐스팅이 일어날 수 있는 조건은 다음과 같다.
  1. 차원의 크기가 1일때 가능하다
    두 배열 간의 연산에서 최소한 하나의 배열의 차원이 1이라면(0번 축이든 1번 축이든; 1행이든 1열이든) 가능하다.
  2. 차원의 짝이 맞을 때 가능하다
    차원에 대해 축의 길이가 동일하면 브로드캐스팅이 가능하다.


두 번째 그림은 3x3 인 배열과 1x3인 배열의 연산이다. 2번 조건을 만족하여 브로드캐스팅이 수행되었다. 1x3 배열이 브로드캐스팅되어 3x3 배열로 변형되어 연산이 수행되었다.


세 번째 그림은 3x1 인 배열과 1x3인 배열의 연산이다. 1번 조건을 만족하여 브로드캐스팅이 수행되었다. 3x1인 배열은 길이가 1인 차원에 대해 반복 확장되었고, 1x3인 배열도 마찬가지로 길이가 1인 차원에 대해 반복 확장되었고 확장된 두 배열 간에 연산이 일어났다.




아래는 3차원 배열의 0번 축으로 브로드캐스팅 된 예시이다. 만족스러운 그림이 없어서 직접 만들어보았다. 3,4,2의 길이를 가진 3차원 배열과 4,2의 길이를 가진 2차원 배열이 있다. 위에서 말한 조건처럼 차원의 짝이 맞아야 브로드캐스팅이 수행될 수 있다. 2차원 배열의 4,2가 3차원 배열의 4,2와 짝이 맞기 때문에 누락된 부분인 3만큼의 차원 확장(broadcasting)이 되고 합 연산이 수행된다. 0번 축으로 브로드캐스팅이 되었다.

브로드캐스팅은 저차원의 배열을 연산을 위해서 고차원 배열로 확장시키는 것과 같다.


위의 그림을 NumPy로 브로드캐스팅을 통해 쉽게 연산이 된다. 아래는 위의 그림을 코드로 돌려본 결과이다.


In [171]: array3d = np.array([[[0,1], [2,3],  [4,5],  [6,7]],

     ...:          [[8,9],  [10,11],[12,13],[14,15]],

     ...:          [[16,17],[18,19],[20,21],[22,23]]])

     ...:


In [172]: array3d

Out[172]:

array([[[ 0,  1],

        [ 2,  3],

        [ 4,  5],

        [ 6,  7]],


       [[ 8,  9],

        [10, 11],

        [12, 13],

        [14, 15]],


       [[16, 17],

        [18, 19],

        [20, 21],

        [22, 23]]])


In [173]: array2d = np.array([[0,1],[2,3],[4,5],[6,7]])


In [174]: array2d

Out[174]:

array([[0, 1],

       [2, 3],

       [4, 5],

       [6, 7]])


In [175]: array3d + array2d

Out[175]:

array([[[ 0,  2],

        [ 4,  6],

        [ 8, 10],

        [12, 14]],


       [[ 8, 10],

        [12, 14],

        [16, 18],

        [20, 22]],


       [[16, 18],

        [20, 22],

        [24, 26],

        [28, 30]]])




아래는 array를 차원에 따라 어떤 모양을 하고 있는지 그리고 축(axis)은 어떻게 구성되었는지 참고할 만한 그림이다.


출처: https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch01.html







참고자료

1. 웨스 맥키니. 파이썬 라이브러리를 활용한 데이터분석. 한빛미디어. 2013

2. https://kakalabblog.wordpress.com/2017/04/03/zen-of-numpy/


댓글