- 큰 차수의 다항식을 고등학교식으로 전개하면 모든 항 쌍을 곱해야 하므로 O(n²) 비용이 빠르게 병목이 됨
- 다항식의 계수 벡터 곱셈은 이산 신호의 컨볼루션과 같으며,
[2, 3, 4]와[5, 6, 7]의 결과는[10, 27, 52, 45, 28]임 - DFT는 이산 신호를 주파수 영역으로 옮기고, FFT는 같은 변환을 O(n log n)에 계산해 큰 입력에서 차이를 만듦
- 시간 영역의 컨볼루션은 주파수 영역의 원소별 곱셈으로 바뀌므로, FFT로 변환해 곱한 뒤 IFFT로 되돌리면 다항식 곱셈을 더 빠르게 처리할 수 있음
- 작은 차수에서는 FFT/IFFT 왕복 비용이 이득을 상쇄할 수 있지만, 차수가 커질수록 FFT 방식이 더 효율적임
다항식 곱셈이 느려지는 이유
- 다항식
P(x)는 계수a_k와 변수x의 거듭제곱 항을 더한 형태로 표현됨- 예시
P(x)=5x²+2x+9는 차수가 2인 다항식임 - 계수 벡터는 표기 방식에 따라
[5, 2, 9]또는[9, 2, 5]처럼 나타낼 수 있음
- 예시
- 덧셈과 뺄셈은 같은 차수의 항끼리 더하거나 빼면 되므로 비교적 단순함
- Python에서는
zip(p, q)로 각 계수를 순회하며a + b또는a - b를 계산할 수 있음 - 차수가 다르면
zip_longest를 사용할 수 있음
- Python에서는
- 곱셈은 각 항을 서로 곱한 뒤 같은 차수 항을 다시 합쳐야 해 계산량이 커짐
(2x²+3x+4) × (5x²+6x+7)의 결과는10x⁴+27x³+52x²+45x+28임- 이 방식의 복잡도는 O(n²) 이며, 차수가 커질수록 필요한 곱셈 수가 늘어남
계수 벡터와 컨볼루션
- 이산 영역에서 두 신호
p와q의 컨볼루션은y[n]=Σ p[k]·q[n-k]로 정의됨 - 계산은
q를 뒤집은 뒤p위에서 왼쪽에서 오른쪽으로 이동시키며, 겹치는 원소의 곱을 더하는 방식임 - 예시 신호는 다음과 같음
p = [2, 3, 4]q = [5, 6, 7]
q를 뒤집어 이동하면 각 출력 계수는 다음 순서로 만들어짐2×5 = 102×6 + 3×5 = 272×7 + 3×6 + 4×5 = 523×7 + 4×6 = 454×7 = 28
- 컨볼루션 결과는
y = [10, 27, 52, 45, 28]임- 이는 다항식 곱셈으로 얻은
10x⁴+27x³+52x²+45x+28의 계수와 같음 - 따라서 다항식 곱셈은 계수 벡터의 컨볼루션으로 볼 수 있음
- 이는 다항식 곱셈으로 얻은
푸리에 변환과 FFT
- 푸리에 변환은 신호를 시간 영역에서 주파수 영역으로 변환함
- 시간 관점에서는 특정 시점의 값으로 신호를 봄
- 주파수 관점에서는 서로 다른 진동 주파수들의 합으로 신호를 해석함
- 진동 주파수는 사인과 코사인으로 표현되며, 각각 계수와 위상을 가짐
- 5Hz 순수 사인파에 FFT를 적용하면 주파수 영역에서 5Hz 위치에 델타처럼 나타남
- 이는 시간 영역의 사인파가 5Hz 사인 하나로 표현될 수 있음을 보여줌
- 관련 용어는 다음처럼 구분됨
- Fourier Transform(FT): 연속 영역에서 정의된 푸리에 변환
- Discrete Fourier Transform(DFT): 이산 신호에 대해 정의된 푸리에 변환
- Fast Fourier Transform(FFT): DFT를 O(n²) 대신 O(n log n) 에 계산하는 알고리듬
- DFT는 이산 시간 신호
x[n]을 주파수 영역의X[k]로 바꿈- 각
X[k]는 입력 샘플을 특정 주파수를 나타내는 복소수와 곱해 더해 계산됨
- 각
주파수 영역에서 곱셈으로 바꾸기
- DFT와 주파수 영역의 핵심 장점은 컨볼루션을 원소별 곱셈으로 바꿀 수 있다는 점임
- 시간 영역에서 두 신호를 컨볼루션하는 것은 주파수 영역에서 두 신호를 곱하는 것과 같음
- 곱셈은 컨볼루션보다 더 빠르게 계산할 수 있음
- 다항식 곱셈을 빠르게 수행하는 절차는 다음과 같음
- 다항식을 FFT로 주파수 영역으로 변환함: O(n log n)
- 주파수 영역에서 원소별로 곱함: O(n)
- 결과를 IFFT로 다시 시간 영역으로 변환함: O(n log n)
- 전체적으로 FFT를 이용하면 다항식 곱셈을 O(n log n) 복잡도로 수행할 수 있음
- 큰 다항식에서는 고등학교식 O(n²) 곱셈보다 빠름
Python 구현과 벤치마크
multiply_naive는 이중 반복문으로 모든 계수 쌍을 곱해 결과 위치i + j에 더함- 결과 길이는
len(p) + len(q) - 1임 - 복잡도는 O(n²) 임
- 결과 길이는
multiply_fft는 FFT/IFFT 기반으로 계수 곱셈을 수행함- 결과 길이를 담을 수 있도록
len(p) + len(q) - 1이상인 2의 거듭제곱 길이를 계산함 np.pad로 두 입력을 패딩함np.fft.fft로 변환한 값을 원소별로 곱함np.fft.ifft로 되돌린 뒤 실수부를 반올림해 정수 계수로 변환함
- 결과 길이를 담을 수 있도록
- 예시 입력
p = [2, 3, 4],q = [5, 6, 7]에서는 두 방식 모두[10, 27, 52, 45, 28]을 반환함 - 벤치마크에서는
multiply_naive대신np.convolve를 사용하는multiply_convolve와 FFT 방식을 비교함multiply_naive는 Python 반복문이 느려,np.fft.fft를 쓰는 FFT 방식과 직접 비교하기 어렵기 때문임np.convolve는 같은 연산을 저수준 C 코드로 수행함
- 차수는
range(1, 30000, 1000)범위로 늘리고, 각 차수에서 1부터 999999 사이의 임의 계수로 두 다항식을 생성함- 각 방식은
n_runs = 5로 평균 시간을 측정함 - 낮은 차수에서는 FFT/IFFT 왕복 변환 비용 때문에 FFT 방식이 유리하지 않을 수 있음
- 차수가 증가하면 FFT 방식이 훨씬 효율적인 결과를 보임
- 각 방식은