Modelos Lineales y Regularización en Machine Learning

Modelos Lineales Básicos

Los modelos lineales constituyen la base fundamental del machine learning supervisado, proporcionando una comprensión esencial de cómo las variables predictoras se relacionan con la variable objetivo de manera matemáticamente interpretable.

Regresión Lineal

La regresión lineal modela la relación entre variables mediante una función lineal, buscando la línea que mejor se ajuste a los datos minimizando los errores de predicción. La ecuación fundamental es:

y = β₀ + β₁x₁ + β₂x₂ + ... + βₘxₘ + ε

Donde:

  • y: variable dependiente (objetivo)
  • βᵢ: coeficientes de regresión
  • xᵢ: variables independientes (características)
  • ε: término de error

Implementación en Python

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# Generar datos de ejemplo
np.random.seed(42)
X = np.random.randn(100, 3)
y = 2*X[:, 0] + 3*X[:, 1] - 1.5*X[:, 2] + np.random.randn(100)*0.1

# Dividir los datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Crear y entrenar el modelo
modelo_lineal = LinearRegression()
modelo_lineal.fit(X_train, y_train)

# Realizar predicciones
y_pred = modelo_lineal.predict(X_test)

# Evaluación del modelo
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

print(f"Coeficientes: {modelo_lineal.coef_}")
print(f"Intercepto: {modelo_lineal.intercept_}")
print(f"MSE: {mse:.4f}")
print(f"R²: {r2:.4f}")

Características principales:

  • Simplicidad: Fácil de interpretar y implementar
  • Eficiencia computacional: Rápida de entrenar y predecir
  • Transparencia: Los coeficientes revelan la importancia relativa de cada característica

Regresión Logística

La regresión logística extiende los conceptos lineales a problemas de clasificación, utilizando la función logística (sigmoid) para mapear cualquier valor real a un rango entre 0 y 1.

Función Logística

p = 1 / (1 + e^(-z))
donde z = β₀ + β₁x₁ + β₂x₂ + ... + βₘxₘ

Implementación en Python

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
from sklearn.metrics import accuracy_score, classification_report

# Generar datos de clasificación
X, y = make_classification(n_samples=1000, n_features=4, n_redundant=0, 
                         n_informative=2, n_clusters_per_class=1, random_state=42)

# Dividir los datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Crear y entrenar el modelo
modelo_logistico = LogisticRegression()
modelo_logistico.fit(X_train, y_train)

# Realizar predicciones
y_pred = modelo_logistico.predict(X_test)
y_prob = modelo_logistico.predict_proba(X_test)

# Evaluación
accuracy = accuracy_score(y_test, y_pred)
print(f"Precisión: {accuracy:.4f}")
print("\nReporte de clasificación:")
print(classification_report(y_test, y_pred))

Ventajas de la regresión logística:

  • Probabilidades interpretables: Proporciona probabilidades de pertenencia a cada clase
  • No requiere distribución normal: Funciona con variables categóricas y continuas
  • Manejo de multicolinealidad: Mejor que otros métodos en presencia de características correlacionadas

Técnicas de Regularización

La regularización es fundamental para prevenir el sobreajuste y mejorar la capacidad de generalización de los modelos. Estas técnicas agregan términos de penalización a la función de pérdida original.

Regularización L1 (Lasso)

La regularización L1 añade la suma de los valores absolutos de los coeficientes como término de penalización.

Función de costo:

Costo = MSE + λ∑|βᵢ|

Características principales:

  • Selección automática de características: Puede llevar coeficientes exactamente a cero
  • Modelos parsimoniosos: Produce modelos más simples e interpretables
  • Útil con alta dimensionalidad: Efectiva cuando hay muchas características irrelevantes

Implementación en Python

from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler

# Preparar datos
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train_scaled, X_test_scaled, y_train, y_test = train_test_split(
    X_scaled, y, test_size=0.2, random_state=42)

