Acerca de este Codelab ...

¡Bienvenidos a esta microtutorial sobre Regresión Logística!

Con este tutorial se pretende presentar los conceptos claves de este método ampliamente utilizado en el campo de la analítica predictiva y machine learning. Queremos hacerlo de una manera sencilla y accesible, utilizando ejemplos simples y prácticos.

En este tutorial vamos a incluir elementos teóricos y de programación para que puedas entender y aplicar el método de Regresión Logística en tus propios proyectos.

A lo largo del tutorial van a encontrar notas aclaratorias, ejemplos, ejercicios y advertencias como las que se presentan a continuación:

Las cuales se van a utilizar para resaltar información relevante que deberán tener en cuenta a lo largo del tutorial.

Resumen General y Ruta de Aprendizaje

Al finalizar este tutorial, el lector podrá:

Para lograrlo hemos decidido dividir este tutorial en tres grandes partes:

  1. Se presentará una definición, un contexto histórico y se repasarán unos conceptos matemáticos necesarios para entender mejor el modelo de Regresión Logística.
  2. Se explicará paso a paso el método de Regresión Logistica, argumentando matemáticamente cada una de las definiciones y fórmulas del modelo.
  3. Finalmente se realizará una implementación práctica del algoritmo de Regresión Logística en Python, mediante el uso de la plataforma de Google Colab.

Infografía

La Regresión Logística es un método estadístico que se utiliza para predecir la probabilidad de que un evento ocurra o no, basándose en variables predictoras. Es una técnica de análisis de datos que se utiliza en el campo de la analítica predictiva y machine learning.

La principal aplicación de los modelos de Regresión Logística se encuentra en problemas de clasificación binaria. Esto es, problemas donde el modelo debe predecir entre dos escenarios posibles, como por ejemplo, si un cliente va a comprar un producto o no, si un paciente tiene una enfermedad o no, etc.

Es un método de aprendizaje supervisado, es decir, requiere de datos que previamente hayan sido etiquetados por un humano. Así, si quiero predecir si un cliente va a comprar un producto o no, necesito datos de clientes que ya hayan comprado el producto y datos de clientes que no hayan comprado el producto.

También es un metodo basado en parámetros, por lo tanto, en el proceso de entrenamiento se van a ajustar los párametros de lo modelo. Dichos parámetros son los mismos de una regresión lineal, es decir, $\theta_0,~\theta_1,~\theta_2,~\ldots,~\theta_n$.

Imagen 1

Hay tres conceptos claves para entender mejor la Regresión Logística (los cuales no serán explicados en detalle en este tutorial):

  1. Propiedades de los logaritmos.
  2. Regla de la cadena.
  3. Gradiente Descendente.

Propiedades de los logaritmos

En esta parte vamos a repasar algunas de las propiedades más importantes de los logaritmos que nos van a ayudar a entender mejor el funcionamiento de las ecuaciones que rigen el modelo de Regresión Logística.

En Machine Learning (y específicamente en Regresión Logística), usamos logaritmos para convertir multiplicaciones complejas de probabilidades en sumas simples, lo que facilita enormemente el cálculo de la derivada.

Estas propiedades nos permiten manipular las ecuaciones de la función de coste.

Regla de la cadena

La Regla de la Cadena permite derivar funciones compuestas ("una función dentro de otra"). Es la base matemática del Backpropagation en redes neuronales, ya que nos permite entender cómo un cambio en los pesos iniciales afecta el error final a través de muchas capas.

Si tenemos una variable $y$ que depende de $x$, y a su vez $x$ depende de $t$, es decir, $y = f(x)$ y $x = g(t)$, la derivada de $y$ respecto a $t$ es el producto de las derivadas individuales:

$$\frac{dy}{dt} = \frac{dy}{dx} \cdot \frac{dx}{dt}$$

Gradiente Descendente

Es un algoritmo de optimización iterativo que busca minimizar la función de coste ajustando los pesos del modelo paso a paso en dirección opuesta a la pendiente del error.

La ecuación que rige el gradiente descendente es la siguiente:

