2022년 10월 3일 월요일

고속 푸리에 변환(FFT: Fast Fourier Transform)

[경고] 아래 글을 읽지 않고 "고속 푸리에 변환"을 보면 바보로 느껴질 수 있습니다.


[고속 푸리에 변환의 쉬운 이해]

통신 이론과 신호 처리에 필수적인 고속 푸리에 변환(fast Fourier transform, FFT)이산 푸리에 변환(discrete Fourier transform, DFT)의 항들을 우함수(even function)기함수(odd function)로 모아서 급수를 빠르게 계산하기 위한 혁신적인 알고리즘이다[1], [2]. FFT는 1805년가우스 28세, 조선 순조 시절 무렵에 가우스Carl Friedrich Gauss(1777–1855)가 먼저 고안해 사용했다는 전설을 가지고 있다.[푸리에 교수가 푸리에 급수(Fourier series)를 공개한 시점이 1807년이다.] 현대적인 FFT는 뼈대 있는 수학자 룽에Carl Runge(1856–1927)의 1903년룽에 47세, 대한제국 시절 기여를 거쳐 미국 수학자 쿨리James Cooley(1926–2016)와 투키John Tukey(1915–2000)가 1965년쿨리 39세, 박정희 정부 시절에 재발견했다[3]. 쿨리와 투키가 FFT를 구현한 방법론은 쿨리–투키 알고리즘(Cooley–Tukey algorithm)으로 부른다. 이 알고리즘의 유도는 기존 DFT에 대한 관찰로부터 시작한다.

                  (1)

여기서 M은 표본 개수, fm은 시계열(time sequence), Fk는 푸리에 계수(Fourier coefficient)이다. DFT의 모든 항을 항상 이등분할 수 있도록, FFT에서는 보통 M을 2의 거듭제곱인 M = 2N으로 가정한다. 여기서 N은 1이상의 자연수이다. 먼저 식 (1)에서 복소 지수 함수를 WM으로 치환해서 멱급수(power series) 형태로 다시 쓴다.

                  (2)

복소 지수 함수 WM의 거듭제곱은 다음 성질을 가진다.

                  (3a)

                  (3b)

그 다음에 식 (2)를 이등분해서 DFT 항을 WMk에 대한 우함수와 기함수인 부분으로 나눈다.

                  (4)

                  (5)

여기서 k = 0,1,,M/21, 원래 DFT 급수를 1번 분해한 E1(x)O1(x)는 각각 짝수 및 홀수 밑수를 가진 fk만을 모은 다항식이다. 그러면 식 (3a)의 둘째식과 식 (3b)의 첫째식을 이용해서 Fk+M/2를 다시 쓸 수 있다.

                  (6)

여기서 WMk+M/2 = WMk이다. 그러면 식 (6)을 다시 계산할 필요없이, 식 (4)에서 구한 E1()O1()의 결과값에서 부호만 (+)에서 ()로 바꾸면 된다. 즉, DFT 급수를 우함수와 기함수로 분해함으로써 계산 속도를 2배만큼 증가시킬 수 있다. 이 방식을 염두에 두고 식 (4)를 새롭게 보면, E1(x)O1(x)는 항의 개수가 반[= M/2]만큼 줄어든 또 다른 DFT 급수이다. 그래서 재귀적인 방법으로 식 (5)를 다시 우함수와 기함수 관련 다항식으로 모아서 계속 연산을 가속화시킨다. 이와 같은 기법이 바로 쿨리–투키 알고리즘의 핵심이. 예를 들어, 재귀 관계에서 E1()O1()은 다음 다항식으로 재분해된다.

                  (7a)

                  (7b)

                  (8a)

                  (8b)

여기서 k = 0,1,,M/41이다. 식 (8a)에서 E2|E1(x)와 O2|E1(x)는 각각 E1(x)를 구성하는 x의 짝수차와 홀수차 항을 모아서 만든 다항식이다. 비슷하게 E2|O1(x)와 O2|O1(x)는 각각 O1(x)의 짝수차와 홀수차 항으로 구성한다.
좀더 쉽게 쿨리–투키 알고리즘을 이해하기 위해, 예시적으로 M = 21인 경우부터 출발해 식 (4)를 다시 기술한다.

                  (9)

표본 크기 M이 2인 FFT 결과를 가시화하려 나비 모양처럼 생긴 도표를 [그림 1]처럼 도입한다.

[그림 1] 기본 2×2 나비 도표(사진 출처: wikipedia.org)

[그림 2] 변형 2×2 나비 도표

[그림 1]은 나비를 닮아서 나비 도표(butterfly diagram)라고 명한다. 기본 나비 도표의 f1W를 곱한 변형은 [그림 2]처럼 화살표를 하나 추가해서 그린다. 표본 크기를 더 증가시킨 M = 4인 경우도 식 (4)를 바탕으로 우함수와 기함수를 분리한다.

                  (10)

[그림 3] 4×4 나비 도표