# Modelo Lasso con diferentes valores de alpha
alphas = [0.001, 0.01, 0.1, 1.0, 10.0]
for alpha in alphas:
    lasso = Lasso(alpha=alpha)
    lasso.fit(X_train_scaled, y_train)
    y_pred = lasso.predict(X_test_scaled)
    mse = mean_squared_error(y_test, y_pred)
    
    # Contar coeficientes no cero
    non_zero_coefs = np.sum(lasso.coef_ != 0)
    
    print(f"Alpha: {alpha}")
    print(f"MSE: {mse:.4f}")
    print(f"Coeficientes no cero: {non_zero_coefs}")
    print(f"Coeficientes: {lasso.coef_}")
    print("-" * 40)

Regularización L2 (Ridge)

La regularización L2 añade la suma de los cuadrados de los coeficientes como penalización.

Función de costo:

Costo = MSE + λ∑βᵢ²

Características principales:

  • Reducción uniforme: Reduce todos los coeficientes pero no los elimina completamente
  • Manejo de multicolinealidad: Excelente para características altamente correlacionadas
  • Estabilidad: Más estable que Lasso en presencia de ruido

Implementación en Python

from sklearn.linear_model import Ridge

# Comparación de Ridge con diferentes alphas
alphas = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
results = []

for alpha in alphas:
    ridge = Ridge(alpha=alpha)
    ridge.fit(X_train_scaled, y_train)
    y_pred = ridge.predict(X_test_scaled)
    mse = mean_squared_error(y_test, y_pred)
    
    # Calcular la norma L2 de los coeficientes
    l2_norm = np.sqrt(np.sum(ridge.coef_**2))
    
    results.append({
        'alpha': alpha,
        'mse': mse,
        'l2_norm': l2_norm,
        'coeficientes': ridge.coef_
    })

# Mostrar resultados
for result in results:
    print(f"Alpha: {result['alpha']}")
    print(f"MSE: {result['mse']:.4f}")
    print(f"Norma L2: {result['l2_norm']:.4f}")
    print("-" * 40)

Red Elástica (Elastic Net)

La Red Elástica combina las penalizaciones L1 y L2, ofreciendo un equilibrio entre selección de características y estabilidad.

Función de costo:

Costo = MSE + λ₁∑|βᵢ| + λ₂∑βᵢ²

En scikit-learn se parametriza como:

Penalización = α × [l1_ratio × ∑|βᵢ| + (1 - l1_ratio) × ∑βᵢ²]

Implementación completa en Python

from sklearn.linear_model import ElasticNet, ElasticNetCV
from sklearn.model_selection import GridSearchCV

# Elastic Net con búsqueda de hiperparámetros
param_grid = {
    'alpha': [0.001, 0.01, 0.1, 1.0, 10.0],
    'l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9]
}

elastic_net = ElasticNet(max_iter=1000)
grid_search = GridSearchCV(
    elastic_net, param_grid, 
    cv=5, scoring='neg_mean_squared_error',
    n_jobs=-1
)

grid_search.fit(X_train_scaled, y_train)

# Mejor modelo
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test_scaled)
mse = mean_squared_error(y_test, y_pred)

print(f"Mejores parámetros: {grid_search.best_params_}")
print(f"MSE del mejor modelo: {mse:.4f}")
print(f"Coeficientes del mejor modelo: {best_model.coef_}")

# Usar ElasticNetCV para optimización automática
elastic_cv = ElasticNetCV(
    alphas=[0.001, 0.01, 0.1, 1.0, 10.0],
    l1_ratio=[0.1, 0.5, 0.9],
    cv=5,
    random_state=42
)

elastic_cv.fit(X_train_scaled, y_train)
print(f"\nElasticNetCV - Alpha óptimo: {elastic_cv.alpha_:.4f}")
print(f"ElasticNetCV - L1 ratio óptimo: {elastic_cv.l1_ratio_:.4f}")

Sesgo vs Varianza: El Trade-off Fundamental

El equilibrio entre sesgo y varianza es uno de los conceptos más importantes en machine learning, determinando la capacidad de generalización del modelo.

Definiciones Fundamentales

Sesgo (Bias): Error sistemático debido a suposiciones simplificadoras del modelo:

  • Modelos con alto sesgo tienden al underfitting
  • Hacen suposiciones demasiado fuertes sobre los datos
  • Fallan en capturar patrones complejos

Varianza: Sensibilidad del modelo a pequeños cambios en los datos de entrenamiento:

  • Modelos con alta varianza tienden al overfitting
  • Se ajustan demasiado a ruido específico del conjunto de entrenamiento
  • Funcionan mal con datos nuevos

