Preparación para entrevistas de ML Clásico

Preguntas de Entrevista para Ingenieros de Machine Learning Clásico

15 preguntas seleccionadas de entrevista sobre machine learning clásico, agrupadas por nivel de experiencia. Úsalas para repasar los fundamentos, las compensaciones prácticas y el razonamiento de producción a nivel senior.

Iniciar una Entrevista de IA de ML ClásicoNo se requiere tarjeta de crédito. 1 sesión gratuita disponible.
Práctica de entrevistas técnicas en inglésDiseñado para hablantes no nativos que desean practicar entrevistas técnicas en inglés.

Preguntas para Junior

1¿Cuál es la diferencia entre un parámetro de modelo y un hiperparámetro en el aprendizaje supervisado?

En el aprendizaje automático supervisado, los parámetros del modelo son variables internas que se aprenden directamente de los datos de entrenamiento a través de un algoritmo de optimización (como el descenso de gradiente o las ecuaciones normales). Ejemplos incluyen los pesos de regresión y el sesgo en modelos lineales o los umbrales de división en árboles de decisión. Por el contrario, los hiperparámetros son configuraciones externas especificadas antes del entrenamiento que gobiernan el proceso de aprendizaje, la capacidad del modelo o su arquitectura. No pueden aprenderse directamente mediante la minimización estándar de la función de pérdida del entrenamiento porque el optimizador se sobreajustaría trivialmente (por ejemplo, al establecer la profundidad del árbol en infinito). Ejemplos incluyen la tasa de aprendizaje, la fuerza de regularización (lambda/C), el número de árboles en un bosque y la profundidad máxima del árbol. Los hiperparámetros se ajustan utilizando datos de validación o validación cruzada.

from sklearn.linear_model import Ridge
import numpy as np

X = np.array([[1.0], [2.0], [3.0]])
y = np.array([2.0, 4.0, 6.0])

# Hyperparameter: alpha (regularization strength set beforehand)
model = Ridge(alpha=1.0)

# Fitting optimizes internal parameters on training data
model.fit(X, y)

# Learned parameters
print(f"Weight (Parameter): {model.coef_[0]:.4f}")
print(f"Intercept (Parameter): {model.intercept_:.4f}")
Probar responder esta pregunta con un coach de IA

2¿Qué supuestos hace la regresión lineal de mínimos cuadrados ordinarios (OLS) y cómo revelarían los diagnósticos de residuos las violaciones de los supuestos?

La regresión lineal de mínimos cuadrados ordinarios (OLS) se basa en varios supuestos clave: 1. Linealidad: La relación entre los predictores y el resultado es lineal en los parámetros. 2. Independencia de los errores: Las observaciones y los errores residuales son mutuamente independientes (sin autocorrelación). 3. Homocedasticidad: Los términos de error tienen varianza constante en todos los niveles de los predictores. 4. Normalidad de los residuos: Los términos de error están distribuidos normalmente (necesario para intervalos de confianza y pruebas de hipótesis válidas). 5. No multicolinealidad: Los predictores no son linealmente dependientes (la matriz de diseño tiene rango de columna completo). Los diagnósticos de residuos revelan las violaciones de la siguiente manera: - Gráfico de Residuos vs. Valores Ajustados: La curvatura o patrones no aleatorios revelan no linealidad; una forma de embudo o abanico revela heterocedasticidad (varianza no constante). - Gráfico Q-Q Normal: La desviación sistemática de la línea diagonal recta (p. ej., curvas en S o colas pesadas) revela no normalidad. - Gráfico de Residuos vs. Orden/Tiempo: Las tendencias sistemáticas o patrones cíclicos revelan errores autocorrelacionados. - Gráfico de Apalancamiento / Distancia de Cook: Identifica valores atípicos de alto apalancamiento o puntos influyentes que desplazan desproporcionadamente el modelo ajustado.

import numpy as np
import statsmodels.api as sm

np.random.seed(42)
X = np.linspace(1, 10, 50)
# Quadratic underlying pattern creates a linearity violation
y = 2 * X + 0.5 * (X ** 2) + np.random.normal(0, 2, 50)

X_with_const = sm.add_constant(X)
model = sm.OLS(y, X_with_const).fit()
residuals = model.resid

print(f"Mean Residual: {np.mean(residuals):.4f}")
print(f"Curvature in residuals indicates model misspecification.")
Probar responder esta pregunta con un coach de IA

3¿Cómo modela la regresión logística la clasificación binaria y cuál es el papel de la función sigmoide?

