EDA with Python - NumPy Broadcasting

Page content

강의 홍보

공지

제 수업을 듣는 사람들이 계속적으로 실습할 수 있도록 강의 파일을 만들었습니다. 늘 도움이 되기를 바라며. 참고했던 교재 및 Reference는 꼭 확인하셔서 교재 구매 또는 관련 Reference를 확인하시기를 바랍니다.

I. 개요

  • NumPy는 C언어로 구성되었으며, 고성능의 수치계산을 위해 나온 패키지이며, Numerical Python의 약자이다.
  • Python을 활용한 데이터 분석을 수행할 때, 그리고 데이터 시각화나 전처리를 수행할 때, NumPy는 매우 자주 사용되기 때문에 한번쯤은 꼭 다듬고 가는 것이 중요하다.
  • 이전 포스트에서는 Python - NumPy 소개 및 다양한 객체 생성에 대해 다루었으니, 본 포스트 읽기에 앞서서 기본적인 개념에 대해 확인하기를 바란다.

II. 모듈 Import

import numpy as np
print(np.__version__)
1.18.4

III. NumPy 기본 활용법

  • NumPy 객체 생성을 한 뒤에, 파일 저장, 서로 다른 배열끼리의 사칙연산 등을 수행할 수 있다.

(1) NumPy 객체 파일 저장 및 불러오기

  • savetxt, loadtxt, 그리고 genfromtxt 함수를 활용하여 객체를 불러오는 예제를 실습한다.
# 객체 생성 후 저장하기
x = np.arange(0.0, 50.0, 1.0)
print(x)
np.savetxt('data.out', x, delimiter=',')
[ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17.
 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35.
 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.]
!ls
data.out  sample_data
  • 현재 폴더에 data.out 파일이 생성된 것을 확인할 수 있다.
# `data.out` 불러오기
z = np.loadtxt('data.out', unpack=True)
print(z)
[ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17.
 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35.
 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.]
  • 정상적으로 data.out을 불러와서 z객체에 저장된 것을 확인할 수 있다.
# genfromtxt 활용
my_array2 = np.genfromtxt('data.out', 
                          skip_header=1, 
                          filling_values=-999)
print(my_array2)
[ 1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17. 18.
 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36.
 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.]
  • z객체와 마찬가지로 my_array2도 객체가 정상적으로 생성된 것을 확인할 수 있다.

  • loadtxtgenfromtxt의 차이점이 있기는 하다. 결론부터 말하면, genfromtxt가 다양한 옵션을 제공한다. 간단한 예를 들면, genfromtxt의 경우 열들의 자료형을 자동으로 결정해주어 사용자들이 좀 더 편안하게 사용할 수 있도록 도와준다 (Clinton, 2016, p. 281-2).

(2) 2차원 배열 Inspection

  • ndim, size, flags, itemsize, nbytes를 활용하여 배열의 정보를 획득한다.
  • 특히, 딥러닝 모형 정의 및 학습할 때, NumPy 배열 에러 등이 종종 발생하기 때문에 기본 개념은 학습하는 것을 추천한다.
my2D_Array = np.array([[1,2,3,4], [2,4,6,8], [3,6,9,12]])
print(my2D_Array)
[[ 1  2  3  4]
 [ 2  4  6  8]
 [ 3  6  9 12]]
# ndim
print(my2D_Array.ndim)
2
# size (각 item의 개수를 의미)
print(my2D_Array.size)
12
# 2차원 배열의 memory layout 확인 
print(my2D_Array.flags)
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False
# 1차원의 길이 in bytes (예. 숫자는 2bytes)
print(my2D_Array.itemsize)
8
# 전체 bytes
print(my2D_Array.nbytes)
96

IV. Broadcasting(=브로드 캐스팅)

  • 개념 설명은 다음과 같다.

Broadcasting is a mechanism that permits NumPy to operate with arrays of different shapes when performing arithmetic operations:

간단하게 해석하면 브로드캐스팅(Broadcasting)은 모양이 다른 배열들 간의 사칙연산도 가능하게끔 도와주는 일종의 mechanism이다. 그런데, 여기에는 기본적인 Rule이 있다.

(1) Rule 1. Eqaul Dimensions between A and B

  • 우선 모양이 같은 AB NumPy 객체를 생성한다.
  • A + B를 연산하여 출력한다.
# A 객체
A = np.ones((5,3))
print(A)
print(A.shape)
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
(5, 3)
# B 객체
B = np.random.random((5,3))
print(B)
print(B.shape)
[[0.13526155 0.07276429 0.25635867]
 [0.74909147 0.09515243 0.20379472]
 [0.33115034 0.13771662 0.61417221]
 [0.79164803 0.72754531 0.29071265]
 [0.36780642 0.50456743 0.19266307]]
(5, 3)
print(A+B)
[[1.13526155 1.07276429 1.25635867]
 [1.74909147 1.09515243 1.20379472]
 [1.33115034 1.13771662 1.61417221]
 [1.79164803 1.72754531 1.29071265]
 [1.36780642 1.50456743 1.19266307]]
  • 정상적으로 덧셈이 연산된 것을 확인할 수 있다.

(2) Rule 2. Compatible Dimensions when one of them is 1

  • 이번에는 X와 Y 객체를 생성하고 shape를 통해 차원이 어떻게 다른지 확인한다.
