무효 클릭 IP 추적 중...
머신러닝,딥러닝/넘파이,numpy

[넘파이 기초] broadcasting(브로드 캐스팅) 파헤치기 1편

꼬예 2021. 4. 16.

오늘은 넘파이를 사용할때 필수적으로 사용하는 기능인 broadcasting 에 대해서 알아보도록 하겠습니다.

사실 이 녀석은 잘알면 너무나도 편한 기능이지만 애매 하게 알면.. 혼란을 야기하는 녀석이기도 한데요.

왜냐하면 shape에 따라 작동을 안하기도 하고, 또한 차원에 따라 다른 동작을 하거든요!

 

각 차원별로 어떻게 내부적으로 연산을 하는지 그림을 통해서 하나하나 뜯어보도록 하겠습니다. 

 

브로드캐스팅이 어떤 기능인지 낯선 분들을 위해 간단한 설명을 하고 시작하도록 하겠습니다. 

 

 

브로드 캐스팅(broadcasting)

실제 수학에서는 (2,3,4) X 2 = (4,6,8) 이런식으로 가능하죠?

하지만 실제수학에서는 (2,3,4) + (2)에 연산은 가능하지 않습니다. 하지만.. 넘파이에선 가능하죠.

import numpy as np

A = np.array([2,3,4])
B = A + 2
print(B)

output:

숫자 를 하나 더했을뿐인데 각요소에 알아서 들어간걸 볼수가있죠. 

(2,3,4) + 2 만 했을 뿐인데 자동적으로 (2, 3, 4) + (2, 2, 2) 로 내부적으로 같은 shape을 만들어 계산을 해주는것입니다.

 

예시와 벡터의 갯수가 작을때는 티가 안나지만 데이터가 커지면 커질수록  아주 유용한 기능이 겠지요..?

 

덧셈 연산 뿐만 아니라 다양한 연산이가능 합니다.

import numpy as np

A = np.array([2,3,4])
B = 2
print("a * b: ", A.__mul__(B))
print("a / b: ", A.__truediv__(B))
print("a // b: ", A.__floordiv__(B))
print("a % b: ", A.__mod__(B))
print("a ** b: ", A.__pow__(B))

output:

 

 

2차원일때 broadcasting

import numpy as np

A = np.arange(9).reshape(3,3)
B = 10*np.arange(3).reshape((1,-1)) # row 자리에 1을 넣어서 row 벡터 형태로 만듬.
C = A + B

print("A : {}/{}\n{}".format(A.ndim, A.shape, A))  # ndim은 몇차원인지 알기 위한 property임.
print("B : {}/{}\n{}\n".format(B.ndim, B.shape, B))
print("A + B: {}/{}\n{}".format(C.ndim,C.shape, C))

output :

A, B 둘다 2차원으로 같은 차원인걸 확인할 수있네요!

 

넘파이에서 내부적으로 아래와 같은 식으로 연산을 수행하는데요.

연산을 수행하기 위해서 넘파이는 계산하게될 두 데이터값의 shape을 맞춰주는 작업을 합니다.

b의 shape (1,3)으로 A shape과 다르므로 (3,3)으로 변경 시켜줍니다. 변경 시켜줄때 기존에 있는 값을 복사하면서 같은 shape으로 만들어줍니다.

 

shape가 맞춰졌기 때문에 각 위치에 상응하는 값끼리 연산을 수행할 수있습니다.

 

 

이해를 돕기위해 좀 다른 형태로 볼까요?

 

 

import numpy as np

A = np.arange(9).reshape(3,3)
B = 10*np.arange(3).reshape((-1,1)) # 컬럼에 자리에 1을 넣어서 컬럼 형태로 만듬. 
C = A + B

print("A : {}/{}\n{}".format(A.ndim, A.shape, A))
print("B : {}/{}\n{}\n".format(B.ndim, B.shape, B))
print("A + B: {}/{}\n{}".format(C.ndim,C.shape, C))

output:

 

A, B 둘다 2차원으로 같은 차원인걸 확인할 수있네요!

 

마찬가지로 넘파이에서 내부적으로 아래와 같은 식으로 연산을 수행합니다.

 

(3,1)의 shape을 가지고 있는 B는 (3,3)의 형태로 바꾸기 위해  기존값을 복사하면서 shape을 맞춥니다. 

shape가 맞춰졌기 때문에 각 위치에 상응하는 값끼리 연산을 수행할 수 있습니다.

 

여기까지 보시면 어떠한 규칙을 발견하실 수 있을 겁니다. 

앞선 첫번째 예에서 B 의 shape이 (1, 3) -> (3, 3)  1 이 3으로 바뀌었죠?

두번째 예에선 B의 shape이 (3, 1) -> (3,3) 마찬가지로 1이 3으로 바뀌었습니다. 

 

그렇다면..

A 벡터가 (3,1) 의 형태이고 B 벡터가 (1,3)일 형태일때는 어떻게 연산을 수행할까요??

 

 

A벡터에서 1은 column 입니다. B 벡터의 column을 shape을 보니 3이네요.1을 3으로 변경해주면 됩니다.

 

반대로 B백터에서 1은 row입니다. A 벡터의 row의 shape을보니 3이네요.마찬가지로 1을 3으로 변경해주면 됩니다.

 