La regresión logística modela la clasificación binaria estimando la probabilidad de clase posterior $P(Y=1|X)$. Para asegurar que las probabilidades predichas se mantengan acotadas dentro de $(0, 1)$, la regresión logística modela las log-odds (logit) de la clase positiva como una función lineal de las entradas: $\ln\left(\frac{p}{1-p}\right) = w^T x + b$. La función sigmoide (logística), $\sigma(z) = \frac{1}{1 + e^{-z}}$, sirve como la función de enlace que mapea cualquier puntuación lineal de valor real $z = w^T x + b \in (-\infty, +\infty)$ monótonamente a una probabilidad válida $p \in (0, 1)$. Las decisiones de clase discretas se toman aplicando un umbral de decisión $\tau$ (típicamente 0.5): $\hat{y} = 1$ si $P(Y=1|X) \ge \tau$, de lo contrario $0$. Debido a que $\sigma(z) = 0.5$ ocurre precisamente cuando $z = 0$, el límite de decisión en el espacio de características es el hiperplano lineal $w^T x + b = 0$, lo que convierte a la regresión logística estándar en un clasificador lineal.

import numpy as np

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

w = np.array([1.5, -2.0])
b = 0.5
x = np.array([2.0, 1.0])

z = np.dot(w, x) + b
prob = sigmoid(z)
threshold = 0.5
pred = int(prob >= threshold)

print(f"Log-odds (z): {z:.2f}")
print(f"Probability: {prob:.4f}")
print(f"Class Prediction: {pred}")
Probar responder esta pregunta con un coach de IA

4¿Qué es la regularización L2 y cómo la regresión Ridge cambia el objetivo y las estimaciones de los coeficientes?

La regularización L2 (regresión Ridge) añade una penalización proporcional a la suma de los pesos al cuadrado a la función de pérdida de mínimos cuadrados ordinarios (OLS): $$\min_w \|y - Xw\|_2^2 + \lambda \|w\|_2^2$$ Analíticamente, Ridge modifica las ecuaciones normales añadiendo $\lambda I$ a la matriz de Gram antes de la inversión: $$w_{\text{ridge}} = (X^T X + \lambda I)^{-1} X^T y$$ Impactos clave en el objetivo y las estimaciones de los coeficientes: 1. **Reducción (Shrinkage)**: Los coeficientes se reducen hacia cero en proporción a la varianza y correlación de las características, disminuyendo la complejidad del modelo sin forzarlos a ser exactamente cero. 2. **Multicolinealidad e Invertibilidad**: Cuando las características son colineales o $p > N$, $X^T X$ es singular o está mal condicionada. Añadir $\lambda I$ asegura que $(X^T X + \lambda I)$ sea estrictamente definida positiva e invertible, estabilizando las estimaciones de los parámetros. 3. **Compromiso Sesgo-Varianza (Bias-Variance Trade-off)**: Aumentar $\lambda$ introduce un sesgo intencional en las estimaciones de los coeficientes, al tiempo que reduce significativamente la varianza, lo que resulta en un error de generalización esperado menor en datos no vistos. 4. **Requisito de Escalado de Características**: Debido a que la penalización trata todos los pesos por igual, las características en escalas más grandes se regularizarían desproporcionadamente. Las características deben estandarizarse (media cero, varianza unitaria) antes del ajuste.

import numpy as np

def ridge_regression(X, y, alpha):
    X_std = (X - np.mean(X, axis=0)) / np.std(X, axis=0)
    n_features = X_std.shape[1]
    
    I = np.eye(n_features)
    beta = np.linalg.inv(X_std.T @ X_std + alpha * I) @ X_std.T @ y
    return beta

X = np.array([[1.0, 2.0], [2.0, 4.1], [3.0, 5.9], [4.0, 8.2]])
y = np.array([2.1, 4.0, 6.2, 8.1])
weights = ridge_regression(X, y, alpha=1.0)
print('Ridge Weights:', weights)
Probar responder esta pregunta con un coach de IA

5¿Cómo un árbol de decisión particiona recursivamente el espacio de características y qué criterios se utilizan para elegir las divisiones de clasificación?

