Lab Opcional: a Função de Custo
No post passado você achou e brincando com o slider até o erro zerar. Beleza, funcionou. Mas para. Como você sabia que tinha achado o "melhor" ajuste? Você olhou pro número de erro total caindo até zero e confiou nele.
Esse post é sobre dar nome, sobrenome e fórmula pra esse número. Ele se chama , escrita .
Eu sei, parece assustador. Vamos abrir essa fórmula peça por peça, tipo desmontando um Lego.
Cuidado com a palavra "custo"
Antes de mais nada: aqui custo não tem nada a ver com preço de casa. É a medida de quão errado o modelo está. Pra preço da casa a gente continua usando a palavra preço mesmo. Essa confusão de vocabulário pega muita gente no começo, porque "custo" no dia a dia é sempre dinheiro.
Desmontando a fórmula
Pensa numa prova de tiro ao alvo. Cada casa do seu conjunto de treino é uma tentativa de acertar o centro do alvo (o preço real, ). é onde a sua flecha caiu (a previsão do modelo). A distância entre onde a flecha caiu e o centro é o daquele tiro específico.
| Pedaço da fórmula | Nome | O que faz |
|---|---|---|
| erro (resíduo) | a distância entre a previsão e o real | |
| erro ao quadrado | tira o sinal (errar pra cima ou pra baixo pesa igual) e pune erro grande com força bem maior | |
| soma | junta o erro de todo mundo | |
| média (com o 2 extra) | dividir por vira média (senão o custo só cresceria por ter mais dados, mesmo que o ajuste seja igualmente bom), e o 2 é só um ajuste de conta pro próximo post ficar mais limpo, explico melhor lá |
Duas coisas saem direto dessa fórmula, e eu nem precisei decorar nada:
- nunca é negativo. É soma de coisas ao quadrado, então o menor valor possível é zero.
- significa ajuste perfeito. A reta passa exatamente em cima de todos os pontos.
E por que elevar ao quadrado em vez de, sei lá, pegar o módulo do erro? Porque o quadrado castiga desproporcionalmente os erros grandes. Errar o dobro custa quatro vezes mais caro em , não o dobro. Isso faz o modelo detestar previsões muito ruins de um jeito que o módulo não faria, e é justamente esse comportamento que dá pro custo o formato de U que a gente vai ver daqui a pouco. Voltando pro tiro ao alvo: é como se o juiz da competição desse nota exponencialmente pior quanto mais longe do centro a flecha cair. Errar por pouco dói pouco, errar feio dói muito mais que o dobro.
Colocando isso em código
def compute_cost(x, y, w, b):
"""
Calcula a função de custo da regressão linear.
Args:
x (ndarray (m,)) : dados de entrada, m exemplos
y (ndarray (m,)) : valores-alvo
w, b (scalar) : parâmetros do modelo
Returns:
total_cost (float): o custo de usar w e b como parâmetros para ajustar
os pontos (x, y)
"""
m = x.shape[0]
cost_sum = 0
for i in range(m):
f_wb = w * x[i] + b # previsão do modelo pro exemplo i
cost = (f_wb - y[i]) ** 2 # erro do exemplo i, ao quadrado
cost_sum = cost_sum + cost # acumula no total
total_cost = (1 / (2 * m)) * cost_sum
return total_cost
Repara que é o mesmo laço for do post passado (o compute_model_output), só que em vez de guardar as previsões, a gente acumula o erro ao quadrado de cada uma. Estrutura idêntica, propósito diferente.
Congelando um parâmetro pra enxergar o outro
Com dois parâmetros pra mexer ( e ), eu tive dificuldade de visualizar o custo direto. Truque clássico: trava um deles e olha só pro outro. Fixa e mexe só em .
Arrasta o slider abaixo e olha duas coisas ao mesmo tempo: como a reta (painel esquerdo) se aproxima ou se afasta dos pontos, e onde o ponto vermelho fica na curva (painel direito). Conforme a reta melhora, o ponto desce na curva.
Repara três coisas:
- O custo é mínimo exatamente em , o mesmo valor que você achou no olho no post passado. Com , o custo nesse ponto vai a zero, porque a reta passa nos dois pontos.
- O custo explode rápido quando fica muito grande ou muito pequeno. É o quadrado da fórmula fazendo esse trabalho.
- Aí eu saquei: minimizar o custo é o mesmo que achar o melhor ajuste. Não é coincidência, é a definição. Escolher e que minimizam é literalmente o que a gente chama de treinar um modelo.
Experimenta levar o slider até : a reta fica horizontal (prevê o mesmo preço pra qualquer tamanho de casa) e o custo dispara.
O mundo real não tem só 2 pontos perfeitos
Até aqui, com 2 pontos e 2 parâmetros, eu conseguia zerar o custo. Isso é raro. Bora trocar o conjunto de treino por um mais realista, com 6 casas, onde os preços não caem exatamente sobre reta nenhuma (ruído de verdade, tipo casas parecidas vendendo por preços um pouco diferentes):
| Tamanho (1000 pés²) | Preço (1000 dólares) |
|---|---|
| 1.0 | 250 |
| 1.7 | 300 |
| 2.0 | 480 |
| 2.5 | 430 |
| 3.0 | 630 |
| 3.2 | 730 |
Pergunta que fica no ar: será que ainda existe algum que zere o custo aqui?
Mexe nos dois sliders abaixo (agora e ao mesmo tempo) e tenta chegar no centro do mapa de calor, onde o custo é mais baixo. Alterna pra visão 3D e gira a superfície com o dedo ou o mouse pra sentir o formato do "vale".
custo J(w,b) = 11862.5
Você deve ter percebido: dessa vez o custo não zera. O melhor que dá pra conseguir fica perto de e , com custo em torno de 1736. Por quê? Porque essas 6 casas não estão alinhadas. Nenhuma reta única passa exatamente por todas. O melhor ajuste é aquele que deixa o menor erro quadrático total possível, e é isso que a função de custo define como "o melhor".
Guarda essa ideia: custo mínimo diferente de zero é o normal, não um bug. Custo zero costuma acontecer quando você tem poucos dados demais (como no nosso caso de 2 pontos) ou quando o modelo decorou os dados em vez de aprender o padrão deles (isso tem nome, , mas isso é assunto pra mais pra frente no curso).
Por que a superfície é sempre uma tigela
Girando a superfície 3D acima você deve ter notado: ela sempre tem essa cara de tigela de sopa, um único vale, sem picos falsos no meio do caminho. Isso não é coincidência do nosso exemplo específico, é consequência direta de elevar o erro ao quadrado na fórmula. Toda vez que a gente eleva algo ao quadrado, o resultado é uma superfície .
Pra ver isso de um jeito mais limpo, sem a distorção de escala que os dados reais trazem (repara que vai de 0 a 400 e de -200 a 200 no gráfico acima, o que estica o vale), aqui vai a versão idealizada da mesma forma, só , com os dois eixos na mesma escala:
Por que essa forma importa tanto? Porque uma superfície convexa garante que só existe um mínimo, o mínimo global. Não tem vale escondido em outro canto pra um algoritmo de busca cair e ficar preso, achando que já chegou no fundo sem ter chegado. Essa garantia é o que torna confiável o próximo passo do curso: um jeito automático de descer até o fundo dessa tigela, sem você precisar arrastar slider a vida inteira.
Fechando
| Conceito | O que ficou estabelecido |
|---|---|
| Função de custo | , um número que mede o quanto as previsões erram os dados de treino |
| Por que ao quadrado | remove o sinal do erro e pune fortemente erros grandes, além de garantir uma superfície convexa |
| Por que | transforma soma em média, e o 2 simplifica a conta que vem no próximo post |
| Formato | curva em U (um parâmetro) e tigela de sopa (dois parâmetros) |
| Treinar o modelo | é achar o par que minimiza |
| Custo mínimo diferente de zero | normal quando os dados têm ruído, não é sinal de erro |
No próximo post: você já sentiu na mão como é procurar o fundo da tigela arrastando slider. Não escala. O próximo post apresenta o gradiente descendente, um algoritmo que usa a inclinação da superfície de custo pra descer até o fundo sozinho, sem você precisar chutar valor nenhum.
Aplicação prática
Mesmo dataset real de imóveis do post anterior (500 casas, Housing Prices Regression, Kaggle). Agora com compute_cost de verdade.
def compute_cost(x, y, w, b):
m = x.shape[0]
cost_sum = 0
for i in range(m):
f_wb = w * x[i] + b
cost_sum += (f_wb - y[i]) ** 2
return (1 / (2 * m)) * cost_sum
# x_sqft já em "centenas de pés²", y_price já em "milhares de dólares"
# (mesma amostra de 50 casas do post anterior)
print("custo com um chute ruim, w=50, b=100:", compute_cost(x_sqft, y_price, 50, 100))
print("custo perto do ajuste ótimo, w=116, b=399:", compute_cost(x_sqft, y_price, 116, 399))
Saída:
custo com um chute ruim, w=50, b=100: 90317.6/custo perto do ajuste ótimo, w=116, b=399: 5189.6
Quase 20 vezes menos custo só de acertar melhor os parâmetros. Mexe você mesmo nos sliders e tenta chegar perto desses ~5189 (repara: não dá pra zerar, o mínimo real é isso mesmo, dado real tem ruído):
Carregando dados reais...