Relación Matemática

El error total de un modelo se descompone en tres componentes:

Error Total = Sesgo² + Varianza + Error Irreducible

Demostración Práctica del Trade-off

import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

# Función para generar datos con ruido
def generar_datos_complejos(n_samples=100, noise_level=0.1):
    np.random.seed(42)
    X = np.linspace(0, 1, n_samples).reshape(-1, 1)
    y = np.sin(2 * np.pi * X.ravel()) + np.random.normal(0, noise_level, n_samples)
    return X, y

X, y = generar_datos_complejos()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# Modelos con diferentes niveles de complejidad
modelos = {
    'Lineal (Alto Sesgo)': LinearRegression(),
    'Polinomial Grado 3': Pipeline([
        ('poly', PolynomialFeatures(3)),
        ('linear', LinearRegression())
    ]),
    'Polinomial Grado 10': Pipeline([
        ('poly', PolynomialFeatures(10)),
        ('linear', LinearRegression())
    ]),
    'Árbol Profundo (Alta Varianza)': DecisionTreeRegressor(max_depth=None, random_state=42)
}

# Evaluar cada modelo
resultados = {}
for nombre, modelo in modelos.items():
    modelo.fit(X_train, y_train)
    y_pred_train = modelo.predict(X_train)
    y_pred_test = modelo.predict(X_test)
    
    error_train = mean_squared_error(y_train, y_pred_train)
    error_test = mean_squared_error(y_test, y_pred_test)
    
    resultados[nombre] = {
        'error_train': error_train,
        'error_test': error_test,
        'gap': error_test - error_train
    }

# Mostrar resultados
print("Análisis del Trade-off Sesgo-Varianza:")
print("-" * 50)
for nombre, metricas in resultados.items():
    print(f"{nombre}:")
    print(f"  Error Entrenamiento: {metricas['error_train']:.4f}")
    print(f"  Error Prueba: {metricas['error_test']:.4f}")
    print(f"  Gap (Overfitting): {metricas['gap']:.4f}")
    print()

Estrategias de Equilibrio

Para reducir el sesgo:

  • Aumentar la complejidad del modelo
  • Agregar más características
  • Usar modelos más flexibles

Para reducir la varianza:

  • Regularización: L1, L2, Elastic Net
  • Más datos de entrenamiento: Especialmente efectivo para alta varianza
  • Ensemble methods: Combinar múltiples modelos
  • Validación cruzada: Para evaluación robusta

Overfitting y Underfitting

Overfitting (Sobreajuste)

Ocurre cuando el modelo aprende patrones específicos del conjunto de entrenamiento, incluyendo ruido, resultando en pobre generalización.

Síntomas del Overfitting:

  • Alto rendimiento en datos de entrenamiento
  • Bajo rendimiento en datos de prueba
  • Gran diferencia entre errores de entrenamiento y validación

Código para detectar Overfitting:

from sklearn.model_selection import learning_curve

def plot_learning_curves(modelo, X, y, title="Curvas de Aprendizaje"):
    """Graficar curvas de aprendizaje para detectar overfitting"""
    train_sizes, train_scores, val_scores = learning_curve(
        modelo, X, y, cv=5, n_jobs=-1,
        train_sizes=np.linspace(0.1, 1.0, 10),
        scoring='neg_mean_squared_error'
    )
    
    # Convertir a valores positivos
    train_scores = -train_scores
    val_scores = -val_scores
    
    # Calcular medias y desviaciones estándar
    train_mean = np.mean(train_scores, axis=1)
    train_std = np.std(train_scores, axis=1)
    val_mean = np.mean(val_scores, axis=1)
    val_std = np.std(val_scores, axis=1)
    
    # Graficar
    plt.figure(figsize=(10, 6))
    plt.plot(train_sizes, train_mean, 'o-', color='blue', label='Entrenamiento')
    plt.fill_between(train_sizes, train_mean - train_std, train_mean + train_std, 
                     alpha=0.1, color='blue')
    
    plt.plot(train_sizes, val_mean, 'o-', color='red', label='Validación')
    plt.fill_between(train_sizes, val_mean - val_std, val_mean + val_std, 
                     alpha=0.1, color='red')
    
    plt.xlabel('Tamaño del conjunto de entrenamiento')
    plt.ylabel('Error (MSE)')
    plt.title(title)
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()

