FFT
이번에 학교에서 연구를 진행하면서 FFT를 공부하게 되었다. 이는 나의 공부과정을 정리한 내용이며 틀린 내용이 있을 수 있으니 만일 보시는 분들이 있다면 참고바란다.
부끄러우니 다른거 보세요
GOAL : Fast Multiplication in polynomial
다항식의 곱은 Convolution으로 해결할 수 있다.
위의 과정은 단간하게 표현한 Convolution의 과정이다.
일반적인 Convolution은 O(N2)의 시간복잡도를 갖는다. 그러나 FFT를 활용한다면 보다 효율적으로 계산을 수행할 수 있다.
Representation of polynomial
우선 다항식을 표현하는 두가지 방법이 있다는 것을 알아두자.
- Coefficient representation
A(x)=j=0∑N−1ajxjA(x)=a0+x(a1+x(a2+⋯+x(aN−2+x(aN−1))))A(x)=aN−1xN−1+⋯+a1x1+a0
- Point-value representation
(x0,A(x0)),(x1,A(x1)),(x2,A(x2)),⋯(xN−1,A(xN−1))
즉, 서로다른 x에 대해 고유의 값 A(x)를 N개의 집합 표현하는 것이다.
행렬로 표현한다면 다음과 같다.
⎣⎢⎢⎢⎢⎢⎢⎡A(x0)A(x1)A(x2)⋮A(xN−1)⎦⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎡x00x10x20⋮xN−10x01x11x21⋮xN−11x02x12x22⋮xN−12⋯⋯⋯⋯⋯x0N−1x1N−1x2N−1⋮xN−1N−1⎦⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎡a0a1a2⋮aN−1⎦⎥⎥⎥⎥⎥⎥⎤
벌써부터 머리가 아프다. 🥲
Why?
왜 이런식으로 표현할까? 혹은, 이런식으로 표현하는 것이 허용될까?
다음과 같은 순서쌍의 집합이 있다고 가정해 보자. 모든 점을 지나는 다항식은 몇개나 존재할까? 답은 무수히 많을 것이다. 그러나 놀랍게도 모든 점을 지날 수 있는 다항식의 최소 차수는 N−1 차 이며 무려 유일하다.
그렇다면 다음의 식이 성립한다. 다항식 A(x)와 B(x)가 있다고 하자. 이 둘의 곱을 구하고자 하는데 만일 두 다항식이 어떠한 값 ω에 대하여 (ω,A(ω)),(ω,B(ω))가 주어져 있다고 하면 두 다항식의 곱 C(ω)는 (ω,A(ω)⋅B(ω))를 만족할 것이며 역시나 유일할 것이다.
도대체 이렇게 꼴을 바꾸어서 곱을 수행하는게 왜 유리할까? 조금만 생각해보자 기존에는 Convolution과 같은 알고리즘을 이용하여 O(N2)에 수행되었지만, 이런식으로 꼴을 바꾼다면 각 ω일 때, A(ω)⋅B(ω)만을 수행하면 되므로 O(N)안에 수행되게 된다.
FFT
앞서 알아본 두가지의 표현법 간의 변환을 빨리 해주는 것에 FFT가 이용된다.
Coefficient representation to point-value representation
Point-value representation to Coefficient representation
- Coefficient representation to point-value representation
일반적인 경우 N개의 A(xi)를 구하기 위해 N개의 ∑j=0N−1xijaj 를 수행하야 하기 때문에 시간복잡도는 O(N2)이 될 것이다.
이를 빠르게 수행하는 것이 FFT 이다.
만일 다항식 A와 B가 있다고 가정하자. 다항식의 차수는 N-1차 다항식이며 이 둘의 곱을 C라 하면, C는 2N-2차 다항식이 될 것이다. 여기서 우리가 2N-1개의 대한 서로다른 (xi,C(xi))가 있다면, 두 다항식의 곱을 구해낼 수 있을 것이다.
생각해보면 FFT를 이용하지 않는다고 가정하면 다항식 A와 B가 둘다 N차 다항식이라 가정할 때, 각각의 다항식을 변환하기 위해 O(N2)이 소요되므로 일반적으로 보았을 때 시간복잡도는 O(N2+N2)=O(2N2)으로 별 차이 없을것이다.
A와 B를 각각 (xi,A(xi))와 (xi,B(xi))로 표현한 뒤, 이를 이용하여 (xi,C(xi))를 구해낸다고 해보자.
여기서 FFT는 아래의 디테일을 이용하여 계산시간을 획기적으로 줄인다.
A(x)=x4+7x2+10x0B(x)=2x5+3x3+11x1
위와 같이 A는 짝수항을 갖는 다항식, B는 홀수항을 갖는 다항식이라 해보자.
여기서 우리가 임의의 (xi,A(xi))와 (xi,B(xi))를 구했다고 해보자. 그렇다면 다음의 항도 쉽게 구할 수 있을 것이다.
(xi,A(xi)),(−xi,A(xi))(xi,B(xi)),(−xi,−B(xi))
이는 짝수항만을 갖는 다항식과, 홀수항만을 갖는 다항식으로 가정했기 때문에 가능한 것이며, 임의의 다항식이 주어졌을 때 각각 짝수, 홀수항만을 갖는 다항식으로 만든뒤 두 다항식을 더해주는 연산은 다항식의 최고차수에 비례하기 때문에 크게 문제되지 않을 것이다.
일반적인 경우에는 다항식을 짝수, 홀수항만을 갖는 다항식으로 나눈다면 계산이 절반으로 줄어들 것이다.
P(x)=Pe(x2)+xPo(x2)P(xi)=Pe(xi2)+xiPo(xI2)P(−xi)=Pe(xi2)−xiPo(xi2)
이를 보다 이해하기 쉽게 표현해보자.
A(xi)=a0xi0+a1xi1∗⋯+aN−1xiN−1=(a0xi0+a2xi2+⋯+aN−2xiN−2)∗xi(a2xi0+a4xi2+⋯+aN−1xiN−2)
여기서 우리는
Pe(xi2)=(a0xi0+a2xi2+⋯+aN−2xiN−2),Po(xi2)=(a2xi0+a4xi2+⋯+aN−1xiN−2)
라 하는 것이다.
그러나 이런식으로 계산과정을 절반으로 줄였다고 하더라도 결국 큰 효과를 보지는 못한다.
O((2N)2+(2N)2)=O(2N2)=O(N2). 사실 의미 없다.
이를 빨리 수행하기 위해 등장하는 개념이 바로 FFT이다.
즉, 임의의 xi를 ωN=cos(N2π)+i⋅sin(N2π)로 설정하는 것이다. 기존의 N을 절반으로밖에 줄이지 못했었지만 이렇게 설정한다면, 무려 O(logN)안에 수행할 수 있다.
왜 이렇게 되는지는 다음의 동영상을 참고하면 매우 도움이 된다.
세상을 바꾼 알고리즘
목소리가 좋으시다
But what is the Fourier Transform? A visual introduction.
요약 : 사인파, 코사인파의 주기성 등 다양한 특성을 이용하면 주기가 다른 파형에 대해서 규칙성을 보여 값의 재사용이 허용된다. 쉽게 말해 앞서 홀수항과, 짝수항을 나누었던 과정을 그 길이가 1이 될 때 까지 무한정 반복할 수 있다는 것이다.
이 컨셉에 대하여 잠시 알아보자. 어째서 홀수항과 짝수항을 무한히 구분할 수 있는 것인가? 정확히 표현하자면 무한히 홀수항과 짝수항을 나눌수 있는 것이 아닌 이전의 계산한 값을 재사용할 수 있어 연산의 양이 무한히 절반으로 줄어드는 것이다.
우리는 임의의 xi를 ωN=cos(N2π)+i⋅sin(N2π)로 설정했던것을 기억하자. 정말 자연스레 다음의 식도 성립을 한다. ωN2k=ω2Nk
조금 더 풀어서 보도록 하자. 만일 N=8 이라고 하면, 다음과 같을 것이다.
A(ω80)=Pe(ω80)+ω80⋅Po(ω80)=Pe(ω40)+ω80⋅Po(ω40)A(ω81)=Pe(ω82)+ω81⋅Po(ω82)=Pe(ω41)+ω81⋅Po(ω41)A(ω82)=Pe(ω84)+ω82⋅Po(ω84)=Pe(ω42)+ω82⋅Po(ω42)⋮A(ω86)=Pe(ω812)+ω86⋅Po(ω812)=Pe(ω46)+ω86⋅Po(ω46)A(ω87)=Pe(ω814)+ω87⋅Po(ω814)=Pe(ω47)+ω87⋅Po(ω47)
이렇게 보면은 별 의미가 없어보이지만 우리가 임의의 값을 ωN=cos(N2π)+i⋅sin(N2π)로 설정했다는 것을 기억하자. ω4를 예시로 들자면, ω44=e42πi⋅4=e2πi=1 임을 알 수 있다. 즉, 다음과 같다는 의미이다.
Pe(ω44)+ω84⋅Po(ω44)=Pe(ω40)+ω84⋅Po(ω40)Pe(ω45)+ω85⋅Po(ω45)=Pe(ω41)+ω85⋅Po(ω41)Pe(ω46)+ω86⋅Po(ω46)=Pe(ω42)+ω86⋅Po(ω42)Pe(ω47)+ω87⋅Po(ω47)=Pe(ω43)+ω87⋅Po(ω43)
ω45=ω44⋅ω41
여기서 끝이 아니다. 우리는 다음과 같이 또 표현할 수 있다.
끝이 없어 흑흑
Pe(ω40)=Pe(ω20)+ω40⋅Po(ω20)Pe(ω41)=Pe(ω22)+ω41⋅Po(ω22)Pe(ω42)=Pe(ω24)+ω42⋅Po(ω24)Pe(ω43)=Pe(ω26)+ω43⋅Po(ω26)
이는 Odd Poly에 대해서도 마찬가지 이다.
Po(ω40)=Pe(ω20)+ω40⋅Po(ω20)Po(ω41)=Pe(ω22)+ω41⋅Po(ω22)Po(ω42)=Pe(ω24)+ω42⋅Po(ω24)Po(ω43)=Pe(ω26)+ω43⋅Po(ω26)
이쯤 되면 규칙성이 보인다.
ω22=1 이며, ω20=ω22=ω24=ω26 임을 유추할 수 있다.
물론 단순 연산의 양은 동일하다고 할 수 있지만, 크나큰 차이점이 하나 생긴다. 바로 값의 재사용이 일어난다는 것이다. 이러한 성질을 이용하여 우리는 O(N2)만큼의 연산을 O(logN)내에서 해결할 수 있게 된 것이다.
이후의 곱의 연산은 간단하다. (ω,A(ω)),(ω,B(ω))를 알고있으니, C(x)=A(x)⋅B(x)라 할 때 (ω,A(ω)⋅B(ω))를 구하면 될 것이다.
그러나 우리가 원하는 형식은 C(x)임을 기억하자. 그러나 우리가 구한 것은 (ω,C(ω))이다. 이를 다시 C(x)로 되돌리기 위해서는 어떻게 해야할까? 이 과정 바로 Inverse FFT 이다.
여기까지 우리는 (xi,C(xi))를 구해냈다고 생각해보자. 이를 식으로 나타내면 다음과 같다.
⎣⎢⎢⎢⎢⎢⎢⎡C(w0)C(w1)C(w2)⋮C(wN−1)⎦⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎡w0w0w0⋮w0w0w1w2⋮wN−1w0w2w4⋮w2(N−1)⋯⋯⋯⋯⋯w0wN−1w2(N−1)⋮w(N−1)(N−1)⎦⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎡c0c1c2⋮cN−1⎦⎥⎥⎥⎥⎥⎥⎤
여기서 우리가 원하는 것은 바로 c0,c1,c2,⋯,cN−1 이다. 이를 구하는 방법은 직관적이다.
⎣⎢⎢⎢⎢⎢⎢⎡c0c1c2⋮cN−1⎦⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎡w0w0w0⋮w0w0w1w2⋮wN−1w0w2w4⋮w2(N−1)⋯⋯⋯⋯⋯w0wN−1w2(N−1)⋮w(N−1)(N−1)⎦⎥⎥⎥⎥⎥⎥⎤−1⎣⎢⎢⎢⎢⎢⎢⎡C(w0)C(w1)C(w2)⋮C(wN−1)⎦⎥⎥⎥⎥⎥⎥⎤
을 구하면 될 것이다.
이러한 역연산은 어떤식으로 구해야 할까?
⎣⎢⎢⎢⎢⎢⎢⎡w0w0w0⋮w0w0w1w2⋮wN−1w0w2w4⋮w2(N−1)⋯⋯⋯⋯⋯w0wN−1w2(N−1)⋮w(N−1)(N−1)⎦⎥⎥⎥⎥⎥⎥⎤−1=n1⎣⎢⎢⎢⎢⎢⎢⎡w0w0w0⋮w0w0w−1w−2⋮w−(N−1)w0w−2w−4⋮w−2(N−1)⋯⋯⋯⋯⋯w0w−(N−1)w−2(N−1)⋮w−(N−1)(N−1)⎦⎥⎥⎥⎥⎥⎥⎤
임이 잘 알려져 있다. 즉, w의 값을 적절히 변경한다면 쉽게 역변환을 수행할 수 있을 것이다.
Reference
FFT
FFT in competitive programming