메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

에릭하게만 시리즈 4 - 수치처리 파이썬 모듈에 대하여

한빛미디어

|

2002-05-29

|

by HANBIT

15,315

저자: 에릭 하게만(Eric Hagemann), 역 전순재

들어가는 말

본 기사에서는 수치처리 파이썬(NumPy)을 본격적으로 살펴보고 더욱 자세하게 수치처리 파이썬의 특징과 능력을 탐험해 보겠다. 수치처리 파이썬의 핵심은 우리가 지금까지 탐험해온 다중배열(multiarray)이라는 데이터형이지만, NumPy 배포본에 포장되어 있는 모듈들을 사용하면 프로그래머는 다중배열(multiarray)이라는 개념을 더욱 더 효과적으로 사용할 수 있다. NumPy는 선형 대수학을 지원하고, 고속 푸리에 변환(Fast Fourier Transforms)을 지원하며, 무작위 수 생성을 지원한다. 이 외에도 상업용 행렬 처리 패키지인 MATLAB에 익숙한 사용자들을 위한 도움도 제공한다. NumPy의 추가 모듈들의 기초부터 살펴본 후 다음 기사에서는 직접 손으로 코딩할 준비를 하자. 그 추가 모듈들은 NumPy에 날개를 달아줄 것이다.

본격적 시작!

NumPy에서 확산(broadcasting)이란 더 큰 차원의 다중 배열과 자연스럽게 작동되도록 하기 위하여 더 작은 차원의 다중배열이 반복되거나 확장되는 것을 가리키는데 사용되는 용어이다. 확산(broadcasting)에 대한 세부사항은 다음 기사에서 더 자세하게 설명하겠지만 이 개념을 이해해야만 NumPy의 잠재능력을 최대한 끌어낼 수 있다. 확산(broadcasting)의 복잡한 규칙에 대한 다름 기사에서 벌어질 토론에 대한 서론격으로 다중 배열 처리에 대한 기초부터 살펴볼 필요가 있다. 자세하게 언급하자면 인덱싱(indexing), 슬라이싱(slicing), 그리고 일립스(ellipses)가 바로 그것이다.

다중 배열을 손에 익히면, 인덱싱과 슬라이싱에 대하여 융통성 있는 규칙들이 있음을 알게 된다.

다음과 같이 정수들을 담은 행렬이 있다고 하고서 입력되는 배열로 무엇을 할 수 있는지 살펴 보자.
>>> a = array(((0,1,2,3,4,5,6,7,8,9),
               (9,8,7,6,5,4,3,2,1,0)))
>>> print a
[[0 1 2 3 4 5 6 7 8 9]
 [9 8 7 6 5 4 3 2 1 0]]
Indexing (지표화)

인덱싱에는 두 가지 스타일이 있다. [][]이거나 또는 [,]이다. (논의를 2 차원에 한정시키겠지만 그 개념들은 쉽게 다 차원의 다중배열로 확장된다.)
>>> print a[1][1]
8
>>> print a[1,1]
8
두 인덱싱 스타일 간의 차이점은 단계적 추출(cascaded extraction) 대 직접적 추출(direct)로 특징지을 수 있다. 다음 방식 a[1][1](a[1])[1]으로 간주하자. 첫 번째 연산 (a[1])은 다중배열 a의 두 번째 행 전체를 반환한다. 따라서 두 번째 연산인 [1]은 그 추출된 열의 두 번째 요소에 인덱스를 설정한다. 이러한 추출은 "폭포수가 떨어지는 것처럼" 단계적으로 처리된다. 두 번째 방식에서 a[1,1]a[(1,1)]으로 생각하자. 이렇게 사용하면 터플을 배열 주소처럼 사용할 수 있으며 원소들을 직접적으로 열람할 수 있다. 위의 두 방법 중에서 두 번째의 ([,]) 방법이 더 사용하기 편하다. 두 번째 방법은 예상대로 아주 복잡한 사례들로 확장할 수 있지만, 첫 번째 방법은 예상치 못한 결과에 봉착할 수도 있다.

Slicing (조각썰기)

다음은 슬라이싱의 한 예이다. 이 경우에 다음 조각코드 a[:,2]은 모든 행에서 두 번째 열을 추출한다. 이차원 행렬에 대한 지표는 다음과 같이 [rows,cols]이라는 것을 기억해 두어라. 다음의 : 연산자는 "슬라이싱" 연산자처럼 작동한다. 연산자는 여러 행태로 사용할 수 있다. 단독으로 사용되면 [:]는 모든 것을 선택한다는 뜻이다. 그렇지 않고 끝점과 함께 사용되어 [start:stop]와 같이 사용되면 start로부터 stop 바로 전까지 모두 선택하겠다는 뜻이다. 또는 다음과 같이 [start:stop:step]로 사용되면 start로부터 stop 바로 전까지 주어진 값(step)만큼 증가하면서 모두 선택하겠다는 뜻이다.
>>> print a[:,2]
[2 7]
다음 예제를 보면 [][]이 슬라이싱에 포함되면 [,]과 같지 않다는 것을 알 수 있다. 이 조각코드(snippet)는 다중 배열 a 전체에서 두 번째 행을 열람하려고 시도한다.
>>> print a[:][2]
Traceback (innermost last):
  File "", line 1, in ?
