Validación Cruzada y Optimización: Sesión 4
La validación cruzada y la optimización de hiperparámetros son técnicas fundamentales en machine learning para evaluar y mejorar el rendimiento de los modelos. Esta sesión abarca los conceptos clave y técnicas más utilizadas para encontrar los mejores parámetros de configuración de los modelos.
K-Fold Cross Validation
La validación cruzada K-Fold es una técnica robusta para evaluar el rendimiento de un modelo dividiendo los datos en k subconjuntos o “pliegues”. Esta metodología permite una evaluación más confiable que una simple división train-test.
Funcionamiento del K-Fold
El algoritmo funciona de la siguiente manera:
- División: Los datos se dividen aleatoriamente en k grupos de tamaño aproximadamente igual
- Iteración: Para cada uno de los k grupos:
- Se usa el grupo como conjunto de validación
- Los k-1 grupos restantes se usan para entrenamiento
- Se entrena el modelo y se evalúa en el conjunto de validación
- Agregación: Se calcula el promedio de las métricas obtenidas en cada iteración
Ejemplo de implementación en Python
from sklearn.model_selection import cross_val_score, KFold
from sklearn.svm import SVC
from sklearn.datasets import load_iris
# Cargar datos
iris = load_iris()
X, y = iris.data, iris.target
# Crear clasificador SVM
svm_classifier = SVC(kernel='linear')
# Definir número de pliegues
num_folds = 5
kf = KFold(n_splits=num_folds, shuffle=True, random_state=42)
# Realizar validación cruzada k-fold
cross_val_results = cross_val_score(svm_classifier, X, y, cv=kf)
# Mostrar resultados
print("Cross-Validation Results (Accuracy):")
for i, result in enumerate(cross_val_results, 1):
print(f"Fold {i}: {result * 100:.2f}%")
print(f'Mean Accuracy: {cross_val_results.mean()* 100:.2f}%')
Ventajas del K-Fold
- Robustez: Cada observación se usa tanto para entrenamiento como para validación
- Reducción de sesgo: Proporciona una estimación menos sesgada del rendimiento del modelo
- Mayor utilización de datos: Aprovecha todos los datos disponibles para entrenamiento y validación
Búsqueda de Hiperparámetros
Los hiperparámetros son configuraciones que controlan cómo aprende un modelo y deben establecerse antes del entrenamiento. La optimización de estos parámetros es crucial para obtener el mejor rendimiento posible.
Grid Search: Búsqueda Exhaustiva
Grid Search es un método de fuerza bruta que evalúa todas las combinaciones posibles de hiperparámetros especificados.
Características del Grid Search
- Exhaustivo: Garantiza encontrar la mejor combinación dentro del espacio de búsqueda definido
- Computacionalmente costoso: El tiempo de ejecución crece exponencialmente con el número de hiperparámetros
- Determinístico: Siempre produce los mismos resultados para los mismos parámetros
Implementación de Grid Search
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
import numpy as np
from sklearn.datasets import make_classification
# Generar datos de ejemplo
X, y = make_classification(
n_samples=1000, n_features=20, n_informative=10,
n_classes=2, random_state=42)
# Definir espacio de búsqueda
c_space = np.logspace(-5, 8, 15)
param_grid = {'C': c_space}
# Crear modelo
logreg = LogisticRegression()
# Configurar Grid Search con validación cruzada
logreg_cv = GridSearchCV(logreg, param_grid, cv=5)
# Entrenar y encontrar mejores parámetros
logreg_cv.fit(X, y)
print("Mejores parámetros:", logreg_cv.best_params_)
print("Mejor puntuación:", logreg_cv.best_score_)
Random Search: Búsqueda Aleatoria
Random Search selecciona aleatoriamente combinaciones de hiperparámetros de un espacio de búsqueda predefinido.
Ventajas del Random Search
- Eficiencia computacional: Más rápido que Grid Search, especialmente en espacios de alta dimensionalidad
- Mejor en espacios complejos: Puede encontrar mejores soluciones cuando solo algunos hiperparámetros son realmente importantes
- Escalabilidad: El tiempo de ejecución es independiente del tamaño del espacio de búsqueda
Comparación Grid Search vs Random Search
from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
# Definir espacio de parámetros para Random Forest
param_distributions = {
'n_estimators': [10, 50, 100, 200],
'max_depth': [3, 5, 7, 10, None],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
# Grid Search
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_distributions, cv=5, n_jobs=-1
)
# Random Search
random_search = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
param_distributions, n_iter=50, cv=5,
random_state=42, n_jobs=-1
)
# Comparar tiempos de ejecución
import time
start_time = time.time()
grid_search.fit(X, y)
grid_time = time.time() - start_time
start_time = time.time()
random_search.fit(X, y)
random_time = time.time() - start_time
print(f"Grid Search: {grid_time:.2f}s, Best Score: {grid_search.best_score_:.3f}")
print(f"Random Search: {random_time:.2f}s, Best Score: {random_search.best_score_:.3f}")
Descenso del Gradiente
El descenso del gradiente es un algoritmo de optimización fundamental utilizado para minimizar funciones de costo en machine learning.
Principio Básico
El algoritmo actualiza iterativamente los parámetros del modelo en la dirección opuesta al gradiente de la función de costo:
donde:
- son los parámetros del modelo
- es la tasa de aprendizaje (learning rate)
- es el gradiente de la función de costo
Implementación básica
import numpy as np
import matplotlib.pyplot as plt
def gradient_descent(X, y, learning_rate=0.01, n_iterations=1000):
m, n = X.shape
theta = np.zeros(n) # Inicializar parámetros
costs = []
for i in range(n_iterations):
# Predicciones
predictions = X.dot(theta)
# Calcular costo (MSE)
cost = (1/(2*m)) * np.sum((predictions - y)**2)
costs.append(cost)
# Calcular gradientes
gradients = (1/m) * X.T.dot(predictions - y)
# Actualizar parámetros
theta = theta - learning_rate * gradients
return theta, costs
# Ejemplo con datos sintéticos
np.random.seed(42)
m = 100
X = np.random.randn(m, 1)
X_with_bias = np.c_[np.ones((m, 1)), X] # Añadir término de sesgo
y = 4 + 3 * X.ravel() + np.random.randn(m)
# Aplicar descenso del gradiente
theta_optimal, cost_history = gradient_descent(X_with_bias, y)
print(f"Parámetros óptimos: {theta_optimal}")
Gradiente Estocástico (SGD)
El Stochastic Gradient Descent (SGD) es una variante más eficiente que actualiza los parámetros usando un solo ejemplo de entrenamiento en cada iteración.
Diferencias principales con Batch Gradient Descent
| Aspecto | Batch GD | Stochastic GD |
|---|---|---|
| Datos utilizados | Todo el dataset | Una muestra por vez |
| Velocidad de convergencia | Lenta pero estable | Rápida pero ruidosa |
| Uso de memoria | Alto | Bajo |
| Escape de mínimos locales | Difícil | Más fácil |
| Precisión del gradiente | Alta | Baja (ruidosa) |
Implementación de SGD
def stochastic_gradient_descent(X, y, learning_rate=0.01, n_epochs=100):
m, n = X.shape
theta = np.random.randn(n)
costs = []
for epoch in range(n_epochs):
epoch_cost = 0
# Mezclar datos en cada época
indices = np.random.permutation(m)
for i in indices:
# Seleccionar una muestra
xi = X[i:i+1]
yi = y[i:i+1]
# Calcular predicción y error
prediction = xi.dot(theta)
error = prediction - yi
# Calcular gradiente para esta muestra
gradient = xi.T.dot(error)
# Actualizar parámetros
theta = theta - learning_rate * gradient.ravel()
epoch_cost += error**2
costs.append(epoch_cost / m)
return theta, costs
# Aplicar SGD
theta_sgd, cost_history_sgd = stochastic_gradient_descent(X_with_bias, y)
print(f"Parámetros SGD: {theta_sgd}")
Mini-batch Gradient Descent
Una variante híbrida que procesa pequeños grupos de muestras (batches) en cada iteración:
def mini_batch_gradient_descent(X, y, batch_size=32, learning_rate=0.01, n_epochs=100):
m, n = X.shape
theta = np.random.randn(n)
costs = []
for epoch in range(n_epochs):
epoch_cost = 0
# Crear mini-batches
for i in range(0, m, batch_size):
X_batch = X[i:i+batch_size]
y_batch = y[i:i+batch_size]
# Calcular predicciones y costo
predictions = X_batch.dot(theta)
cost = np.mean((predictions - y_batch)**2)
epoch_cost += cost
# Calcular gradientes
gradients = (2/len(X_batch)) * X_batch.T.dot(predictions - y_batch)
# Actualizar parámetros
theta = theta - learning_rate * gradients
costs.append(epoch_cost)
return theta, costs
Learning Rate: Hiperparámetro Crítico
La tasa de aprendizaje controla el tamaño de los pasos que da el algoritmo hacia el mínimo. Es uno de los hiperparámetros más importantes en optimización.
Efectos del Learning Rate
- Learning Rate alto: Convergencia rápida pero puede sobrepasar el mínimo
- Learning Rate bajo: Convergencia lenta pero estable
- Learning Rate adaptativo: Ajusta automáticamente durante el entrenamiento
Ejemplo de comparación de learning rates
# Comparar diferentes learning rates
learning_rates = [0.001, 0.01, 0.1, 0.5]
plt.figure(figsize=(12, 8))
for i, lr in enumerate(learning_rates):
theta, costs = gradient_descent(X_with_bias, y, learning_rate=lr, n_iterations=200)
plt.subplot(2, 2, i+1)
plt.plot(costs)
plt.title(f'Learning Rate = {lr}')
plt.xlabel('Iterations')
plt.ylabel('Cost')
plt.grid(True)
plt.tight_layout()
plt.show()
Técnicas de Learning Rate Adaptativo
class AdaptiveLearningRate:
def __init__(self, initial_lr=0.01, decay_rate=0.95, decay_steps=100):
self.initial_lr = initial_lr
self.decay_rate = decay_rate
self.decay_steps = decay_steps
def exponential_decay(self, step):
return self.initial_lr * (self.decay_rate ** (step // self.decay_steps))
def time_decay(self, step):
return self.initial_lr / (1 + step * 0.001)
def step_decay(self, step):
if step < 50:
return 0.1
elif step < 100:
return 0.01
else:
return 0.001
# Ejemplo de uso
adaptive_lr = AdaptiveLearningRate()
for step in range(200):
current_lr = adaptive_lr.exponential_decay(step)
if step % 50 == 0:
print(f"Step {step}: Learning Rate = {current_lr:.6f}")
Best Estimator: Mejor Configuración
Después de realizar validación cruzada y búsqueda de hiperparámetros, el best estimator representa el modelo con la configuración óptima encontrada.
Obtención del Best Estimator
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import make_classification
from sklearn.metrics import classification_report
# Generar datos
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# Definir parámetros a optimizar
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7, 10],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
# Configurar Grid Search
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid=param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1,
verbose=1
)
# Entrenar y encontrar mejor modelo
grid_search.fit(X, y)
# Obtener el mejor estimador
best_model = grid_search.best_estimator_
print("Mejores parámetros:", grid_search.best_params_)
print("Mejor puntuación CV:", grid_search.best_score_)
# Usar el mejor modelo para predicciones
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# El mejor estimador ya está entrenado con todos los datos
predictions = best_model.predict(X_test)
print("\nReporte de clasificación:")
print(classification_report(y_test, predictions))
Validación final del Best Estimator
# Evaluación más robusta del mejor modelo
from sklearn.model_selection import cross_validate
# Evaluar el mejor modelo con validación cruzada
cv_results = cross_validate(
best_model, X, y,
cv=5,
scoring=['accuracy', 'precision', 'recall', 'f1'],
return_train_score=True
)
print("Resultados de validación cruzada del mejor modelo:")
for metric in ['accuracy', 'precision', 'recall', 'f1']:
test_scores = cv_results[f'test_{metric}']
train_scores = cv_results[f'train_{metric}']
print(f"{metric.upper()}:")
print(f" Test: {test_scores.mean():.3f} ± {test_scores.std():.3f}")
print(f" Train: {train_scores.mean():.3f} ± {train_scores.std():.3f}")
Flujo de trabajo completo
def complete_model_optimization_pipeline(X, y, models, param_grids):
"""
Pipeline completo de optimización de modelos
"""
results = {}
for model_name, (model, param_grid) in zip(models.keys(),
zip(models.values(), param_grids.values())):
print(f"\nOptimizando {model_name}...")
# Grid Search con validación cruzada
grid_search = GridSearchCV(
model, param_grid, cv=5, scoring='accuracy',
n_jobs=-1, verbose=0
)
grid_search.fit(X, y)
# Guardar resultados
results[model_name] = {
'best_estimator': grid_search.best_estimator_,
'best_params': grid_search.best_params_,
'best_score': grid_search.best_score_,
'cv_results': grid_search.cv_results_
}
print(f"Mejores parámetros: {grid_search.best_params_}")
print(f"Mejor puntuación: {grid_search.best_score_:.3f}")
# Encontrar el mejor modelo general
best_model_name = max(results, key=lambda x: results[x]['best_score'])
best_overall_model = results[best_model_name]['best_estimator']
print(f"\nMejor modelo general: {best_model_name}")
print(f"Puntuación: {results[best_model_name]['best_score']:.3f}")
return results, best_overall_model
# Ejemplo de uso
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
models = {
'Random Forest': RandomForestClassifier(random_state=42),
'SVM': SVC(random_state=42),
'Logistic Regression': LogisticRegression(random_state=42, max_iter=1000)
}
param_grids = {
'Random Forest': {'n_estimators': [50, 100], 'max_depth': [3, 5, 7]},
'SVM': {'C': [0.1, 1, 10], 'kernel': ['rbf', 'linear']},
'Logistic Regression': {'C': [0.1, 1, 10], 'solver': ['lbfgs', 'liblinear']}
}
# Ejecutar pipeline
results, best_model = complete_model_optimization_pipeline(X, y, models, param_grids)
La validación cruzada y optimización de hiperparámetros son procesos iterativos que requieren experimentación y comprensión profunda de los algoritmos utilizados. La combinación de estas técnicas permite desarrollar modelos robustos y bien optimizados que generalizan efectivamente a datos no vistos.
Hi :)
Matemáticas
Vectores
Álgebra Lineal
Geometría Analítica
Producto Punto
Espacios Vectoriales
Ortogonalidad
Normalización
Funciones
Álgebra
Composición de Funciones
Función Inversa
Combinación de Funciones
Transformaciones Gráficas
Aplicaciones Económicas
Interés Compuesto
Proporcionalidad
R
Data
Machine Learning
Aprendizaje Supervisado
Inteligencia Artificial
Clasificación
Regresión
Deep Learning