Phase 01 - Lesson 19
Números Complexos para IA
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
A raiz quadrada de -1 não é imaginária. Ela é a chave para rotações, frequências e metade do processamento de sinais.
Tipo: Aprender Linguagem: Python Pré-requisitos: Fase 1, Lições 01-04 (álgebra linear, cálculo) Tempo: ~60 minutos
Objetivos de Aprendizagem
- Realizar aritmética complexa (somar, multiplicar, dividir, conjugar) tanto na forma retangular quanto na polar
- Aplicar a fórmula de Euler para converter entre exponenciais complexas e funções trigonométricas
- Implementar a Transformada Discreta de Fourier usando as raízes complexas da unidade
- Explicar como as rotações complexas estão por trás do RoPE e das codificações posicionais senoidais nos transformers
O Problema
Você abre um artigo sobre transformadas de Fourier e há i em todo lugar. Você olha as codificações posicionais de transformers e vê sin e cos em diferentes frequências -- as partes real e imaginária de exponenciais complexas. Você lê sobre computação quântica e encontra tudo expresso em espaços vetoriais complexos.
Os números complexos parecem abstratos. Um sistema numérico construído sobre a raiz quadrada de -1 parece um truque matemático. Mas não é um truque. É a linguagem natural das rotações e oscilações. Toda vez que algo gira, vibra ou oscila, os números complexos são a ferramenta certa.
Sem entender números complexos, você não consegue entender a Transformada Discreta de Fourier. Você não consegue entender a FFT. Você não consegue entender como o RoPE (Rotary Position Embedding) funciona em modelos de linguagem modernos. Você não consegue entender por que as codificações posicionais senoidais do artigo original do Transformer usam as frequências que usam.
Esta lição constrói a aritmética complexa do zero, conecta-a à geometria e mostra exatamente onde os números complexos aparecem em machine learning.
O Conceito
O que é um número complexo?
Um número complexo tem duas partes: uma parte real e uma parte imaginária.
z = a + bi
where:
a is the real part
b is the imaginary part
i is the imaginary unit, defined by i^2 = -1
É só isso. Você estende a reta numérica para um plano. Os números reais ficam em um eixo. Os números imaginários ficam no outro. Todo número complexo é um ponto nesse plano.
Aritmética complexa
Adição. Some as partes reais entre si, some as partes imaginárias entre si.
(a + bi) + (c + di) = (a + c) + (b + d)i
Example: (3 + 2i) + (1 + 4i) = 4 + 6i
Multiplicação. Use a propriedade distributiva e lembre que i^2 = -1.
(a + bi)(c + di) = ac + adi + bci + bdi^2
= ac + adi + bci - bd
= (ac - bd) + (ad + bc)i
Example: (3 + 2i)(1 + 4i) = 3 + 12i + 2i + 8i^2
= 3 + 14i - 8
= -5 + 14i
Conjugado. Inverta o sinal da parte imaginária.
conjugate of (a + bi) = a - bi
O produto de um número complexo e seu conjugado é sempre real:
(a + bi)(a - bi) = a^2 + b^2
Divisão. Multiplique o numerador e o denominador pelo conjugado do denominador.
(a + bi) / (c + di) = (a + bi)(c - di) / (c^2 + d^2)
Isso elimina a parte imaginária do denominador, fornecendo a você um número complexo limpo.
O plano complexo
O plano complexo mapeia todo número complexo a um ponto 2D. O eixo horizontal é o eixo real, o eixo vertical é o eixo imaginário.
z = 3 + 2i corresponds to the point (3, 2)
z = -1 + 0i corresponds to the point (-1, 0) on the real axis
z = 0 + 4i corresponds to the point (0, 4) on the imaginary axis
Um número complexo é simultaneamente um ponto e um vetor a partir da origem. Essa interpretação dupla é o que torna os números complexos úteis para a geometria.
Forma polar
Qualquer ponto no plano pode ser descrito por sua distância da origem e seu ângulo em relação ao eixo real positivo.
z = r * (cos(theta) + i*sin(theta))
where:
r = |z| = sqrt(a^2 + b^2) (magnitude, or modulus)
theta = atan2(b, a) (phase, or argument)
A forma retangular (a + bi) é boa para adição. A forma polar (r, theta) é boa para multiplicação.
Multiplicação na forma polar. Multiplique as magnitudes, some os ângulos.
z1 = r1 * e^(i*theta1)
z2 = r2 * e^(i*theta2)
z1 * z2 = (r1 * r2) * e^(i*(theta1 + theta2))
É por isso que os números complexos são perfeitos para rotações. Multiplicar por um número complexo de magnitude 1 é uma rotação pura.
Fórmula de Euler
A ponte entre exponenciais complexas e trigonometria:
e^(i*theta) = cos(theta) + i*sin(theta)
Esta é a fórmula mais importante desta lição. Quando theta = pi:
e^(i*pi) = cos(pi) + i*sin(pi) = -1 + 0i = -1
Therefore: e^(i*pi) + 1 = 0
Cinco constantes fundamentais (e, i, pi, 1, 0) ligadas em uma única equação.
Por que a fórmula de Euler importa para ML
A fórmula de Euler diz que e^(i*theta) traça o círculo unitário conforme theta varia. Em theta = 0, você está em (1, 0). Em theta = pi/2, você está em (0, 1). Em theta = pi, você está em (-1, 0). Em theta = 3pi/2, você está em (0, -1). Uma rotação completa é theta = 2pi.
Isso significa que exponenciais complexas SÃO rotações. E rotações estão em todo lugar no processamento de sinais e em ML.
Conexão com rotações 2D
Multiplicar o número complexo (x + yi) por e^(i*theta) rotaciona o ponto (x, y) por um ângulo theta em torno da origem.
Rotation via complex multiplication:
(x + yi) * (cos(theta) + i*sin(theta))
= (x*cos(theta) - y*sin(theta)) + (x*sin(theta) + y*cos(theta))i
Rotation via matrix multiplication:
[cos(theta) -sin(theta)] [x] [x*cos(theta) - y*sin(theta)]
[sin(theta) cos(theta)] [y] = [x*sin(theta) + y*cos(theta)]
Eles produzem resultados idênticos. A multiplicação complexa É a rotação 2D. A matriz de rotação é apenas a multiplicação complexa escrita em notação matricial.
graph TD
subgraph "Complex Multiplication = 2D Rotation"
A["z = x + yi<br/>Point (x, y)"] -->|"multiply by e^(i*theta)"| B["z' = z * e^(i*theta)<br/>Point rotated by theta"]
end
subgraph "Equivalent Matrix Form"
C["vector [x, y]"] -->|"multiply by rotation matrix"| D["[x cos theta - y sin theta,<br/> x sin theta + y cos theta]"]
end
B -.->|"same result"| D
Fasores e sinais rotativos
Uma exponencial complexa e^(iomegat) é um ponto girando em torno do círculo unitário com frequência angular omega. Conforme t aumenta, o ponto traça o círculo.
A parte real desse ponto rotativo é cos(omegat). A parte imaginária é sin(omegat). Um sinal senoidal é a sombra de um número complexo em rotação.
e^(i*omega*t) = cos(omega*t) + i*sin(omega*t)
Real part: cos(omega*t) -- a cosine wave
Imaginary part: sin(omega*t) -- a sine wave
Esta é a representação por fasores. Em vez de acompanhar uma onda senoidal sinuosa, você acompanha uma seta que gira suavemente. Deslocamentos de fase se tornam deslocamentos de ângulo. Mudanças de amplitude se tornam mudanças de magnitude. A adição de sinais se torna adição vetorial.
Raízes da unidade
As N raízes N-ésimas da unidade são N pontos igualmente espaçados no círculo unitário:
w_k = e^(2*pi*i*k/N) for k = 0, 1, 2, ..., N-1
Para N = 4, as raízes são: 1, i, -1, -i (os quatro pontos cardeais). Para N = 8, você obtém os quatro pontos cardeais mais as quatro diagonais.
As raízes da unidade são a base da Transformada Discreta de Fourier. A DFT decompõe um sinal em componentes nessas N frequências igualmente espaçadas.
Conexão com a DFT
A Transformada Discreta de Fourier de um sinal x[0], x[1], ..., x[N-1] é:
X[k] = sum_{n=0}^{N-1} x[n] * e^(-2*pi*i*k*n/N)
Cada X[k] mede o quanto o sinal se correlaciona com a k-ésima raiz da unidade -- uma senoide complexa na frequência k. A DFT decompõe um sinal em N fasores rotativos e informa a amplitude e a fase de cada um.
Por que i não é imaginário
A palavra "imaginário" é um acidente histórico. Descartes a usou de forma depreciativa. Mas i não é mais imaginário do que os números negativos eram quando as pessoas os rejeitaram pela primeira vez. Os números negativos respondem "de quanto você subtrai 5 de 3 para obter?". A unidade imaginária responde "o que você eleva ao quadrado para obter -1?".
De forma mais útil: i é um operador de rotação de 90 graus. Multiplique um número real por i uma vez e você rotaciona 90 graus para o eixo imaginário. Multiplique por i de novo (i^2) e você rotaciona mais 90 graus -- agora você está apontando na direção real negativa. É por isso que i^2 = -1. Não é misterioso. É uma meia-volta construída a partir de duas voltas de um quarto.
É por isso que os números complexos estão em todo lugar na engenharia. Qualquer coisa que gire -- ondas eletromagnéticas, estados quânticos, oscilações de sinais, codificações posicionais -- é naturalmente descrita por números complexos.
Exponenciais complexas vs funções trigonométricas
Antes da fórmula de Euler, os engenheiros escreviam sinais como Acos(omegat + phi) -- amplitude A, frequência omega, fase phi. Isso funciona, mas torna a aritmética dolorosa. Somar dois cossenos com fases diferentes exige identidades trigonométricas.
Com exponenciais complexas, o mesmo sinal é Ae^(i(omega*t + phi)). Somar dois sinais é apenas somar dois números complexos. Multiplicar (modular) é apenas multiplicar magnitudes e somar ângulos. Deslocamentos de fase se tornam adições de ângulo. Deslocamentos de frequência se tornam multiplicações por fasores.
Todo o campo do processamento de sinais migrou para a notação de exponenciais complexas porque a matemática é mais limpa. O "sinal real" é sempre apenas a parte real da representação complexa. A parte imaginária é carregada junto como contabilidade, fazendo toda a álgebra funcionar naturalmente.
Conexão com transformers
Codificações posicionais senoidais (artigo original do Transformer):
PE(pos, 2i) = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
Os pares sin e cos são as partes real e imaginária de exponenciais complexas em diferentes frequências. Cada frequência fornece uma "resolução" diferente para codificar a posição. Frequências baixas mudam lentamente (posição grosseira). Frequências altas mudam rapidamente (posição fina). Juntas, elas dão a cada posição uma impressão digital de frequência única.
RoPE (Rotary Position Embedding) leva isso adiante. Ele multiplica explicitamente os vetores de query e key por matrizes de rotação complexa. A posição relativa entre dois tokens se torna um ângulo de rotação. A atenção é computada usando esses vetores rotacionados, tornando o modelo sensível à posição relativa por meio da multiplicação complexa.
| Operação | Forma Algébrica | Significado Geométrico |
|---|---|---|
| Adição | (a+c) + (b+d)i | Adição vetorial no plano |
| Multiplicação | (ac-bd) + (ad+bc)i | Rotacionar e escalar |
| Conjugado | a - bi | Refletir sobre o eixo real |
| Magnitude | sqrt(a^2 + b^2) | Distância da origem |
| Fase | atan2(b, a) | Ângulo a partir do eixo real positivo |
| Divisão | multiplicar pelo conjugado | Reverter a rotação e reescalar |
| Potência | r^n * e^(intheta) | Rotacionar n vezes, escalar por r^n |
graph LR
subgraph "Unit Circle"
direction TB
U1["e^(i*0) = 1"] -.-> U2["e^(i*pi/2) = i"]
U2 -.-> U3["e^(i*pi) = -1"]
U3 -.-> U4["e^(i*3pi/2) = -i"]
U4 -.-> U1
end
subgraph "Applications"
A1["Euler's formula:<br/>e^(i*theta) = cos + i*sin"]
A2["DFT uses roots of unity:<br/>e^(2*pi*i*k/N)"]
A3["RoPE uses rotation:<br/>q * e^(i*m*theta)"]
end
U1 --> A1
U1 --> A2
U1 --> A3
Construa
Passo 1: Classe Complex
Construa uma classe de número complexo que suporte aritmética, magnitude, fase e conversão entre as formas retangular e polar.
import math
class Complex:
def __init__(self, real, imag=0.0):
self.real = real
self.imag = imag
def __add__(self, other):
return Complex(self.real + other.real, self.imag + other.imag)
def __mul__(self, other):
r = self.real * other.real - self.imag * other.imag
i = self.real * other.imag + self.imag * other.real
return Complex(r, i)
def __truediv__(self, other):
denom = other.real ** 2 + other.imag ** 2
r = (self.real * other.real + self.imag * other.imag) / denom
i = (self.imag * other.real - self.real * other.imag) / denom
return Complex(r, i)
def magnitude(self):
return math.sqrt(self.real ** 2 + self.imag ** 2)
def phase(self):
return math.atan2(self.imag, self.real)
def conjugate(self):
return Complex(self.real, -self.imag)
Passo 2: Conversão polar e fórmula de Euler
def to_polar(z):
return z.magnitude(), z.phase()
def from_polar(r, theta):
return Complex(r * math.cos(theta), r * math.sin(theta))
def euler(theta):
return Complex(math.cos(theta), math.sin(theta))
Verifique: euler(theta).magnitude() deve sempre ser 1.0. euler(0) deve dar (1, 0). euler(pi) deve dar (-1, 0).
Passo 3: Rotação
Rotacionar um ponto (x, y) por um ângulo theta é uma multiplicação complexa:
point = Complex(3, 4)
rotated = point * euler(math.pi / 4)
A magnitude permanece a mesma. Apenas o ângulo muda.
Passo 4: DFT a partir da aritmética complexa
def dft(signal):
N = len(signal)
result = []
for k in range(N):
total = Complex(0, 0)
for n in range(N):
angle = -2 * math.pi * k * n / N
total = total + Complex(signal[n], 0) * euler(angle)
result.append(total)
return result
Esta é a DFT O(N^2). Cada saída X[k] é a soma das amostras do sinal multiplicadas pelas raízes da unidade.
Passo 5: DFT inversa
A DFT inversa reconstrói o sinal original a partir do seu espectro. As únicas mudanças em relação à DFT direta: inverter o sinal no expoente e dividir por N.
def idft(spectrum):
N = len(spectrum)
result = []
for n in range(N):
total = Complex(0, 0)
for k in range(N):
angle = 2 * math.pi * k * n / N
total = total + spectrum[k] * euler(angle)
result.append(Complex(total.real / N, total.imag / N))
return result
Isso fornece reconstrução perfeita. Aplique a DFT, depois a IDFT, e você recupera o sinal original com precisão de máquina. Nenhuma informação é perdida.
Passo 6: Raízes da unidade
def roots_of_unity(N):
return [euler(2 * math.pi * k / N) for k in range(N)]
Verifique duas propriedades:
- Toda raiz tem magnitude exatamente 1.
- A soma de todas as N raízes é zero (elas se cancelam por simetria).
Essas propriedades são o que torna a DFT invertível. As raízes da unidade formam uma base ortogonal para o domínio da frequência.
Use
Python tem suporte nativo a números complexos. O literal j representa a unidade imaginária.
z = 3 + 2j
w = 1 + 4j
print(z + w)
print(z * w)
print(abs(z))
import cmath
print(cmath.phase(z))
print(cmath.exp(1j * cmath.pi))
Para arrays, o numpy lida com números complexos nativamente:
import numpy as np
z = np.array([1+2j, 3+4j, 5+6j])
print(np.abs(z))
print(np.angle(z))
print(np.conj(z))
print(np.real(z))
print(np.imag(z))
signal = np.sin(2 * np.pi * 5 * np.linspace(0, 1, 128))
spectrum = np.fft.fft(signal)
freqs = np.fft.fftfreq(128, d=1/128)
Entregue
Execute code/complex_numbers.py para gerar outputs/skill-complex-arithmetic.md.
Exercícios
Aritmética complexa à mão. Compute (2 + 3i) * (4 - i) e verifique com o código. Depois compute (5 + 2i) / (1 - 3i). Desenhe ambos os resultados no plano complexo e confira que a multiplicação rotacionou e escalou o primeiro número.
Sequência de rotação. Comece com o ponto (1, 0). Multiplique por e^(i*pi/6) doze vezes. Verifique que você retorna a (1, 0) após 12 multiplicações. Imprima as coordenadas em cada passo e confirme que elas traçam um polígono regular de 12 lados.
DFT de um sinal conhecido. Crie um sinal que seja a soma de sin(2pi3t) e 0.5sin(2pi7*t) amostrado em 32 pontos. Execute sua DFT. Verifique que o espectro de magnitude tem picos nas frequências 3 e 7, com o pico em 7 sendo metade da altura do pico em 3.
Visualização das raízes da unidade. Compute as 8 raízes da unidade. Verifique que elas somam zero. Verifique que multiplicar qualquer raiz pela raiz primitiva e^(2pii/8) fornece a próxima raiz.
Equivalência com a matriz de rotação. Para 10 ângulos aleatórios e 10 pontos aleatórios, verifique que a multiplicação complexa fornece o mesmo resultado que a multiplicação matriz-vetor com a matriz de rotação 2x2. Imprima a diferença numérica máxima.
Termos-Chave
| Termo | O que significa |
|---|---|
| Número complexo | Um número a + bi, onde a é a parte real, b é a parte imaginária e i^2 = -1 |
| Unidade imaginária | O número i, definido por i^2 = -1. Não imaginário no sentido filosófico -- é um operador de rotação |
| Plano complexo | O plano 2D onde o eixo x é real e o eixo y é imaginário. Também chamado de plano de Argand |
| Magnitude (módulo) | A distância da origem: sqrt(a^2 + b^2). Escrita como |z| |
| Fase (argumento) | O ângulo a partir do eixo real positivo: atan2(b, a). Escrita como arg(z) |
| Conjugado | A imagem espelhada através do eixo real: o conjugado de a + bi é a - bi |
| Forma polar | Expressar z como r * e^(i*theta) em vez de a + bi. Torna a multiplicação fácil |
| Fórmula de Euler | e^(itheta) = cos(theta) + isin(theta). Conecta exponenciais à trigonometria |
| Fasor | Um número complexo rotativo e^(iomegat) representando um sinal senoidal |
| Raízes da unidade | Os N números complexos e^(2pii*k/N) para k = 0 até N-1. N pontos igualmente espaçados no círculo unitário |
| DFT | Transformada Discreta de Fourier. Decompõe um sinal em componentes senoidais complexos usando as raízes da unidade |
| RoPE | Rotary Position Embedding. Usa a multiplicação complexa para codificar a posição relativa na atenção dos transformers |
Leitura Complementar
- Visual Introduction to Euler's Formula - constrói intuição geométrica sem notação pesada
- Su et al.: RoFormer (2021) - o artigo que introduz o Rotary Position Embedding usando rotações complexas
- Vaswani et al.: Attention Is All You Need (2017) - o artigo original do Transformer com codificações posicionais senoidais
- 3Blue1Brown: Euler's formula with introductory group theory - explicação visual de por que e^(i*pi) = -1
- Needham: Visual Complex Analysis - o melhor tratamento visual de números complexos, repleto de insight geométrico
- Strang: Introduction to Linear Algebra, Ch. 10 - números complexos no contexto da álgebra linear e autovalores