# Comparar modelo simple vs complejo
X_complex, y_complex = generar_datos_complejos(n_samples=200)

# Modelo con overfitting
modelo_overfitting = Pipeline([
    ('poly', PolynomialFeatures(15)),
    ('linear', LinearRegression())
])

plot_learning_curves(modelo_overfitting, X_complex, y_complex, 
                    "Modelo con Overfitting (Polinomial Grado 15)")

Underfitting (Subajuste)

Sucede cuando el modelo es demasiado simple para capturar los patrones subyacentes en los datos.

Síntomas del Underfitting:

  • Bajo rendimiento tanto en entrenamiento como en prueba
  • El modelo no logra capturar la complejidad de los datos
  • Error de sesgo alto

Validación Cruzada

La validación cruzada es esencial para evaluar la estabilidad y capacidad de generalización del modelo.

K-Fold Cross-Validation

from sklearn.model_selection import cross_val_score, KFold, StratifiedKFold

def evaluar_modelo_cv(modelo, X, y, cv_folds=5):
    """Evaluación completa con validación cruzada"""
    
    # K-Fold Cross-Validation
    kfold = KFold(n_splits=cv_folds, shuffle=True, random_state=42)
    cv_scores = cross_val_score(modelo, X, y, cv=kfold, 
                               scoring='neg_mean_squared_error', n_jobs=-1)
    
    # Convertir a valores positivos
    cv_scores = -cv_scores
    
    print(f"Validación Cruzada ({cv_folds}-fold):")
    print(f"Scores individuales: {cv_scores}")
    print(f"Media: {cv_scores.mean():.4f}")
    print(f"Desviación estándar: {cv_scores.std():.4f}")
    print(f"Intervalo de confianza 95%: [{cv_scores.mean() - 2*cv_scores.std():.4f}, "
          f"{cv_scores.mean() + 2*cv_scores.std():.4f}]")
    
    return cv_scores

# Evaluar diferentes modelos con regularización
modelos_regularizados = {
    'Sin regularización': LinearRegression(),
    'Ridge (α=1.0)': Ridge(alpha=1.0),
    'Lasso (α=0.1)': Lasso(alpha=0.1),
    'Elastic Net (α=0.5, l1_ratio=0.5)': ElasticNet(alpha=0.5, l1_ratio=0.5)
}

print("Comparación de modelos con validación cruzada:")
print("=" * 60)

for nombre, modelo in modelos_regularizados.items():
    print(f"\n{nombre}:")
    print("-" * len(nombre))
    evaluar_modelo_cv(modelo, X_train_scaled, y_train)

Validación Cruzada Estratificada para Clasificación

from sklearn.model_selection import StratifiedKFold

def evaluar_clasificacion_cv(modelo, X, y, cv_folds=5):
    """Validación cruzada para problemas de clasificación"""
    
    # Usar StratifiedKFold para mantener proporciones de clases
    skfold = StratifiedKFold(n_splits=cv_folds, shuffle=True, random_state=42)
    
    # Múltiples métricas
    metricas = ['accuracy', 'precision_macro', 'recall_macro', 'f1_macro']
    
    for metrica in metricas:
        scores = cross_val_score(modelo, X, y, cv=skfold, scoring=metrica)
        print(f"{metrica.capitalize()}: {scores.mean():.4f} (+/- {scores.std() * 2:.4f})")

# Ejemplo con regresión logística regularizada
X_class, y_class = make_classification(n_samples=1000, n_features=10, 
                                     n_informative=5, n_redundant=3, 
                                     n_clusters_per_class=1, random_state=42)

modelos_clasificacion = {
    'Logística sin regularización': LogisticRegression(penalty=None, max_iter=1000),
    'Logística L1': LogisticRegression(penalty='l1', solver='liblinear', C=1.0),
    'Logística L2': LogisticRegression(penalty='l2', C=1.0),
    'Logística Elastic Net': LogisticRegression(penalty='elasticnet', 
                                              solver='saga', l1_ratio=0.5, C=1.0)
}

