githublinkedinemail
← /articles

Gradient descent explicado visualmente

Função de custo é uma superfície. Derivada é inclinação. θ = θ − α·∇J(θ). Toda rede neural que você já ouviu falar é essa regra, repetida.

5 minml

Gradient descent explicado visualmente

Todo modelo de machine learning que você usou na vida treinou com isso.

Regressão linear, regressão logística, rede neural, transformer.

Tudo a mesma regra, repetida bilhões de vezes:

θ = θ - α · ∇J(θ)

Se você não enxerga essa fórmula como geometria, você não entende ML. Só decorou.


A função de custo é uma superfície

Imagine que seu modelo tem dois parâmetros: θ₀ e θ₁.

Pra cada par (θ₀, θ₁) existe um erro: J(θ₀, θ₁).

Plotando os três eixos, você tem uma superfície:

        J(θ)
         │
         │       ╱╲
         │      ╱  ╲___
         │     ╱       ╲___
         │    ╱             ╲___
         │___╱                  ╲___
         └─────────────────────────── θ

Treinar = descer a colina até o ponto mais baixo.

Esse ponto mais baixo é onde o erro é mínimo. É onde o modelo "aprendeu".

Não tem mistério. É geometria.


A derivada é a inclinação

Pega um ponto qualquer da superfície.

A derivada nesse ponto diz pra que lado a colina sobe.

J(θ)
 │
 │    •  ← você está aqui
 │   ╱
 │  ╱   derivada > 0  → sobe pra direita
 │ ╱                  → então desce indo pra esquerda
 │╱
 └────────────────── θ

Se a derivada é positiva, você está numa subida olhando pra direita.

Pra descer, vai pra esquerda. Ou seja: subtrai da posição atual.

Isso é tudo o que a regra de update diz:

θ_novo = θ_atual - α · derivada

A derivada aponta pra cima. Você anda no sentido contrário. Você desce.


A taxa de aprendizado (α)

α é o tamanho do passo.

Não é detalhe. É o parâmetro mais sensível de todo o algoritmo.

α muito grande — você passa direto

J(θ)
 │  •
 │ ╱ ╲       •
 │╱   ╲     ╱
 │     ╲   ╱   •
 │      ╲ ╱   ╱
 │       •───╱
 └──────────────── θ

Zigue-zague. Você pula o vale. Em casos piores, o erro até cresce.

α muito pequeno — você rasteja

J(θ)
 │•
 │ •
 │  •
 │   •
 │    •
 │     •     (1000 épocas depois ainda no mesmo lugar)
 └──────────────── θ

Convergência infinita. Modelo nunca treina de verdade.

α na medida — desce bonito

J(θ)
 │•
 │  •
 │     •
 │        •
 │           •
 │              •──•──•
 └──────────────────────── θ

Decai rápido, estabiliza, pronto.

Achar esse α é metade do trabalho na prática.


Convexo importa

Olha essas duas superfícies:

Convexa                      Não-convexa
                            
   ╲           ╱             ╲    ╱╲   ╱
    ╲         ╱               ╲  ╱  ╲ ╱
     ╲       ╱                 ╲╱    •
      ╲     ╱                   •
       ╲   ╱                   (mínimo local)
        ╲ ╱                
         •                  
     (mínimo global)        

Regressão linear com MSE → convexa. Tem um mínimo. Gradient descent sempre acha.

Rede neural → não-convexa. Tem mínimos locais, platôs, vales estreitos. Gradient descent acha um mínimo. Não necessariamente o melhor.

É por isso que treinar rede neural é arte. E treinar regressão linear é receita.


Batch, SGD, mini-batch

Mesma regra. Muda o que você usa pra calcular o gradiente.

Batch — usa o dataset inteiro por update

épocas
 1   ████████████████  → calcula ∇J → 1 passo
 2   ████████████████  → calcula ∇J → 1 passo
 3   ████████████████  → calcula ∇J → 1 passo