$$\theta_{nuevo} = \theta_{viejo} - \alpha \frac{dE_T}{d\theta}$$

Donde,

Cuando el modelo tiene múltiples parámetros (como $\theta_0$ para el sesgo y $\theta_1$ para el peso), se usan **derivadas parciales **.

$$\theta_j = \theta_j - \alpha \frac{\partial E_T}{\partial \theta_j}$$

Esto significa que el algoritmo calcula cómo afecta cada peso individualmente al error y los actualiza todos al mismo tiempo.

La Regresión logística es un método simple para realizar clasificación biclase. Para que el método funcione se requiere que los datos sean linealmente separables, esto es, que exista una recta en el espacio n-dimenesional $\mathbb{R}^n$ que separe los datos en dos clases.

Regresión Logística

Durante el proceso de entrenamiento la pertenencia a una clase u otra se define mediante probabilidades. Entonces, lo que se busca es encontrar una ecuación de una recta que separe los datos en dos clases.

Recta de separación

Los datos que estén muy alejados a esta recta, en una dirección u otra, indicarán que el modelo tiene mucha certeza de la pertenencia de ese dato a una clase específica. Por el contrario datos cercanos a la recta de separación indican cierta incertidumbre de lo modelo. Por tanto, el método de regresión logística convierte estas distancias en probabilidades mediante el uso de la función sigmoide.

Función sigmoide

Partimos de una regresión lineal múltiple: $$z = \Theta^T x = \theta_0 x_0 + \theta_1 x_1 + \dots + \theta_d x_d$$

Usamos la Relación de Probabilidades: $$RP = \frac{p}{1-p}$$ (Donde $p$ es la probabilidad de éxito. Si $p \to 1$, $RP \to \infty$).

Aplicamos logaritmo natural al RP para linealizar el rango: $$z = \text{logit}(p) = \log\left(\frac{p}{1-p}\right)$$

Para obtener la probabilidad $p$ (o $\phi(z)$) a partir de $z$, despejamos:

  1. Extraemos la exponencial a ambos lados: $$e^z = \frac{\phi(z)}{1-\phi(z)}$$
  2. Despejamos $\phi(z)$: $$\phi(z) = \frac{1}{1 + e^{-z}}$$

Sustituyendo $z$ por la combinación lineal $\theta^T x$: $$\phi(z) = \frac{1}{1 + e^{-\theta^T x}}$$

El objetivo final será maximizar la probabilidad condicional: $$L(\Theta) = P(y|x; \theta)$$

Suponiendo independencia de los eventos de salida respecto a las observaciones, $L$ se puede definir como:

$$L(\theta) = \prod_{i=1}^{m} [\phi(z)]^{(i)} [1 - \phi(z)]^{1-y^{(i)}}$$

Por lo tanto, se debe buscar un algoritmo que ayude a maximizar la función $l(\theta)$ o, lo que es lo mismo, minimizar $-l(\theta)$. De esta manera se define la función de coste para la regresión logística como:

$$ J(\phi(z), y; \theta) = -\log(L(\theta)) $$

$$ J(\phi(z), y; \theta) = - \left[ \sum_{i=1}^{m} y^{(i)} \log([\phi(z)]) + (1 - y^{(i)}) \log([1 - \phi(z)]) \right] $$

De una forma más sintética la función de coste se puede escribir como:

$$ J(\phi(z), y; \theta) = \begin{cases} -\log(\phi(z)), & si \ y = 1 \
-\log(1 - \phi(z)), & si \ y = 0 \end{cases} $$

Para optimizar la función de coste se utilizará el algoritmo de Gradiente Descendente:

$$ \frac{\partial J(\phi(z), y; \theta)}{\partial \theta_j} = - \left[ \sum_{i=1}^{m} \frac{y^{(i)}}{\phi(z)} \frac{\partial \phi(z)}{\partial \theta_j} - \frac{1 - y^{(i)}}{1 - \phi(z)} \frac{\partial \phi(z)}{\partial \theta_j} \right] $$

Inicialmente se calcula $\frac{\partial \phi(z)}{\partial z}$, teniendo en cuenta que $\phi(z) = \frac{1}{1+e^{-z}}$:

