AulaML Python · C++
Python intermedio · 50 min · Python para Machine Learning

Regresión lineal y logística

Ajustar una recta, medir su error y convertirla en una probabilidad

La regresión es el primer modelo que conviene entender de verdad: una recta que minimiza el error al cuadrado. Aquí la ajustas a mano, la comparas con scikit-learn, aprendes qué significan MSE, MAE y R², y das el salto a la regresión logística para clasificar.

Lección 2 de 7 del módulo
Al terminar esta lección podrás
  • Ajustar una recta por mínimos cuadrados y explicar qué minimiza
  • Calcular MSE, MAE y R² e interpretar cada cifra en sus unidades
  • Entrenar modelos con LinearRegression y LogisticRegression de scikit-learn
  • Explicar cómo la función sigmoide convierte una puntuación en probabilidad
  • Leer los coeficientes de un modelo sin confundir asociación con causalidad

Antes necesitas: Fundamentos de Machine Learning · NumPy: vectores, matrices y vectorización · Estadística y análisis exploratorio

En esta lección
  1. ¿Qué es una regresión?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro? La recta y la pérdida
  4. Sintaxis: dos modelos, una misma API
  5. Ejemplo básico: una recta que predice
  6. Visualización: datos, predicciones y residuos
  7. Ejemplo intermedio: mínimos cuadrados a mano
  8. Traza: un paso de gradiente descendente
  9. Las métricas y qué significa cada una
  10. Regresión logística: del número a la probabilidad
  11. Caso práctico: clasificar con una probabilidad
  12. Cómo se leen los coeficientes
  13. Errores comunes
  14. Buenas prácticas
  15. Ejercicios
  16. Soluciones explicadas
  17. Resumen
  18. Evaluación

Tienes las horas de estudio de varios estudiantes y sus notas. Quieres responder dos preguntas distintas: «¿cuánto sacará alguien que estudie 4 horas?» y «¿aprobará?». La primera pide un número; la segunda, una decisión. La regresión lineal y la logística resuelven cada una.

Las dos son primas: construyen una combinación de las entradas, w · x + b, y solo cambian lo que hacen con el resultado. Entender esa base te sirve para casi todo lo que viene después, incluidas las redes neuronales.

¿Qué es una regresión?

Una regresión es un modelo que predice una cantidad a partir de una o más características. En la lineal, la predicción es una recta (o un plano, con varias entradas): ŷ = w₁x₁ + w₂x₂ + … + b. Los pesos w dicen cuánto pesa cada característica y b es el valor base cuando todas valen cero.

La regresión logística usa la misma combinación, pero le aplica la función sigmoide para comprimir el resultado entre 0 y 1. Ese número se interpreta como la probabilidad de pertenecer a la clase positiva, así que sirve para clasificación binaria: aprobó o no, spam o no, abandona o no.

¿Para qué sirve?

  • Precios y demanda: estimar cuánto costará un departamento o cuántas unidades se venderán el próximo mes.
  • Riesgo de crédito: estimar la probabilidad de que un cliente no pague.
  • Medicina: relacionar dosis y respuesta, o probabilidad de complicación según variables clínicas.
  • Línea base de cualquier proyecto: antes de probar un modelo complejo, la regresión dice cuánto se puede explicar con una relación simple.

¿Cómo funciona por dentro? La recta y la pérdida

Con una sola entrada, el modelo es ŷ = w x + b. Para cada punto, el residuo es y - ŷ: cuánto se aleja la recta del dato real. Los mínimos cuadrados eligen w y b de modo que la suma de los residuos al cuadrado sea la menor posible. Elevar al cuadrado evita que los errores positivos y negativos se cancelen.

Con muchas entradas no hay fórmula tan cómoda y se usa gradiente descendente: calculas la pendiente de la pérdida respecto a cada peso, das un paso pequeño en dirección contraria y repites. El tamaño del paso es la tasa de aprendizaje; si es enorme, el modelo diverge; si es diminuta, tarda una eternidad.

Sintaxis: dos modelos, una misma API

scikit-learn unifica todo en tres llamadas: crear, fit y predict. Instálalo una vez en tu entorno virtual:

SHELL Instalación
python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
python -m pip install scikit-learn
PYTHON el_mismo_esquema.py
from sklearn.linear_model import LinearRegression, LogisticRegression

X = [[1], [2], [3], [7], [8], [9]]
notas = [2.8, 3.4, 4.1, 6.9, 8.2, 9.0]   # regresión: una nota
aprobo = [0, 0, 0, 1, 1, 1]              # clasificación: una categoría

