Regularização: Deixa Todas as Features, Só Não Deixa Nenhuma Mandar Demais
O lab passado fechou com três saídas pro overfitting: mais dados, menos features, ou regularização. As duas primeiras têm um problema: mais dados nem sempre existe, e cortar features acaba jogando fora informação que podia ser útil. Regularização resolve diferente: mantém todas as features, só desencoraja qualquer peso de crescer demais. O b fica de fora dessa penalidade, só os w_j sentem o freio.
Custo com regularização
Pra regressão linear, o termo novo é só uma soma dos pesos ao quadrado, multiplicada por lambda e dividida por 2m:
def compute_cost_linear_reg(X, y, w, b, lambda_ = 1):
m = X.shape[0]
n = len(w)
cost = 0.
for i in range(m):
f_wb_i = np.dot(X[i], w) + b
cost = cost + (f_wb_i - y[i])**2
cost = cost / (2 * m)
reg_cost = 0
for j in range(n):
reg_cost += (w[j]**2)
reg_cost = (lambda_/(2*m)) * reg_cost
return cost + reg_cost
A primeira metade da função é o custo de sempre, sem mudar nada. A segunda metade (reg_cost) só soma o quadrado de cada peso, multiplica por lambda_/(2m), e soma no final. Testando com dados aleatórios fixos (np.random.seed(1), seis features, lambda=0.7):
Saída:
Regularized cost: 0.07917239320214275
Pra regressão logística, a mudança é idêntica, o termo de regularização é copiado e colado:
def compute_cost_logistic_reg(X, y, w, b, lambda_ = 1):
m, n = X.shape
cost = 0.
for i in range(m):
z_i = np.dot(X[i], w) + b
f_wb_i = sigmoid(z_i)
cost += -y[i]*np.log(f_wb_i) - (1-y[i])*np.log(1-f_wb_i)
cost = cost/m
reg_cost = 0
for j in range(n):
reg_cost += (w[j]**2)
reg_cost = (lambda_/(2*m)) * reg_cost
return cost + reg_cost
O custo de sempre na primeira metade, o mesmo reg_cost de cima colado na segunda. Testando com os mesmos dados aleatórios:
Saída:
Regularized cost: 0.6850849138741673
Gradiente com regularização
O gradiente também ganha um termo extra, só em cima de w_j (o b continua sem penalidade nenhuma):
O termo (lambda/m) * w_j é a única diferença entre esse gradiente e o de sempre, pra regressão linear ou logística, a única coisa que muda entre as duas é como f é calculado:
def compute_gradient_logistic_reg(X, y, w, b, lambda_):
m, n = X.shape
dj_dw = np.zeros((n,))
dj_db = 0.0
for i in range(m):
f_wb_i = sigmoid(np.dot(X[i], w) + b)
err_i = f_wb_i - y[i]
for j in range(n):
dj_dw[j] = dj_dw[j] + err_i * X[i, j]
dj_db = dj_db + err_i
dj_dw = dj_dw / m
dj_db = dj_db / m
for j in range(n):
dj_dw[j] = dj_dw[j] + (lambda_/m) * w[j]
return dj_db, dj_dw
O for i é o gradiente logístico de sempre, sem mudar nada. O for j extra no final é a única linha nova: soma (lambda_/m) * w[j] em cada posição de dj_dw, depois que a média já foi tirada. Testando (linear e logística, mesmos dados aleatórios, lambda=0.7):
Saída (linear):
dj_db: 0.6648774569425726·dj_dw: [0.2965, 0.4912, 0.2165]Saída (logística):
dj_db: 0.341798994972791·dj_dw: [0.1738, 0.3201, 0.1078]
Rodando de novo o exemplo do anel
O mesmo componente do lab passado, agora com o slider de lambda no centro das atenções. Deixa o grau fixo em 10 (já bem overfitado, como vimos) e sobe o lambda aos poucos:
grau 10 · lambda 0 · acurácia de treino ≈ 1.0000
A acurácia continua em 100% em quase todo o intervalo de lambda, porque esse dataset de brinquedo é fácil demais pra confundir o modelo. Mas repara na intensidade da cor: com lambda=0, a região central fica bem saturada, o modelo tem quase certeza absoluta (acima de 99.9%) até bem perto da borda do anel. Com lambda=5, aquela mesma borda fica bem mais clara, a confiança do modelo caiu pra perto de 81.6% na mesma posição. Regularização não mudou a decisão final aqui, mas deixou o modelo menos "espalhafatoso", menos convicto além do que os dados sustentam. Ainda dá pra ir longe demais: sobe o lambda até 10 e repara a região central começar a encolher, sinal de que agora o modelo já está regularizado demais, puxando os pesos pra perto de zero a ponto de prejudicar até a decisão simples que ele já tinha acertado.
O gancho com a tarefa avaliada da semana
A tarefa avaliada dessa semana (a parte que eu não converto aqui porque os dados brutos dela não vieram junto com o notebook) pega um dataset de duas notas de prova pra decidir admissão, e depois um de duas medidas de teste de qualidade pra decidir se um microchip passa ou não. A segunda parte pede exatamente o que esse post já construiu: mapear duas features num polinômio de grau alto (a função map_feature do curso é o mesmo mapeamento que o componente acima já faz por baixo dos panos) e depois regularizar a regressão logística pra essa fronteira, livre pra curvar, não ficar maluca. Você já viu o resultado final funcionando aqui em cima, só que num dataset diferente.
Fechando
| O que eu já sabia | O que essa aula assentou |
|---|---|
| Overfitting tem três saídas: mais dados, menos features, ou regularização | Regularização é a mais usada porque mantém toda a informação, só freia o peso de cada uma |
| O gradiente logístico de sempre | O termo (lambda/m) * w_j, somado só depois da média, sem tocar no b |
| Grau alto deixa a fronteira maluca | lambda alto acalma a confiança do modelo sem necessariamente mudar a decisão, até passar do ponto e regularizar demais |
Aplicação Prática
No dataset real de câncer de mama (grau fixo em 8, já visto no lab passado como levemente overfitado), varrendo lambda:
| lambda | custo treino | custo teste | acurácia treino | acurácia teste |
|---|---|---|---|---|
| 0 | 0.1335 | 0.0814 | 93.5% | 94.7% |
| 1 | 0.1363 | 0.0851 | 94.5% | 95.6% |
| 2 | 0.1385 | 0.0891 | 94.7% | 95.6% |
| 5 | 0.1440 | 0.0991 | 94.3% | 93.9% |
| 10 | 0.1516 | 0.1114 | 93.6% | 93.9% |
| 50 | 0.1905 | 0.1606 | 94.5% | 94.7% |
Um lambda pequeno (1 ou 2) realmente recupera um pontinho de acurácia de teste em cima do lambda=0 (94.7% → 95.6%), mas o efeito aqui é modesto, bem mais sutil que no anel de brinquedo. Faz sentido: com só duas features, a fronteira desse dataset real já é quase uma reta por natureza, overfitting nunca foi um problema grave aqui pra começar. E o oposto também aparece nítido: lambda=10 já piora as duas acurácias em relação a lambda=1, regularização em excesso prejudicando um problema que não precisava de tanto freio.
grau 8 · lambda 0 · acurácia de treino ≈ 0.9833
Sobe o lambda devagar e repara as dobras nas bordas do lab passado suavizando. Regularização não é uma vitória automática, é uma ferramenta: ajuda muito quando o overfitting é grave, ajuda pouco (e pode até atrapalhar) quando o problema já não pedia tanta flexibilidade assim.