$$ \frac{\partial \phi(z)}{\partial z} = \frac{e^{-z}}{(1 + e^{-z})^2} = \frac{1}{1 + e^{-z}} \left( 1 - \frac{1}{1 + e^{-z}} \right) $$

$$ \frac{\partial \phi(z)}{\partial z} = \phi(z)(1 - \phi(z)) $$

El paso siguiente es calcular $\frac{\partial z}{\partial \theta_j}$, así:

$$ \frac{\partial z}{\partial \theta_j} = \frac{\partial}{\partial \theta_j} (\theta_0 x_0 + \theta_1 x_1 + \dots + \theta_d x_d) = x_j $$

Por regla de la cadena:

$$ \frac{\partial \phi(z)}{\partial \theta_j} = \frac{\partial \phi(z)}{\partial z} \frac{\partial z}{\partial \theta_j} = \phi(z)(1 - \phi(z))x_j $$

Finalmente se remplaza este resultado en la ecuación del Gradiente Descendente:

Para optimizar la función de coste se utilizará el algoritmo de Gradiente Descendente:

$$ \frac{\partial J(\phi(z), y; \theta)}{\partial \theta_j} = - \left[ \sum_{i=1}^{m} \frac{y^{(i)}}{\phi(z)} \frac{\partial \phi(z)}{\partial \theta_j} - \frac{1 - y^{(i)}}{1 - \phi(z)} \frac{\partial \phi(z)}{\partial \theta_j} \right] $$

Inicialmente se calcula $\frac{\partial \phi(z)}{\partial z}$, teniendo en cuenta que $\phi(z) = \frac{1}{1+e^{-z}}$:

$$ \frac{\partial \phi(z)}{\partial z} = \frac{e^{-z}}{(1 + e^{-z})^2} = \frac{1}{1 + e^{-z}} \left( 1 - \frac{1}{1 + e^{-z}} \right) $$

$$ \frac{\partial \phi(z)}{\partial z} = \phi(z)(1 - \phi(z)) $$

El paso siguiente es calcular $\frac{\partial z}{\partial \theta_j}$, así:

$$ \frac{\partial z}{\partial \theta_j} = \frac{\partial}{\partial \theta_j} (\theta_0 x_0 + \theta_1 x_1 + \dots + \theta_d x_d) = x_j $$

Por regla de la cadena:

$$ \frac{\partial \phi(z)}{\partial \theta_j} = \frac{\partial \phi(z)}{\partial z} \frac{\partial z}{\partial \theta_j} = \phi(z)(1 - \phi(z))x_j $$

Finalmente se remplaza este resultado en la ecuación del Gradiente Descendente:

$$ \frac{\partial J(\phi(z), y; \theta)}{\partial \theta_j} = - \left[ \sum_{i=1}^{m} \frac{y^{(i)} \partial \phi(z)}{\phi(z) \partial \theta_j} - \frac{1 - y^{(i)} \partial \phi(z)}{1 - \phi(z) \partial \theta_j} \right] $$

$$ \frac{\partial J(\phi(z), y; \theta)}{\partial \theta_j} = - \sum_{i=1}^{m} [y^{(i)}(1 - \phi(z)) - (1 - y^{(i)})\phi(z)]x_j $$

$$ \frac{\partial J(\phi(z), y; \theta)}{\partial \theta_j} = - \sum_{i=1}^{m} [y^{(i)} - \phi(z^{(i)})]x_j $$

Y la ecuación de Gradiente Descendente será:

$$ \theta_j = \theta_j - \alpha \frac{\partial J}{\partial \theta_j} $$

La cual puede ser representada como:

$$ \theta_j = \theta_j + \alpha \sum_{i=1}^{m} [y^{(i)} - \phi(z^{(i)})]x_j^{(i)} = \theta_j - \eta \sum_{i=1}^{m} [\phi(z^{(i)}) - y^{(i)}]x_j^{(i)} $$

¿Y qué métricas de desempeño se usan?

Métricas Generales:

Accuracy (Acc): $$ Acc = \frac{TP + TN}{TP + TN + FP + FN} $$