IndexError: index out of bounds
왜 그럴까? a[:][2]를 평가하면 (a[:])[2]와 같이 된다고 생각하자. a[:] 부분은 단지 a만을 반환한다. 이제 a[2]를 평가하면 행렬 a의 (0에서 시작하여) 세 번째 행을 실제로 반환하려고 시도할 것이다. 그런데 그 행은 존재하지 않는다! 그리하여 예외(exception)가 발생한다.

슬라이싱 연산자를 더욱 복잡한 형태로 사용하면, 다음 예제에서 첫 번째 행의 값들인 (1,3,5)를 산출한다.
 
>>> print a[0,1:7:2]
[1 3 5]
일립스(Ellipses)

거대 다차원 배열을 다룬다면 일립스(ellipses (...))를 사용하여 부정(indeterminate) 개수의 조각들을 다룰 수 있다. 일립스는 기본적으로 편법이며 지정되지 않은 차원을 대신해준다. 생략이 여러 개가 있다면 오직 첫 번째 생략만이, 가장 왼쪽 생략만이 확대될 수 있다. 생략은 지정되지 않고 남아 있는 차원의 개수만큼 확대될 것이다.

예를 들어 만약 a가 4차원 이상의 배열이라고 한다면 다음 코드 조각은 참이다.
>>> a[5,:,:,1] == a[5,...,1]
이 예제를 보면 :,:를 대신하여 중간에 있는 모든 차원을 선택한다는 의미로 를 사용하고 있는 것을 볼 수 있다.

연습삼아 테스트 배열을 약간 만들어 보고 손수 인덱싱과 슬라이싱을 시험해보자. 만약 예제가 더 필요하면 NumPy 문서에서 다른 예제들을 더 많이 볼 수 있다. 인덱싱과 슬라이싱의 요점을 이해하면 더욱 강력한 NumPy 사용법을 향하여 멋지게 출발할 수 있다.

모듈

다중배열에 대한 기본적인 조작방법을 이해했으므로, 이제 NumPy 패키지에 기본으로 포함되어 있는 모듈 네 개를 잠깐 살펴보자.

선형 대수학(Linear algebra) 모듈

LinearAlgebra 모듈은 LAPACK 라이브러리가 지원되어 가장 많이 쓰이는 패키지이다. LAPACK 라이브러리는 자유롭게 사용할 수 있는 라이브러리로 Fortran77으로 작성된 고속 행렬 루틴들로 구성되어 있다. LAPACK 라이브러리 덕분에 선형 대수학 패키지가 속도를 낼 수 있다. 우리는 "수치처리 파이썬 I"이라는 기사에서 이 패키지를 사용하여 행렬 곱셈을 수행했을 뿐만 아니라 한 행렬의 곱셈 역행렬을 찾을 수 있었다.

또 이 모듈이 담고있는 find 함수로 고유 값 계산과 특이 값 분해를 계산할 수 있다. 특이 값 분해 함수란 행렬들을 기본적인 컴포넌트로 분해하여 주어 원래 행렬식의 구조를 들여다 볼 수 있도록 해주는 함수이다. 행렬에 있어서 구조(그 행렬 안에 저장된 숫자들 사이의 관계)는 그 행렬이 담고 있는 실제 값들만큼이나 중요하다. 이 모듈에서 보게 될 함수들은 한 행렬식을 계산해줄 뿐만 아니라 최소 제곱 문제(least squares problem)[1]도 해결한다. 이 모듈에 있는 함수들, 물론 모두는 아니겠지만 과학, 비즈니스, 공학에서 사용되는 행렬 문제들을 해결하는 것과 연관된 대부분의 수학적 계산을 수행할 수 있다.

고속 푸리에(Fast Fourier) 모듈

푸리에 변환(Fourier transform)은 과학자와 공학자가 주파수 영역과 시간 영역 사이에 수치적 연속열의 표현들을 변환하는데 사용하는 수학적 연산이다. 만약 연속적인 샘플들이 시간으로 측정된 양을 표현한다면, 측정된 사건들의 주파수(또는 주기)를 푸리에 변환(Fourier transform)을 사용하여 계산할 수 있다.