x = np.ones((3,4))
print(x)
print(x.shape)
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]
(3, 4)
y = np.arange(4)
print(y)
print(y.shape)
[0 1 2 3]
(4,)
# 뺄셈 연산 수행 (x-y)
print(x-y)
[[ 1.  0. -1. -2.]
 [ 1.  0. -1. -2.]
 [ 1.  0. -1. -2.]]
  • 위 코드에서 중요한 것은 숫자 4이다. 즉, 1차원 길이가 다르면 연산은 에러가 발생한다.
  • y의 숫자 4 대신 5를 대입해서 적용해보자.
x = np.ones((3,4))
y = np.arange(5)
print(x-y)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-33-d91ef8252b95> in <module>()
      1 x = np.ones((3,4))
      2 y = np.arange(5)
----> 3 print(x-y)


ValueError: operands could not be broadcast together with shapes (3,4) (5,) 
  • 위 에러 문구(operands could not be broadcast together with shapes (3,4) (5,))에서 확인 할 수 있는 것처럼 1차원 길이가 다르면 에러가 발생한다.

(3) Rule 3. Compatitble in all of the dimensions

  • 이번에는 서로 다른 모양의 객체를 확인한다.
  • 그리고 덧셈을 수행해보자.
x = np.ones((6,5))
print(x)
print(x.shape)
[[1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]
(6, 5)
y = np.random.random((3, 1, 5))
print(y)
print(y.shape)
[[[0.68586586 0.9179277  0.88033036 0.6306833  0.17327179]]

 [[0.21184583 0.28629664 0.00532677 0.49662726 0.73354008]]

 [[0.82677113 0.49873148 0.56901096 0.82320785 0.04587492]]]
(3, 1, 5)
print(x+y)
[[[1.68586586 1.9179277  1.88033036 1.6306833  1.17327179]
  [1.68586586 1.9179277  1.88033036 1.6306833  1.17327179]
  [1.68586586 1.9179277  1.88033036 1.6306833  1.17327179]
  [1.68586586 1.9179277  1.88033036 1.6306833  1.17327179]
  [1.68586586 1.9179277  1.88033036 1.6306833  1.17327179]
  [1.68586586 1.9179277  1.88033036 1.6306833  1.17327179]]

 [[1.21184583 1.28629664 1.00532677 1.49662726 1.73354008]
  [1.21184583 1.28629664 1.00532677 1.49662726 1.73354008]
  [1.21184583 1.28629664 1.00532677 1.49662726 1.73354008]
  [1.21184583 1.28629664 1.00532677 1.49662726 1.73354008]
  [1.21184583 1.28629664 1.00532677 1.49662726 1.73354008]
  [1.21184583 1.28629664 1.00532677 1.49662726 1.73354008]]

 [[1.82677113 1.49873148 1.56901096 1.82320785 1.04587492]
  [1.82677113 1.49873148 1.56901096 1.82320785 1.04587492]
  [1.82677113 1.49873148 1.56901096 1.82320785 1.04587492]
  [1.82677113 1.49873148 1.56901096 1.82320785 1.04587492]
  [1.82677113 1.49873148 1.56901096 1.82320785 1.04587492]
  [1.82677113 1.49873148 1.56901096 1.82320785 1.04587492]]]
  • 만약에 여기에서 y 값을 (3,2,5) 또는 (3,1,6)으로 바꾸면 어떻게 될까?
y = np.random.random((3, 2, 5))
print(y)
print(y.shape)
[[[0.95042186 0.52979398 0.30549682 0.93579665 0.03784094]
  [0.27023327 0.19952522 0.68376492 0.11982131 0.45394735]]

 [[0.69797278 0.85359121 0.01493669 0.06111047 0.25285451]
  [0.57293653 0.28453573 0.18296577 0.12696192 0.28530794]]

 [[0.75410798 0.88487199 0.78635143 0.05059668 0.48753369]
  [0.65395856 0.74907103 0.47013049 0.14938089 0.36961655]]]
(3, 2, 5)
print(x+y)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-43-39cb3db33052> in <module>()
----> 1 print(x+y)


ValueError: operands could not be broadcast together with shapes (6,5) (3,2,5) 
  • 에러가 나는 것을 확인할 수 있다.
  • 왜 에러가 날까? x의 2차원 배열의 숫자가 앞에 6으로 되어 있기 때문이다.
  • y값은 그대로 놔둔채, x의 값 6 대신 2로 바꿔주고 덧셈을 하면 성공적으로 연산이 수행됨을 알 수 있다.
x = np.ones((2,5))
print(x)
print(x.shape)
[[1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]
(2, 5)
print(x+y)
[[[1.95042186 1.52979398 1.30549682 1.93579665 1.03784094]
  [1.27023327 1.19952522 1.68376492 1.11982131 1.45394735]]

 [[1.69797278 1.85359121 1.01493669 1.06111047 1.25285451]
  [1.57293653 1.28453573 1.18296577 1.12696192 1.28530794]]

 [[1.75410798 1.88487199 1.78635143 1.05059668 1.48753369]
  [1.65395856 1.74907103 1.47013049 1.14938089 1.36961655]]]

V. 결론

  • 머신러닝 & 딥러닝을 수행할 때, NumPy을 활용한 객체 생성은 매우 빈번하다.
  • 대부분은 서로 다른 차원으로 인해 연산 오류가 자주 발생하는데, 이 때 Broadcasting의 개념을 알고 있으면 연산 오류를 줄일 수 있다.

Reference

Brownley, Clinton W. Foundations for Analytics with Python. O’Reilly Media, Inc., 2016.

Mukhiya, Suresh Kumar, and Usman Ahmed. “Hands-On Exploratory Data Analysis with Python.” Packt Publishing, Mar. 2020, www.packtpub.com/data/hands-on-exploratory-data-analysis-with-python.