Un árbol de decisión particiona el espacio de características mediante un algoritmo codicioso, de arriba hacia abajo, llamado **particionamiento binario recursivo**. Comenzando en el nodo raíz con todos los datos de entrenamiento, el algoritmo busca en todas las características y posibles valores umbral para encontrar la única división alineada con los ejes ($X_j \le t$) que maximiza la reducción de impureza. El conjunto de datos se divide en dos nodos hijos, y este procedimiento se aplica recursivamente en cada nodo hijo hasta que se alcanza un criterio de detención (por ejemplo, profundidad máxima, número mínimo de muestras por hoja o nodos puros). Debido a que las divisiones evalúan una característica a la vez contra un umbral, los límites de decisión resultantes son hiperplanos ortogonales (regiones rectangulares alineadas con los ejes en el espacio de características). Para evaluar y seleccionar la mejor división en los árboles de clasificación, se utilizan dos criterios principales de impureza: 1. **Impureza de Gini (utilizada en CART)**: Mide la probabilidad de que una muestra elegida aleatoriamente sea mal clasificada si se etiqueta aleatoriamente según la distribución de clases del nodo. Para $K$ clases con proporciones $p_k$: $$I_G = 1 - \sum_{k=1}^K p_k^2$$ 2. **Entropía y Ganancia de Información (utilizadas en ID3, C4.5)**: La entropía mide la incertidumbre en un nodo: $H = -\sum_{k=1}^K p_k \log_2(p_k)$. La división se elige para maximizar la **Ganancia de Información**, que es la entropía del nodo padre menos la entropía promedio ponderada de los nodos hijos: $$IG = H(\text{padre}) - \sum_{c \in \{\text{izquierda, derecha}\}} \frac{N_c}{N} H(c)$$ Ambas métricas alcanzan 0 cuando un nodo es completamente puro (todas las muestras pertenecen a una sola clase) y alcanzan su máximo cuando las clases están igualmente distribuidas.

import numpy as np

def gini(labels):
    _, counts = np.unique(labels, return_counts=True)
    p = counts / len(labels)
    return 1.0 - np.sum(p ** 2)

def entropy(labels):
    _, counts = np.unique(labels, return_counts=True)
    p = counts / len(labels)
    return -np.sum(p * np.log2(p + 1e-12))

# Evenly split node (impure) vs single-class node (pure)
impure_node = np.array([0]*10 + [1]*10)
pure_node = np.array([0]*20)

print(f"Impure - Gini: {gini(impure_node):.2f}, Entropy: {entropy(impure_node):.2f}")
print(f"Pure   - Gini: {gini(pure_node):.2f}, Entropy: {entropy(pure_node):.2f}")
Probar responder esta pregunta con un coach de IA

6¿Qué es k-nearest neighbors (kNN) y cómo realiza predicciones para clasificación y regresión?

k-Nearest Neighbors (kNN) es un algoritmo de aprendizaje supervisado no paramétrico, basado en instancias (o 'perezoso'). No entrena un modelo paramétrico explícito; en su lugar, almacena el conjunto de datos de entrenamiento y realiza todos los cálculos durante la inferencia. Flujo de trabajo de predicción: 1. Cálculo de la distancia: Cuando se evalúa una instancia de consulta, el algoritmo calcula su distancia a todas las instancias de entrenamiento almacenadas utilizando una métrica específica (como la distancia euclidiana, de Manhattan o de Minkowski). 2. Selección de vecinos: Selecciona las $k$ instancias de entrenamiento con las distancias más pequeñas a la instancia de consulta. 3. Agregación: - Clasificación: Asigna la clase mediante voto mayoritario (moda) entre los $k$ vecinos (o voto ponderado por la distancia). - Regresión: Predice el objetivo continuo tomando el promedio local (media o mediana) de los valores objetivo de los $k$ vecinos (o promedio ponderado por la distancia). Debido a que los cálculos de distancia dependen directamente de las escalas de las características, la normalización o estandarización de características es esencial para evitar que las características de gran magnitud dominen los cálculos de distancia.

from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
import numpy as np

X_train = np.array([[1000.0, 1.0], [2000.0, 2.0], [1500.0, 1.5], [5000.0, 5.0]])
y_cls = np.array([0, 0, 0, 1])
y_reg = np.array([10.0, 20.0, 15.0, 50.0])

# Feature scaling is mandatory for distance-based algorithms
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)

# 1. Classification (Majority Vote)
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_scaled, y_cls)

# 2. Regression (Local Average)
reg = KNeighborsRegressor(n_neighbors=3)
reg.fit(X_scaled, y_reg)

query = scaler.transform([[1800.0, 1.8]])
print('Classification:', clf.predict(query))
print('Regression:', reg.predict(query))
Probar responder esta pregunta con un coach de IA

