Ir para o conteúdo

Gradiente Descendente & Regularização

A solução em forma fechada dos mínimos quadrados ordinários (OLS) é elegante, mas não escala para grandes quantidades de atributos, não existe para a maioria dos outros modelos e nada diz sobre controlar o sobreajuste. Esta aula acrescenta as duas ferramentas que fazem isso: o gradiente descendente — o motor de otimização por trás de quase todo o ML moderno — e a regularização — o freio padrão para a complexidade do modelo.

Gradiente descendente

A ideia é a que fecha a aula anterior. Para achar o mínimo de uma perda \(J(w)\), em vez de resolver \(\nabla J = 0\) no papel, desça a ladeira: meça a inclinação onde você está, dê um passo contra ela, repita.

\[ w^{(t+1)} = w^{(t)} - \eta \, \nabla_w J\big(w^{(t)}\big) \]

O \(\eta\) é a taxa de aprendizado, o tamanho do passo. O gradiente \(\nabla_w J\) aponta para onde a perda mais cresce, então é o sinal de menos que faz o método descer em vez de subir.

Falta uma coisa só para isso virar código: saber o que é \(\nabla_w J\). Ele é uma lista de derivadas parciais, uma para cada parâmetro, que vale a pena montar com as mãos antes de aceitar a forma matricial pronta.

De onde vem o gradiente

Comece pelo caso menor, a reta de dois parâmetros. Escreva a perda como média, para a escala não depender de \(n\):

\[ J(w_0, w_1) = \frac{1}{n}\sum_{i=1}^{n}\big(\underbrace{y_i - w_0 - w_1 x_i}_{e_i}\big)^2 \]

Cada parcela é o quadrado de um resíduo, \(e_i^2\). Pela regra da cadeia, a derivada de \(e_i^2\) em relação a qualquer parâmetro é \(2e_i\) vezes a derivada de \(e_i\) em relação a esse parâmetro. Ou seja: tudo se reduz a saber como o resíduo reage a cada peso. E isso é simples, porque \(e_i\) depende dos pesos de forma linear:

\[ \frac{\partial e_i}{\partial w_0} = -1, \qquad \frac{\partial e_i}{\partial w_1} = -x_i \]

O \(-1\) aparece porque subir o intercepto em uma unidade baixa todo resíduo em uma unidade. O \(-x_i\) aparece porque subir a inclinação em uma unidade baixa o resíduo em \(x_i\) — quanto mais longe o ponto está da origem, mais ele sente uma mudança de inclinação.

Substituindo:

\[ \frac{\partial J}{\partial w_0} = \frac{1}{n}\sum_i 2 e_i \cdot (-1) = -\frac{2}{n}\sum_i e_i \]
\[ \frac{\partial J}{\partial w_1} = \frac{1}{n}\sum_i 2 e_i \cdot (-x_i) = -\frac{2}{n}\sum_i e_i x_i \]

E assim por diante: o parâmetro \(j\)

Com \(d\) atributos o modelo é \(\hat{y}_i = w_0 + w_1x_{i1} + \dots + w_dx_{id}\). Adote a convenção \(x_{i0} = 1\) — a coluna de 1s que já está na matriz de projeto — e o intercepto deixa de ser caso especial: ele passa a ser só o peso de uma coluna constante. Com isso a derivada do resíduo tem sempre a mesma cara,

\[ \frac{\partial e_i}{\partial w_j} = -x_{ij} \]

e uma fórmula só cobre todos os parâmetros:

\[ \frac{\partial J}{\partial w_j} = -\frac{2}{n}\sum_{i=1}^{n} e_i\,x_{ij} \]
parâmetro coluna de \(X\) que lhe corresponde derivada parcial
\(w_0\) a coluna de 1s \(-\frac{2}{n}\sum_i e_i \cdot 1 = -\frac{2}{n}\sum_i e_i\)
\(w_1\) \(x_1\) \(-\frac{2}{n}\sum_i e_i x_{i1}\)
\(w_2\) \(x_2\) \(-\frac{2}{n}\sum_i e_i x_{i2}\)
\(w_j\) \(x_j\) \(-\frac{2}{n}\sum_i e_i x_{ij}\)

