ML/AIMLE·AIMachine Learning Engineering
Machine Learning · Clasificación · Artículo completo

Regresión logística

El clasificador lineal por excelencia: la sigmoide, la entropía cruzada, cómo se entrena por descenso de gradiente y por qué el umbral de decisión es una decisión de negocio.

Resultados obtenidos con scikit-learn 1.8.0 · misma partición que el artículo de Random Forest · random_state = 42 · Laboratorio relacionado

01Qué es

La regresión logística es un modelo de clasificación, a pesar de su nombre. Calcula una suma ponderada de las variables, como la regresión lineal, y la pasa por la función sigmoide, que la convierte en una probabilidad entre 0 y 1. Si la probabilidad supera un umbral (normalmente 0,5), predice la clase positiva.

Es el punto de partida de cualquier problema de clasificación y, además, cada neurona con activación sigmoide de una red es exactamente una regresión logística.

02Intuición

El modelo traza una frontera recta (un hiperplano) que separa las dos clases. Los puntos lejos de la frontera reciben probabilidades cercanas a 0 o a 1; los que están cerca, alrededor de 0,5. Así no solo dice «maligno» o «benigno», sino con cuánta seguridad.

Cada peso indica cuánto cambian las odds (probabilidad a favor frente a en contra) cuando la variable sube una unidad: multiplicarlas por ewje^{w_j}.

Juega con la sigmoide en el laboratorio de la función sigmoide, y mira en el de frontera de decisión cómo una red sin capas ocultas (que es una regresión logística) solo sabe trazar rectas.

03Fundamento matemático

p(y=1∣x)=σ(w⊤x+b)=11+e−(w⊤x+b),log⁡p1−p=w⊤x+bp(y=1\mid x) = \sigma(w^{\top}x + b) = \frac{1}{1 + e^{-(w^{\top}x + b)}}, \qquad \log\frac{p}{1-p} = w^{\top}x + b

Los pesos se eligen maximizando la verosimilitud de los datos, que equivale a minimizar la entropía cruzada (log loss):

L(w)=−1n∑i=1n[yilog⁡pi+(1−yi)log⁡(1−pi)]+λ2∥w∥2L(w) = -\frac{1}{n}\sum_{i=1}^{n}\big[y_i \log p_i + (1-y_i)\log(1-p_i)\big] + \frac{\lambda}{2}\lVert w\rVert^2

Su gradiente tiene una forma sorprendentemente limpia, igual que la de la regresión lineal pero con p en lugar de la predicción:

∇wL=1nX⊤(p−y)+λw\nabla_w L = \frac{1}{n} X^{\top}(p - y) + \lambda w

No existe una fórmula cerrada como la ecuación normal, pero la pérdida es convexa: el descenso de gradiente (o métodos de segundo orden como L-BFGS, el que usa scikit-learn por defecto) llega siempre al mismo mínimo global.

04Algoritmo paso a paso

  1. Estandariza las variables: el optimizador converge mucho mejor y la regularización trata a todas por igual.
  2. Inicializa los pesos a cero.
  3. Calcula la probabilidad de cada ejemplo con la sigmoide.
  4. Calcula el gradiente X⊤(p−y)/nX^{\top}(p-y)/n y actualiza los pesos en sentido contrario.
  5. Repite hasta que la pérdida deje de bajar.
  6. Elige el umbral de decisión según el coste de cada tipo de error.

05Pseudocódigo

función AJUSTAR(X, y, η, λ, iteraciones):
    w ← 0 ; b ← 0
    repetir iteraciones veces:
        p ← σ(X·w + b)
        w ← w − η · ( Xᵀ(p − y)/n + λ·w )
        b ← b − η · media(p − y)
    devolver w, b

función PREDECIR(w, b, x, umbral):
    devolver 1 si σ(w·x + b) ≥ umbral, si no 0

06Implementación desde cero en Python

Python
import numpy as np
from sklearn.preprocessing import StandardScaler

sc = StandardScaler().fit(X_tr)
Xs, Xts = sc.transform(X_tr), sc.transform(X_te)

sigmoide = lambda z: 1 / (1 + np.exp(-z))
w, b, eta, lam = np.zeros(X.shape[1]), 0.0, 0.1, 0.01

