Convolução, Transformada Rápida de Fourier e Polinômios (2022)
(alvarorevuelta.com)- Expandir polinômios de grau alto da forma ensinada no ensino médio exige multiplicar todos os pares de termos, então o custo O(n²) rapidamente se torna um gargalo
- A multiplicação dos vetores de coeficientes de polinômios é equivalente à convolução de sinais discretos, e o resultado de
[2, 3, 4]e[5, 6, 7]é[10, 27, 52, 45, 28] - A DFT leva um sinal discreto para o domínio da frequência, e a FFT calcula a mesma transformação em O(n log n), fazendo diferença em entradas grandes
- Como a convolução no domínio do tempo se transforma em multiplicação elemento a elemento no domínio da frequência, é possível multiplicar polinômios mais rapidamente convertendo com FFT, multiplicando e depois retornando com IFFT
- Em graus pequenos, o custo de ida e volta de FFT/IFFT pode anular o ganho, mas à medida que o grau cresce, o método com FFT se torna mais eficiente
Por que a multiplicação de polinômios fica lenta
- Um polinômio
P(x)é representado como a soma de termos formados pelos coeficientesa_ke potências da variávelx- Exemplo:
P(x)=5x²+2x+9é um polinômio de grau 2 - Dependendo da notação, o vetor de coeficientes pode ser escrito como
[5, 2, 9]ou[9, 2, 5]
- Exemplo:
- Soma e subtração são relativamente simples, pois basta somar ou subtrair os termos de mesmo grau
- Em Python, é possível percorrer cada coeficiente com
zip(p, q)e calculara + boua - b - Se os graus forem diferentes, pode-se usar
zip_longest
- Em Python, é possível percorrer cada coeficiente com
- A multiplicação exige multiplicar cada termo pelos demais e depois somar novamente os termos de mesmo grau, o que aumenta o volume de cálculo
- O resultado de
(2x²+3x+4) × (5x²+6x+7)é10x⁴+27x³+52x²+45x+28 - A complexidade desse método é O(n²), e o número de multiplicações necessárias cresce com o grau
- O resultado de
Vetores de coeficientes e convolução
- No domínio discreto, a convolução de dois sinais
peqé definida comoy[n]=Σ p[k]·q[n-k] - O cálculo é feito invertendo
qe depois deslocando-o sobrepda esquerda para a direita, somando os produtos dos elementos sobrepostos - Os sinais de exemplo são os seguintes
p = [2, 3, 4]q = [5, 6, 7]
- Ao inverter e deslocar
q, cada coeficiente de saída é gerado na seguinte ordem2×5 = 102×6 + 3×5 = 272×7 + 3×6 + 4×5 = 523×7 + 4×6 = 454×7 = 28
- O resultado da convolução é
y = [10, 27, 52, 45, 28]- Isso corresponde aos coeficientes de
10x⁴+27x³+52x²+45x+28, obtidos pela multiplicação dos polinômios - Portanto, a multiplicação de polinômios pode ser vista como a convolução dos vetores de coeficientes
- Isso corresponde aos coeficientes de
Transformada de Fourier e FFT
- A transformada de Fourier converte um sinal do domínio do tempo para o domínio da frequência
- Na visão temporal, o sinal é observado como valores em instantes específicos
- Na visão em frequência, o sinal é interpretado como a soma de diferentes frequências de oscilação
- As frequências de oscilação são representadas por seno e cosseno, cada um com coeficiente e fase
- Ao aplicar FFT a uma onda senoidal pura de 5Hz, ela aparece como um delta na posição de 5Hz no domínio da frequência
- Isso mostra que a senoide no domínio do tempo pode ser expressa por um único seno de 5Hz
- Os termos relacionados se distinguem assim
- Fourier Transform (FT): transformada de Fourier definida no domínio contínuo
- Discrete Fourier Transform (DFT): transformada de Fourier definida para sinais discretos
- Fast Fourier Transform (FFT): algoritmo que calcula a DFT em O(n log n) em vez de O(n²)
- A DFT transforma o sinal discreto no tempo
x[n]emX[k]no domínio da frequência- Cada
X[k]é calculado multiplicando as amostras de entrada por números complexos que representam uma frequência específica e somando os resultados
- Cada
Transformando em multiplicação no domínio da frequência
- A principal vantagem da DFT e do domínio da frequência é que a convolução pode ser convertida em multiplicação elemento a elemento
- Convoluir dois sinais no domínio do tempo equivale a multiplicá-los no domínio da frequência
- Multiplicação pode ser calculada mais rapidamente do que convolução
- O procedimento para multiplicar polinômios com mais rapidez é o seguinte
- Transformar os polinômios para o domínio da frequência com FFT: O(n log n)
- Multiplicar elemento a elemento no domínio da frequência: O(n)
- Transformar o resultado de volta para o domínio do tempo com IFFT: O(n log n)
- No geral, usando FFT, a multiplicação de polinômios pode ser feita com complexidade O(n log n)
- Para polinômios grandes, isso é mais rápido do que a multiplicação escolar em O(n²)
Implementação em Python e benchmark
multiply_naivemultiplica todos os pares de coeficientes com um laço duplo e soma o resultado na posiçãoi + j- O comprimento do resultado é
len(p) + len(q) - 1 - A complexidade é O(n²)
- O comprimento do resultado é
multiply_fftrealiza a multiplicação dos coeficientes com base em FFT/IFFT- Ele calcula um comprimento que seja potência de 2 e pelo menos
len(p) + len(q) - 1para acomodar o tamanho do resultado - Usa
np.padpara fazer padding nas duas entradas - Multiplica elemento a elemento os valores transformados com
np.fft.fft - Depois volta com
np.fft.iffte arredonda a parte real para obter coeficientes inteiros
- Ele calcula um comprimento que seja potência de 2 e pelo menos
- No exemplo com
p = [2, 3, 4]eq = [5, 6, 7], os dois métodos retornam[10, 27, 52, 45, 28] - No benchmark, em vez de
multiply_naive, compara-se o método com FFT commultiply_convolve, que usanp.convolve- Isso porque
multiply_naivecom laços em Python é lento demais para uma comparação direta com o método FFT usandonp.fft.fft np.convolveexecuta a mesma operação em código C de baixo nível
- Isso porque
- O grau é aumentado no intervalo
range(1, 30000, 1000), e em cada grau são gerados dois polinômios com coeficientes aleatórios entre 1 e 999999- Em cada método, o tempo médio é medido com
n_runs = 5 - Em graus baixos, o custo da conversão de ida e volta com FFT/IFFT pode fazer o método FFT não ser vantajoso
- À medida que o grau aumenta, o método com FFT mostra resultados muito mais eficientes
- Em cada método, o tempo médio é medido com
1 comentários
Comentários do Hacker News
O que sempre me incomoda em explicações assim é que normalmente se esquecem dos erros numéricos.
Não dá para simplesmente abstrair a multiplicação dos coeficientes como “tempo constante”. Se for fazer isso, dá no mesmo abstrair a multiplicação inteira desde o começo. Levando em conta a precisão numérica, fica mais perto de O(n (log n)^3) [1]
[1]: http://numbers.computation.free.fr/Constants/Algorithms/fft....
[1] One-Dimensional Quaternion Discrete Fourier Transform and an Approach to Its Fast Computation:
https://www.mdpi.com/2079-9292/12/24/4974
[2] Convolution Theorems for Quaternion Fourier Transform: Properties and Applications:
https://onlinelibrary.wiley.com/doi/10.1155/2013/162769
[3] On the Matrix Form of the Quaternion Fourier Transform and Quaternion Convolution:
https://arxiv.org/abs/2307.01836
Dá para multiplicar números longos usando esse método. O ponto principal é que a multiplicação de polinômios é igual à multiplicação longa comum de números grandes sem fazer o vai-um (carry).
Por exemplo, se você tiver um número de 1000 dígitos, use cada dígito como coeficiente de um polinômio com 1000 elementos. Depois, dá para multiplicar esses polinômios com o método de FFT descrito no texto. Para transformar o resultado de volta em número, é preciso processar os carries. Se algum elemento for maior que 10, passa-se o excedente para a próxima casa e então se convertem os coeficientes em número.
A ideia básica é essa, e há sutilezas na precisão necessária para os carries e em garantir que arredondar o resultado da FFT para o inteiro mais próximo ainda dê certo. É assim que a GMP, a principal biblioteca dessa área, faz multiplicação de números grandes
Fico curioso sobre quão grande o número precisa ser para isso realmente fazer sentido, e onde isso é usado
Se ainda não viu, recomendo este vídeo
https://youtu.be/h7apO7q16V0?si=bmgUEMTQSqU3flIv
Ele deriva o algoritmo de FFT a partir da multiplicação de polinômios, e é realmente excelente. Reassisto mais ou menos a cada 6 meses
A propriedade da FFT de que “convolução é multiplicação ponto a ponto” também vale em qualquer grupo multiplicativo cíclico. Para uma derivação mais algébrica, veja https://www.sciencedirect.com/science/article/pii/S002200007...
Às vezes chamam isso de “FFT harmônica”, e também existem FFTs não harmônicas: [LCH14] “additive NTT” sobre GF(2^n), [HLP24] circle FFT sobre o círculo unitário X^2+Y^2=1 em corpos finitos, e [BCKL21] ecfft sobre uma sequência de isogenias de curvas elípticas
[LCH14]: https://arxiv.org/abs/1404.3458
[HLP24]: https://eprint.iacr.org/2024/278
[BCKL21]: https://arxiv.org/pdf/2107.08473
Quem foi a primeira pessoa a propor usar FFT para uma multiplicação de polinômios mais rápida?
Fiquei curioso recentemente e fui procurar; embora eu não tenha conseguido rastrear muito bem as citações, cheguei até um artigo de 1995 de David Eppstein [0]. Nele, isso é usado para resolver de forma eficiente o problema da soma parcial após atualizações incrementais. Tenho quase certeza de que isso já aparecia antes no TAOCP, de Knuth
Também foi bem chocante descobrir que é possível resolver o problema exato da soma de subconjuntos com repetição permitida em tempo subexponencial usando multiplicação de polinômios por FFT [1]. O ponto importante é que esse algoritmo é O(N log N), em que N não é o tamanho do conjunto, mas o maior elemento; portanto, não é algo como um contraexemplo a P ≠ NP
[0] https://escholarship.org/content/qt6sd695gn/qt6sd695gn.pdf
[1] https://x.com/festivitymn/status/1788362552998580473?s=46&t=...
Dizem que Strassen descobriu uma abordagem ao estilo de Pollard em 1968, mas não há registro documental. Também é preciso levar em conta que, mesmo que não tenha sido o nascimento da FFT em si, o artigo de 1965 de Cooley-Tukey [4] foi o que realmente impulsionou as pesquisas sobre FFT e suas aplicações. Isso aconteceu alguns anos depois
[1] https://doi.org/10.1090/S0025-5718-1971-0301966-0
[2] https://doi.org/10.1016/S0022-0000(71)80014-4
[3] https://doi.org/10.1007/BF02242355
[4] https://doi.org/10.1090/S0025-5718-1965-0178586-1
https://www.cis.rit.edu/class/simg716/FFT_Fun_Profit.pdf
Acho que todo aprendizado de máquina consiste em resolver equações de convolução
Este artigo trata disso no contexto de aprendizado por reforço https://arxiv.org/abs/1712.06115, mas a maioria das abordagens se encaixa nesse paradigma
Acabei de implementar um algoritmo (matrix profile) que usa FFT para calcular produtos internos de um grande conjunto de subsequências de séries temporais. O comprimento n da série temporal pode chegar à casa das centenas de milhões
O cálculo rápido de convoluções usando FFT reduz o tempo de computação de O(n) para O(log n), e nessa escala o ganho de velocidade é enorme. Usando GPU, fica ainda mais rápido: algo como processar 10 milhões de pontos de dados em 0,1 segundo em um notebook
O “truque” central dessa operação parece ser esta percepção:
Existe a propriedade de que “realizar a convolução de dois sinais no domínio do tempo é o mesmo que multiplicar os dois sinais no domínio da frequência”, e a FFT permite converter do domínio do tempo para o domínio da frequência. Então levamos os polinômios para o domínio da frequência com FFT e, nesse domínio, basta multiplicar. É mais rápido do que a convolução. Fico curioso se isso deixa clara a etapa que estava faltando; se ainda houver alguma lacuna, posso atualizar o texto
Então a fatoração de inteiros seria uma deconvolução discreta? Fico me perguntando se, ao colocar lado a lado a representação por FFT, isto é, a operação inversa da multiplicação ponto a ponto, e tableax, isto é, a multiplicação longa/soma com transporte comum, a simetria se quebra e fornece informações suficientes para um algoritmo rápido
Claro, a multiplicação ingênua de polinômios é lenta em relação ao grau do polinômio. Mas, na prática, quando é que alguém precisa lidar com dois polinômios de grau 100?
Por esse motivo, fica a impressão de que sistemas de álgebra computacional não usam esse método
https://www.youtube.com/watch?v=CcZf_7Fb4Us
https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_cor... é um exemplo
[1]: https://github.com/8051enthusiast/delsum
Os posts do blog da Hazy Research de 2020 a 2023 têm bastante informação sobre essa abordagem
“(...) Em uma pesquisa de física, já lidei com uma expressão de quase 1 terabyte de comprimento e com mais de 100 milhões de termos”