티스토리 뷰

개발/python

[3.6.2] numpy 맛보기

Jaeyeon Baek 2017.10.02 09:42

python 에는 다차원 배열을 다루는 numpy 라는 유용한 라이브러리가 있는데 여러 분야에서 강력하게 사용될 수 있으므로 사용법을 익혀두도록 한다. 특히 머신러닝의 연산과정을 이해하기 위해서는 필수라고 할 수 있겠다. 이번 포스팅은 실습코드가 많기 때문에 특별히 Jupyter notebook 을 통해 내용을 기록하도록 한다.

numpy 모듈 사용

우선 실습(혹은 사용)을 위해 numpyimport 하도록 한다. 설치되어 있지 않다면 주석처럼 설치하면 된다.

In [1]:
# pip install numpy
import numpy as np

numpy 모듈을 불러와서 앞으로 np 라는 이름으로 alias 하겠다는 의미 정도로 생각하면 좋다.

numpy 배열의 생성과 이해

In [2]:
arr = np.array([[1, 2], [3, 4]])

위에서 생성된 2차원 배열인 $arr$ 은 $\begin{pmatrix} 1_{1,1} & 2_{1,2} \\ 3_{2,1} & 4_{2,2} \\ \end{pmatrix}$ 행렬로 생각할 수 있다. 행렬의 첫번째 요소 접근은 $1_{1,1}$ 이지만 프로그래밍 언어에서 인덱스는 0 부터 시작이라는 점을 인지하고 각 요소에 접근해보면 $\begin{pmatrix} 1_{0,0} & 2_{0,1} \\ 3_{1,0} & 4_{1,1} \\ \end{pmatrix}$ 이런 모양이 될 것이다. 실제로 python 에서 흔히 사용하는 배열 요소 접근을 통해 알아보자. 아래는 2차원 배열(행렬) arr 의 0,0 인덱스와 1,1 인덱스를 출력하고 있다.

In [3]:
print('arr[0][0]: ', arr[0][0], '\narr[1,1]: ', arr[1,1])
arr[0][0]:  1 
arr[1,1]:  4

아래와 같이 행렬의 모양을 확인할 수 있다. 앞서 선언한 arr 은 2 x 2 행렬이다.

In [4]:
arr.shape
Out[4]:
(2, 2)

전통적으로 행렬을 표기할 때 행을 먼저 명시하는데, (4 x 2) 행렬의 경우에는 아래와 같이 선언할 수 있을 것이다. shape 도 같은 규칙으로 출력되는 것을 확인할 수 있다. 매우 간단한 부분인데 정확하게 이해하지 못하면 나중에 골치를 앓게 된다.

In [5]:
arr = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
arr.shape
Out[5]:
(4, 2)

행렬 덧셈

numpy 는 행렬의 덧셈을 일반 변수의 덧셈처럼 취급한다. 아래 두 개의 행렬이 있다.

In [6]:
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[0, 1], [2, 3]])

행렬의 합은 아래와 같이 각요소를 더해서 계산될 것이다.

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix}$ + $\begin{pmatrix} 0 & 1 \\ 2 & 3 \\ \end{pmatrix}$ = $\begin{pmatrix} 1+0 & 2+1 \\ 3+2 & 4+3 \\ \end{pmatrix}$ = $\begin{pmatrix} 1 & 3 \\ 5 & 7 \\ \end{pmatrix}$

python 코드를 통해 확인해보자.

In [7]:
arr1 + arr2
Out[7]:
array([[1, 3],
       [5, 7]])

예상한 결과처럼 행렬의 덧셈은 두 행렬의 각 요소를 정확하게 더해준다. 또한 numpybroadcast 를 통해 행렬에 scala 를 연산시킬 수 있다. 아래 예제처럼 말이다.

In [8]:
arr1 + 1
Out[8]:
array([[2, 3],
       [4, 5]])

arr1 에 더해진 값은 1 이라는 값이지만 스칼라 값 1은 arr1 의 차원에 맞게 reshape 된다. 이것을 브로드캐스트 라고 한다.

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix}$ + $\begin{pmatrix} 1 & 1 \\ 1 & 1 \\ \end{pmatrix}$ = $\begin{pmatrix} 2 & 3 \\ 4 & 5 \\ \end{pmatrix}$