for nombre, modelo in modelos_clasificacion.items():
    print(f"\n{nombre}:")
    print("-" * len(nombre))
    evaluar_clasificacion_cv(modelo, X_class, y_class)

Validación Cruzada Temporal (Time Series)

Para datos temporales, es crucial mantener el orden cronológico:

from sklearn.model_selection import TimeSeriesSplit

def validacion_temporal(modelo, X, y, n_splits=5):
    """Validación cruzada para series temporales"""
    
    tscv = TimeSeriesSplit(n_splits=n_splits)
    scores = []
    
    for fold, (train_idx, val_idx) in enumerate(tscv.split(X)):
        X_train_fold, X_val_fold = X[train_idx], X[val_idx]
        y_train_fold, y_val_fold = y[train_idx], y[val_idx]
        
        modelo.fit(X_train_fold, y_train_fold)
        y_pred = modelo.predict(X_val_fold)
        score = mean_squared_error(y_val_fold, y_pred)
        scores.append(score)
        
        print(f"Fold {fold + 1}: MSE = {score:.4f}")
    
    print(f"Promedio: {np.mean(scores):.4f} (+/- {np.std(scores) * 2:.4f})")
    return scores

# Simular datos temporales
np.random.seed(42)
n_tiempo = 200
X_temporal = np.random.randn(n_tiempo, 3)
y_temporal = np.cumsum(X_temporal[:, 0]) + np.random.randn(n_tiempo) * 0.1

print("Validación Cruzada Temporal:")
validacion_temporal(Ridge(alpha=1.0), X_temporal, y_temporal)

Ejemplo Integral: Proyecto Completo

Implementemos un ejemplo completo que integre todos los conceptos:

import warnings
warnings.filterwarnings('ignore')