for it in range(3000):
    p = sigmoide(Xs @ w + b)                       # probabilidad de la clase 1
    grad_w = Xs.T @ (p - y_tr) / len(y_tr) + lam * w   # gradiente de la entropía cruzada + L2
    grad_b = (p - y_tr).mean()
    w -= eta * grad_w
    b -= eta * grad_b

prob_test = sigmoide(Xts @ w + b)
pred_test = (prob_test >= 0.5).astype(int)
0.00.20.40.601010010003000iteración (escala por posición)
La pérdida empieza en ln 2 = 0,693 (probabilidad 0,5 para todo el mundo, porque los pesos son cero) y baja a 0,071.

Esta implementación consigue un 98,6 % de acierto y un ROC-AUC de 0,9975 en test, prácticamente lo mismo que scikit-learn.

07Implementación con Scikit-learn

Python
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

modelo = make_pipeline(StandardScaler(), LogisticRegression(max_iter=5000))
modelo.fit(X_tr, y_tr)
modelo.predict_proba(X_te)[:, 1]   # probabilidad de «benigno»
modelo[-1].coef_                    # un peso por variable (en unidades estandarizadas)

Sin StandardScaler, el optimizador no converge en 100 iteraciones con estos datos y scikit-learn avisa con un ConvergenceWarning: las variables van desde centésimas (como mean fractal dimension) hasta más de 4.000 (worst area). En scikit-learn 1.8 el parámetro penalty está obsoleto: la regularización se controla con C (inversa de λ) y l1_ratio.

08Dataset de ejemplo

El mismo que en el artículo de Random Forest: Breast Cancer Wisconsin (Diagnostic), 569 biopsias con 30 variables numéricas (212 malignas, 357 benignas), y la misma partición estratificada 75/25 con random_state=42. Así los resultados de los dos modelos son directamente comparables.

09Entrenamiento

Con variables estandarizadas, L-BFGS converge en milisegundos. La regularización por defecto (C = 1) ya funciona bien aquí; en otros problemas conviene buscar C con LogisticRegressionCV.

10Evaluación

Python
from sklearn.metrics import accuracy_score, roc_auc_score, log_loss, confusion_matrix
from sklearn.model_selection import cross_val_score

prob = modelo.predict_proba(X_te)[:, 1]
print(accuracy_score(y_te, prob >= 0.5))     # 0.986
print(roc_auc_score(y_te, prob))             # 0.998
print(log_loss(y_te, prob))                  # 0.068
print(confusion_matrix(y_te, prob >= 0.5))   # [[52  1]
                                             #  [ 1 89]]
Modelo (misma partición)Acierto en testROC-AUCValidación cruzada (5)
Árbol de decisión92,3 %——
Random Forest, 300 árboles95,8 %0,99596,1 % ± 2,5
Regresión logística98,6 %0,99898,1 % ± 0,7

El modelo lineal gana al bosque en estos datos. No es una rareza: las 30 variables ya son medidas bien elegidas de los núcleos celulares y la frontera entre clases es casi lineal. Un modelo más flexible no aporta nada y añade varianza. Por eso siempre hay que probar primero el modelo sencillo.

11Métricas

log loss=−1n∑[ylog⁡p+(1−y)log⁡(1−p)],AUC=P(p+>p−)\text{log loss} = -\frac{1}{n}\sum [y\log p + (1-y)\log(1-p)], \qquad \text{AUC} = P(p_{+} > p_{-})

El ROC-AUC es la probabilidad de que un caso positivo elegido al azar reciba más probabilidad que uno negativo: 0,998 significa que el modelo ordena casi perfectamente. El log loss mide además si las probabilidades son fiables, no solo si el orden es bueno.

El umbral es una decisión, no un dato

Python
prob_maligno = 1 - modelo.predict_proba(X_te)[:, 1]
for umbral in (0.5, 0.3, 0.1):
    alerta = prob_maligno >= umbral
    print(umbral, (alerta & (y_te == 0)).sum(), (alerta & (y_te == 1)).sum())
Umbral de alerta (prob. de maligno)Malignos detectadosFalsas alarmas
0,552 de 531
0,352 de 536
0,153 de 5311

Con el umbral por defecto se escapa un tumor maligno. Bajando el umbral al 10 % se detectan los 53, a cambio de 11 falsas alarmas entre 90 casos benignos. Qué umbral elegir depende del coste de cada error, y eso lo decide el problema, no el modelo.