Repare que a primeira linha não é uma exceção à última: ela é a última com \(x_{i0}=1\). É o mesmo truque da coluna de 1s que transformou \(\hat{y} = w_0 + w_1x\) em \(\hat{y} = Xw\).

Empilhando tudo: a forma matricial

Agora junte essas \(d+1\) derivadas num vetor. A entrada \(j\) é \(\sum_i e_i x_{ij}\), que é o produto da coluna \(j\) de \(X\) pelo vetor de resíduos. Fazer isso para todas as colunas de uma vez é multiplicar por \(X^\top\):

\[ \nabla_w J = -\frac{2}{n}X^\top e = -\frac{2}{n}X^\top\big(y - Xw\big) \]

Não há mágica nessa fórmula: ela é a tabela acima, escrita de forma compacta. É por isso que uma linha de numpy calcula o gradiente de um modelo com um milhão de parâmetros — a estrutura é exatamente a de \(w_0\) e \(w_1\), repetida.

Duas leituras saem daí e as duas vão longe.

Primeira: iguale o gradiente a zero e você recupera as equações normais, \(X^\top(y - Xw) = 0\) — que é a solução em forma fechada da aula passada. A forma fechada e o gradiente são a mesma afirmação por dois caminhos: um resolve \(\nabla J = 0\) de uma vez, o outro caminha até lá.

Segunda: veja do que o gradiente é feito — uma soma ponderada pelos resíduos. O ponto que o modelo já acerta entra com \(e_i \approx 0\) e quase não vota; o ponto em que ele erra feio domina o passo. Então a regra \(w \leftarrow w - \eta\,\nabla J\) lê-se, em português claro: mova cada peso na direção para onde os erros apontam, numa quantidade proporcional ao tamanho desses erros. Essa frase continua verdadeira, sem trocar uma vírgula, quando o modelo for uma rede de 100 camadas e \(\nabla J\) vier da retropropagação.

Um passo, à mão

Três pontos — \((1,2), (2,4), (3,7)\) — partindo de \(w_0 = w_1 = 0\), com \(\eta = 0{,}1\).

Com os dois pesos zerados a reta prevê 0 em toda parte, então o resíduo é o próprio \(y\):

\[ e = (2,\ 4,\ 7), \qquad \sum_i e_i = 13, \qquad \sum_i e_i x_i = 2(1) + 4(2) + 7(3) = 31 \]

Jogue nas duas fórmulas que acabamos de deduzir:

\[ \frac{\partial J}{\partial w_0} = -\frac{2}{3}(13) = -8{,}667, \qquad \frac{\partial J}{\partial w_1} = -\frac{2}{3}(31) = -20{,}667 \]

As duas deram negativas, o que faz sentido: os dois pesos estão baixos demais e aumentar qualquer um reduz a perda. O passo, então, sobe com ambos:

\[ w_0 \leftarrow 0 - 0{,}1(-8{,}667) = 0{,}867, \qquad w_1 \leftarrow 0 - 0{,}1(-20{,}667) = 2{,}067 \]

Só nesse passo a perda cai de \(J = 23\) para \(J = 0{,}625\). Repetindo o procedimento:

iteração \(w_0\) \(w_1\) \(J\) \(\partial J/\partial w_0\) \(\partial J/\partial w_1\)
0 0,000 0,000 23,000 −8,667 −20,667
1 0,867 2,067 0,625 1,333 2,089
2 0,733 1,858 0,344 0,231 −0,394
3 0,710 1,897 0,327 0,343 −0,119

Leia a tabela devagar, porque ela mostra três coisas de uma vez.

O primeiro passo é enorme: a perda cai de 23 para 0,63, quase todo o trabalho. Depois o progresso se arrasta — 0,344, depois 0,327. Esse formato é a regra, não a exceção — é por isso que ver \(J\) cair rápido no início não diz quase nada sobre ter convergido.