def proyecto_completo_regularizacion():
    """Ejemplo integral de regularización"""
    
    # 1. Generar datos sintéticos complejos
    print("1. GENERACIÓN DE DATOS")
    print("=" * 40)
    
    np.random.seed(42)
    n_samples, n_features = 200, 15
    
    # Características informativas y redundantes
    X_informative = np.random.randn(n_samples, 5)
    X_redundant = X_informative[:, [0, 1, 2]] + np.random.randn(n_samples, 3) * 0.1
    X_noise = np.random.randn(n_samples, 7)
    
    X = np.concatenate([X_informative, X_redundant, X_noise], axis=1)
    
    # Variable objetivo con relación compleja
    y = (2 * X[:, 0] + 3 * X[:, 1] - 1.5 * X[:, 2] + 0.5 * X[:, 3] + 
         np.random.randn(n_samples) * 0.1)
    
    # División y escalado
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)
    
    print(f"Datos generados: {X.shape} muestras, {X.shape} características")
    print(f"Entrenamiento: {X_train.shape} muestras")
    print(f"Prueba: {X_test.shape} muestras")
    
    # 2. Comparación de técnicas de regularización
    print("\n2. COMPARACIÓN DE MODELOS")
    print("=" * 40)
    
    modelos = {
        'Linear Regression': LinearRegression(),
        'Ridge (α=0.1)': Ridge(alpha=0.1),
        'Ridge (α=1.0)': Ridge(alpha=1.0),
        'Ridge (α=10.0)': Ridge(alpha=10.0),
        'Lasso (α=0.01)': Lasso(alpha=0.01),
        'Lasso (α=0.1)': Lasso(alpha=0.1),
        'Lasso (α=1.0)': Lasso(alpha=1.0),
        'Elastic Net (α=0.1, l1=0.5)': ElasticNet(alpha=0.1, l1_ratio=0.5),
        'Elastic Net (α=1.0, l1=0.7)': ElasticNet(alpha=1.0, l1_ratio=0.7)
    }
    
    resultados_completos = []
    
    for nombre, modelo in modelos.items():
        # Entrenamiento
        modelo.fit(X_train_scaled, y_train)
        
        # Predicciones
        y_pred_train = modelo.predict(X_train_scaled)
        y_pred_test = modelo.predict(X_test_scaled)
        
        # Métricas
        mse_train = mean_squared_error(y_train, y_pred_train)
        mse_test = mean_squared_error(y_test, y_pred_test)
        r2_train = r2_score(y_train, y_pred_train)
        r2_test = r2_score(y_test, y_pred_test)
        
        # Análisis de coeficientes
        if hasattr(modelo, 'coef_'):
            coefs = modelo.coef_
            n_zero_coefs = np.sum(np.abs(coefs) < 1e-6)
            l1_norm = np.sum(np.abs(coefs))
            l2_norm = np.sqrt(np.sum(coefs**2))
        else:
            n_zero_coefs, l1_norm, l2_norm = 0, 0, 0
        
        # Validación cruzada
        cv_scores = cross_val_score(modelo, X_train_scaled, y_train, 
                                   cv=5, scoring='neg_mean_squared_error')
        cv_mean = -cv_scores.mean()
        cv_std = cv_scores.std()
        
        resultado = {
            'modelo': nombre,
            'mse_train': mse_train,
            'mse_test': mse_test,
            'r2_train': r2_train,
            'r2_test': r2_test,
            'overfitting': mse_test - mse_train,
            'coefs_zero': n_zero_coefs,
            'l1_norm': l1_norm,
            'l2_norm': l2_norm,
            'cv_mean': cv_mean,
            'cv_std': cv_std
        }
        
        resultados_completos.append(resultado)
    
    # 3. Análisis de resultados
    print("\n3. ANÁLISIS DETALLADO")
    print("=" * 40)
    
    df_resultados = pd.DataFrame(resultados_completos)
    
    print("Rendimiento de modelos:")
    print(df_resultados[['modelo', 'mse_train', 'mse_test', 'r2_test', 
                        'overfitting', 'coefs_zero']].round(4))
    
    # 4. Selección del mejor modelo
    print("\n4. SELECCIÓN DEL MEJOR MODELO")
    print("=" * 40)
    
    # Criterio: menor MSE de prueba con menor overfitting
    df_resultados['score_compuesto'] = (df_resultados['mse_test'] + 
                                       0.1 * df_resultados['overfitting'])
    
    mejor_modelo = df_resultados.loc[df_resultados['score_compuesto'].idxmin()]
    print(f"Mejor modelo: {mejor_modelo['modelo']}")
    print(f"MSE Test: {mejor_modelo['mse_test']:.4f}")
    print(f"R² Test: {mejor_modelo['r2_test']:.4f}")
    print(f"Coeficientes cero: {mejor_modelo['coefs_zero']}")
    
    # 5. Optimización de hiperparámetros
    print("\n5. OPTIMIZACIÓN DE HIPERPARÁMETROS")
    print("=" * 40)
    
    # Grid Search para Elastic Net
    param_grid = {
        'alpha': np.logspace(-3, 1, 20),
        'l1_ratio': np.linspace(0.1, 0.9, 9)
    }
    
    elastic_net = ElasticNet(max_iter=1000)
    grid_search = GridSearchCV(
        elastic_net, param_grid, cv=5, 
        scoring='neg_mean_squared_error', n_jobs=-1
    )
    
    grid_search.fit(X_train_scaled, y_train)
    
    print(f"Mejores parámetros: {grid_search.best_params_}")
    print(f"Mejor score CV: {-grid_search.best_score_:.4f}")
    
    # Evaluación del modelo optimizado
    modelo_optimizado = grid_search.best_estimator_
    y_pred_opt = modelo_optimizado.predict(X_test_scaled)
    mse_opt = mean_squared_error(y_test, y_pred_opt)
    r2_opt = r2_score(y_test, y_pred_opt)
    
    print(f"MSE optimizado: {mse_opt:.4f}")
    print(f"R² optimizado: {r2_opt:.4f}")
    
    return df_resultados, modelo_optimizado

# Ejecutar el proyecto completo
resultados, modelo_final = proyecto_completo_regularizacion()

La regularización es fundamental para construir modelos robustos que generalicen bien a datos nuevos. La elección entre L1, L2 o Elastic Net depende del problema específico: L1 para selección automática de características, L2 para estabilidad con características correlacionadas, y Elastic Net como compromiso entre ambos enfoques. La validación cruzada proporciona una evaluación confiable de la capacidad de generalización, mientras que el equilibrio sesgo-varianza guía las decisiones de diseño del modelo.

Modelos Lineales y Regularización - Machine Learning

Author

Juan Fuentes

Publish Date

06 - 15 - 2023