lineal = LinearRegression().fit(X, notas)
logistica = LogisticRegression().fit(X, aprobo)

print("nota estimada a las 4 h:", round(lineal.predict([[4]])[0], 2))
print("clase predicha a las 8 h:", logistica.predict([[8]])[0])

LinearRegression produce valores sin límite. LogisticRegression expone predict_proba, que devuelve dos columnas: probabilidad de la clase 0 y de la clase 1. El método predict aplica un umbral de 0.5 y devuelve ya la etiqueta.

Ejemplo básico: una recta que predice

Supón que ya ajustaste el modelo y obtuviste w = 0.6 y b = 2.2. Predecir es sustituir:

PYTHON predecir.py
def predecir(horas):
    return 2.2 + 0.6 * horas

for h in [1, 3, 5]:
    print(h, "->", round(predecir(h), 2))

La salida es 1 -> 2.8, 3 -> 4.0 y 5 -> 5.2. Cada hora adicional suma 0.6 unidades a la predicción; con cero horas, el modelo parte de 2.2. Cuando la relación es curvada, una recta se queda corta por definición, y ahí conviene añadir términos cuadráticos o cambiar de modelo.

Visualización: datos, predicciones y residuos

Estos cinco puntos tienen una relación casi lineal. La cuarta columna muestra el residuo de cada uno; fíjate en que los positivos y los negativos se compensan.

Mapa de datos ·Datos, predicción y residuo con ŷ = 2.2 + 0.6x
5 × 4 rango [-0.8, 5.2] En x = 1 la recta predice 2.8 y el valor real es 2: el residuo es -0.8.
x
y real
ŷ
y − ŷ
p1
1
2
2.8
-0.8
p2
2
4
3.4
0.6
p3
3
5
4
1
p4
4
4
4.6
-0.6
p5
5
5
5.2
-0.2

Recorre los pasos para ver cómo cambian las celdas.

Observa el patrón: la recta sobreestima en los extremos y subestima en el centro. Eso indica que la relación real tiene curvatura. Ningún ajuste de w y b elimina ese patrón; solo un modelo con más flexibilidad puede.

Ejemplo intermedio: mínimos cuadrados a mano

Con una entrada, los mínimos cuadrados tienen fórmula cerrada: w = Σ(xᵢ − x̄)(yᵢ − ȳ) / Σ(xᵢ − x̄)² y b = ȳ − w x̄. Aplícala a x = [1, 2, 3, 4, 5] e y = [2, 4, 5, 4, 5].

PYTHON minimos_cuadrados.py
x = [1, 2, 3, 4, 5]
y = [2, 4, 5, 4, 5]

n = len(x)
media_x = sum(x) / n
media_y = sum(y) / n

sxy = sum((xi - media_x) * (yi - media_y) for xi, yi in zip(x, y))
sxx = sum((xi - media_x) ** 2 for xi in x)

pendiente = sxy / sxx
intercepto = media_y - pendiente * media_x

print("pendiente:", round(pendiente, 2))
print("intercepto:", round(intercepto, 2))

La media de x es 3 y la de y es 4. El numerador vale 6, el denominador 10, así que w = 0.6. El intercepto es 4 − 0.6 · 3 = 2.2, justo los valores que usamos arriba. La salida lo confirma: pendiente: 0.6 e intercepto: 2.2.

Traza: un paso de gradiente descendente

Cuando no hay fórmula cerrada, el modelo se ajusta por pasos. Con dos puntos (x = [1, 2], y = [3, 4]), tasa 0.1 y pesos iniciales en cero, observa cómo se mueven w y b en la primera iteración:

Ejecución visual ·Un paso de gradiente descendente
1 / 9
1 x = [1, 2]
2 y = [3, 4]
3 w, b = 0.0, 0.0
4 lr = 0.1
5
6 for paso in range(2):
7 pred = [w * xi + b for xi in x]
8 error = [p - yi for p, yi in zip(pred, y)]
9 grad_w = 2 * sum(e * xi for e, xi in zip(error, x)) / len(x)
10 grad_b = 2 * sum(error) / len(x)
11 w = w - lr * grad_w
12 b = b - lr * grad_b
13 print(paso, round(w, 2), round(b, 2))
El modelo arranca plano: w = 0 y b = 0. Todas sus predicciones serán cero.
Variables
x = [1, 2]
y = [3, 4]
w = 0.0
b = 0.0
lr = 0.1
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