고속 푸리에 변환(Fast Fourier Transform (FFT)) 모듈은 최적화된 방법으로 1960년대 중반 쿨리(Cooley)와 터키(Tukey)가 실현한 푸리에 변환(Fourier transform)을 계산한다. 어떤 사람들은 고속 푸리에 변환[3]이라는 이 알고리즘의 실현이 신호 파형을 컴퓨터로 처리할 수 있게 만들어 준 가장 중요한 독보적인 사건이었다고 주장한다. 디지털 신호 처리(DSP, Digital Signal Processing)는 우리가 오늘날 의존하고 있는 많은 산업재와 소비재의 핵심 그 자체이다. 그 중에서도 가장 중요한 것은 컴퓨터 안에 모뎀이 장착되어 있으므로 이 기사를 읽을 수 있다는 것이다!

FFT 모듈은 그러한 변형을 효율적으로 계산하는데 최적화된 라이브러리에 접근할 수 있게 해준다. FFT 모듈에 접근하려면 먼저 그 모듈을 임포트해야 한다.
>>>from FFT import *
지원되는 함수들에는 실수와 복소수 수치 연속열용 전진 FFT(forward FFT)와 역 FFT(inverse FFT)가 있으며 (이미지 처리에 유용한) 이차원 데이터 집합용 전진 FFT들(forward FFTs)이 있다.

일반적으로 푸리에 변형(Fourier transform)은 어느 길이의 데이터 연속 열에도 계산될 수 있다. 그렇지만 FFT 알고리즘의 이점을 최대한 이용하려면 그 데이터는 길이가 2의 제곱이 될 필요가 있다 (2,4,8,16,32…..).

가장 많이 사용되는 함수들 중에
fft(data,n=None,axis=-1) 함수는 전진 FFT(forward FFT)를 수행하고
inverse_fft(data,n=None,axis=-1) 함수는 역 FFT(inverse FFT)를 수행한다.

수치처리 파이썬 II로 되돌아가 보면 거기에서 두 개의 사인 곡선의 합을 계산하여 만들어 낸 흥미로운 예제는 DISLIN을 사용하여 plot() 함수로 만들었다. 비슷한 코드를 사용하여 FFT 루틴이 우리에게 무엇을 해줄 수 있는지 예제 하나를 살펴 보자.
>>> x=arange(128.0)
>>> y=sin(x/3)+cos(x/5)
>>> plot(x,y)
위의 코드조각은 전과 같이 도표를 만들어 낸다. 만약 x 축이 시간을 나타낸다고 간주하면 FFT를 취하여 관련된 주파수들을 측정할 수 있을 것이다.


결과 도표

다음 도표는 아래의 코드조각으로 만든다. 두 개의 정점이 보이는 것에 주목하라. 각 정점은 두 개의 사인 곡선의 진폭이 하나로 합쳐졌다는 것을 나타낸다. 이 경우에 그 해답은 알려져 있지만 이와 똑같은 보편적인 테크닉을 사용하면 (만들어진 것이 아닌) 측정된 데이터로 진폭과 주파수 표현을 볼 수 있다. 이런 형태는 특히 강력한 분석 도구이다.
>>> plot(x[0:64],abs(fft(y)[0:64]))


결과 주파수 도표

코드의 마지막 라인을 다시 한 번 살펴 보라. 조각썰기(Slicing)는 오직 전반부 64개의 데이터 샘플만을 추출하고 도표하는데 사용되었다. 왜냐하면 FFT는 (복소수와 대조되는) 실수 샘플로 표현되는 데이터에 대해서는 대칭적이기 때문에 FFT의 후반부는 전반부와 똑같기 때문이다. 우리는 전반부만 도표하면 된다.

무작위 수(Random numbers)

RandomArray 모듈은 무작위 수로 구성된 다중배열을 만들 수 있도록 지원한다. 무작위 수(Random numbers)는 우연한 또는 제멋대로인 측정 데이터 요소를 표현하는 시뮬레이션 알고리즘에 많이 사용된다. 컴퓨터가 그 숫자들을 만들어 내므로 엄밀히 말해 그 숫자들이 "무작위(random)"인 것은 아니지만 약간은 진짜 무작위 수를 모사하는 통계적인 속성이 있다.

대부분의 무작위 수 패키지처럼 RandomArray 모듈로 사용자는 종자 값(seed value)을 선택할 수 있다. 컴퓨터는 무작위 수 생성기를 선택된 종자 값으로 초기화한다. 알고리즘이 무작위 수를 사용할 때마다 서로 다른 종자 값을 선택하면 무작위적 속성은 증가한다. 실행 때마다 연산이 똑같기를 원한다면 그냥 그 씨 값을 실행할 때마다 같은 값으로 고정시키기만 하면 된다.

RandomArray 모듈을 사용하려면, 그 모듈을 다음과 같이 임포트하면 된다.
>>> from RandomArray import *.
그런 후 종자 값을 다음과 같이 설정하자.
>>>seed(x=0,y=0)
아무 인수도 지정하지 않으면 값들은 시스템 시계에 근거하여 대치될 것이다(나쁜 선택은 아니다). 전형적으로 seed()는 한 세션이 시작할 때에만 (또는 이미 만들어진 연속열을 재시작하기 원할 때만) 호출된다.