Os gradientes trocam de sinal entre as iterações 1 e 2. Isso é o passo tendo ultrapassado o mínimo e sendo desandado — o mesmo vaivém que o simulador da taxa de aprendizado mostra adiante, aqui em escala pequena.

E os dois parâmetros não andam no mesmo ritmo: na iteração 0 a derivada em \(w_1\) é 2,4 vezes a de \(w_0\), porque cada resíduo entra na conta de \(w_1\) multiplicado por \(x_i\), que vale até 3. Guarde essa assimetria — ela é o assunto da paisagem logo abaixo.

Deixando rodar até 2000 iterações chega-se a \(w_0 = -0{,}667\) e \(w_1 = 2{,}5\), exatamente o que as equações normais dão — a mesma resposta, por um caminho mais lento.

A paisagem que você está descendo

Até aqui foram números numa tabela. Vale ver o que eles são geometricamente, porque a figura explica de uma vez tanto a lentidão quanto a cura.

Pense em \(J(w_0, w_1)\) como uma paisagem: cada ponto do plano é um par de pesos, ou seja, uma reta candidata, cuja altura naquele ponto é o erro que essa reta comete. Minimizar é procurar o fundo do vale. O gradiente é a direção de maior subida naquele ponto, então \(-\nabla J\) é a direção da descida mais íngreme.

No painel da esquerda estão as curvas de nível dessa paisagem, com o ótimo em forma fechada marcado. À direita, os mesmos pesos desenhados como uma reta sobre os dados. Clique à esquerda para soltar um palpite inicial e vá clicando em Passo:

Agora ligue centrar x e recomece de um ponto parecido. Mesmos dados, mesmo algoritmo, comportamento completamente diferente.

Sem centrar, as curvas de nível formam um vale longo e estreito. A razão é a mesma assimetria que você viu na tabela: \(w_0\) e \(w_1\) ficam acoplados, porque mexer na inclinação de uma reta cujos \(x\) estão todos longe de zero também levanta ou abaixa a altura dela. O gradiente aponta atravessado ao vale em vez de ao longo dele, então o caminho ziguezagueia e demora. Centrado, o vale vira uma tigela redonda e os mesmos passos vão quase direto à resposta.

É por isso que escalonar vem antes de ajustar

A forma fechada não liga a nada disso: \((X^\top X)^{-1}X^\top y\) devolve a mesma reta nos dois casos. A rota iterativa depende disso inteiramente. Essa é a ligação entre pré-processamento e otimização — padronizar atributos não é arrumação cosmética, é remodelar a superfície que o otimizador vai ter de percorrer.

A taxa de aprendizado

  • \(\eta\) pequeno demais → passos minúsculos, convergência dolorosamente lenta;
  • \(\eta\) grande demais → os passos ultrapassam o mínimo; a perda oscila ou diverge;
  • receita prática: experimente \(\eta \in \{10^{-3}, 10^{-2}, 10^{-1}\}\), monitore a curva de perda de treino — ela deve cair suavemente.

Sinta você mesmo. Na perda abaixo o limiar fica exatamente em \(\eta = 1\), que põe a história inteira num lugar só: em \(\eta = 0{,}5\) o passo acerta o mínimo de uma vez; abaixo disso ele se aproxima aos poucos; entre 0,5 e 1 ele passa do ponto mas ainda fecha, pulando de um lado para o outro; em \(\eta = 1\) exatamente ele fica batendo entre os mesmos dois pontos para sempre, sem nunca chegar mais perto; acima de 1 ele explode. Defina \(\eta = 1{,}05\) e avance passo a passo:

Batch, estocástico e mini-batch

