SECCIÓN 14

MÓDULO 1.LinearLogistic()

MATEMÁTICA DE LA PREDICCIÓN

Regresión Lineal y Logística

Derivación matemática completa desde función de costo hasta implementación distribuida en Spark ML.

01. Regresión Lineal: Derivación Completa

Función de Costo (Mean Squared Error)

Modelo:
$$ \hat{y}_i = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \cdots + \beta_p x_{ip} = \beta^T \mathbf{x}_i $$
En notación matricial: \(\hat{\mathbf{y}} = X\boldsymbol{\beta}\), donde \(X \in \mathbb{R}^{n \times p}\), \(\boldsymbol{\beta} \in \mathbb{R}^p\)
Función de costo (minimizar):
$$ \begin{aligned} J(\boldsymbol{\beta}) &= \frac{1}{2n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 \\[0.5em] &= \frac{1}{2n} \sum_{i=1}^{n} (y_i - \boldsymbol{\beta}^T \mathbf{x}_i)^2 \\[0.5em] &= \frac{1}{2n} ||X\boldsymbol{\beta} - \mathbf{y}||^2 \end{aligned} $$
El factor \(\frac{1}{2}\) simplifica la derivada. El \(\frac{1}{n}\) normaliza por cantidad de datos.
Gradiente (derivar respecto a β):
$$ \begin{aligned} \nabla J(\boldsymbol{\beta}) &= \frac{\partial J}{\partial \boldsymbol{\beta}} \\[0.5em] &= \frac{1}{n} X^T (X\boldsymbol{\beta} - \mathbf{y}) \\[0.5em] &= \frac{1}{n} \sum_{i=1}^{n} ((\boldsymbol{\beta}^T \mathbf{x}_i) - y_i) \mathbf{x}_i \end{aligned} $$
Este gradiente se puede calcular distribuido: cada partición computa su suma parcial, luego se agregan.
Solución analítica (Normal Equations):
$$ \begin{aligned} \nabla J(\boldsymbol{\beta}) &= 0 \\[0.5em] X^T X \boldsymbol{\beta} &= X^T \mathbf{y} \\[0.5em] \hat{\boldsymbol{\beta}} &= (X^T X)^{-1} X^T \mathbf{y} \end{aligned} $$
Problema: Requiere invertir \(X^T X\) (matriz \(p \times p\)). Si \(p\) es grande o \(X^T X\) es singular, falla numéricamente.

Solver: Normal Equations

from pyspark.ml.regression import LinearRegression

lr = LinearRegression(
    featuresCol="features",
    labelCol="label",
    solver="normal"  # Usa (X^T X)^{-1} X^T y
)

model = lr.fit(train_df)

# Coeficientes aprendidos
print(f"β: {model.coefficients}")
print(f"β₀: {model.intercept}")
Mejor si: p < 4096, datos caben en memoria, X^T X invertible

Solver: L-BFGS (Iterativo)

from pyspark.ml.regression import LinearRegression

lr = LinearRegression(
    featuresCol="features",
    labelCol="label",
    solver="l-bfgs",  # Quasi-Newton (default)
    maxIter=100,
    tol=1e-6
)

model = lr.fit(train_df)

# Converge iterativamente usando ∇J
Mejor si: p > 4096, Big Data, necesitas regularización

02. Interpretación Geométrica

Proyección Ortogonal

La regresión lineal encuentra la proyección de \(\mathbf{y}\) sobre el espacio columna de \(X\).

$$ \begin{aligned} \text{Col}(X) &= \{\mathbf{v} : \mathbf{v} = X\boldsymbol{\beta} \text{ para algún } \boldsymbol{\beta}\} \\[0.5em] \hat{\mathbf{y}} &= \text{Proj}_{\text{Col}(X)}(\mathbf{y}) \\[0.5em] &= X\hat{\boldsymbol{\beta}} \end{aligned} $$
Residuales: $$ \mathbf{e} = \mathbf{y} - \hat{\mathbf{y}} $$ Son perpendiculares al espacio Col(X): $$ X^T \mathbf{e} = X^T (\mathbf{y} - X\hat{\boldsymbol{\beta}}) = \mathbf{0} $$
        y (vector real)
         ↑
         |\\
         | \\  e (residual)
         |  \\
         |   \\
         |    ŷ (proyección)
         |   /
         |  /
         | /
         |/________________→ Col(X)
                            
Intuición: ŷ es el punto en Col(X) más cercano a y. El error e es mínimo cuando e ⊥ Col(X).

03. Regresión Logística: De Probabilidades a Log-Odds

Derivación Paso a Paso

Paso 1: Modelo de Probabilidad
$$ \begin{aligned} P(y=1|\mathbf{x}) &= \sigma(\boldsymbol{\beta}^T \mathbf{x}) \\[0.5em] \text{donde } \sigma(z) &= \frac{1}{1 + e^{-z}} \quad \text{(función sigmoide)} \end{aligned} $$
La sigmoide mapea \((-\infty, +\infty) \to (0, 1)\), ideal para probabilidades.
Paso 2: Log-Likelihood
$$ \begin{aligned} \mathcal{L}(\boldsymbol{\beta}) &= \prod_{i=1}^{n} P(y_i|\mathbf{x}_i) \\[0.5em] &= \prod_{i=1}^{n} [\sigma(\boldsymbol{\beta}^T \mathbf{x}_i)]^{y_i} [1-\sigma(\boldsymbol{\beta}^T \mathbf{x}_i)]^{1-y_i} \\[0.5em] \ell(\boldsymbol{\beta}) &= \log \mathcal{L}(\boldsymbol{\beta}) \\[0.5em] &= \sum_{i=1}^{n} [y_i \log p_i + (1-y_i) \log (1-p_i)] \end{aligned} $$
Donde \(p_i = \sigma(\boldsymbol{\beta}^T \mathbf{x}_i)\)
Paso 3: Función de Costo (Cross-Entropy)
$$ \begin{aligned} J(\boldsymbol{\beta}) &= -\frac{1}{n} \ell(\boldsymbol{\beta}) \\[0.5em] &= -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\sigma(\boldsymbol{\beta}^T \mathbf{x}_i)) + (1-y_i) \log(1-\sigma(\boldsymbol{\beta}^T \mathbf{x}_i))] \end{aligned} $$
Minimizar J(β) ≡ Maximizar likelihood
Paso 4: Gradiente
$$ \begin{aligned} \nabla J(\boldsymbol{\beta}) &= \frac{1}{n} X^T (\boldsymbol{\sigma} - \mathbf{y}) \\[0.5em] \text{donde } \boldsymbol{\sigma} &= [\sigma(\boldsymbol{\beta}^T \mathbf{x}_1), \ldots, \sigma(\boldsymbol{\beta}^T \mathbf{x}_n)]^T \end{aligned} $$
Sorprendentemente similar al gradiente de regresión lineal! Solo cambia \(X\boldsymbol{\beta}\) por \(\boldsymbol{\sigma}\).