무작위 수는 분포(distribution)에 의해서 지정된다. 자연스런 처리과정을 다양하게 흉내내려면 서로 다른 분포가 사용된다. 가장 많이 사용되는 분포 두 가지는 균일 분포(uniform distribution)와 정규(normal) 분포 또는 가우시안(Gaussian) 분포이다. 균일 분포는 경계가 양쪽으로 주어지면 (1과 10사이에서 아무 숫자나 선택하는 것과 같이) 그 사이에서 똑같이 무작위적으로 선택하는 것을 모사하는데 사용된다. 정규 분포 혹은 가우시안 분포는 자연스러운 잡음을 모사하는데 사용된다 (그 옛날 고등 학교 시절에는 "종형 곡선(bell curve)"으로 부르기도 했다).

RandomArray 모듈은 이 두 분포뿐만 아니라 다른 분포(이항, 포아송, 카이-제곱, F, 감마, 베타, 그리고 지수)[1]들을 사용하여 연속 열들을 계산해낸다.

모든 분포 함수들은 원하는 출력 행렬 크기를 지정해 주는 꼴 터플(shape tuple)을 받아들인다. 게다가 어떤 함수들은 원하는 분포에 관한 정보를 요구하기도 한다. 다음 코드는 평균이 0이고 1.0을 분산으로 하여 정상적으로 분포된 무작위 수들을 담은 3Ⅹ3 행렬을 계산한다.
>>> a = normal(0.0,1.0,(3,3))
>>> a
array([[ 2.27323961, -0.67850655,  0.39735967],
       [ 1.22253323,  1.09771109,  1.43763125],
       [ 0.53184462,  0.10846746,  1.87741685]])
>>>
Mlab

Mlab (또는 MATLAB) 모듈은 MathWorks사의 상업용 행렬 처리 패키지인 MATLAB에 익숙한 사용자들을 위해 편리한 루틴을 제공한다. 이 모듈에는 배열 초기화 루틴(eye, zeros, ones)이 포함되어 있고, LinearAlgebra 모듈로부터 고유 값 분해 함수와 단일값 분해 함수(eig 와 svd)를 사용할 수 있도록 단축 함수가 포함된다. 뿐만 아니라 신호 처리에 사용되는 함수(sinc, cov, hamming 그리고 barlett)도 많이 포함되어 있다.

게임 끝

NumPy의 기본적인 루틴이 다중배열 처리라는 근육을 제공해 준다면 NumPy 배포본의 번쩍이는 크롬빛 피부는 추가 모듈들 덕분이다. 본 기사에서 필자가 소개한 모듈들은 기본 패키지에 있는 모듈들일 뿐이다. 소개한 모듈 이외에도 NumPy의 사용자들의 참여로 만들어진 모듈들은 python.org와 트래빌스 올리펀트(Travis Oliphant)의 파이썬 페이지에 가면 훨씬 더 많이 볼 수 있다.

NumPy에 더 깊이 들어갈수록 NumPy의 힘과 유용성이 점점 그 모습을 드러낸다. 어서 예제들을 시험해보자! NumPy 문서를 보면 이러한 모듈들에 대한 예제들이 추가로 들어 있으며 그 기능들에 대하여 완전하게 정의되어 있는 것을 볼 수 있다.
[1] 순서대로 binomial, Poisson, chi-square, F, gamma, beta, and exponential
- 이산형 분포: bernoulli(근로자의 발병률, 생산품의 불량률, 프린터 사용률), binomail(자료 전송중인 통신 이용자율, 불량품 개수, 근로자의 유병률), poisson(고객의 도착률), geometric(계약 전에 만난 고객의 수)
- 연속형 분포: uniform, normal(시험성적, 몸무게 분포, 키 분포), exponential(고객이 나간 후 다른 고객이 도착할 때까지의 시간, 응급실의 환자치료 시간, 공작기계의 고장률, 전화통화 대기율), gamma(상품판매량), weibul(공작기계의 고장률), lognormal(작업시간의 분포), beta(프로젝트 활동시간 분포), chi-square, cauchy
[2] 측정한 실험자료로 그 자료 값들을 가장 잘 대표할 수 있는 함수를 구하는 방법 중 하나
[3] 고속 푸리에 변환이란 이산 푸리에 변환의 삼각함수의 주기성을 이용하여 계산속도의 효율을 높이는 알고리즘
[4] 어떤 계가 일정 규칙에 따라 움직이다가 불규칙하고 불안정한 행동을 보여 예측할 수 없는 파형을 나타날 때
TAG :
댓글 입력
자료실

최근 본 책0