7Exponga el supuesto de independencia condicional de Naive Bayes y explique por qué Naive Bayes puede seguir funcionando bien cuando se viola.

El supuesto de independencia condicional de Naive Bayes establece que, dada la etiqueta de clase Y = y, todas las características X_1, X_2, ..., X_d son mutuamente independientes: P(X_1, ..., X_d | Y = y) = \prod_{j=1}^d P(X_j | Y = y). Usando el teorema de Bayes, la probabilidad posterior es: P(Y = y | X) \propto P(Y = y) \prod_{j=1}^d P(X_j | Y = y), donde P(Y = y) es la probabilidad a priori de la clase y P(X_j | Y = y) es la verosimilitud condicional de la clase (por ejemplo, Gaussiana para características continuas, Multinomial para recuentos). Naive Bayes a menudo funciona bien en la práctica a pesar de las violaciones de independencia porque la clasificación se basa en la regla de decisión argmax (argmax_y P(Y=y | X)) en lugar de una calibración precisa de la probabilidad. Incluso si las correlaciones de características hacen que las probabilidades predichas se vuelvan demasiado confiadas o distorsionadas, la clase correcta a menudo conserva la clasificación relativa más alta. Siempre y cuando la correlación no invierta la clasificación de las verosimilitudes de clase, la decisión de clasificación 0-1 sigue siendo precisa.

from sklearn.naive_bayes import GaussianNB
import numpy as np

X = np.array([[1.0, 1.1], [1.2, 0.9], [-1.0, -1.2], [-0.8, -1.1]])
y = np.array([1, 1, 0, 0])

model = GaussianNB()
model.fit(X, y)
# Prediction uses argmax over class posterior scores
print("Predicted class:", model.predict([[1.1, 1.0]]))
Probar responder esta pregunta con un coach de IA

Preguntas para Middle

8Derivar o explicar la solución de forma cerrada de los Mínimos Cuadrados Ordinarios (OLS) y establecer cuándo existe de forma única.

El objetivo de los Mínimos Cuadrados Ordinarios (OLS) minimiza la suma de los cuadrados residuales: $S(\beta) = \|y - X\beta\|^2 = (y - X\beta)^T (y - X\beta) = y^T y - 2\beta^T X^T y + \beta^T X^T X \beta$. Igualando el gradiente con respecto a $\beta$ a cero: $$\nabla_\beta S(\beta) = -2 X^T y + 2 X^T X \beta = 0 \implies X^T X \beta = X^T y$$ Estas son las ecuaciones normales. Cuando $X^T X$ no es singular (invertible), la solución de forma cerrada única es: $$\hat{\beta} = (X^T X)^{-1} X^T y$$ Geométricamente, $\hat{y} = X\hat{\beta} = X(X^T X)^{-1} X^T y = H y$ representa la proyección ortogonal del vector objetivo $y$ sobre el espacio de columnas de la matriz de diseño $X$, donde $H$ es la matriz de proyección (hat). La solución existe de forma única si y solo si $X^T X$ es invertible, lo que requiere que la matriz de diseño $N \times P$, $X$, tenga rango de columna completo ($Rank(X) = P$). Esto requiere $N \ge P$ y que no haya multicolinealidad exacta (ninguna característica es una combinación lineal de otras). Si $X$ es de rango deficiente, $X^T X$ es singular, lo que lleva a infinitas soluciones, a menudo abordadas mediante regularización o la pseudoinversa de Moore-Penrose $X^+ y$.

import numpy as np

# Design matrix X (with intercept column) and target y
X = np.array([[1, 1], [1, 2], [1, 3], [1, 4]])
y = np.array([2.1, 3.9, 6.2, 8.0])

# Normal equations: (X^T X)^(-1) X^T y
beta_hat = np.linalg.inv(X.T @ X) @ X.T @ y
H = X @ np.linalg.inv(X.T @ X) @ X.T
y_hat = H @ y

print(f"Beta: {beta_hat}")
print(f"Predictions: {y_hat}")
Probar responder esta pregunta con un coach de IA

9¿Qué es la estimación de máxima verosimilitud, y cómo conduce a la función objetivo de entropía cruzada de la regresión logística?

