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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).