[F2PY] 02. Basic Example: Moving Average
F2Py란?
- 수치 계산의 '우사인 볼트'라 할 수 있는 Fortran은 단점으로 다양한 파일의 입출력 같은 부가기능이 약합니다.
- 다재다능 Python은 단점으로 단순 계산이 조금 느립니다.
- 그래서 Python 뼈대에 계산 부분만 Fortran을 불러올 수 있게 도와주는 것이 F2PY입니다.
- F2PY는 Numpy에 기본으로 내재되어 있습니다.
- 이 포스팅은 기본적으로 Linux 환경에서 Python3를 이용함을 알려드립니다.
1. Intro
이번에는 Moving Average 이동평균을 계산해 보겠습니다.
가장 간단한 구현 방법은 Loop를 2번 돌리면 되겠습니다.
subroutine ma1d(A,B,m,mpd)
!!!
!!! Moving average from -mpd to +mpd
!!!
implicit none
integer :: m,i,j,mpd
real(8), dimension(m),intent(in) :: A
real(8), dimension(m),intent(out) :: B
!F2PY INTENT(HIDE) :: m
!F2PY INTENT(IN) :: mpd
B=0.d0
do i=1+mpd,m-mpd
do j=-mpd,mpd
B(i)=B(i)+A(i+j)
enddo
enddo
B=B/dble(mpd*2+1)
end subroutine ma1d
위는 Fortran90 코드이며, 다음과 같이 컴파일을 해줍니다.
$ f2py3 -c -m moving_average moving_average.f90
그러면 "moving_average.cpython-34m.so"라는 파일이 생성되며, Python에서 읽을 준비가 되었습니다.
위 함수를 불러들이고 실행할 모체 Python 프로그램은 다음과 같습니다.
import moving_average as mvavg
import numpy as np
import timeit
import sys
print("*** Module Document ***")
print(mvavg.__doc__)
print("*** Function Document ***")
print(mvavg.ma1d.__doc__)
### Prepare input array for test
nt=1000000
array=np.sin(np.arange(nt))
mpd=501; mpd_half=int((mpd-1)/2)
#array=np.arange(8); mpd_half=2
print("*** Start Loop ***")
time_record=[]
for i in range(20):
start = timeit.default_timer()
c= mvavg.ma1d(array,mpd_half)
stop = timeit.default_timer()
time_record.append(stop-start)
print("1st: {:.5f}sec 2nd: {:.5f}sec 3rd: {:.5f}sec".format(*sorted(time_record)[:3]))
print(c[1000:1003])
길이 1,000,000의 1D Vector에 501항목의 이동평균을 계산해서 역시 길이 1,000,000의 결과물을 내놓습니다.
위 프로그램을 실행시키면 다음과 같은 결과가 나옵니다.
*** Module Document ***
This module 'moving_average' is auto-generated with f2py (version:2).
Functions:
b = ma1d(a,mpd)
b = ma1dv2(a,mpd)
.
*** Function Document ***
b = ma1d(a,mpd)
Wrapper for ``ma1d``.
Parameters
----------
a : input rank-1 array('d') with bounds (m)
mpd : input int
Returns
-------
b : rank-1 array('d') with bounds (m)
*** Start Loop ***
1st: 0.91144sec 2nd: 0.91146sec 3rd: 0.91152sec
[-0.00253436 -0.00281975 -0.00051267]
대략 0.91초 정도가 걸립니다.
(참고로, 길이 1,000,000인 Vector에 8Byte-float가 저장되었으므로 데이타 크기는 8MB입니다.)
2. If using Python only
Python만으로 같은 조건을 계산하여 비교해보겠습니다.
전편에서도 얘기했지만, Python - Numpy는 행렬 통째로 계산하는게 빠릅니다.
그래서 다음과 같은 함수를 만들었습니다.
import numpy as np
def ma1d0(A,mpd):
"""
Input: 1D array, and half period of moving average window
Output: Same dimension as input array
To do: Moving average with given window size
"""
m=A.shape[0]
B=np.zeros_like(A)
for i in range(-mpd,mpd+1,1):
B[mpd:m-mpd]+=A[mpd+i:m-mpd+i]
B=B/float(mpd*2+1)
return B
위 함수를 모체에서 불러와 같은 조건으로 실행시키면 다음과 같은 결과가 나옵니다.
*** Start Loop ***
1st: 1.29482sec 2nd: 1.29516sec 3rd: 1.29565sec
[-0.00253436 -0.00281975 -0.00051267]
약 1.29초로써 꽤 선전한 모습입니다.
3. Improving for Better Result
이동 평균 Moving Average에 대해 검색해보니 그 유명한 StackOverflow에서 다음과 같은 글을 찾았습니다.
https://stackoverflow.com/questions/14313510/how-to-calculate-moving-average-using-numpy
요약하자면, Numpy에 내재된 함수 중 cumulative sum이라는 함수를 이용하면 계산이 더 빠르다는 내용입니다.
Numpy.cumsum()
예를 들자면 1D array A에 대하여,
Sum(A[i-n:i+n+1])=Sum(A[:i+n+1])-Sum(A[:i-n]) 임을 이용한다는 것입니다.
그래서 Python 함수를 다음과 같이 고쳤습니다.
def ma1d1(A, mpd) :
mpd2=mpd*2+1
B = np.cumsum(A)
tmp=B[mpd*2]
B[mpd+1:-mpd]=B[mpd2:]-B[:-mpd2]
B[mpd]=tmp
return B/float(mpd2)
그러면 다음과 같이 시간이 줄어듭니다.
*** Start Loop ***
1st: 0.01452sec 2nd: 0.01452sec 3rd: 0.01452sec
[-0.00253436 -0.00281975 -0.00051267]
약 0.0145초입니다.
제일 위의 Fortran 결과보다 훨씬 빨라졌죠?
그럼 마지막으로 같은 알고리즘을 Fortran으로도 구현해볼게요.
subroutine ma1dv2(A,B,m,mpd)
!!!
!!! Moving average from -mpd to +mpd
!!!
implicit none
integer :: m,i,j,mpd,mpd2
real(8), dimension(m),intent(in) :: A
real(8), dimension(m),intent(out) :: B
real(8) :: tmp
!F2PY INTENT(HIDE) :: m
!F2PY INTENT(IN) :: mpd
!!! Cumulative Sum
B=0.d0; B(1)=A(1)
do i=2,m
B(i)=B(i-1)+A(i)
enddo
!!! Moving Average
mpd2=mpd*2+1
tmp=B(mpd2)
B(mpd+2:m-mpd)=B(mpd2+1:)-B(m-mpd2)
B(mpd+1)=tmp
B=B/dble(mpd2)
end subroutine ma1dv2
그러면 결과는
*** Start Loop ***
1st: 0.01274sec 2nd: 0.01275sec 3rd: 0.01275sec
[-0.00253436 -0.00281975 -0.00051267]
약 0.0127초 정도 되겠습니다.
4. Summary and Conclusion
길이 1,000,000인 1D-Array (or Vector)를 가지고 501항목의 이동 평균을 계산하면,
Program | Algorithm | Running Time |
---|---|---|
Python | By Array | 1.29 sec |
Python | Using Cumsum | 0.0145 sec |
Fortran | By Element | 0.91 s |
Fortran | Using Cumsum | 0.0127 sec |
Fortran은 대체로 Python-Numpy보다 빠릅니다. 하지만 그보다 더 중요한 것은 최적화된 알고리즘입니다.
"""
제 개인적 목표는
- Object-Oriented Programming과 친해지기
- Github와 친해지기 입니다.
이 목표에 닿기 위해 일단 제가 나름 좀 아는 Python, 그 중에서도 Numpy와 Matplotlib로부터 시작하려 합니다.
"""
Matplotlib List
[Matplotlib] 00. Intro + 01. Page Setup
[Matplotlib] 02. Axes Setup: Subplots
[Matplotlib] 03. Axes Setup: Text, Label, and Annotation
[Matplotlib] 04. Axes Setup: Ticks and Tick Labels
[Matplotlib] 05. Plot Accessories: Grid and Supporting Lines
[Matplotlib] 06. Plot Accessories: Legend
[Matplotlib] 07. Plot Main: Plot
[Matplotlib] 08. Plot Main: Imshow
[Matplotlib] 09. Plot Accessary: Color Map (part1)
[Matplotlib] 10. Plot Accessary: Color Map (part2) + Color Bar
F2PY List
[F2PY] 01. Basic Example: Simple Gaussian 2D Filter
[F2PY] 02. Basic Example: Moving Average
오, Fortran도 하시는군요!
아뇨, 사실은 포트란을 하는데 요새 파이쏜도 하는 거에요 ㅎㅎ
역시 싸이언티스트! ㅎㅎ
ㅋㅋ 저도 예전에 하드코어 계산할 때 포트란 엄청 돌렸었는데 요새는 다들 파이썬을 주로 쓰더라구요;
요즘은 매틀랩이나 메스메티카 패키지도 많이 개발되어 빠른 결과를 얻는게 아니면 종종 쓴다고는 하는데 아직도 많은 사람들이 포트란을 선호하고 있죠!
병렬 컴퓨팅도 혹시 다루시나요ㅎㅎ
다 추억으로 남기만 한 기억들이라 이젠 기억의 저편으로 ㅋㅋ;
저희 분야 아니면 사실 이제 포트란 사용자 찾기 힘들죠 ㅎㅎ
그러지 않아도 다음편에는 OpenMP 소개하려고 준비하고 있어요~
아.. 머리가 핑글핑글 돕니다.
예전에 잠시 프로그램을 건드릴랑 말랑 했었는데
... 취미로 배워볼만한게 뭐가 있을까여?
취미로 해도 무언가 목적이 있어야 손이 가지 않을까요? ^^
목적에 따라 천차 만별이긴 한데, 일단 가볍고 쉬운건 역시 Python이죠. 한단계 더 어려운걸로는 JAVA가 있고요. Text처리는 Pearl이 좋다는 말도 있구요.
웹페이지나 이쪽은 제가 잘 모르는데, 자바스크립트 이런게 많이 쓰이는듯?
음... 해보고 싶은 것은 있는데 라기보단 필요한 것은 있는데 간단한게 아니라서요.
뭐가 되었든 실제로 해봐야하는데 그게 제일 힘드네요 ㅎㅎ
맞아요. 저도 무언가 새로운 걸 시작하는게 점점 더 힘들어지네요.
어지러워요.ㅎ@@
존경스럽습니다.
충분히 이해합니다.
저는 그저 업무상 필요한 부분을 정리하고자 작성한 글이라 이렇게 여러 분들이 방문해주실지 몰랐어요 ^^