Variante Gradiente calculado sobre Custo por passo Comportamento
Batch GD o conjunto de dados completo \(O(nd)\) descida exata e suave
GD estocástico (SGD) uma amostra aleatória \(O(d)\) ruidoso mas barato; escapa de armadilhas rasas
Mini-batch GD um lote de ~32–512 intermediário o padrão moderno (vetoriza bem)
from sklearn.linear_model import SGDRegressor
model = SGDRegressor(loss='squared_error', penalty='l2', alpha=1e-4,
                     learning_rate='invscaling', max_iter=1000)

O mesmo laço — com perdas diferentes — treina a regressão logística, as SVMs e as redes neurais. Aprenda-o uma vez, reutilize em todo lugar.

De retas a curvas: atributos polinomiais

A regressão linear é linear nos parâmetros, não necessariamente nas entradas. Expandir os atributos para potências, \(x \mapsto (x, x^2, \dots, x^p)\), ajusta polinômios com a mesma maquinaria do OLS:

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures

model = make_pipeline(PolynomialFeatures(degree=3), LinearRegression())

Mas a flexibilidade corta dos dois lados:

Polinômio com subajuste, sobreajuste e regularização Ridge

O grau 1 subajusta — rígido demais para seguir o seno. O grau 15 sobreajusta — 16 parâmetros perseguem 25 pontos ruidosos, produzindo oscilações selvagens. O painel da direita mantém todos os 16 parâmetros mas adiciona uma penalidade Ridge: a curva relaxa de volta ao sinal. Isso é a regularização em ação.

Subajuste e sobreajuste, medidos

A figura convence o olho. Vale medir, porque o número mostra uma coisa que o desenho esconde.

Os dados são 100 pontos de \(y = 0{,}5x^2 + x + 2\) com ruído de desvio 3, para \(x\) entre \(-3\) e \(3\). Três modelos: grau 1, grau 2 — o grau certo — e grau 30. Para cada um, o erro no próprio treino ao lado do erro em validação cruzada de cinco dobras:

grau RMSE de treino RMSE de validação (média) desvio entre as dobras
1 3,22 3,26 0,19
2 2,64 2,75 0,33
30 2,46 10,43 13,46

O grau 30 tem o menor erro de treino dos três. Quem escolhesse o modelo por essa coluna escolheria justamente o pior.

A coluna mais instrutiva, porém, é a última. Veja as cinco dobras do grau 30 uma a uma:

\[ 3{,}14 \qquad 3{,}02 \qquad 5{,}51 \qquad \mathbf{37{,}29} \qquad 3{,}20 \]

Em três das cinco ele vai tão bem quanto o grau 2. Em uma delas, explode. Sobreajuste não é errar sempre — é errar de forma imprevisível — é por isso que a variação entre dobras denuncia o problema antes da média denunciar.

Daí sai um diagnóstico de bolso:

simples demais (subajuste) complexo demais (sobreajuste)
erro de treino alto baixo
erro de validação alto, de forma consistente alto na média, às vezes baixo
variação entre dobras pequena grande
o que fazer mais capacidade, atributos melhores regularizar, mais dados, menos capacidade

Existe também um piso. O ruído tem desvio 3 e essa parcela do \(y\) não depende de \(x\), então nenhum modelo consegue removê-la. O grau 2 chega perto desse piso, que é tudo o que se pode pedir de um modelo.

"Complexo demais" é relativo à quantidade de dados

Complexidade não é propriedade só do modelo: ela é a relação entre o número de parâmetros e o número de pontos.

Um polinômio de grau 10 tem 11 parâmetros livres — dez potências mais o intercepto. Enquanto os pontos de treino não passarem disso, a curva consegue atravessar exatamente todos eles:

pontos de treino 5 8 10 11 15 30
RMSE de treino 3·10⁻¹⁵ 1·10⁻¹¹ 1·10⁻¹¹ 0,40 0,77 1,53

Erro de treino zero não é sinal de sucesso: é sinal de que não sobrou informação. Com dez pontos e onze parâmetros, o modelo não precisou aprender coisa alguma sobre a forma da curva — bastou memorizar. A partir do décimo primeiro ponto ele passa a ter de escolher e o erro de treino sai do chão.

