Redes Neuronales para Regresión con Scikit-Learn

MLPRegressor: Implementación de Redes Neuronales

MLPRegressor es la implementación de redes neuronales multicapa (Multi-Layer Perceptron) en scikit-learn específicamente diseñada para problemas de regresión. Esta clase utiliza algoritmos de optimización como LBFGS o descenso de gradiente estocástico para minimizar el error cuadrático.

Características Principales

Arquitectura Automática para Regresión:

  • Capa de salida: Automáticamente configura una neurona única para predecir valores continuos
  • Función de activación de salida: Utiliza la función “identity” (lineal) por defecto, definida como f(x) = x
  • Rango de salida: (-∞, +∞), permitiendo predicciones en cualquier rango real

Diferencias con Clasificación:

  • Clasificación binaria: 1 neurona + sigmoid
  • Clasificación multiclase: n neuronas + softmax
  • Regresión: 1 neurona + identity (lineal)

Ejemplo de Implementación Básica

from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score

# Configuración completa de MLPRegressor para regresión
mlp_regressor = MLPRegressor(
    hidden_layer_sizes=(100, 50, 25),    # Arquitectura: 3 capas ocultas
    activation='relu',                    # Función de activación en capas ocultas
    solver='adam',                        # Optimizador
    alpha=0.001,                         # Regularización L2
    learning_rate_init=0.001,            # Tasa de aprendizaje inicial
    max_iter=2000,                       # Máximo número de iteraciones
    random_state=42,                     # Semilla para reproducibilidad
    early_stopping=True,                 # Parada temprana
    validation_fraction=0.1,             # Fracción para validación
    n_iter_no_change=10                  # Iteraciones sin mejora para parar
)

# IMPORTANTE: Para regresión, la capa de salida usa automáticamente:
# - Una neurona (para valores continuos)
# - Función de activación 'identity' (lineal: f(x) = x)

# Entrenar el modelo
mlp_regressor.fit(X_train, y_train)

# Hacer predicciones
y_pred = mlp_regressor.predict(X_test)

# Evaluar el modelo
r2 = r2_score(y_test, y_pred)
rmse = sqrt(mean_squared_error(y_test, y_pred))

La función de activación lineal es especialmente importante en regresión porque no restringe el rango de valores de salida, permitiendo que el modelo prediga cualquier valor real. Esto contrasta con funciones como sigmoid (rango 0-1) o tanh (rango -1 a 1) que limitarían las predicciones.

Pipelines Avanzados

TransformedTargetRegressor: Transformaciones en Variable Objetivo

TransformedTargetRegressor es una herramienta meta-estimador que permite aplicar transformaciones no lineales a la variable objetivo (y) antes del entrenamiento y automáticamente aplica la transformación inversa durante las predicciones. Es especialmente útil cuando la variable objetivo tiene distribuciones sesgadas o no normales.

Funcionamiento:

  • Durante entrenamiento: regressor.fit(X, func(y))
  • Durante predicción: inverse_func(regressor.predict(X))
from sklearn.compose import TransformedTargetRegressor
import numpy as np

# Crear el regresor con transformación del target
transformed_regressor = TransformedTargetRegressor(
    regressor=MLPRegressor(hidden_layer_sizes=(100, 50), random_state=42),
    func=np.log,           # Función de transformación
    inverse_func=np.exp    # Función inversa para las predicciones
)

# Entrenar el modelo (automáticamente aplica log(y))
transformed_regressor.fit(X_train, y_train)

# Hacer predicciones (automáticamente aplica exp() para volver a escala original)
predictions = transformed_regressor.predict(X_test)

Casos de Uso Comunes:

  • Variables objetivo con distribución exponencial → transformación logarítmica
  • Variables objetivo muy sesgadas → transformaciones de Box-Cox
  • Variables objetivo con valores muy grandes → normalización/escalado

ColumnTransformer: Manejo Diferenciado por Tipo de Variable

ColumnTransformer permite aplicar diferentes transformaciones a diferentes subconjuntos de características en paralelo. Esta herramienta es fundamental cuando se trabaja con datasets que contienen tipos de variables mixtos (numéricas y categóricas).

Ventajas Clave:

  • Transformación selectiva: Aplica transformaciones específicas a subconjuntos de columnas
  • Integración con pipelines: Se integra perfectamente con Pipeline de scikit-learn
  • Organización del código: Encapsula toda la lógica de preprocesamiento
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.neural_network import MLPRegressor
from sklearn.pipeline import Pipeline

