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