Código: Regresión Logística en PySpark

from pyspark.ml.classification import LogisticRegression

# Clasificación binaria
lr_log = LogisticRegression(
    featuresCol="features",
    labelCol="label",  # 0 o 1
    maxIter=100,
    regParam=0.01,  # Regularización L2 (Ridge)
    elasticNetParam=0.0  # 0=Ridge, 1=Lasso
)

model = lr_log.fit(train_df)

# Predecir probabilidades
predictions = model.transform(test_df)

predictions.select("features", "label", "probability", "prediction").show(5)

# +--------+-----+--------------------+----------+
# |features|label|probability         |prediction|
# +--------+-----+--------------------+----------+
# |[2.0,..]|1    |[0.15, 0.85]        |1.0       |  # P(y=1) = 0.85
# |[1.5,..]|0    |[0.92, 0.08]        |0.0       |  # P(y=1) = 0.08
# +--------+-----+--------------------+----------+

04. Métricas de Evaluación

Regresión Lineal

RMSE (Root Mean Squared Error): $$ \text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2} $$
Unidades: mismas que y. Penaliza errores grandes.
R² (Coeficiente de Determinación): $$ R^2 = 1 - \frac{SS_{\text{res}}}{SS_{\text{tot}}} = 1 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2} $$
Rango: [0, 1]. R²=1 → ajuste perfecto. R²=0 → modelo no mejor que media.
MAE (Mean Absolute Error): $$ \text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i| $$
Más robusto a outliers que RMSE.
from pyspark.ml.evaluation import RegressionEvaluator