Sensibilidad (Sen): $$ Sen = \frac{TP}{TP + FN} $$

Especificidad (Esp): $$ Esp = \frac{TN}{TN + FP} $$

Precisión (Pre): $$ Pre = \frac{TP}{TP + FP} $$

$$ F1_{score} = 2 \times \frac{Pre \times recall}{Pre + recall} $$

A continuación, presentaremos un paso a paso de cómo implementar un modelo de regresión logística usando Python usando Google Colab.

Primero, importamos todas las bibliotecas necesarias. Utilizaremos Pandas y Numpy para la manipulación de datos, Matplotlib y Seaborn para la visualización, y varios módulos de Scikit-Learn para construir y evaluar nuestro modelo de regresión logística.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.metrics import ConfusionMatrixDisplay
from sklearn.preprocessing import StandardScaler

plt.rc('font', size=14)

A continuación, definimos los nombres de las columnas para nuestro dataset, cargamos el archivo CSV desde la ruta especificada (en este caso, Google Drive) y asignamos los nombres de columna correctos al DataFrame. Finalmente, mostramos las primeras filas para verificar que los datos se cargaron correctamente.

col_names = ['pregnant', 'glucose', 'bp', 'skin', 'insulin', 'bmi', 'pedigree', 'age', 'label']
path = 'drive/MyDrive/Datos/'
pima = pd.read_csv(path + 'diabetes.csv')
pima=pima.set_axis(col_names, axis=1)
pima.head()

Utilizamos seaborn para crear un gráfico conjunto (jointplot). Esto nos permite visualizar la relación bivariada entre los niveles de ‘glucosa' y la ‘edad', diferenciando los puntos por su ‘etiqueta' (si tienen diabetes o no). Además, superponemos un gráfico de densidad (KDE) para ver dónde se concentran más los datos.

g= sns.jointplot(data=pima, x='glucose', y='age', hue='label', height=8)
g.plot_joint(sns.kdeplot, zorder=0, n_levels=8);

Jointplot

Se prepara los datos para el entrenamiento del modelo dividiéndolos en dos componentes: la matriz de características $X$ y el vector objetivo $y$.

X= pima.drop('label', axis=1)
y= pima.label
X.shape

Se prepara los datos para el aprendizaje automático dividiéndolos primero en un conjunto de entrenamiento (75%) para enseñar al modelo y uno de prueba (25%). Luego, utiliza StandardScaler para poner todas las variables en la misma escala numérica (normalización); el punto clave es que el escalador "aprende" las estadísticas solo de los datos de entrenamiento (fit_transform) y luego aplica esas mismas reglas exactas a los datos de prueba (transform), asegurando que el modelo se enfrente a la evaluación como si fueran datos del mundo real.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=4)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

Se entrena el modelo de Regresión Logística con los datos de entrenamiento y se realiza la predicción en el conjunto de prueba.

logreg= LogisticRegression()
logreg.fit(X_train, y_train)
y_pred= logreg.predict(X_test)

Se evalúa el rendimiento del modelo imprimiendo la exactitud del modelo en el conjunto de entrenamiento y de prueba.

print('Accuracy of Logistic Regression  classifier on training set: {:.2f}'
.format(logreg.score(X_train, y_train)))
print('Accuracy of Logistic Regression classifier on test set: {:.2f}'
.format(logreg.score(X_test, y_test)))

Se evalúa el rendimiento del modelo imprimiendo la matriz de confusión y las diferentes métrices de desempeño.

print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))

Se grafica la matriz de confusión.

cm = confusion_matrix(y_test, y_pred)
cmn = confusion_matrix(y_test, y_pred, normalize='true')
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=logreg.classes_)
dispn = ConfusionMatrixDisplay(confusion_matrix=cmn, display_labels=logreg.classes_)
fig, (ax_nonorm, ax_norm) = plt.subplots(1,2, figsize=(15, 5.5))
disp.plot(cmap='GnBu', ax=ax_nonorm)
dispn.plot(cmap='GnBu', ax=ax_norm)
ax_nonorm.set_title('Matriz de confusión sin normalizar');
ax_norm.set_title('Matriz de confusión normalizada');