La Estimación de Máxima Verosimilitud (MLE) es un método para estimar los parámetros del modelo $\theta$ eligiendo valores que maximicen la verosimilitud $L(\theta) = P(\mathcal{D}|\theta)$ del conjunto de datos observado. En la regresión logística binaria, cada etiqueta $y_i \in \{0, 1\}$ se modela como una variable aleatoria de Bernoulli independiente condicionada a $x_i$, con una probabilidad de éxito $p_i = \sigma(w^T x_i + b)$. La función de masa de probabilidad para la observación $i$ es $P(y_i|x_i) = p_i^{y_i} (1 - p_i)^{1 - y_i}$. Asumiendo muestras i.i.d., la verosimilitud conjunta es: $$L(w, b) = \prod_{i=1}^N p_i^{y_i} (1 - p_i)^{1 - y_i}$$ Tomar el logaritmo natural convierte el producto en una suma de verosimilitudes logarítmicas computacionalmente manejable: $$\ell(w, b) = \sum_{i=1}^N \left[ y_i \ln(p_i) + (1 - y_i) \ln(1 - p_i) \right]$$ Dado que los algoritmos de optimización se plantean normalmente como problemas de minimización, negamos la verosimilitud logarítmica y normalizamos por el tamaño de la muestra $N$, obteniendo la Verosimilitud Logarítmica Negativa (NLL), que es exactamente la función objetivo de Entropía Cruzada Binaria (pérdida logarítmica): $$J(w, b) = -\frac{1}{N} \sum_{i=1}^N \left[ y_i \ln(p_i) + (1 - y_i) \ln(1 - p_i) \right]$$ Este objetivo es convexo con respecto a los logits/pesos lineales, por lo que los solucionadores numéricos apropiados optimizan un objetivo global. La convexidad estricta y una MLE único finito requieren condiciones adicionales como un rango de características suficiente, regularización y ninguna separación de clases perfecta.

import numpy as np

y_true = np.array([1, 0, 1, 1])
y_prob = np.array([0.9, 0.2, 0.8, 0.4])

# Binary cross-entropy (Negative Log-Likelihood)
epsilon = 1e-15  # prevent log(0)
y_prob = np.clip(y_prob, epsilon, 1 - epsilon)
bce_loss = -np.mean(y_true * np.log(y_prob) + (1 - y_true) * np.log(1 - y_prob))

print(f"Binary Cross-Entropy Loss: {bce_loss:.4f}")
Probar responder esta pregunta con un coach de IA

10¿Cómo optimiza el descenso de gradiente un objetivo de aprendizaje automático (ML) clásico y cómo afectan la tasa de aprendizaje, la convergencia y la convexidad al entrenamiento?

El descenso de gradiente minimiza una función de pérdida empírica actualizando iterativamente los parámetros del modelo en la dirección opuesta al gradiente de la función objetivo con respecto a esos parámetros: $\theta_{t+1} = \theta_t - \eta \nabla L(\theta_t)$. Los factores clave que influyen en el entrenamiento incluyen: 1. **Tasa de Aprendizaje ($\eta$):** Controla el tamaño del paso. Si se establece demasiado pequeña, la convergencia es excesivamente lenta y el entrenamiento puede estancarse. Si se establece demasiado grande, las actualizaciones sobrepasarán el mínimo, causando oscilación o divergencia numérica. 2. **Convergencia:** Se determina monitoreando criterios de parada como una pequeña norma del gradiente ($||\nabla L(\theta)|| \le \epsilon$), un cambio mínimo de parámetros o una estabilización de la pérdida en iteraciones consecutivas. 3. **Convexidad:** En objetivos convexos (por ejemplo, regresión lineal de mínimos cuadrados ordinarios (OLS) estándar o regresión logística), cualquier mínimo local está garantizado para ser un mínimo global, permitiendo que el descenso de gradiente converja de forma fiable con tamaños de paso apropiados. En objetivos no convexos (por ejemplo, redes neuronales multicapa), el paisaje de pérdida contiene múltiples mínimos locales, puntos de silla y mesetas, lo que hace que la solución final sea sensible a la inicialización. 4. **Optimización vs. Generalización:** La convergencia en la pérdida de entrenamiento refleja el éxito de la optimización, mientras que la pérdida de validación evalúa la generalización. Alcanzar una pérdida de entrenamiento baja con un error de validación alto indica sobreajuste (overfitting) en lugar de un fallo de optimización.

import numpy as np

def gradient_descent(X, y, lr=0.01, max_iters=1000, tol=1e-6):
    n_samples, n_features = X.shape
    theta = np.zeros(n_features)
    prev_loss = float('inf')
    
    for i in range(max_iters):
        predictions = X @ theta
        error = predictions - y
        loss = (1 / (2 * n_samples)) * np.dot(error, error)
        
        if abs(prev_loss - loss) < tol:
            print(f'Converged at iteration {i}')
            break
        prev_loss = loss
        
        grad = (1 / n_samples) * (X.T @ error)
        theta -= lr * grad
        
    return theta