# Definir transformaciones por tipo de variable
preprocessor = ColumnTransformer(
    transformers=[
        # Variables numéricas: escalado estándar
        ('num', StandardScaler(), ['edad', 'salario', 'experiencia']),
        
        # Variables categóricas: codificación one-hot
        ('cat', OneHotEncoder(drop='first', sparse=False), ['ciudad', 'educacion'])
    ],
    remainder='drop'  # Eliminar columnas no especificadas
)

# Pipeline completo
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('regressor', MLPRegressor(
        hidden_layer_sizes=(100, 50),
        activation='relu',
        random_state=42,
        max_iter=2000
    ))
])

# Entrenar el pipeline
pipeline.fit(X_train, y_train)

# El pipeline maneja automáticamente:
# 1. Transformación de datos de entrenamiento
# 2. Entrenamiento del modelo
# 3. Transformación de datos nuevos durante predicción
predictions = pipeline.predict(X_test)

Parámetro remainder:

  • 'passthrough': Mantiene columnas no transformadas en la salida
  • 'drop': Elimina columnas no especificadas
  • Transformer personalizado: Aplica transformación específica a columnas restantes

Consideraciones para Producción

Consistencia en Transformaciones entre Entrenamiento y Predicción

Uno de los problemas más críticos en producción es la inconsistencia entre las transformaciones aplicadas durante el entrenamiento y las aplicadas durante la predicción. Esta inconsistencia puede causar predicciones completamente erróneas.

Problema Común:

  • Entrenar con ciertas transformaciones en desarrollo
  • Aplicar transformaciones diferentes o en orden distinto en producción
  • Resultado: Predicciones sistemáticamente incorrectas

Solución: Serialización de Pipelines Completos

import pickle
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.neural_network import MLPRegressor

# 1. ENTRENAR PIPELINE COMPLETO
pipeline_prod = Pipeline([
    ('preprocessor', ColumnTransformer([
        ('num', StandardScaler(), ['feature1', 'feature2']),
        ('cat', OneHotEncoder(drop='first'), ['category'])
    ])),
    ('model', MLPRegressor(hidden_layer_sizes=(100, 50)))
])

# Entrenar
pipeline_prod.fit(X_train, y_train)

# 2. GUARDAR PIPELINE COMPLETO
with open('model_pipeline.pkl', 'wb') as f:
    pickle.dump(pipeline_prod, f)

# 3. CARGAR EN PRODUCCIÓN
with open('model_pipeline.pkl', 'rb') as f:
    loaded_pipeline = pickle.load(f)

# 4. USAR EN PRODUCCIÓN (mismo preprocesamiento automático)
predictions = loaded_pipeline.predict(new_data)

Validación de Disponibilidad de Variables en Tiempo Real

La validación de datos de entrada es crucial para garantizar predicciones confiables en producción. Los datos pueden tener problemas de calidad, valores faltantes o estar fuera de los rangos esperados.

# EJEMPLO: VALIDACIÓN DE DATOS EN TIEMPO REAL

import pandas as pd
import numpy as np
from typing import Dict, Any, List

class ProductionValidator:
    def __init__(self, training_stats: Dict):
        self.training_stats = training_stats
        
    def validate_input(self, input_data: pd.DataFrame) -> Dict[str, Any]:
        """
        Valida datos de entrada antes de hacer predicciones
        """
        validation_results = {
            'is_valid': True,
            'errors': [],
            'warnings': []
        }
        
        # 1. Verificar columnas requeridas
        required_columns = self.training_stats['required_columns']
        missing_columns = set(required_columns) - set(input_data.columns)
        
        if missing_columns:
            validation_results['is_valid'] = False
            validation_results['errors'].append(
                f"Columnas faltantes: {missing_columns}"
            )
        
        # 2. Verificar tipos de datos
        for col, expected_type in self.training_stats['column_types'].items():
            if col in input_data.columns:
                if input_data[col].dtype != expected_type:
                    validation_results['warnings'].append(
                        f"Tipo de datos inesperado en {col}: {input_data[col].dtype} vs {expected_type}"
                    )
        
        # 3. Verificar rangos de variables numéricas
        for col in self.training_stats['numeric_ranges']:
            if col in input_data.columns:
                col_min, col_max = self.training_stats['numeric_ranges'][col]
                out_of_range = (
                    (input_data[col] < col_min) | 
                    (input_data[col] > col_max)
                ).sum()
                
                if out_of_range > 0:
                    validation_results['warnings'].append(
                        f"{out_of_range} valores fuera de rango en {col}"
                    )
        
        return validation_results

