Anteriormente exploramos o Lasso e Ridge como técnicas de regularização separadas. Analogamente, a Rede Elástica (Elastic Net) combina as penalidades L1 e L2, oferecendo os benefícios de ambas as abordagens em um único modelo.
Conceito Fundamental da Elastic Net
Primordialmente, a Elastic Net aborda algumas limitações do Lasso, especialmente quando lidamos com features altamente correlacionadas. Decerto, enquanto o Lasso tende a selecionar apenas uma feature de um grupo correlacionado, a Elastic Net pode selecionar todo o grupo.
Conforme a documentação do scikit-learn, a Elastic Net é particularmente útil quando o número de features é maior que o número de amostras, ou quando há alta correlação entre as features. Similarmente às outras técnicas de regularização, ela ajuda a prevenir overfitting.
Formulação Matemática
O objetivo da Elastic Net é minimizar a seguinte função:
\(\min_{w} \frac{1}{2n}||Xw – y||_2^2 + \alpha\rho||w||_1 + \frac{\alpha(1-\rho)}{2}||w||_2^2\)Onde:
- X é a matriz de features
- y é o vetor target
- w são os coeficientes do modelo
- α é o parâmetro de regularização principal
- ρ é a razão de mistura L1 (l1_ratio)
- ||w||₁ é a norma L1 (penalidade Lasso)
- ||w||₂² é a norma L2 ao quadrado (penalidade Ridge)
Características Principais
Inegavelmente, a Elastic Net combina as melhores propriedades do Lasso e Ridge:
- Seleção de features: Herda a capacidade de zerar coeficientes do Lasso
- Estabilidade com correlação: Herda a robustez do Ridge com features correlacionadas
- Controle flexível: Parâmetro l1_ratio controla o balanceamento entre L1 e L2
- Performance em high-dimension: Funciona bem quando p > n
Interpretação do Parâmetro l1_ratio
O parâmetro l1_ratio controla a mistura entre Lasso e Ridge:
- l1_ratio = 1: Elastic Net se torna Lasso puro
- l1_ratio = 0: Elastic Net se torna Ridge puro
- 0 < l1_ratio < 1: Combinação balanceada entre L1 e L2
Vantagens sobre Lasso e Ridge
Embora Lasso e Ridge sejam úteis, a Elastic Net oferece vantagens em cenários específicos:
- Features correlacionadas: Lasso seleciona aleatoriamente, Elastic Net seleciona grupo
- Seleção de variáveis: Mantém a capacidade de seleção do Lasso
- Estabilidade: Mais estável que Lasso com alta correlação
- Performance preditiva: Frequentemente supera ambos em prática
Exemplo Prático: Elastic Net em Ação
Ademais, vejamos um exemplo completo demonstrando o uso da Elastic Net:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
''' Aplicação da Elastic Net para Regressão Este exemplo demonstra como a Elastic Net combina as vantagens do Lasso e Ridge em diferentes cenários ''' import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_regression from sklearn.linear_model import ElasticNet, Lasso, Ridge from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.preprocessing import StandardScaler from sklearn.metrics import mean_squared_error, r2_score # Gerar dataset com features correlacionadas n_samples = 500 n_features = 20 n_informative = 8 print("Configuração do Dataset:") print(f"Número de amostras: {n_samples}") print(f"Número de features: {n_features}") print(f"Features informativas: {n_informative}") # Gerar dados com grupos de features correlacionadas X, y, true_coef = make_regression(n_samples=n_samples, n_features=n_features, n_informative=n_informative, noise=20, coef=True, random_state=42) # Adicionar correlação entre algumas features rng = np.random.RandomState(42) for i in range(0, n_informative, 2): if i + 1 < n_informative: X[:, i+1] = X[:, i] * 0.7 + rng.normal(0, 0.1, n_samples) print(f"\nDimensões: X {X.shape}, y {y.shape}") # Dividir em treino e teste X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # Normalizar os dados scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) print(f"\n--- Comparação: Elastic Net vs Lasso vs Ridge ---") # Elastic Net elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5, max_iter=10000, random_state=42) elastic_net.fit(X_train_scaled, y_train) y_pred_en = elastic_net.predict(X_test_scaled) mse_en = mean_squared_error(y_test, y_pred_en) r2_en = r2_score(y_test, y_pred_en) # Lasso lasso = Lasso(alpha=0.1, max_iter=10000, random_state=42) lasso.fit(X_train_scaled, y_train) y_pred_lasso = lasso.predict(X_test_scaled) mse_lasso = mean_squared_error(y_test, y_pred_lasso) r2_lasso = r2_score(y_test, y_pred_lasso) # Ridge ridge = Ridge(alpha=0.1, random_state=42) ridge.fit(X_train_scaled, y_train) y_pred_ridge = ridge.predict(X_test_scaled) mse_ridge = mean_squared_error(y_test, y_pred_ridge) r2_ridge = r2_score(y_test, y_pred_ridge) print(f"\nElastic Net:") print(f"MSE: {mse_en:.2f}, R²: {r2_en:.4f}") print(f"Features selecionadas: {np.sum(elastic_net.coef_ != 0)}") print(f"\nLasso:") print(f"MSE: {mse_lasso:.2f}, R²: {r2_lasso:.4f}") print(f"Features selecionadas: {np.sum(lasso.coef_ != 0)}") print(f"\nRidge:") print(f"MSE: {mse_ridge:.2f}, R²: {r2_ridge:.4f}") print(f"Features selecionadas: {np.sum(ridge.coef_ != 0)}") # Otimização dos hiperparâmetros da Elastic Net print(f"\n--- Otimização dos Hiperparâmetros da Elastic Net ---") param_grid = { 'alpha': [0.001, 0.01, 0.1, 1, 10], 'l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9, 1.0] } grid_search = GridSearchCV(ElasticNet(max_iter=10000, random_state=42), param_grid, cv=5, scoring='neg_mean_squared_error', n_jobs=-1) grid_search.fit(X_train_scaled, y_train) print(f"Melhores parâmetros: {grid_search.best_params_}") print(f"Melhor MSE na validação: {-grid_search.best_score_:.2f}") # Modelo Elastic Net otimizado best_en = grid_search.best_estimator_ y_pred_best = best_en.predict(X_test_scaled) final_mse = mean_squared_error(y_test, y_pred_best) final_r2 = r2_score(y_test, y_pred_best) print(f"\nElastic Net Otimizado:") print(f"MSE: {final_mse:.2f}, R²: {final_r2:.4f}") print(f"Features selecionadas: {np.sum(best_en.coef_ != 0)}") # Visualização dos resultados plt.figure(figsize=(15, 10)) # Plot 1: Comparação de coeficientes plt.subplot(2, 3, 1) features = range(len(true_coef)) plt.plot(features, true_coef, 'o-', label='Verdadeiro', linewidth=2, markersize=6) plt.plot(features, elastic_net.coef_, 's-', label='Elastic Net', alpha=0.8) plt.plot(features, lasso.coef_, '^-', label='Lasso', alpha=0.8) plt.plot(features, ridge.coef_, 'd-', label='Ridge', alpha=0.8) plt.xlabel('Feature Index') plt.ylabel('Valor do Coeficiente') plt.title('Comparação dos Coeficientes') plt.legend() plt.grid(True, alpha=0.3) # Plot 2: Features selecionadas plt.subplot(2, 3, 2) methods = ['Elastic Net', 'Lasso', 'Ridge'] n_selected = [np.sum(elastic_net.coef_ != 0), np.sum(lasso.coef_ != 0), np.sum(ridge.coef_ != 0)] plt.bar(methods, n_selected, color=['lightcoral', 'lightgreen', 'skyblue']) plt.ylabel('Número de Features Selecionadas') plt.title('Seleção de Features') plt.xticks(rotation=45) # Plot 3: Comparação de performance plt.subplot(2, 3, 3) mses = [mse_en, mse_lasso, mse_ridge] plt.bar(methods, mses, color=['lightcoral', 'lightgreen', 'skyblue']) plt.ylabel('MSE') plt.title('Comparação de Performance') plt.xticks(rotation=45) # Plot 4: Análise do parâmetro l1_ratio plt.subplot(2, 3, 4) l1_ratios = param_grid['l1_ratio'] cv_scores = [] for l1_ratio in l1_ratios: model = ElasticNet(alpha=0.1, l1_ratio=l1_ratio, max_iter=10000, random_state=42) model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled) mse = mean_squared_error(y_test, y_pred) cv_scores.append(mse) plt.plot(l1_ratios, cv_scores, 'o-', linewidth=2) plt.xlabel('l1_ratio') plt.ylabel('MSE') plt.title('Performance vs l1_ratio (alpha=0.1)') plt.grid(True, alpha=0.3) # Plot 5: Esparsidade vs l1_ratio plt.subplot(2, 3, 5) sparsity_levels = [] for l1_ratio in l1_ratios: model = ElasticNet(alpha=0.1, l1_ratio=l1_ratio, max_iter=10000, random_state=42) model.fit(X_train_scaled, y_train) sparsity = np.sum(model.coef_ != 0) sparsity_levels.append(sparsity) plt.plot(l1_ratios, sparsity_levels, 'o-', linewidth=2) plt.xlabel('l1_ratio') plt.ylabel('Features Não-Zero') plt.title('Esparsidade vs l1_ratio') plt.grid(True, alpha=0.3) # Plot 6: Matriz de resultados do GridSearch plt.subplot(2, 3, 6) results = grid_search.cv_results_ scores = -results['mean_test_score'] alphas = results['param_alpha'].data l1_ratios = results['param_l1_ratio'].data # Encontrar melhor combinação best_idx = np.argmin(scores) best_alpha = alphas[best_idx] best_l1_ratio = l1_ratios[best_idx] plt.scatter(l1_ratios, scores, c=scores, cmap='viridis', s=100, alpha=0.7) plt.colorbar(label='MSE') plt.xlabel('l1_ratio') plt.ylabel('MSE') plt.title(f'GridSearch: Melhor alpha={best_alpha}, l1_ratio={best_l1_ratio}') plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # Análise de grupos correlacionados print(f"\n--- Análise de Grupos Correlacionados ---") # Identificar features correlacionadas correlation_matrix = np.corrcoef(X_train_scaled.T) high_correlation = np.sum(np.abs(correlation_matrix) > 0.6) - n_features print(f"Pares de features com correlação > 0.6: {high_correlation//2}") # Verificar como cada método lida com features correlacionadas print(f"\nComportamento com Features Correlacionadas:") # Encontrar pares correlacionados correlated_pairs = [] for i in range(n_features): for j in range(i+1, n_features): if abs(correlation_matrix[i, j]) > 0.6: correlated_pairs.append((i, j)) for i, (idx1, idx2) in enumerate(correlated_pairs[:3]): # Mostrar apenas os 3 primeiros print(f"\nPar {i+1}: Features {idx1} e {idx2}") print(f" Elastic Net: {best_en.coef_[idx1]:.3f}, {best_en.coef_[idx2]:.3f}") print(f" Lasso: {lasso.coef_[idx1]:.3f}, {lasso.coef_[idx2]:.3f}") print(f" Ridge: {ridge.coef_[idx1]:.3f}, {ridge.coef_[idx2]:.3f}") # Resumo final print(f"\n--- Resumo Final ---") print(f"Melhor modelo: Elastic Net com l1_ratio={grid_search.best_params_['l1_ratio']}") print(f"Melhoria sobre Lasso: {(mse_lasso - final_mse)/mse_lasso*100:.1f}%") print(f"Melhoria sobre Ridge: {(mse_ridge - final_mse)/mse_ridge*100:.1f}%") print(f"Features corretamente identificadas: {np.sum((best_en.coef_ != 0) == (true_coef != 0))}/{n_features}") |
Casos de Uso Recomendados
A Elastic Net é particularmente eficaz em:
- Features altamente correlacionadas: Quando o Lasso falha em selecionar grupos
- Problemas de alta dimensionalidade: Quando p > n
- Seleção de variáveis com estabilidade: Combinando sparsity e robustez
- Quando não se sabe qual regularização usar: Elastic Net como abordagem padrão
Considerações Práticas
Algumas recomendações importantes para uso eficaz:
- Sempre normalize os dados antes de usar Elastic Net
- Use GridSearchCV ou RandomizedSearchCV para otimizar alpha e l1_ratio
- Comece com l1_ratio=0.5 como ponto de partida
- Considere a complexidade computacional para datasets muito grandes
Variantes da Elastic Net
O scikit-learn também oferece variantes especializadas:
- ElasticNetCV: Com validação cruzada embutida para seleção de parâmetros
- MultiTaskElasticNet: Para problemas com múltiplos targets
Enfim, a Elastic Net representa uma abordagem robusta e flexível que combina os pontos fortes do Lasso e Ridge, tornando-se frequentemente a escolha preferida para problemas de regressão regularizada na prática.
Referência: https://scikit-learn.org/0.21/modules/linear_model.html#elastic-net