Probar responder esta pregunta con un coach de IA

11Compare la regularización L1, L2 y ElasticNet en términos de dispersión, características correlacionadas y selección práctica de modelos.

La regularización L1 (Lasso), L2 (Ridge) y ElasticNet difieren en la formulación de la penalización, la geometría de la restricción, la dispersión y el manejo de predictores correlacionados: 1. **Dispersión y Geometría:** - L1 utiliza una penalización de valor absoluto ($\lambda \|w\|_1$). Su límite de restricción es un diamante/politopo con vértices agudos en los ejes de coordenadas. Cuando los contornos de pérdida se intersecan con estas esquinas, los pesos se reducen a cero exacto, realizando una selección automática de características. - L2 utiliza una penalización de norma euclidiana al cuadrado ($\lambda \|w\|_2^2$). Su límite de restricción es una hiperesfera suave sin esquinas, que encoge los pesos hacia cero asintóticamente pero rara vez los establece a cero exacto. 2. **Características Correlacionadas:** - Bajo una fuerte colinealidad, L1 tiende a elegir arbitrariamente una característica de un grupo de predictores correlacionados y establece los coeficientes restantes a cero, lo que resulta en estimaciones inestables a través de las remuestras. - L2 retiene todas las características correlacionadas, distribuyendo los pesos entre ellas y encogiéndolas conjuntamente. 3. **ElasticNet:** - Combina ambas penalizaciones: $\lambda_1 \|w\|_1 + \lambda_2 \|w\|_2^2$ (a menudo parametrizado con $\alpha$ y $l_1\_\text{ratio}$). - Ofrece la dispersión y selección de características de Lasso mientras preserva el efecto de agrupamiento de Ridge, seleccionando clústeres de predictores correlacionados conjuntamente. Es especialmente útil cuando $p > N$ o bajo una multicolinealidad severa.

from sklearn.linear_model import Ridge, Lasso, ElasticNet
import numpy as np

np.random.seed(42)
X1 = np.random.randn(100, 1)
X2 = X1 + np.random.randn(100, 1) * 0.01  # highly correlated
X = np.hstack([X1, X2])
y = 3 * X1.ravel() + np.random.randn(100) * 0.1

ridge = Ridge(alpha=1.0).fit(X, y)
lasso = Lasso(alpha=0.1).fit(X, y)
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5).fit(X, y)

print('Ridge coefs:', ridge.coef_)
print('Lasso coefs:', lasso.coef_)
print('ElasticNet coefs:', elastic.coef_)
Probar responder esta pregunta con un coach de IA

12¿Cuál es la diferencia, a alto nivel, entre la regresión Ridge, la Regresión de Componentes Principales y los Mínimos Cuadrados Parciales?

La Regresión Ridge, la Regresión de Componentes Principales (PCR) y los Mínimos Cuadrados Parciales (PLS) son tres técnicas lineales utilizadas para manejar la multicolinealidad y la alta dimensionalidad, pero difieren en cómo reducen la varianza y si la reducción es continua o supervisada: 1. **Regresión Ridge**: Retiene todas las $p$ características originales y aplica una contracción continua a las magnitudes de los coeficientes mediante una penalización L2. No construye componentes latentes de menor dimensión ni descarta dimensiones de características; más bien, encoge la varianza a lo largo de las direcciones de autovalor bajo de $X^T X$. 2. **Regresión de Componentes Principales (PCR)**: Un método de reducción de dimensionalidad no supervisado de dos pasos. Primero aplica el Análisis de Componentes Principales (PCA) estrictamente a la matriz de predictores $X$ para encontrar direcciones ortogonales de varianza máxima, mantiene los $k$ componentes principales superiores y ajusta una regresión OLS (Mínimos Cuadrados Ordinarios) sobre esos $k$ componentes. Dado que PCA ignora la variable objetivo $y$, PCR corre el riesgo de descartar componentes que tienen baja varianza en $X$ pero un alto poder predictivo para $y$. 3. **Mínimos Cuadrados Parciales (PLS)**: Un método de reducción de dimensionalidad supervisado. Construye $k$ componentes latentes ortogonales al encontrar combinaciones lineales de $X$ que maximizan la covarianza entre $X$ y la respuesta $y$. Al incorporar explícitamente información del objetivo, PLS identifica componentes que explican tanto la varianza de las características como la variación de la respuesta.