evaluator = RegressionEvaluator(
    labelCol="label",
    predictionCol="prediction"
)

rmse = evaluator.evaluate(
    predictions,
    {evaluator.metricName: "rmse"}
)
r2 = evaluator.evaluate(
    predictions,
    {evaluator.metricName: "r2"}
)

print(f"RMSE: {rmse:.2f}")
print(f"R²: {r2:.4f}")

Regresión Logística

Matriz de Confusión
Predicción
Neg (0) Pos (1)
Real Neg (0) TN FP
Real Pos (1) FN TP
$$ \text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN} $$
$$ \text{Precision} = \frac{TP}{TP + FP} $$
$$ \text{Recall (Sensitivity)} = \frac{TP}{TP + FN} $$
$$ \text{F1-Score} = 2 \cdot \frac{\text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}} $$
from pyspark.ml.evaluation import BinaryClassificationEvaluator

evaluator = BinaryClassificationEvaluator(
    labelCol="label",
    rawPredictionCol="rawPrediction"
)

auc = evaluator.evaluate(
    predictions,
    {evaluator.metricName: "areaUnderROC"}
)
print(f"AUC-ROC: {auc:.4f}")

05. Solvers en Spark: Cuándo Usar Cada Uno

Solver Método Complejidad Mejor para Limitaciones
normal (X^T X)^{-1} X^T y O(np² + p³) p < 4096, datos pequeños Falla si X^T X singular, no regularización
l-bfgs Quasi-Newton O(np × iter) p > 4096, Big Data, regularización Requiere tuning de maxIter, tol
auto Elige automático Depende No sabes qué usar Menos control
💡 Recomendación: Usa solver='auto' por defecto. Spark elige 'normal' si p < 4096 y sin regularización, caso contrario 'l-bfgs'.

06. Ejemplo Completo: Predicción de Salarios

Pipeline End-to-End

from pyspark.ml import Pipeline
from pyspark.ml.feature import VectorAssembler, StandardScaler
from pyspark.ml.regression import LinearRegression
from pyspark.ml.evaluation import RegressionEvaluator

# Dataset: salario ~ edad + educacion_años + experiencia
df = spark.read.csv("salarios.csv", header=True, inferSchema=True)

# Split
train_df, test_df = df.randomSplit([0.8, 0.2], seed=42)

# Pipeline
assembler = VectorAssembler(
    inputCols=["edad", "educacion_años", "experiencia"],
    outputCol="features_raw"
)

scaler = StandardScaler(
    inputCol="features_raw",
    outputCol="features",
    withMean=True,
    withStd=True
)

lr = LinearRegression(
    featuresCol="features",
    labelCol="salario",
    maxIter=100
)

pipeline = Pipeline(stages=[assembler, scaler, lr])

# Entrenar
model = pipeline.fit(train_df)

# Predecir
predictions = model.transform(test_df)

# Evaluar
evaluator = RegressionEvaluator(labelCol="salario", predictionCol="prediction")
rmse = evaluator.evaluate(predictions, {evaluator.metricName: "rmse"})
r2 = evaluator.evaluate(predictions, {evaluator.metricName: "r2"})

print(f"RMSE: ${rmse:.2f}")
print(f"R²: {r2:.4f}")

# Coeficientes
lr_model = model.stages[2]  # LinearRegression es la 3ra etapa
print(f"β (edad, educacion, experiencia): {lr_model.coefficients}")
print(f"β₀ (intercept): {lr_model.intercept:.2f}")

# Interpretación: Por cada año adicional de educación, el salario aumenta β₁ (todo lo demás constante)
Resultado ejemplo:
RMSE: $5,234.12
R²: 0.7856
β: [850.23, 3200.45, 1100.67] → [edad, educacion, experiencia]

Interpretación: Cada año adicional de educación incrementa el salario en ~$3,200 (manteniendo edad y experiencia constantes).