Cálculo, IA e álgebra linear: um guia de campo compacto

🇺🇸 Read in English
Índice
Para quem é este artigo

Você escreve ou revisa código de ML e quer uma revisão rápida, com foco em código, sobre o cálculo e a álgebra linear por trás de gradientes, Jacobianos e SVD.

A maior parte do código de ML é apenas cálculo e álgebra linear disfarçados. Aqui vai uma revisão concisa com trechos executáveis.

Gradientes à vista

Um gradiente é o vetor de derivadas parciais. Para uma função escalar f(x,y)f(x, y):

f=[fx,fy]\nabla f = \left[\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right]

Exemplo: f(x,y)=x2+xy+3y2f(x, y) = x^2 + xy + 3y^2 resulta em f=[2x+y, x+6y]\nabla f = [2x + y,\ x + 6y].

import numpy as np

def f(xy):
    x, y = xy
    return x**2 + x*y + 3*y**2

# analytic gradient
def grad(xy):
    x, y = xy
    return np.array([2*x + y, x + 6*y])

pt = np.array([2.0, -1.0])
print("f:", f(pt))
print("grad:", grad(pt))

Diferenças finitas são uma verificação rápida de sanidade:

def finite_diff(fn, pt, eps=1e-5):
    g = np.zeros_like(pt)
    for i in range(len(pt)):
        step = np.zeros_like(pt)
        step[i] = eps
        g[i] = (fn(pt + step) - fn(pt - step)) / (2 * eps)
    return g

print("finite diff:", finite_diff(f, pt))

Jacobianos: saídas vetoriais

Para g:RnRmg: \mathbb{R}^n \to \mathbb{R}^m, o Jacobiano empilha os gradientes de cada componente de saída. Uma função simples com duas saídas:

g(x,y)=[x2+yxy]g(x, y) = \begin{bmatrix} x^2 + y \\ xy \end{bmatrix}

Seu Jacobiano é:

J=[2x1yx]J = \begin{bmatrix} 2x & 1 \\ y & x \end{bmatrix}
def g(xy):
    x, y = xy
    return np.array([x**2 + y, x*y])

def jacobian(xy):
    x, y = xy
    return np.array([[2*x, 1], [y, x]])

pt = np.array([1.5, 0.5])
print("g(pt):", g(pt))
print("J(pt):\n", jacobian(pt))

Combustível de álgebra linear: projeções e SVD

A análise de componentes principais (PCA) é apenas a decomposição em valores singulares (SVD): X=UΣVTX = U\Sigma V^T. Os vetores singulares à direita mais importantes em VV são as direções principais.

rng = np.random.default_rng(7)
X = rng.normal(size=(6, 3))  # 6 samples, 3 features

# center
Xc = X - X.mean(axis=0, keepdims=True)

# SVD
U, S, Vt = np.linalg.svd(Xc, full_matrices=False)

print("singular values:", S)
print("first principal direction:", Vt[0])

# project to 2D
X2 = Xc @ Vt[:2].T
print("projected shape:", X2.shape)

A projeção de um vetor vv sobre uma direção uu é:

proju(v)=vuu2u\text{proj}_u(v) = \frac{v \cdot u}{\lVert u \rVert^2} u
v = np.array([2.0, 1.0, -1.0])
u = Vt[0]  # principal direction
proj = (v @ u) / (u @ u) * u
print("projection:", proj)
graph LR;
    Data["High-dimensional data X"] --> Center["Center columns"];
    Center --> SVD["SVD: X = U Σ Vᵀ"];
    SVD --> PCs["Take top k rows of Vᵀ (principal directions)"];
    PCs --> Project["Project: X · V_kᵀ"];
    Project --> Embeddings["Lower-dimensional embeddings"];

Por que isso importa para IA

  • Gradientes impulsionam otimizadores (SGD, Adam); Jacobianos sustentam a retropropagação.
  • SVD/PCA reduz dimensionalidade e remove ruído de embeddings.
  • Projeções ajudam em busca por recuperação e similaridade ao isolar eixos informativos.

Se você mantiver esses primitivos afiados, a maior parte do código de modelos se torna mais fácil de entender e depurar.