Machine Learning con Regresión Logística en Python
Aprende cómo funciona la regresión logística, cómo entrenar clasificadores binarios y multiclase en Python con scikit-learn, evaluarlos y ajustar la regularización.
La regresión logística es un algoritmo de clasificación supervisada que estima la probabilidad de que una muestra pertenezca a una clase determinada. A pesar de la palabra "regresión" en su nombre, se utiliza para tareas de clasificación — predecir una etiqueta categórica como spam/no-spam, enfermedad/sano o clic/sin-clic.
Este capítulo cubre:
- Cómo funciona la regresión logística (la función sigmoide y el log-odds)
- Construcción de un clasificador binario en Python con
scikit-learn - Evaluación de un clasificador más allá de la precisión bruta
- Manejo de problemas multiclase
- Regularización y cuándo ajustarla
- Escalado de características y por qué importa
- Cuándo usar regresión logística frente a otros clasificadores
Cómo Funciona la Regresión Logística
De la Regresión Lineal a las Probabilidades
La regresión lineal predice un valor continuo. Si intentaras usarla para clasificación, las predicciones podrían caer fuera del rango [0, 1], lo que las haría imposibles de interpretar como probabilidades. La regresión logística resuelve esto pasando la combinación lineal por la función sigmoide:
σ(z) = 1 / (1 + e^(-z))Donde z = w₀ + w₁x₁ + w₂x₂ + … + wₙxₙ es la suma ponderada de las características de entrada. La sigmoide comprime cualquier número real al rango (0, 1), proporcionando una estimación de probabilidad válida.
Frontera de Decisión
El modelo predice la clase 1 cuando la probabilidad supera un umbral (0.5 por defecto), y la clase 0 en caso contrario:
ŷ = 1 if σ(z) ≥ 0.5
ŷ = 0 if σ(z) < 0.5El umbral σ(z) = 0.5 corresponde a z = 0, lo que define la frontera de decisión — un hiperplano en el espacio de características que separa las dos clases.
Log-Odds (Logit)
Tomar el logaritmo del cociente de probabilidades muestra por qué el modelo es lineal en los parámetros:
log(p / (1 - p)) = w₀ + w₁x₁ + … + wₙxₙCada coeficiente wᵢ representa el cambio en el log-odds por un aumento de una unidad en la característica xᵢ, manteniendo constantes las demás características. Esto hace que la regresión logística sea interpretable.
Cómo Se Aprenden los Parámetros
A diferencia de la regresión lineal, no existe una solución de forma cerrada. El modelo minimiza la función de coste log-loss (entropía cruzada) mediante un optimizador iterativo (por defecto lbfgs en scikit-learn):
Loss = -1/m Σ [ yᵢ log(p̂ᵢ) + (1 - yᵢ) log(1 - p̂ᵢ) ]Un log-loss menor significa que las probabilidades predichas están mejor calibradas con las etiquetas verdaderas.
Clasificación Binaria en Python
El siguiente ejemplo utiliza el conjunto de datos Breast Cancer Wisconsin — 569 muestras, 30 características numéricas, objetivo binario (maligno = 1, benigno = 0). Se incluye con scikit-learn, por lo que no se necesitan archivos externos.
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report
# 1. Load data
data = load_breast_cancer()
X, y = data.data, data.target # X: (569, 30) y: 0=malignant, 1=benign
# 2. Split into train (80 %) and test (20 %)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. Scale features — logistic regression converges faster and more reliably
# when features are on a similar scale
scaler = StandardScaler()
X_train_sc = scaler.fit_transform(X_train)
X_test_sc = scaler.transform(X_test)
# 4. Train
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(X_train_sc, y_train)
# 5. Evaluate
y_pred = clf.predict(X_test_sc)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
print(classification_report(y_test, y_pred, target_names=data.target_names))Salida esperada:
Accuracy: 0.974
precision recall f1-score support
malignant 0.98 0.95 0.96 43
benign 0.97 0.99 0.98 71
accuracy 0.97 114
macro avg 0.97 0.97 0.97 114
weighted avg 0.97 0.97 0.97 114El modelo alcanza ~97 % de precisión en datos no vistos. Ten en cuenta que LogisticRegression de scikit-learn añade regularización L2 por defecto (C=1.0), lo que favorece la generalización.
Por Qué Importa el Escalado de Características
La regresión logística utiliza optimización basada en gradiente. Sin escalado, una característica con valores grandes (p. ej., radio medio ~14) domina las actualizaciones del gradiente, ralentizando la convergencia o provocando fallos en el solver. StandardScaler transforma cada característica a media cero y varianza unitaria.
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Without scaling — needs more iterations, may warn about convergence
clf_raw = LogisticRegression(max_iter=200, random_state=42)
clf_raw.fit(X_train, y_train)
print(f"Unscaled accuracy : {clf_raw.score(X_test, y_test):.3f}")
# With scaling
scaler = StandardScaler()
clf_sc = LogisticRegression(max_iter=200, random_state=42)
clf_sc.fit(scaler.fit_transform(X_train), y_train)
print(f"Scaled accuracy : {clf_sc.score(scaler.transform(X_test), y_test):.3f}")Ajusta siempre el escalador únicamente en el conjunto de entrenamiento y luego utiliza ese mismo escalador ajustado para transformar tanto el conjunto de entrenamiento como el de prueba — esto previene la fuga de datos.
Evaluación de un Clasificador
La precisión por sí sola puede ser engañosa cuando las clases están desbalanceadas. Usa una matriz de confusión y las métricas derivadas de ella.
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_sc = scaler.fit_transform(X_train)
X_test_sc = scaler.transform(X_test)
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(X_train_sc, y_train)
y_pred = clf.predict(X_test_sc)
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(cm, display_labels=data.target_names)
disp.plot(cmap="Blues")
plt.title("Logistic Regression — Breast Cancer")
plt.tight_layout()
plt.savefig("confusion_matrix.png", dpi=150)
plt.show()Métricas clave derivadas de la matriz de confusión:
| Métrica | Fórmula | Significado |
|---|---|---|
| Precisión | TP / (TP + FP) | De todos los positivos predichos, cuántos son verdaderamente positivos |
| Recall (Sensibilidad) | TP / (TP + FN) | De todos los positivos reales, cuántos detectó el modelo |
| F1-score | 2 × (P × R) / (P + R) | Media armónica de precisión y recall |
| Especificidad | TN / (TN + FP) | De todos los negativos reales, cuántos rechazó correctamente el modelo |
En diagnóstico médico, el recall (sensibilidad) suele ser más importante que la precisión — un caso maligno no detectado es peor que una falsa alarma.
Puntuaciones de Probabilidad y la Curva AUC-ROC
En lugar de una predicción discreta, puedes obtener la probabilidad de la clase positiva con predict_proba():
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(scaler.fit_transform(X_train), y_train)
# Probability of the positive class (benign = 1)
y_proba = clf.predict_proba(scaler.transform(X_test))[:, 1]
print(f"AUC-ROC: {roc_auc_score(y_test, y_proba):.3f}")Salida esperada:
AUC-ROC: 0.997Un AUC cercano a 1.0 significa que el modelo clasifica las muestras positivas por encima de las negativas casi a la perfección. Consulta el capítulo Curva AUC-ROC para ver cómo trazar e interpretar la curva completa.
Clasificación Multiclase
Cuando el objetivo tiene más de dos clases, scikit-learn extiende la regresión logística automáticamente. A partir de scikit-learn 1.5, el solver lbfgs siempre utiliza el enfoque multinomial (softmax), que entrena un único modelo con una capa de salida softmax y minimiza la entropía cruzada sobre todas las clases de forma conjunta. Esto generalmente es más preciso que la antigua estrategia One-vs-Rest (OvR), que entrenaba un clasificador binario separado por clase.
El conjunto de datos Iris tiene tres especies de flores — un ejemplo natural de problema multiclase:
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
data = load_iris()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_sc = scaler.fit_transform(X_train)
X_test_sc = scaler.transform(X_test)
# From scikit-learn 1.5+, multinomial softmax is the default for lbfgs
clf = LogisticRegression(solver="lbfgs", max_iter=1000, random_state=42)
clf.fit(X_train_sc, y_train)
y_pred = clf.predict(X_test_sc)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
# Class probabilities for the first three test samples
print("\nClass probabilities (first 3 samples):")
for proba in clf.predict_proba(X_test_sc)[:3]:
print([f"{p:.3f}" for p in proba])Salida esperada:
Accuracy: 1.000
Class probabilities (first 3 samples):
['0.011', '0.876', '0.113']
['0.964', '0.036', '0.000']
['0.000', '0.003', '0.997']Regularización
La regularización penaliza los coeficientes grandes para evitar el sobreajuste. scikit-learn ofrece dos tipos mediante el parámetro penalty:
| Parámetro | Tipo | Efecto |
|---|---|---|
penalty='l2' (por defecto) | Ridge | Reduce todos los coeficientes hacia cero; conserva todas las características |
penalty='l1' | Lasso | Lleva algunos coeficientes exactamente a cero; selección implícita de características |
penalty='elasticnet' | Mezcla | Combina L1 y L2; requiere solver='saga' |
penalty=None | Ninguna | Sin regularización; usar solo si los datos son grandes y limpios |
La intensidad de la regularización se controla con C (el inverso de la fuerza de regularización — un C más pequeño significa una regularización más fuerte):
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
data = load_breast_cancer()
X, y = data.data, data.target
results = {}
for C in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]:
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(C=C, max_iter=1000, random_state=42)),
])
scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
results[C] = scores.mean()
print(f"C={C:7.3f} CV accuracy: {scores.mean():.4f} ± {scores.std():.4f}")Esto utiliza validación cruzada para encontrar el valor de C que mejor generaliza. Para una búsqueda sistemática sobre múltiples hiperparámetros, consulta Grid Search.
Uso de un Pipeline
Un Pipeline encadena el preprocesamiento y el modelo en un único objeto. Esto previene fugas de datos accidentales y simplifica la validación cruzada y el despliegue:
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(C=1.0, max_iter=1000, random_state=42)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
# Predict probabilities on a new sample (raw, unscaled)
new_sample = X_test[:1] # first test sample
print(f"Predicted class : {pipe.predict(new_sample)[0]}")
print(f"Class probability : {pipe.predict_proba(new_sample)[0]}")Salida esperada:
Accuracy: 0.974
Predicted class : 1
Class probability : [0.11359025 0.88640975]El pipeline gestiona el escalado internamente — llamas a predict() con los valores de características sin procesar.
Inspección de Coeficientes
Los coeficientes entrenados revelan qué características empujan la predicción hacia cada clase. Los valores absolutos mayores indican una influencia más fuerte:
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import numpy as np
data = load_breast_cancer()
X, y = data.data, data.target
scaler = StandardScaler()
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(scaler.fit_transform(X), y)
# Sort by absolute coefficient value
coefs = clf.coef_[0] # shape (n_features,) for binary classification
sorted_idx = np.argsort(np.abs(coefs))[::-1]
print(f"{'Feature':<35} {'Coefficient':>12}")
print("-" * 48)
for i in sorted_idx[:5]:
print(f"{data.feature_names[i]:<35} {coefs[i]:>12.4f}")Salida esperada (top 5 características por peso absoluto):
Feature Coefficient
------------------------------------------------
worst texture -1.3206
radius error -1.2893
worst radius -1.0266
area error -0.9989
worst area -0.9947Los coeficientes negativos (después del escalado) empujan hacia la clase 0 (maligno); los coeficientes positivos empujan hacia la clase 1 (benigno).
Ventajas y Limitaciones
Cuándo usar la regresión logística
- Necesitas estimaciones de probabilidad, no solo etiquetas de clase.
- La relación entre las características y el log-odds es aproximadamente lineal.
- Necesitas un modelo interpretable — los coeficientes tienen significado.
- Como línea base rápida antes de probar modelos más complejos como Árboles de Decisión o métodos de conjunto.
- Los conjuntos de datos son grandes (la regresión logística escala bien con muchas muestras).
Limitaciones
| Limitación | Mitigación |
|---|---|
| Asume una frontera de decisión lineal | Usa características polinómicas o cambia a Árbol de Decisión / K-Vecinos Más Cercanos |
| Sensible al escalado de características | Aplica siempre StandardScaler o MinMaxScaler |
| Problemas con características altamente correlacionadas | Elimínalas o regulariza con L1 (penalty='l1') |
| No es adecuada para interacciones de características muy complejas | Usa métodos de conjunto o redes neuronales |
Regresión Logística vs. Clasificadores Relacionados
| Algoritmo | Frontera de decisión | Escalado necesario | Salida probabilística |
|---|---|---|---|
| Regresión Logística | Lineal | Sí | Sí (calibrada) |
| Árbol de Decisión | No lineal (alineada con ejes) | No | Sí (menos calibrada) |
| K-Vecinos Más Cercanos | No lineal (basada en instancias) | Sí | Sí |
| Regresión Lineal | Lineal (salida continua) | Sí | No |
Conclusiones Clave
- La regresión logística estima una probabilidad mediante la función sigmoide; la clase se asigna aplicando un umbral a esa probabilidad.
- Escala siempre las características con
StandardScalerantes de entrenar — acelera la convergencia y mejora la precisión. - Usa un Pipeline para agrupar el escalado y el modelo; evita la fuga de datos y simplifica el despliegue.
- Evalúa con precisión, recall, F1 y AUC-ROC en lugar de solo la exactitud, especialmente con datos desbalanceados. Consulta los capítulos Matriz de Confusión y Curva AUC-ROC.
- Controla el sobreajuste con el parámetro
C(menor = regularización más fuerte); usa validación cruzada o grid search para ajustarlo. - Para problemas multiclase, usa
solver="lbfgs"(el valor por defecto); scikit-learn 1.5+ siempre usa softmax (multinomial), que maneja bien las clases superpuestas.