식 (10)을 나비 도표로 나타낸 결과인 [그림 3]은 약간 복잡하지만 명확한 규칙이 보인다. 시계열 fk를 시작할 때, k를 이진수로 나타내어 비트를 역전시킨다. 이 과정은 FFT의 비트 역전(bit reversal)이라 이름 붙인다. 그러면 [그림 3]의 입력은 0, 2, 1, 3의 순서로 들어간다. 그 다음에 [그림 1]처럼 M = 2에 대한 FFT를 각각 수행하고, 이 결과를 모아서 각 화살표에 대해 M = 4에 대한 FFT 연산을 다시 적용한다. 식 (4)와 (7)은 비트 역전이 필요한 이유를 잘 보여주고 있다. 우리가 홀짝을 나눈 다음에 표본 개수 M과 지수 k를 1/2하고, 우함수와 기함수 관련 다항식을 각각 홀짝으로 다시 분류한다. 지수 k를 줄이는 단계는 2의 거듭제곱으로 나누면서 계속 반복된다. 최종적으로 항이 2개만 남으면 멈춘 후, 나비 도표인 [그림 1]을 이용해 계산한다. 시계열 fkk가 줄어서 마지막에 선택된 두 항[그림 1에서 0과 1로 표기]k를 이진수로 표시할 때 최상위 비트(most significant bit, MSB)가 0이나 1인 경우이다. [그림 1]을 포함한 [그림 3]에서 나비 도표의 둘째 단계는 MSB의 그 다음 비트가 0이나 1을 가진 k를 처리한다. 따라서 FFT에서는 [그림 3]처럼 비트 역전을 통해서 입력 시계열 fk를 두 개씩 모아야 한다.

[그림 4] 8×8 나비 도표

[그림 4]는 [그림 3]을 2개 나열해서 8×8 나비 도표를 구현한 결과를 보여준다. 시계열 fkk는 비트 역전을 통해서 결정한다. 입력이 8개라서 우함수와 기함수 부분인 다항식 E1()O1()은 각각 4개의 표본을 가지고 있다. 식 (4)를 사용해서 유도한 M = 8인 FFT 관계식은 다음과 같다.

                  (11)

여기서 k = 0,1,2,3, E1()O1()은 [그림 3]으로 계산한다.
표본 개수가 M = 2N이 아니더라도 [1]을 참고해서 FFT를 수행할 수 있다. 하지만 M이 2의 거듭제곱이 아니면 홀짝 모으기가 번거로워진다. 만약 표본이 작아서 M < 2N이면, M을 증가시켜 강제로 M = 2N을 만들고 모르는 fk0으로 채워서 FFT를 돌린다. 이런 부가적인 과정은 FFT의 영 채우기(zero padding)로 이름 붙인다. 영 채우기의 조건은 붙여넣은 fk의 크기가 매우 작아서 0으로 근사할 수 있다는 가정이다. 예를 들어, M0개로 영 채우기를 하면 표본수는 M+M0으로 늘고 전체 표본화 시간은 T = (M+M0)Δt로 증가하다. 여기서 Δt표본화 주기(sampling period)이다. 이로 인해 주파수 영역의 각주파수는 pk = kΔp로 변한다. 여기서 k = 0,1, , M+M02,M+M01; 각주파수 간격은 Δp = 2π/[(M+M0)Δt]가 되어 이전보다 줄어든다. 그래서 시간 영역의 표본수를 늘리면, 그만큼 주파수 영역의 표본 간격이 축소된다. 하지만 pk의 범위는 (M+M0)Δp = 2π/Δt이므로, 영 채우기로는 pk의 최대 한계를 늘릴 수 없다. 여기서 fs = 1/Δtωs = 2π/Δt는 각각 표본화 주파수(sampling frequency)와 표본화 각주파수(sampling angular frequency)로 부른다.
주파수 영역에도 영 채우기가 가능하다. 기존 pk의 마지막 값인 pM1이 0에 가깝다고 생각해서, pM 이상을 0으로 간주하고 원하는 만큼 0을 추가한다. 이 경우 표본화 주기 Δt의 고정 유무에 따라 시간 영역에 나타나는 효과가 달라진다. 먼저 Δt를 상수라 가정하고 시간 영역의 영 채우기와 동일한 논리를 적용한다. 그러면 영을 채운 회수 M0을 아무리 늘려도 pk의 영역[= ωs = 2π/Δt]은 증가하지 않고 각주파수 간격 Δp[= 2π/[(M+M0)Δt]]만 좁아진다. 이때 시간 영역에서는 전체 표본화 시간[= (M+M0)Δt]이 주파수 영역에서 영 채우기한 만큼 늘어난다. 반대로 Δt가 바뀔 수 있지만 Δp는 고정이면, 데칼코마니(décalcomanie)처럼 시간 영역의 영 채우기와 대칭적으로 생각한다. 영 채우기로 인해 표본화 각주파수는 (M+M0)Δp로 늘어나지만 Δp 자체는 고정된다. 이 결과를 시간으로 가져가서 tm = mΔt = 2πm/[(M+M0)Δp]로 둔다. 따라서 표본화 주기는 Δt = 2π/[(M+M0)Δp]로 축소되고, 전체 표본화 시간은 (M+M0)Δt = 2π/Δp로 변화 없다.
FFT의 기본 알고리즘은 간단해서 우리가 쉽게 프로그램할 수 있다. 그런데 많은 자료를 진짜 빠르게 처리하려면 전문적인 FFT 함수 집합 혹은 라이브러리(library)를 써야 한다. 우리가 쉽게 구할 수 있는 상용 FFT 라이브러리 중에 FFTW(FFT in the West)가 있다[4]. FFTW는 MATLAB에도 쓰일 정도로 잘 검증된 연산을 제공한다. 다만 FFTW는 비상업적인 용도로 쓸 때에만 무료로 이용할 수 있다. NVIDIA GPU를 사용하는 경우는 cuFFT[CUDA(Compute Unified Device Architecture) 기반 FFT]를 사용할 수 있다[5]. 여러 문헌에 나오는 결과를 종합하면, 표본수가 커질수록 cuFFT는 CPU를 쓰는 FFTW보다 훨씬 빠른 성능을 보인다.