from sklearn.linear_model import Ridge, LinearRegression
from sklearn.decomposition import PCA
from sklearn.cross_decomposition import PLSRegression
from sklearn.pipeline import make_pipeline

# 1. Ridge: Regularized full feature space
ridge = Ridge(alpha=1.0)

# 2. PCR: Unsupervised PCA followed by OLS
pcr = make_pipeline(PCA(n_components=2), LinearRegression())

# 3. PLS: Supervised latent component projection and regression
pls = PLSRegression(n_components=2)
Probar responder esta pregunta con un coach de IA

Preguntas para Senior

13¿Cómo optimizan el entrenamiento o manejan de manera diferente las características tabulares las implementaciones modernas de gradient boosting como XGBoost, LightGBM y CatBoost?

Los frameworks modernos de Gradient Boosting Decision Trees (GBDT) difieren sustancialmente en sus algoritmos de búsqueda de divisiones, estrategias de crecimiento de árboles y manejo de características tabulares/categóricas: 1. **XGBoost**: Tradicionalmente se basa en la búsqueda de divisiones exacta codiciosa (greedy) o de boceto de cuantiles aproximado (y más tarde Fast Hist), utiliza un crecimiento de árbol por niveles (level-wise o depth-wise) y maneja los valores faltantes aprendiendo una dirección de rama predeterminada óptima durante la búsqueda de divisiones. 2. **LightGBM**: Utiliza la búsqueda de divisiones basada en histogramas (agrupando características continuas en cubos discretos, típicamente 256), un crecimiento de árbol por hojas (leaf-wise o best-first) para una reducción más rápida de la pérdida, Gradient-based One-Side Sampling (GOSS) para mantener instancias con gradientes grandes mientras se submuestrean las de gradientes pequeños, y Exclusive Feature Bundling (EFB) para fusionar características dispersas mutuamente excluyentes. Para las variables categóricas, encuentra divisiones óptimas ordenando los bins del histograma categórico ($O(K \log K)$). 3. **CatBoost**: Utiliza árboles de decisión 'oblivious' (simétricos) donde todos los nodos a una profundidad dada comparten exactamente la misma división, lo que permite una puntuación vectorizada rápida en CPU/GPU. Su principal innovación son las Estadísticas de Objetivo Ordenadas (Ordered Target Statistics) y el 'boosting' ordenado, que calcula las estadísticas de objetivo sobre permutaciones aleatorias de los datos de entrenamiento para evitar la fuga de objetivo (target leakage) y el desplazamiento de predicción (prediction shift).

from catboost import CatBoostClassifier
import lightgbm as lgb
import pandas as pd

df = pd.DataFrame({
    'city': ['NY', 'LDN', 'NY', 'PAR', 'LDN', 'TOK'],
    'age': [25, 42, 30, 22, 55, 38],
    'target': [1, 0, 1, 0, 1, 0]
})
cat_cols = ['city']
df['city'] = df['city'].astype('category')

# LightGBM handles pandas 'category' dtype natively via integer binning
lgb_clf = lgb.LGBMClassifier(max_depth=3, n_estimators=10)
lgb_clf.fit(df[['city', 'age']], df['target'])

# CatBoost handles categorical column names natively with ordered TS
cb_clf = CatBoostClassifier(iterations=10, cat_features=cat_cols, verbose=False)
cb_clf.fit(df[['city', 'age']], df['target'])
Probar responder esta pregunta con un coach de IA

14¿Cómo decidiría si una función de pérdida personalizada es apropiada para un modelo de *gradient boosting* bajo costos empresariales asimétricos?