12Visualización

worst texture−1.25
radius error−1.07
worst symmetry−0.96
worst concave points−0.94
area error−0.94
worst area−0.93
Los 6 coeficientes de mayor magnitud (variables estandarizadas). Todos son negativos: valores altos empujan hacia «maligno» (clase 0).
90 %93 %96 %99 %0,0010,010,1110100C = 1/λ (menos regularización hacia la derecha)C = 1
Acierto en test según la regularización. Con C muy pequeño el modelo está demasiado restringido; con C muy grande sobreajusta. El óptimo cae en el valor por defecto.

13Ventajas

  • Da probabilidades, normalmente bien calibradas.
  • Rápida, estable y con un único óptimo.
  • Interpretable mediante los coeficientes y los odds ratios.
  • Funciona muy bien con muchas variables y pocos datos si se regulariza.
  • Se extiende a varias clases con softmax (regresión logística multinomial).

14Limitaciones

  • Frontera lineal: no captura interacciones ni relaciones curvas sin ingeniería de variables.
  • Sensible a variables en escalas muy distintas (hay que estandarizar).
  • Con clases perfectamente separables, sin regularización los pesos crecen sin límite.
  • Los coeficientes pierden interpretabilidad con variables muy correlacionadas.

15Hiperparámetros

ParámetroPor defectoEfecto
C1.0Inversa de la fuerza de regularización. Menor C, pesos más pequeños.
l1_ratio0.00 = L2 (Ridge), 1 = L1 (Lasso); valores intermedios, Elastic Net (con solver saga).
solver"lbfgs"Método de optimización. "saga" admite L1 y datasets grandes.
max_iter100Iteraciones máximas del optimizador. Súbelo si aparece ConvergenceWarning.
class_weightNone"balanced" compensa clases desbalanceadas.
penalty"deprecated"Obsoleto desde scikit-learn 1.8: usa C y l1_ratio.

16Casos de uso

  • Scoring de crédito y de riesgo, donde se exige explicar cada decisión.
  • Diagnóstico médico con variables clínicas.
  • Predicción de clics o conversiones en publicidad.
  • Clasificación de texto con variables TF-IDF (spam, sentimiento).
  • Línea base para cualquier clasificador.

17Aplicaciones industriales

  • Telecomunicaciones: probabilidad de baja de cada cliente para priorizar retenciones.
  • Banca: modelos de riesgo regulados, que deben ser explicables.
  • Industria: probabilidad de pieza defectuosa a partir de parámetros de proceso, con el umbral fijado por el coste de inspección.
  • Seguridad: primera capa de detección de fraude o intrusiones.

18Ejercicio práctico

Dibuja la curva precisión-recall de la clase maligna y encuentra el umbral más alto que detecta los 53 casos. Compara sus falsas alarmas con las del Random Forest en el mismo punto.

Plantilla
# Ejercicio: la curva precisión-recall de la clase maligna.
from sklearn.metrics import precision_recall_curve
prec, rec, umbrales = precision_recall_curve(y_te == 0, 1 - modelo.predict_proba(X_te)[:, 1])
# 1) Dibuja rec frente a prec.
# 2) Encuentra el umbral más alto que da recall = 1 y cuenta sus falsas alarmas.

19Código completo

Todos los experimentos de esta página, listos para ejecutar:

20Referencias y papers

  1. J. Berkson (1944). «Application of the Logistic Function to Bio-Assay». Journal of the American Statistical Association, 39(227), 357–365.
  2. D. R. Cox (1958). «The Regression Analysis of Binary Sequences». Journal of the Royal Statistical Society, Series B, 20(2), 215–242.
  3. W. N. Street, W. H. Wolberg, O. L. Mangasarian (1993). «Nuclear feature extraction for breast tumor diagnosis». Proc. SPIE 1905, Biomedical Image Processing and Biomedical Visualization. (Origen del dataset).
  4. D. W. Hosmer, S. Lemeshow, R. X. Sturdivant (2013). Applied Logistic Regression, 3.ª ed. Wiley.
  5. T. Hastie, R. Tibshirani, J. Friedman (2009). The Elements of Statistical Learning, 2.ª ed., capítulo 4. Springer.