같은 개념으로 벡터도 연산시킬 수 있다.

In [9]:
arr1 + [1, 2]
Out[9]:
array([[2, 4],
       [4, 6]])

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix}$ + $\begin{pmatrix} 1 & 2 \\ 1 & 2 \\ \end{pmatrix}$ = $\begin{pmatrix} 2 & 4 \\ 4 & 6 \\ \end{pmatrix}$

이러한 브로드캐스팅 연산은 덧셈뿐만 아니라 모든 연산에 적용된다는 것을 이해하도록 하자.

행렬 뺏셈

뺄셈도 덧셈과 마찬가지로 연산된다. 덧셈에서 사용했던 두 개의 행렬이 있다.

In [10]:
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[0, 1], [2, 3]])

행렬연산으로 보면 아래와 같은 결과가 나온다.

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix}$ - $\begin{pmatrix} 0 & 1 \\ 2 & 3 \\ \end{pmatrix}$ = $\begin{pmatrix} 1-0 & 2-1 \\ 3-2 & 4-3 \\ \end{pmatrix}$ = $\begin{pmatrix} 1 & 1 \\ 1 & 1 \\ \end{pmatrix}$

python 코드를 통해 확인해보자.

In [11]:
arr1 - arr2
Out[11]:
array([[1, 1],
       [1, 1]])

행렬 곱셈

곱셈은 덧셈이나 뺄셈과 다르게 조금 생각을 해야 한다. 덧셈이나 뺄셈의 경우에는 행렬의 각 요소끼리의 연산을 통해 결과가 나왔지만 행렬 곱셈은 우리가 익히 알고 있듯이 요소간의 곱이 아니다. 덧셈, 뺄셈처럼 곱셈(*) 연산자를 사용하면 아래처럼 요소끼리의 곱에 대한 잘못된 행렬곱의 결과가 나온다.

In [12]:
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[0, 1], [2, 3]])

arr1 * arr2
Out[12]:
array([[ 0,  2],
       [ 6, 12]])

설명을 보태자면 위 결과는 아래와 같이 계산된 것이다.

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix}$ x $\begin{pmatrix} 0 & 1 \\ 2 & 3 \\ \end{pmatrix}$ = $\begin{pmatrix} 1*0 & 2*1 \\ 3*2 & 4*3 \\ \end{pmatrix}$ = $\begin{pmatrix} 0 & 2 \\ 6 & 12 \\ \end{pmatrix}$

우리는 행렬곱이 아래와 같이 계산된다는 것을 이미 학창시절 배웠기 때문에 잘 알고 있다.

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix}$ x $\begin{pmatrix} 0 & 1 \\ 2 & 3 \\ \end{pmatrix}$ = $\begin{pmatrix} 1*0+2*2 & 1*1+2*3 \\ 3*0+4*2 & 3*1+4*3 \\ \end{pmatrix}$ = $\begin{pmatrix} 4 & 7 \\ 8 & 15 \\ \end{pmatrix}$

이런 연산 결과를 얻기 위해서는 numpydot() 이나 matmul() 함수를 사용해주면 된다. 단, matmul 에서는 scala 곱을 지원하지 않기 때문에 scala 곱이 필요하면 dot 을 사용해야 한다. 또한 matmul 은 행렬 스택을 행렬의 요소처럼 브로드캐스트 하기 때문에 적절히 사용하면 된다.

In [13]:
np.matmul(arr1, arr2)
Out[13]:
array([[ 4,  7],
       [ 8, 15]])

행렬을 조금 더 설명하기 위해 arr1 의 행을 변경( 2 -> 3 ) 해보자.

In [14]:
arr1 = np.array([[1, 2], [3, 4], [5, 6]])
print(arr1.shape)
(3, 2)
In [15]:
arr2 = np.array([[0, 1], [2, 3]])
print(arr2.shape)
(2, 2)

이제 (3 x 2), (2 x 2) 두 행렬을 곱해보면 결과는 아래와 같다.

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \\ \end{pmatrix}$ x $\begin{pmatrix} 0 & 1 \\ 2 & 3 \\ \end{pmatrix}$ = $\begin{pmatrix} 1*0+2*2 & 1*1+2*3 \\ 3*0+4*2 & 3*1+4*3 \\ 5*0+6*2 & 5*1+6*3 \\ \end{pmatrix}$ = $\begin{pmatrix} 4 & 7 \\ 8 & 15 \\ 12 & 23 \end{pmatrix}$