Decidir si implementar una función de pérdida personalizada en el *gradient boosting* bajo costos empresariales asimétricos requiere evaluar si la asimetría puede ser manejada *downstream* mediante la calibración de probabilidad y el ajuste de umbrales, o si altera fundamentalmente el panorama de optimización durante la inducción del árbol: 1. **Ajuste de Umbral vs. Pérdida Personalizada:** Para tareas de clasificación con costos de error asimétricos (p. ej., falsos negativos que cuestan `10×` más que los falsos positivos), la *cross-entropy* estándar es una regla de puntuación adecuada que tiene como objetivo estimar las probabilidades posteriores `P(y=1|x)`. Sin embargo, la calibración debe verificarse y, si es necesario, corregirse en los datos de validación. Mover el umbral de decisión de clasificación basado en la matriz de costos empresariales `$\tau = \frac{C_{FP}}{C_{FP} + C_{FN}}$` o aplicar pesos de muestra suele ser más limpio y evita derivadas personalizadas. No obstante, para regresión asimétrica (p. ej., pérdida *pinball* asimétrica para la demanda de inventario) o penalizaciones empresariales no lineales donde los objetivos estándar no pueden guiar la búsqueda de divisiones, se justifica una función de pérdida personalizada. 2. **Requisitos Matemáticos para GBDT (Gradient Boosting Decision Trees):** En los *boosters* de segundo orden (XGBoost, LightGBM), una función de pérdida personalizada `L(y, \hat{y})` normalmente necesita gradientes de primer orden calculables (`$g_i = \partial L / \partial \hat{y}_i$`) y valores de curvatura/Hessianos de segundo orden válidos (`$h_i = \partial^2 L / \partial \hat{y}_i^2$`) para el cálculo de la ganancia de división y los pesos de las hojas (`$w^* = -\sum g_i / (\sum h_i + \lambda)$`). Los Hessianos deben ser no negativos o aproximarse/recortarse de forma segura para la estabilidad numérica; algunas implementaciones soportan objetivos de primer orden o aproximados, por lo que el requisito es específico del *framework*. Las métricas de negocio no diferenciables o discontinuas deben reemplazarse con aproximaciones *surrogate* suaves (p. ej., variantes Huberizadas o *log-cosh*).

import numpy as np
import xgboost as xgb

def asymmetric_mse_objective(preds, dtrain):
    labels = dtrain.get_label()
    residual = preds - labels
    # Penalize underestimation (residual < 0) 5x more heavily than overestimation
    penalty = np.where(residual < 0, 5.0, 1.0)
    grad = 2.0 * penalty * residual
    hess = 2.0 * penalty
    return grad, hess

# Usage:
# model = xgb.train(params, dtrain, obj=asymmetric_mse_objective)
Probar responder esta pregunta con un coach de IA

15¿Qué es LambdaMART y cómo adapta el *gradient boosting* para los objetivos de *learning-to-rank*?

LambdaMART es un algoritmo de *Learning-to-Rank* (LTR) que combina MART (árboles de regresión aditivos múltiples / *gradient boosting*) con el *framework* LambdaRank. En la clasificación (ranking), las métricas objetivo como NDCG (Ganancia Acumulada Descontada Normalizada) y MAP (Precisión Media Promedio) dependen de un orden de clasificación discreto (rangos), lo que las hace planas en casi todas partes y no diferenciables con respecto a las puntuaciones continuas del modelo. LambdaMART elude esto construyendo gradientes virtuales, llamados 'gradientes lambda' ($\lambda_{ij}$), para pares de elementos $(i, j)$ dentro de la misma consulta. El gradiente de pares base proviene de una pérdida logística sobre las diferencias de puntuación ($s_i - s_j$). LambdaMART escala este gradiente por el cambio exacto en la métrica de clasificación objetivo ($|\Delta \text{NDCG}_{ij}|$) que ocurriría si se intercambiaran las posiciones del documento $i$ y el documento $j$: $$\lambda_{ij} = \frac{-\sigma}{1 + e^{\sigma(s_i - s_j)}} |\Delta \text{NDCG}_{ij}|$$ Para cada documento individual $i$, el gradiente neto se calcula agregando los lambdas por pares en todos los pares que involucran al documento $i$: $\lambda_i = \sum_{j: j \succ i} \lambda_{ij} - \sum_{k: i \succ k} \lambda_{ki}$. Los árboles de regresión estándar en el conjunto de *boosting* ajustan estos gradientes lambda compuestos por documento en cada iteración de *boosting*, optimizando directamente las métricas de clasificación *listwise*.

import lightgbm as lgb
import numpy as np

# Simulated query-grouped data: 2 queries with 3 docs each
X = np.random.randn(6, 10)
y = np.array([3, 1, 0, 2, 0, 1])  # Relevance grades (0-3)
group = [3, 3]                     # Query group sizes

train_data = lgb.Dataset(X, label=y, group=group)
params = {
    'objective': 'lambdarank',
    'metric': 'ndcg',
    'ndcg_eval_at': [1, 3],
    'learning_rate': 0.1,
    'n_estimators': 50
}

ranker = lgb.train(params, train_data)
Probar responder esta pregunta con un coach de IA