코드와 그림으로 확인해보겠습니다. 

 

import numpy as np

A = np.arange(3).reshape(-1,1)
B = 10*np.arange(3).reshape((1,-1)) 
C = A + B

print("A : {}/{}\n{}".format(A.ndim, A.shape, A))
print("B : {}/{}\n{}\n".format(B.ndim, B.shape, B))
print("A + B: {}/{}\n{}".format(C.ndim,C.shape, C))

output:

 

 

A, B 둘다 2차원으로 같은 차원인걸 확인했습니다.

 

위 그림과 같이 1인 부분을 연산할 상대 데이터의 (row, column)값으로 각각 바꿔주기 위해,

복사를 수행하는걸 볼 수 있습니다.

 

shape이 같아지면 같은 위치에 있는 값끼리 연산을 수행합니다.

 

 

 

3차원일때 broadcasting

3차원일때를 확인해보겠습니다. 

앞서 배운 2차원일때랑 같은 원리가 적용되니 차근차근 확인해보세요!

 

import numpy as np

A = np.arange(18).reshape((2,3,3))
B = 10*np.arange(9).reshape((1,3,3)) 
C = A + B

print("A : {}/{}\n{}".format(A.ndim, A.shape, A))
print("B : {}/{}\n{}\n".format(B.ndim, B.shape, B))
print("A + B: {}/{}\n{}".format(C.ndim,C.shape, C))

Output:

이번에도 3차원으로 A , B 둘다 차원이같습니다.

2차원이랑 같은 원리라면

B 의 shape(1,3,3)에서 1인 부분인 A의 (2,3,3)에서 2인 부분으로 바뀌어야 된다는걸 이제 다들 눈치채셨겠죠?

A 의 (2,3,3)에서 2는 3x3 행렬이 2개 있다는 의미입니다. 

다시 말하면 B의 (1,3,3)을 (2,3,3)으로 변경해준다는 말은 3x3 행렬을 1개에서 2개로 만들어줘야된다는 뜻이기때문에 위와 같은 연산을 진행하게 됩니다.

 

 

shape이 맞춰진 두 텐서는 아래와 같이 상응하는 위치의 값끼리 연산을 합니다.

 

이해를 돕기 위해 다른 예를 들어보겠습니다. 

 

이번엔 A는 (2,3,3) 의 형태이고 B는 (2,1,3)의 형태입니다. 어떻게 연산이 진행될까요?

 

 

import numpy as np

A = np.arange(18).reshape((2,3,3))
B = 10*np.arange(6).reshape((2,1,3)) 
C = A + B

print("A : {}/{}\n{}".format(A.ndim, A.shape, A))
print("B : {}/{}\n{}\n".format(B.ndim, B.shape, B))
print("A + B: {}/{}\n{}".format(C.ndim,C.shape, C))

output:

 

차원은 3차원으로 같은걸 확인할 수있습니다.

 

 

마찬가지로 B의 (2,1,3)에서 row 부분이 1에서 3으로 바뀌어야합니다. 

그림으로 확인해볼까요?

row가 바뀌어야하니 위와 같은 형태로 복사를 실시합니다. 

2차원에서 배웠던 내용과 비슷하죠? 다만 여기선 3차원으로 행렬이 두개가 있다보니 동시에 두개가 열을 복사하는 것을 볼 수 있습니다. 

 

최종 연산 결과입니다.

 

마지막으로  A : (2,3,3) 과 B : (2,3,1) 연산은 어떻게 될까요?

 

import numpy as np

A = np.arange(18).reshape((2,3,3))
B = 10*np.arange(6).reshape((2,3,1)) 
C = A + B

print("A : {}/{}\n{}".format(A.ndim, A.shape, A))
print("B : {}/{}\n{}\n".format(B.ndim, B.shape, B))
print("A + B: {}/{}\n{}".format(C.ndim,C.shape, C))

output:

 

차원은 3차원으로 같습니다. 

 

column이 바뀌어야하니 위와 같은 형태로 복사를 실시합니다.

 

최종결과는 위와 같습니다. 

 

정리

2차원이든 3차원이든 상관없이 우리는 1을 주목! 하고 그값을 변경하면 모든게 해결되었습니다.

하지만 오늘 배운내용은 A, B 둘의 shape은 달랐지만 차원은 같았습니다.

 

다음 시간에는 차원이 다를경우 어떻게 연산되는지 알아보겠습니다. 

많은 분들이 이둘의 차이를 막연히 이해하고있기때문에 많은 어려움을 겪는데요.

 

1편을 복습하신후! 2편도 꼭 확인해주시기 바랍니다.

broadcasting 2편

 

[넘파이 기초] broadcasting(브로드 캐스팅) 파헤치기 2편

1편에 이어 2편 시작하겠습니다. [넘파이 기초] broadcasting(브로드 캐스팅) 파헤치기 1편 오늘은 넘파이를 사용할때 필수적으로 사용하는 기능인 broadcasting 에 대해서 알아보도록 하겠습니다. 사실

yeko90.tistory.com

 

 

 

이 글과 읽으면 좋은글

 

 

 

 

  • 트위터 공유하기
  • 페이스북 공유하기
  • 카카오톡 공유하기
이 컨텐츠가 마음에 드셨다면 커피 한잔(후원) ☕

댓글