FFT는 중앙화 고속 푸리에 변환(centered FFT, CFFT: CDFT의 고속화), 대칭 고속 푸리에 변환(symmetric FFT, SFFT: SDFT의 고속화), 일반화 고속 푸리에 변환(generalized FFT, GFFT: GDFT의 고속화) 등과 같은 변종이 많이 있다. 하지만 CFFT, SFFT를 위한 고속 알고리즘을 다시 제안하지 않고, 이미 구현된 FFT를 바꾸어서 CFFT, SFFT를 구현한다. CFFT는 순환 편이(circular shift) 연산자 shift()를 도입해서 FFT로 재구성한다.

                          (12)

여기서 Me = M/2, Mo = (M+1)/2, M = Me+Mo; x 혹은 [x]x를 넘지 않는 바닥 함수(floor function), shift()는 표본 수열 fmMe만큼 편이시킨다. 이 개념을 CDFT(centered DFT) 공식에 넣어서 FFT를 포함하도록 변형한다.

                          (13a)

CFFT의 역변환인 ICFFT(inverse centered FFT)도 IFFT(inverse FFT)shift()로 계산한다.

                          (13b)

표본 개수 M이 홀수인 경우는 SFFT와 CFFT의 결과는 같다. 하지만 M이 짝수가 되면 m = 0을 기준으로 음과 양인 m이 대칭적이지 않아서 +0.5만큼 m을 움직여야 한다. 즉, 홀수인 M에서는 t = mΔt (m = 0,±1,,±Me)를 써도 되지만 짝수에서는 t = (m+0.5)Δt (MemMe1)로 만든다. 따라서 SFFT는 식 (13a)처럼 FFT와 shift()를 활용하지만 FFT 전에 +0.5가 만드는 위상 기여를 곱한다.

                          (14a)

여기서 Δe(M)M의 짝수 판별식(discriminant of even number)이다. 역변환인 ISFFT(inverse symmetric FFT)도 FFT로 표현한다.

                          (14b)

이와 같이 FFT는 약방의 감초처럼 우리가 상상하는 다양한 푸리에 변환에 고속화라는 감칠맛을 더한다.  

[참고문헌]
[1] J. W. Cooley and J. W. Tukey, "An algorithm for the machine calculation of complex Fourier series," Math. Comput., vol. 19, no. 90, pp. 297–301, Apr. 1965.
[2] E. O. Brigham and R. E. Morrow, "The fast Fourier transform," IEEE Spectr., vol. 4, no. 12, pp. 63–70, Dec. 1967.
[3] M. T. Heideman, D. H. Johnson, and C. S. Burrus, "Gauss and the history of the fast Fourier transform," IEEE ASSP Mag., vol. 1, no. 4, pp. 14–21, Oct. 1984.
[4] M. Frigo and S. G. Johnson, "C Subroutine Library for Computing the DFT," FFTW 3.3.10, 2022. (방문일 2022-10-04)
[5] cuFFT Library User's Guide, cuFFT API Reference, NVIDIA Corp., California, USA, 2022. (방문일 2022-10-04)

댓글 6개 :

  1. 여러 자료를 읽어보았지만 나비 도표 모양으로 계산이 가능한 과정이 결론을 보고 계산해보니 맞더라 식으로 기술된 곳이 많아 이해에 애를 먹었는데 나비 도표의 구성을 기본 식에서 유도해주셔서 이해할 수 있었습니다. 감사합니다.

    답글삭제
    답글
    1. 익명님, 본 블로그의 지향점을 잘 느껴주셔서 감사합니다^^

      삭제
  2. 혹시나, 웨이블릿 변환에 대해 다루실 예정은 없으신가요?

    답글삭제
    답글
    1. 신호 처리에서 웨이블릿 변환(wavelet transform)도 참 중요한데요, 지금은 여유가 없네요 ㅠㅠ

      삭제
  3. (2)번 식의 맨 오른쪽 항의 지수가 k(M-1)이 되어야 하는 것 아닌가요?

    답글삭제
    답글
    1. 틀렸네요. 지적 정말 감사해요, 익명님^^

      삭제

욕설이나 스팸글은 삭제될 수 있습니다. [전파거북이]는 선플운동의 아름다운 인터넷을 지지합니다.