01.5 넘파이

딥러닝을 구현하다 보면 배열이나 행렬 계산이 많이 등장합니다. 넘파이의 배열 클래스인 numpy.array에는 편리한 메서드가 많이 준비되어 있어, 딥러닝을 구현할 때 이들 메서드를 이용합니다. 이번 절에서는 앞으로 사용할 넘파이에 대해서 간략히 설명합니다.

1.5.1 넘파이 가져오기

넘파이는 외부 라이브러리입니다. 여기서 말하는 ‘외부’는 표준 파이썬에는 포함되지 않는다는 것입니다. 그래서 우선 넘파이 라이브러리를 쓸 수 있도록 가져와야 import 합니다.

>>> import numpy as np

파이썬에서는 라이브러리를 읽기 위해서 import 문을 이용합니다. 여기에서는 import numpy as np라고 썼는데, 직역하면 “numpy를 np라는 이름으로 가져와라”가 됩니다. 이렇게 해두면 앞으로 넘파이가 제공하는 메서드를 np를 통해 참조할 수 있습니다.

1.5.2 넘파이 배열 생성하기

넘파이 배열을 만들 때는 np.array() 메서드를 이용합니다. np.array()는 파이썬의 리스트를 인수로 받아 넘파이 라이브러리가 제공하는 특수한 형태의 배열(numpy.ndarray)을 반환합니다.

>>> x = np.array([1.0, 2.0, 3.0])
>>> print(x)
[ 1.  2.  3.]
>>> type(x)
<class 'numpy.ndarray'>

1.5.3 넘파이의 산술 연산

다음은 넘파이 배열로 산술 연산을 수행하는 예입니다.

>>> x = np.array([1.0, 2.0, 3.0])
>>> y = np.array([2.0, 4.0, 6.0])
>>> x + y   # 원소별 덧셈
array([ 3.,  6.,  9.])
>>> x - y
array([-1., -2., -3.])
>>> x * y   # 원소별 곱셈
array([  2.,   8.,  18.])
>>> x / y
array([ 0.5,  0.5,  0.5])

여기에서 주의할 점은 배열 x와 y의 원소 수가 같다는 것입니다(둘 다 원소를 3개씩 갖는 1차원 배열). x와 y의 원소 수가 같다면 산술 연산은 각 원소에 대해서 행해집니다. 원소 수가 다르면 오류가 발생하니 원소 수 맞추기는 중요하답니다. 참고로, ‘원소별’이라는 말은 영어로 element-wise라고 합니다. 예컨대 ‘원소별 곱셈’은 element-wise product라고 합니다.

넘파이 배열은 원소별 계산뿐 아니라 넘파이 배열과 수치 하나(스칼라값)의 조합으로 된 산술 연산도 수행할 수 있습니다. 이 경우 스칼라값과의 계산이 넘파이 배열의 원소별로 한 번씩 수행됩니다. 이 기능을 브로드캐스트라고 합니다(자세한 것은 1.5.5절에서 설명하겠습니다).

>>> x = np.array([1.0, 2.0, 3.0])
>>> x / 2.0
array([ 0.5,  1. ,  1.5])

1.5.4 넘파이의 N차원 배열

넘파이는 1차원 배열(1줄로 늘어선 배열)뿐 아니라 다차원 배열도 작성할 수 있습니다. 예를 들어 2차원 배열(행렬)은 다음처럼 작성합니다.

>>> A = np.array([[1, 2], [3, 4]])
>>> print(A)
[[1 2]
 [3 4]]
>>> A.shape
(2, 2)
>>> A.dtype
dtype('int64')

방금 $2 \times 2$의 A라는 행렬을 작성했습니다. 행렬의 형상*은 shape으로, 행렬에 담긴 원소의 자료형은 dtype으로 알 수 있습니다. 이어서 행렬의 산술 연산을 봅시다.

>>> B = np.array([[3, 0], [0, 6]])
>>> A + B
array([[ 4,  2],
       [ 3, 10]])
>>> A * B
array([[ 3,  0],
       [ 0, 24]])

형상이 같은 행렬끼리면 행렬의 산술 연산도 대응하는 원소별로 계산됩니다. 배열과 마찬가지로 말이죠. 행렬과 스칼라값의 산술 연산도 가능합니다. 이때도 배열과 마찬가지로 브로드캐스트 기능이 작동합니다.


* 옮긴이_ 이 책에서는 행렬을 포함한 N차원 배열에서 그 배열의 ‘각 차원의 크기(원소 수)’를 배열의 ‘형상’이라 하겠습니다. ** 옮긴이_ 구글의 딥러닝 프레임워크인 텐서플로의 이름이 여기서 유래했습니다. 텐서(tensor)가 신경망을 타고 흐른다(flow)는 뜻이죠.