Caro. Lento. Mas a direção é exata.

SGD — um exemplo por update

 1   █  → ∇ → passo
 1   █  → ∇ → passo
 1   █  → ∇ → passo
 ...

Rápido. Barulhento. Zigue-zaga, mas chega.

Mini-batch — o meio termo (o que todo mundo usa)

 1   ████  → ∇ → passo
 1   ████  → ∇ → passo
 1   ████  → ∇ → passo

Direção razoável, custo razoável. Vence.

O ruído do SGD inclusive ajuda em superfícies não-convexas — escapa de mínimo local.

Defeito virou feature.


Implementando do zero em Ruby

Regressão linear. y = w·x + b. Sem gem. Array puro.

# dados: y ≈ 2x + 1 com um pouco de ruído
xs = [1.0, 2.0, 3.0, 4.0, 5.0]
ys = [3.1, 4.9, 7.2, 9.0, 11.1]

w = 0.0
b = 0.0
alpha = 0.01
epochs = 1000
n = xs.length.to_f

epochs.times do |epoch|
  # forward: previsão
  preds = xs.map { |x| w * x + b }

  # erros
  errors = preds.zip(ys).map { |p, y| p - y }

  # gradientes do MSE
  #   ∂J/∂w = (2/n) Σ (pred - y) · x
  #   ∂J/∂b = (2/n) Σ (pred - y)
  dw = (2.0 / n) * errors.zip(xs).sum { |e, x| e * x }
  db = (2.0 / n) * errors.sum

  # update — A REGRA
  w -= alpha * dw
  b -= alpha * db

  if (epoch % 100).zero?
    mse = errors.sum { |e| e * e } / n
    puts "epoch=#{epoch} w=#{w.round(4)} b=#{b.round(4)} mse=#{mse.round(4)}"
  end
end

puts "final: y = #{w.round(3)} x + #{b.round(3)}"

Saída aproximada:

epoch=0   w=0.4140 b=0.1420 mse=58.4140
epoch=100 w=2.1234 b=0.5012 mse=0.0421
epoch=900 w=2.0231 b=0.9132 mse=0.0098
final: y = 2.020 x + 0.928

Recuperou w ≈ 2, b ≈ 1. Como esperado.

Sem TensorFlow. Sem PyTorch. Sem sklearn.

Só a regra, repetida 1000 vezes.


O que muda quando vira rede neural

Nada muda na regra. Muda só o que é ∇J(θ).

  • regressão linear: derivada do MSE em relação a w e b. Conta fechada.
  • rede neural: derivada do erro em relação a milhões de pesos. Conta fechada também — só que via backpropagation, que é regra da cadeia aplicada em camadas.

A cabeça do algoritmo é sempre:

1. forward pass — calcula previsão
2. calcula erro
3. backward pass — calcula gradiente
4. update — θ = θ - α · ∇J(θ)
5. repete

Cinco passos. Toda rede neural do planeta.


Senior vs junior

  • júnior decora model.fit() e acha que aprendeu ML
  • sênior sabe que fit() é só um loop chamando a regra acima

Quando o modelo não converge, o júnior troca de framework.

O sênior olha pro learning rate. Pro batch size. Pra escala dos dados. Pra inicialização dos pesos.

Porque ele sabe exatamente o que está acontecendo na descida.


Conclusão

Toda rede neural que você já ouviu falar — GPT, ResNet, AlphaGo — é a mesma fórmula:

θ = θ - α · ∇J(θ)

Repetida.

Bilhões de vezes.

Em bilhões de parâmetros.

Com gradientes calculados via backprop.

Mais nada.

O resto é engenharia: como calcular o gradiente rápido, como evitar mínimos ruins, como não estourar memória.

Mas o coração é uma colina e alguém descendo ela um passo de cada vez.

Se você entendeu isso, você entendeu ML.

Gradient descent explicado visualmente
Gradient descent explicado visualmente