numpy 에서 matmul() 로 확인해봐도 결과는 같다.

In [16]:
ret = np.matmul(arr1, arr2)
print("shape: ", ret.shape, "\nret:\n ", ret)
shape:  (3, 2) 
ret:
  [[ 4  7]
 [ 8 15]
 [12 23]]

3 x 2 행렬과 2 x 2 행렬의 곱셈 결과가 3 x 2 가 되었다. 이것은 행렬의 중요한 정의인데, 두 개의 행렬 A, B 가 각각 m x n, n x p 일 때 행렬 곱의 결과는 m x p 가 된다. 두 행렬간 n 이 동일해야 연산이 가능하다는 것을 잊지 말도록 하자.

기타

데이터 생성

아래와 같이 벡터 데이터를 생성할 수 있다.

In [17]:
np.arange(10)
Out[17]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

또한 2~20 까지 2 씩 값을 증가시키는 것처럼 벡터를 생성할 수도 있으며,

In [18]:
np.arange(2, 20, 2)
Out[18]:
array([ 2,  4,  6,  8, 10, 12, 14, 16, 18])

다차원 배열의 생성도 아래처럼 손쉽게 할 수 있다..

In [19]:
np.zeros((3, 2, 4))
Out[19]:
array([[[ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.]],

       [[ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.]],

       [[ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.]]])

배열의 모양 변경

배열의 모양을 변경할 수 있는 reshape 함수가 제공된다. 간단한 예제는 아래와 같다. 4 x 2 행렬이 있고, 이것을 2 x 4 로 변경하는 예제인데 중요한 것은 최종 결과의 요소 개수가 같아야 한다는 것이다. (4 x 2 를 쌩뚱맞게 3 x 3 이렇게 변경하려는 등, 그렇게는 안된다.)

In [20]:
print('shape: ', arr.shape, '\narr:\n', arr) # arr 배열의 모양과 내용 출력
shape:  (4, 2) 
arr:
 [[1 2]
 [3 4]
 [5 6]
 [7 8]]
In [21]:
print('shape: ', arr.shape, '\narr:\n', arr.reshape(2,4)) # arr 배열의 모양을 (2, 4) 로 변경
shape:  (4, 2) 
arr:
 [[1 2 3 4]
 [5 6 7 8]]

앞서 사용했던 arangereshape 을 붙여서 다차원 배열을 생성하는 것도 가능하다.

In [22]:
np.arange(12).reshape(2, 2, 3)
Out[22]:
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]]])

정규 분포로 랜덤 데이터 생성

정규 분포로 고르게 분포된 데이터를 생성하는 것이 가능하다. 이것은 머신러닝에서 초기값 지정을 위해 사용되기 때문에 알아두면 좋다. 아래 예제는 numpy 공식 홈페이지의 코드이다. 나머지는 그래프를 그리기 위한 코드이고, np.random.normal() 에 주목해서 보면 되겠다.

In [23]:
import matplotlib.pyplot as plt

mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)

count, bins, ignored = plt.hist(s, 30, normed=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
                np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
          linewidth=2, color='r')
plt.show()

정리

여기서는 매우 기본적인 요소들만 살펴봤는데 이외에도 numpy 는 많은 기능을 포함하고 있으니 공식문서를 참고하면 좋다. 또한 많은 함수나 기능들이 tensorflow 에서 유사하게 사용할 수 있기 때문에 머신러닝과 더욱 밀접한 느낌이라고 할 수 있겠다.

'개발 > python' 카테고리의 다른 글

[python] The Zen of Python  (0) 2019.03.14
아나콘다 가상환경  (0) 2019.01.25
[3.6.2] numpy 맛보기  (0) 2017.10.02
가상환경(virtualenv)과 jupyter  (0) 2017.09.06
기상청 날씨 API 와 슬랙 연동  (0) 2017.06.14
[python] 설치된 package 버전 확인  (0) 2017.01.05
댓글
댓글쓰기 폼