Matriz de confusión

Se grafica la curva ROC.

plt.figure(figsize=(7,7))
y_pred_proba = logreg.predict_proba(X_test)[:,1]
fpr, tpr, _ = roc_curve(y_test, y_pred_proba)
auc = roc_auc_score(y_test, y_pred_proba)
plt.plot(fpr, tpr, label="Logistic Regression Model, auc={:.2f}".format(auc))
plt.plot([0, 1], [0, 1], color='red', linestyle='--', lw=2, label='Random Classifier')
plt.title('ROC Curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.005])
plt.legend(loc=4);

Curva ROC

Este bloque de código entrena un modelo de Regresión Logística usando solo dos variables (columnas 5 y 6, que corresponden al BMI (Índice de Masa Corporal) y la Función de Pedigree (Función genética)) para generar una visualización de la frontera de decisión.

Primero, crea una cuadrícula fina de puntos (meshgrid) que cubre todo el gráfico y calcula la probabilidad de ser "Enfermo" en cada punto del fondo (pcolormesh), coloreando las áreas de decisión. Finalmente, superpone los datos reales (scatter) y dibuja una línea roja discontinua (contour) justo donde la probabilidad es del 50%, marcando visualmente el límite matemático exacto que separa a los pacientes clasificados como sanos de los enfermos.

X=pima.drop('label', axis=1).values
y=pima.label
clf=LogisticRegression()
clf.fit(X[:,[5,6]],y)
x_min, x_max = X[:, 5].min() - 0.5, X[:, 5].max() + 0.5
y_min, y_max = X[:, 6].min() - 0.5, X[:, 6].max() + 0.5
x1 = np.linspace(x_min, x_max, 200)
y1 = np.linspace(y_min, y_max, 200)
xx, yy = np.meshgrid(x1, y1)
Z=clf.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:,1]
zz=Z.reshape(xx.shape)
plt.figure(figsize=(16,9))
plt.pcolormesh(xx,yy, zz, cmap='PRGn');
plt.colorbar();
scatter= plt.scatter(X[:,5], X[:,6], c=y, cmap='PRGn',
                     edgecolors='w', s=200, alpha=0.8);
Classes = ['Sano', 'Enfermo']
contours = plt.contour(xx, yy, zz, colors='darkred', levels=[0.5],
                       linestyles='dashed', linewidths = 3);
plt.clabel(contours, inline=True, fontsize=16)

plt.legend(handles=scatter.legend_elements()[0], labels=Classes)
plt.title('2-Class Logistic Regressión')
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.xlabel('BMI')
plt.ylabel('Diabetes Pedigree Function');
plt.savefig('areas.pdf', transparent=True)

Frontera de decisión

De esta manera ya tenemos un modelo funcional que puede ser utilizado para predecir si un paciente tiene diabetes o no, que puede ser mejorado si se logra aumentar la cantidad de datos para el entrenamiento.

Si has llegado hasta este punto, siguiendo el tutorial, has demostrado un excelente conocimiento de la Regresión Logística y sus aplicaciones.

Ahora estas en capacidad de aplicar la Regresión Logística para resolver problemas del mundo real. Solo vas a necesitar recoletar y preparar tus datos para comenzar a trabajar.

Este tutorial fue pensado para apoyar algunos conceptos de los cursos de Machine Learning, Procesamiento de Lenguaje Natural, Estadística III, Procesamiento Digital de Imágenes y Bioingenería, sin embargo, los conceptos desarrollados pueden ser extrapolados a diferentes aplicaciones y ramas del conocimiento

¿Qué has aprendido?

¿En qué temas puedo profundizar?

Al ser un tutorial tan corto, y si tienes poca experiencia en el campo, es posible que quieras profundizar en algunos conceptos adicionales que no se cubrieron en este tutorial. Algunos de estos temas son:

Referencias

Cox, D. R. (1958). The regression analysis of binary sequences. Journal of the Royal Statistical Society: Series B (Methodological), 20(2), 215–242.

Sobre el autor

Más Información