Repare no que isso implica: o mesmo modelo de grau 10 é complexo demais para 10 pontos e perfeitamente razoável para 100. Não há um grau "certo" em abstrato, só um grau certo para um volume de dados. A ferramenta que torna essa dependência visível é a curva de aprendizado — erro de treino e de validação em função do tamanho do conjunto —, tratada em seleção de modelos.

Regularização

Em vez de restringir o número de parâmetros, penalize sua magnitude — adicione um termo de complexidade à perda:

Ridge (L2) — Tikhonov, 1943; Hoerl & Kennard, 1970

\[ J(w) = \lVert y - Xw \rVert^2 + \alpha \sum_{j=1}^{d} w_j^2 \]
  • encolhe todos os coeficientes suavemente em direção a zero (nunca exatamente zero);
  • distribui o peso entre atributos correlacionados — a cura padrão para a multicolinearidade;
  • a forma fechada ainda existe: \(\hat{w} = (X^\top X + \alpha I)^{-1} X^\top y\) — o \(\alpha I\) torna a matriz inversível mesmo com atributos colineares.

Lasso (L1) — Tibshirani, 1996

\[ J(w) = \lVert y - Xw \rVert^2 + \alpha \sum_{j=1}^{d} \lvert w_j \rvert \]
  • a penalidade de valor absoluto tem quinas em zero: as soluções pousam exatamente em zero para atributos fracos;
  • realiza seleção automática de atributos — os coeficientes não nulos que sobrevivem nomeiam os atributos que importam;
  • entre um grupo de atributos altamente correlacionados, tende a manter um arbitrariamente e zerar o resto.

O Elastic Net mistura ambas as penalidades (l1_ratio) — um padrão robusto quando os atributos são muitos e correlacionados.

from sklearn.linear_model import Ridge, Lasso, ElasticNet

Ridge(alpha=1.0)
Lasso(alpha=0.1)
ElasticNet(alpha=0.1, l1_ratio=0.5)

Por que o L1 zera e o L2 não

As duas listas acima afirmam a diferença. A razão é geométrica; vale ver em vez de acreditar.

Escreva a penalidade como um orçamento: minimize o erro quadrático sujeito a \(\sum |w_j| \le t\) (lasso) ou \(\sum w_j^2 \le t^2\) (ridge). As duas formulações são equivalentes — todo \(\alpha\) tem um \(t\) correspondente. Agora a figura tem dois objetos: as curvas de nível elípticas do erro, centradas na solução OLS, mais a região viável.

A solução restrita é onde a elipse, crescendo a partir do ótimo OLS, toca primeiro a região:

O círculo não tem cantos, então o ponto de contato pode ficar em qualquer lugar dele — e cai sobre um eixo apenas por coincidência. O losango tem quatro cantos, que ficam exatamente sobre os eixos. Uma curva suave que se expande em direção a uma região pontuda tende a encontrar uma ponta primeiro — e encontrar um canto é um coeficiente ser exatamente zero.

Arraste o ótimo OLS e veja com que frequência o losango é tocado num canto enquanto o círculo quase nunca é. O lasso não seleciona atributos por limiar — seleciona por causa do formato da restrição dele.

O caso unidimensional, em forma fechada

Com um único atributo padronizado as duas penalidades têm solução explícita — e ambas dizem a mesma coisa em álgebra:

\[ \hat{w}^{\text{ridge}} = \frac{\hat{w}}{1+\alpha}, \qquad \hat{w}^{\text{lasso}} = \operatorname{sign}(\hat{w})\,\max\!\left(|\hat{w}| - \tfrac{\alpha}{2},\ 0\right) \]

Com \(\alpha = 1\):

\(\hat{w}\) (OLS) ridge lasso
2,00 1,000 1,500
1,00 0,500 0,500
0,60 0,300 0,100
0,50 0,250 0
0,20 0,100 0