La recta perfecta para esos dos puntos es y = x + 2: pendiente 1, intercepto 2. El gradiente no la alcanza de golpe porque cada paso es pequeño: hacen falta unas quinientas iteraciones para llegar a w = 1.0 y b ≈ 2.0. Esa lentitud es el precio de no usar la fórmula cerrada.

Las métricas y qué significa cada una

Un error solo es útil si sabes en qué unidades está y qué castiga. Estas cuatro cifras resumen el mismo ajuste desde ángulos distintos:

MétricaFórmulaUnidadesQué castiga
MSEmedia de (y − ŷ)²unidades²los errores grandes
RMSEraíz del MSElas de ylos errores grandes
MAEmedia de |y − ŷ|las de ytodos por igual
R²1 − SSE / SSTsin unidadesla proporción explicada
PYTHON metricas.py
x = [1, 2, 3, 4, 5]
y = [2, 4, 5, 4, 5]

w, b = 0.6, 2.2
pred = [w * xi + b for xi in x]
errores = [yi - pi for yi, pi in zip(y, pred)]

mse = sum(e ** 2 for e in errores) / len(y)
mae = sum(abs(e) for e in errores) / len(y)
sst = sum((yi - sum(y) / len(y)) ** 2 for yi in y)
r2 = 1 - sum(e ** 2 for e in errores) / sst

print("MSE:", round(mse, 2))
print("MAE:", round(mae, 2))
print("R2:", round(r2, 2))

La salida es MSE: 0.48, MAE: 0.64 y R2: 0.6. La raíz del MSE vale 0.69: el error típico es de dos tercios de unidad. El R² de 0.6 dice que la recta explica el 60 % de la variación de y; el 40 % restante se debe al patrón curvo que la recta no puede seguir.

Regresión logística: del número a la probabilidad

Para clasificar, la recta sola no sirve: puede devolver -3 o 7, valores que no son probabilidades. La sigmoide los comprime: σ(z) = 1 / (1 + e^(−z)). Con z = 0 da 0.5; los valores grandes se acercan a 1 y los muy negativos, a 0.

PYTHON sigmoide.py
from math import exp

def sigmoide(z):
    return 1 / (1 + exp(-z))

for z in [-2, 0, 1, 2]:
    print(z, "->", round(sigmoide(z), 3))

La salida es -2 -> 0.119, 0 -> 0.5, 1 -> 0.731 y 2 -> 0.881. El umbral habitual es 0.5, que corresponde a z = 0: si la combinación de entradas es positiva, el modelo se inclina por la clase 1. Cambiar el umbral cambia el equilibrio entre falsos positivos y falsos negativos, y es una decisión de negocio, no del algoritmo.

Caso práctico: clasificar con una probabilidad

Seis estudiantes: tres que estudiaron 1, 2 y 3 horas no aprobaron; tres que estudiaron 7, 8 y 9 sí. El modelo debe aprender esa frontera.

PYTHON logistica.py
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

X = [[1], [2], [3], [7], [8], [9]]
y = [0, 0, 0, 1, 1, 1]

modelo = LogisticRegression()
modelo.fit(X, y)

print("predicciones:", modelo.predict([[1], [2], [8]]))
print("exactitud:", accuracy_score(y, modelo.predict(X)))

El modelo imprime predicciones: [0 0 1] y exactitud: 1.0: acierta los seis casos de entrenamiento. Esa exactitud perfecta no es un trofeo, porque los datos son trivialmente separables. La prueba real es cómo se comporta con estudiantes de 4, 5 o 6 horas, justo en la zona ambigua, y para eso necesitas un conjunto reservado.

Cómo se leen los coeficientes

En regresión lineal, un coeficiente de 0.6 significa que una unidad más de esa característica suma 0.6 a la predicción, si todo lo demás se mantiene igual. Ese «si todo lo demás se mantiene» es la parte que se olvida: si dos variables se mueven juntas, el modelo reparte el peso entre ellas de forma inestable.

En logística el coeficiente vive en escala de log-odds. Un coeficiente de 0.7 multiplica las probabilidades a favor por e^0.7 ≈ 2.01: duplica el apoyo a la clase positiva por cada unidad. La dirección sí se lee directamente: signo positivo empuja hacia la clase 1.

La misma predicción en dos niveles
Python
PYTHON prediccion.py
X = [1, 2, 3]
w, b = 0.6, 2.2
y_hat = [round(w * x + b, 2) for x in X]
print(y_hat)  # [2.8, 3.4, 4.0]
C++
CPP prediccion.cpp
#include <iostream>
#include <vector>