Data Drift: Cambios en Distribuciones de Datos

Data drift se refiere a cambios en las distribuciones estadísticas de los datos después de que el modelo ha sido desplegado. Este fenómeno puede degradar significativamente el rendimiento del modelo con el tiempo.

Tipos de Data Drift:

  1. Covariate Drift (Deriva de Covariables):
    • Cambio en la distribución de variables de entrada P(X)
    • Ejemplo: La edad promedio de usuarios cambia de 30 a 45 años
  2. Concept Drift (Deriva de Concepto):
    • Cambio en la relación entre características y objetivo P(y|X)
    • Ejemplo: Las mismas características predicen diferentes precios debido a cambios en el mercado
  3. Prior Probability Drift (Deriva de Probabilidad Previa):
    • Cambio en la distribución del objetivo P(y)
    • Ejemplo: Precios promedio aumentan por inflación

Métodos de Detección:

La literatura identifica aproximadamente 40 enfoques únicos de detección de deriva, agrupados en cinco clases: métricas basadas en distancia, métricas basadas en error, enfoques compuestos, métodos adaptativos y métodos basados en aprendizaje activo.

# EJEMPLO: DETECCIÓN DE DATA DRIFT

import numpy as np
from scipy import stats
from sklearn.metrics import r2_score

class DataDriftDetector:
    def __init__(self, reference_data):
        self.reference_data = reference_data
        self.reference_stats = {
            'mean': np.mean(reference_data, axis=0),
            'std': np.std(reference_data, axis=0)
        }
    
    def detect_drift(self, new_data, alpha=0.05):
        """
        Detecta drift usando test Kolmogorov-Smirnov
        """
        drift_detected = {}
        
        for i in range(new_data.shape):
            # Test K-S para cada característica
            statistic, p_value = stats.ks_2samp(
                self.reference_data[:, i], 
                new_data[:, i]
            )
            
            drift_detected[f'feature_{i}'] = {
                'drift': p_value < alpha,
                'p_value': p_value,
                'statistic': statistic
            }
        
        return drift_detected
    
    def performance_drift(self, model, X_new, y_new, baseline_r2):
        """
        Detecta drift basado en degradación de rendimiento
        """
        current_r2 = r2_score(y_new, model.predict(X_new))
        performance_drop = baseline_r2 - current_r2
        
        return {
            'performance_drift': performance_drop > 0.1,  # Umbral del 10%
            'baseline_r2': baseline_r2,
            'current_r2': current_r2,
            'drop': performance_drop
        }

# USAR EL DETECTOR
detector = DataDriftDetector(X_train)
drift_results = detector.detect_drift(X_production)

Impacto del Data Drift:

  • Reducción de precisión: Los modelos se vuelven menos confiables
  • Problemas de cumplimiento: En industrias reguladas como finanzas o salud
  • Pérdida de confianza: Los usuarios pueden perder confianza en el sistema
  • Aumento de costos: Predicciones erróneas pueden llevar a decisiones comerciales pobres

Estrategias de Manejo:

  • Reentrenamiento periódico: Actualizar modelos con datos recientes
  • Monitoreo continuo: Implementar alertas automáticas cuando se detecta deriva
  • Modelos adaptativos: Usar enfoques que se adapten automáticamente a cambios
  • Líneas base establecidas: Mantener estadísticas de referencia del entrenamiento

Mejores Prácticas para Producción

Diseño del Modelo:

  • Comenzar con arquitecturas simples y aumentar complejidad gradualmente
  • Implementar early stopping para evitar sobreajuste
  • Usar regularización apropiada (parámetro alpha)
  • Validación cruzada para selección de hiperparámetros

Preprocesamiento Robusto:

  • Siempre usar pipelines para garantizar consistencia
  • Manejar valores faltantes explícitamente
  • Escalar variables numéricas apropiadamente
  • Codificar variables categóricas de manera consistente

Monitoreo y Mantenimiento:

  • Implementar detección automática de data drift
  • Mantener logging detallado para debugging
  • Establecer alertas cuando el rendimiento decae
  • Capacidad de rollback para modelos problemáticos

La implementación exitosa de redes neuronales para regresión en producción requiere una comprensión profunda de estos componentes y una estrategia integral que aborde tanto los aspectos técnicos como operacionales del despliegue de modelos de machine learning.

Redes Neuronales para Regresión - MLPRegressor y Pipelines Avanzados

Author

Juan Fuentes

Publish Date

07 - 13 - 2023