O ridge divide; divisão nunca chega a zero. O lasso subtrai uma quantidade fixa e corta em zero — então todo coeficiente abaixo do limiar \(\alpha/2\) não encolhe, desaparece. Essa subtração é o canto do losango, escrito como fórmula.

Acompanhando o caminho

Rode a penalidade ao longo de uma faixa de \(\alpha\) e plote todos os coeficientes. São oito atributos: três carregam sinal real, três são puro ruído, dois são quase cópias um do outro.

α lasso: não nulos ridge: não nulos
0,05 8 / 8 8 / 8
0,2 7 / 8 8 / 8
0,6 6 / 8 8 / 8
2,0 5 / 8 8 / 8

O lasso remove primeiro os atributos de ruído — exatamente a seleção que se esperava — e a contagem segue caindo. O ridge nunca cai abaixo de oito em nenhum \(\alpha\): os coeficientes de ruído ficam pequenos e permanecem.

Passe então ao par correlacionado \(c_1, c_2\), cujos coeficientes verdadeiros são ambos 2. Em \(\alpha = 2\) o lasso reporta (1,89; 0,98) — está começando a escolher um e descartar o outro; qual deles é quase arbitrário. O ridge reporta (1,02; 0,97): divide o peso por igual. É essa a razão inteira de o ridge ser a resposta padrão à multicolinearidade e o lasso não.

Por que o ridge também conserta a numérica

Tome duas colunas quase idênticas. Então \(X^\top X\) fica quase singular — num exemplo desses o número de condição é \(7{,}2\times10^{7}\) — e o OLS devolve \((1{,}000; -0{,}000)\): uma divisão arbitrária do crédito. Somar \(\alpha = 0{,}01\) à diagonal derruba o condicionamento para \(6\times10^{3}\) e a resposta para \((0{,}500; 0{,}500)\), com a soma preservada.

Três benefícios — coeficientes estáveis, matriz invertível, convergência mais rápida do gradiente — não são três mecanismos separados. São o \(\alpha I\) elevando todos os autovalores, visto de três lados.

O botão α

O \(\alpha\) troca fidelidade aos dados por tamanho dos coeficientes:

  • \(\alpha \to 0\): OLS puro (sem freio);
  • \(\alpha \to \infty\): todos os coeficientes esmagados para ~0, o modelo prevê a média (freio total);
  • o \(\alpha\) certo não é conhecido de antemão — é escolhido por validação cruzada (RidgeCV, LassoCV ou uma busca em grade).

Escalone antes de regularizar — e não penalize o intercepto

A penalidade \(\sum w_j^2\) compara coeficientes entre atributos, o que só é justo se os atributos compartilharem uma escala: caso contrário, um atributo medido em quilômetros é penalizado de forma diferente do mesmo em metros. Padronize primeiro (em um Pipeline). Por convenção, o intercepto \(w_0\) é excluído da penalidade — o scikit-learn faz isso por você.

Material de aula

Handout

Gradiente descendente, na prática — notebook no Colab para rodar em sala: uma ladeira de uma variável, a taxa de aprendizado forçada até quebrar, os três pontos de um passo, à mão conferidos número a número, a paisagem da perda com e sem centrar, 20 mil casas da Califórnia com e sem padronizar e mini-batch. Listado na página de handouts.

Notebook da aula (em português)

Notebook prático usado em sala — Aula 12 — Regressão Linear: abrir no Colab


Referências

  • Tikhonov, A. N. "On the stability of inverse problems." C. R. (Doklady) Acad. Sci. URSS, n. Ser. 39 (1943), 176–179. registro zbMATH
  • Robbins, H.; Monro, S. "A Stochastic Approximation Method." Annals of Math. Statistics 22 (1951). DOI
  • Hoerl, A. E.; Kennard, R. W. "Ridge Regression: Biased Estimation for Nonorthogonal Problems." Technometrics 12 (1970). DOI
  • Tibshirani, R. "Regression Shrinkage and Selection via the Lasso." JRSS B 58 (1996). DOI

Bibliografia completa do curso na página de referências.


Quiz