int main() {
    const std::vector<int> x = {1, 2, 3};
    const double w = 0.6, b = 2.2;
    for (int xi : x) {
        std::cout << w * xi + b << '\n';
    }
}

En Python la comprensión oculta el bucle y redondea; en C++ el bucle es explícito y el tipo de cada variable está declarado. scikit-learn añade otra capa: ni siquiera escribes la multiplicación, solo llamas a `predict`.

Errores comunes

  • Usar R² para comparar modelos con distinta escala: el R² depende de la varianza de y; entre datasets distintos no es comparable.
  • Interpretar un coeficiente como causa: un peso alto indica asociación dentro del modelo, no que cambiar esa variable cambie el resultado.
  • Ajustar sin dividir: el MSE de entrenamiento siempre es optimista.
  • Ignorar la escala: una tasa de aprendizaje razonable con variables normalizadas puede divergir con variables en miles.
  • Leer predict_proba como certeza: una probabilidad de 0.9 no significa que el caso sea positivo.

Buenas prácticas

  • Grafica los residuos: un patrón en forma de curva delata que la recta no basta.
  • Normaliza las entradas antes de usar gradiente descendente o regularización.
  • Informa MSE y MAE juntos; el segundo es más fácil de explicar a quien decide.
  • Evalúa la clasificación con exactitud, pero también con precisión y recall si las clases están desbalanceadas.
  • Guarda el umbral elegido y el motivo: forma parte del modelo, no es un detalle.

Ejercicios

Ejercicio 1

Evalúa una recta dada

Para los puntos (1, 3), (2, 4) y (3, 8), calcula las predicciones del modelo ŷ = 2x + 1, los residuos, el MSE y el MAE. Imprime los tres residuos y las dos métricas redondeadas a dos decimales.
Pista

Las predicciones son 3, 5 y 7. Un residuo es y - ŷ; el MSE promedia los cuadrados y el MAE, los valores absolutos.

Ejercicio 2

De la puntuación a la decisión

Escribe una función sigmoide(z) y clasifica con umbral 0.5 los valores z = -1, 0 y 3. Imprime la probabilidad redondeada a tres decimales y la decisión «aprueba» o «no aprueba».
Pista

Compara la probabilidad con 0.5, no con 1. La función exp está en el módulo math.

Completar código Completa las métricas
errores = [yi - pi for yi, pi in zip(y, pred)]

mse = sum(e ** 2 for e in errores) / 
mae = sum(abs(e) for e in errores) / len(errores)

print("MSE:", round(, 2))
print("MAE:", round(mae, 2))

El promedio divide entre la cantidad de errores; la segunda impresión debe mostrar el MSE.

Soluciones explicadas

En el primer ejercicio, el residuo 0 del primer punto no significa que el modelo sea perfecto: solo acierta ahí. El MSE y el MAE coinciden porque ningún error supera una unidad; con un residuo de 5, el MSE subiría a (0 + 1 + 25) / 3 = 8.67 mientras el MAE quedaría en 2.0.

En el segundo, la sigmoide nunca llega a 0 ni a 1: solo se acerca. Por eso una probabilidad de 0.953 no es una promesa. La decisión final depende del umbral, y ese umbral debe elegirse mirando el coste de cada tipo de error, no copiando el 0.5 por costumbre.

Resumen

  • La regresión lineal predice ŷ = w · x + b; los mínimos cuadrados minimizan la suma de residuos al cuadrado.
  • El gradiente descendente actualiza los pesos en dirección contraria a la pendiente, con pasos de tamaño lr.
  • El MSE está en unidades al cuadrado, el MAE y el RMSE en las de y, y el R² no tiene unidades.
  • La sigmoide convierte cualquier puntuación en una probabilidad entre 0 y 1.
  • En logística, un coeficiente positivo empuja hacia la clase 1 y e^w es el factor de cambio en las probabilidades.
  • Un coeficiente describe asociación dentro del modelo; no demuestra causalidad.

Evaluación

Evaluación 1

¿Qué eligen los mínimos cuadrados al ajustar una recta?

Evaluación 2

Un modelo tiene MSE = 0.48 y MAE = 0.64. ¿Qué indica la diferencia?

Evaluación 3

En una regresión logística con umbral 0.5, un caso recibe probabilidad 0.62. ¿Qué ocurre?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.