>>> print(A)
[[1 2]
 [3 4]]
>>> A * 10
array([[ 10,  20],
       [ 30,  40]])

NOTE_ 넘파이 배열(np.array)은 N차원 배열을 작성할 수 있습니다. 1차원 배열, 2차원 배열, 3차원 배열처럼 원하는 차수의 배열을 만들 수 있다는 뜻입니다. 수학에서는 1차원 배열은 벡터 vector, 2차원 배열은 행렬 matrix이라고 부릅니다. 또 벡터와 행렬을 일반화한 것을 텐서 tensor라 합니다.* 이 책에서는 기본적으로 2차원 배열을 ‘행렬’, 3차원 이상의 배열을 ‘다차원 배열’이라 하겠습니다.

1.5.5 브로드캐스트

넘파이에서는 형상이 다른 배열끼리도 계산할 수 있습니다. 앞의 예에서는 $2 \times 2$ 행렬 A에 스칼라값 10을 곱했습니다. 이때 [그림 1-1]과 같이 10이라는 스칼라값이 $2 \times 2$ 행렬로 확대된 후 연산이 이뤄집니다. 이 똑똑한 기능을 브로드캐스트 broadcast라고 합니다.

그림 1-1 브로드캐스트의 예: 스칼라값인 10이 $2 \times 2$ 행렬로 확대된다.

그림 1-1

다른 예를 살펴봅시다.

>>> A = np.array([[1, 2], [3, 4]])
>>> B = np.array([10, 20])
>>> A * B
array([[ 10,  40],
       [ 30,  80]])

여기에서는 [그림 1-2]처럼 1차원 배열인 B가 ‘똑똑하게도’ 2차원 배열 A와 똑같은 형상으로 변형된 후 원소별 연산이 이뤄집니다.

그림 1-2 브로드캐스트의 예 2

그림 1-2

이처럼 넘파이가 제공하는 브로드캐스트 기능 덕분에 형상이 다른 배열끼리의 연산을 스마트하게 할 수 있습니다.

1.5.6 원소 접근

원소의 인덱스는 0부터 시작합니다(기억하시죠?). 그리고 각 원소에 접근하려면 다음과 같이 합니다.

>>> X = np.array([[51, 55], [14, 19], [0, 4]])
>>> print(X)
[[51 55]
 [14 19]
 [ 0  4]]
>>> X[0]         # 0행
array([51, 55])
>>> X[0][1]      # (0, 1) 위치의 원소
55

for 문으로도 각 원소에 접근할 수 있습니다.

>>> for row in X:
...     print(row)
... 
[51 55]
[14 19]
[ 0  4]

넘파이는 지금까지의 방법 외에도, 인덱스를 배열로 지정해 한 번에 여러 원소에 접근할 수도 있습니다.

>>> X = X.flatten()        # X를 1차원 배열로 변환(평탄화)
>>> print(X)
[51 55 14 19  0  4]
>>> X[np.array([0, 2, 4])] # 인덱스가 0, 2, 4인 원소 얻기
array([51, 14,  0])

이 기법을 응용하면 특정 조건을 만족하는 원소만 얻을 수 있습니다. 예컨대 다음과 같이 배열 X에서 15 이상인 값만 구할 수 있습니다.

>>> X > 15
array([ True,  True, False,  True, False, False], dtype=bool)
>>> X[X>15]
array([51, 55, 19])

넘파이 배열에 부등호 연산자를 사용한(앞 예에서 X > 15) 결과는 bool 배열입니다.* 여기에서는 이 bool 배열을 사용해 배열 X에서 True에 해당하는 원소, 즉 값이 15보다 큰 원소만 꺼내고 있습니다.

NOTE_ 파이썬 같은 동적 언어는 C나 C++ 같은 정적 언어(컴파일 언어)보다 처리 속도가 늦다고 합니다. 실제로 무거운 작업을 할 때는 C/C++로 작성한 프로그램을 쓰는 편이 좋습니다. 그래서 파이썬에서 빠른 성능이 요구될 경우, 해당 부분을 C/C++로 구현하곤 합니다. 그때 파이썬은 C/C++로 쓰인 프로그램을 호출해주는, 이른바 ‘중개자’ 같은 역할을 합니다. 넘파이도 주된 처리는 C와 C++로 구현했습니다. 그래서 성능을 해치지 않으면서 파이썬의 편리한 문법을 사용할 수 있는 것이죠.


* 옮긴이_ 넘파이 배열에 부등호 연산을 수행하면 배열의 원소 각각에 부등호 연산을 수행한 bool 배열이 생성됩니다.

서브목차