diciembre 21, 2025
15 min de lectura

Preguntas para Entrevistas de Ingeniero Junior de Machine Learning: Guía Completa

interview
career-advice
job-search
entry-level
Preguntas para Entrevistas de Ingeniero Junior de Machine Learning: Guía Completa
MB

Milad Bonakdar

Autor

Domina los fundamentos de la ingeniería de ML con preguntas esenciales para entrevistas que cubren Python, algoritmos de ML, entrenamiento de modelos, conceptos básicos de implementación y MLOps para ingenieros junior de machine learning.


Introducción

Los ingenieros de Machine Learning construyen, implementan y mantienen sistemas de ML en producción. Se espera que los ingenieros junior de ML tengan sólidas habilidades de programación, comprensión de los algoritmos de ML, experiencia con los frameworks de ML y conocimiento de las prácticas de implementación.

Esta guía cubre las preguntas esenciales de la entrevista para ingenieros junior de Machine Learning. Exploramos la programación en Python, los algoritmos de ML, el entrenamiento y la evaluación de modelos, los fundamentos de la implementación y los fundamentos de MLOps para ayudarte a prepararte para tu primer puesto de ingeniero de ML.


Python y Programación (5 Preguntas)

1. ¿Cómo manejas grandes conjuntos de datos que no caben en la memoria?

Respuesta: Varias técnicas manejan datos más grandes que la RAM disponible:

  • Procesamiento por lotes: Procesa los datos en fragmentos
  • Generadores: Entrega datos bajo demanda
  • Dask/Ray: Frameworks de computación distribuida
  • Consultas a la base de datos: Carga solo los datos necesarios
  • Archivos asignados a la memoria: Accede al disco como si estuviera en la memoria
  • Transmisión de datos: Procesa los datos a medida que llegan
import pandas as pd
import numpy as np

# Mal: Cargar todo el conjunto de datos en la memoria
# df = pd.read_csv('large_file.csv')  # Puede fallar

# Bien: Leer en fragmentos
chunk_size = 10000
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    # Procesar cada fragmento
    processed = chunk[chunk['value'] > 0]
    # Guardar o agregar los resultados
    processed.to_csv('output.csv', mode='a', header=False)

# Usando generadores
def data_generator(filename, batch_size=32):
    while True:
        batch = []
        with open(filename, 'r') as f:
            for line in f:
                batch.append(process_line(line))
                if len(batch) == batch_size:
                    yield np.array(batch)
                    batch = []

# Dask para computación distribuida
import dask.dataframe as dd
ddf = dd.read_csv('large_file.csv')
result = ddf.groupby('category').mean().compute()

Rareza: Muy Común Dificultad: Media


2. Explica los decoradores en Python y da un caso de uso de ML.

Respuesta: Los decoradores modifican o mejoran las funciones sin cambiar su código.

  • Casos de uso en ML:
    • Medición del tiempo de ejecución de la función
    • Registro de predicciones
    • Almacenamiento en caché de los resultados
    • Validación de entradas
import time
import functools

# Decorador de tiempo
def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} tardó {end - start:.2f} segundos")
        return result
    return wrapper

# Decorador de registro
def log_predictions(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        predictions = func(*args, **kwargs)
        print(f"Se hicieron {len(predictions)} predicciones")
        print(f"Distribución de la predicción: {np.bincount(predictions)}")
        return predictions
    return wrapper

# Uso
@timer
@log_predictions
def predict_batch(model, X):
    return model.predict(X)

# Decorador de almacenamiento en caché (memoización)
def cache_results(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@cache_results
def expensive_feature_engineering(data_id):
    # Computación costosa
    return processed_features

Rareza: Común Dificultad: Media


3. ¿Cuál es la diferencia entre @staticmethod y @classmethod?

Respuesta: Ambos definen métodos que no requieren una instancia.

  • @staticmethod: No tiene acceso a la clase o a la instancia
  • @classmethod: Recibe la clase como primer argumento
class MLModel:
    model_type = "classifier"
    
    def __init__(self, name):
        self.name = name
    
    # Método regular - necesita una instancia
    def predict(self, X):
        return self.model.predict(X)
    
    # Método estático - función de utilidad
    @staticmethod
    def preprocess_data(X):
        # No tiene acceso a self o cls
        return (X - X.mean()) / X.std()
    
    # Método de clase - patrón de fábrica
    @classmethod
    def create_default(cls):
        # Tiene acceso a cls
        return cls(name=f"default_{cls.model_type}")
    
    @classmethod
    def from_config(cls, config):
        return cls(name=config['name'])

# Uso
# Método estático - no se necesita instancia
processed = MLModel.preprocess_data(X_train)

# Método de clase - crea una instancia
model = MLModel.create_default()
model2 = MLModel.from_config({'name': 'my_model'})

Rareza: Media Dificultad: Media


4. ¿Cómo manejas las excepciones en las tuberías de ML?

Respuesta: Un manejo adecuado de los errores evita los fallos en la tubería y ayuda a la depuración.

import logging
from typing import Optional

# Configuración del registro
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ModelTrainingError(Exception):
    """Excepción personalizada para los fallos en el entrenamiento del modelo"""
    pass

def train_model(X, y, model_type='random_forest'):
    try:
        logger.info(f"Comenzando el entrenamiento con {model_type}")
        
        # Validar las entradas
        if X.shape[0] != y.shape[0]:
            raise ValueError("X e y deben tener el mismo número de muestras")
        
        if X.shape[0] < 100:
            raise ModelTrainingError("Datos de entrenamiento insuficientes")
        
        # Entrenar el modelo
        if model_type == 'random_forest':
            from sklearn.ensemble import RandomForestClassifier
            model = RandomForestClassifier()
        else:
            raise ValueError(f"Tipo de modelo desconocido: {model_type}")
        
        model.fit(X, y)
        logger.info("Entrenamiento completado con éxito")
        return model
        
    except ValueError as e:
        logger.error(f"Error de validación: {e}")
        raise
    except ModelTrainingError as e:
        logger.error(f"Error de entrenamiento: {e}")
        # Podría recurrir a un modelo más sencillo
        return train_fallback_model(X, y)
    except Exception as e:
        logger.error(f"Error inesperado: {e}")
        raise
    finally:
        logger.info("Intento de entrenamiento finalizado")

# Gestor de contexto para la gestión de recursos
class ModelLoader:
    def __init__(self, model_path):
        self.model_path = model_path
        self.model = None
    
    def __enter__(self):
        logger.info(f"Cargando el modelo desde {self.model_path}")
        self.model = load_model(self.model_path)
        return self.model
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        logger.info("Limpiando los recursos")
        if self.model:
            del self.model
        return False  # No suprimir las excepciones

# Uso
with ModelLoader('model.pkl') as model:
    predictions = model.predict(X_test)

Rareza: Común Dificultad: Media


5. ¿Qué son los generadores de Python y por qué son útiles en ML?

Respuesta: Los generadores producen valores uno a la vez, ahorrando memoria.

  • Beneficios:
    • Eficiencia de memoria
    • Evaluación perezosa
    • Secuencias infinitas
  • Casos de uso de ML:
    • Carga de datos
    • Procesamiento por lotes
    • Aumento de datos
import numpy as np

# Generador para el procesamiento por lotes
def batch_generator(X, y, batch_size=32):
    n_samples = len(X)
    indices = np.arange(n_samples)
    np.random.shuffle(indices)
    
    for start_idx in range(0, n_samples, batch_size):
        end_idx = min(start_idx + batch_size, n_samples)
        batch_indices = indices[start_idx:end_idx]
        yield X[batch_indices], y[batch_indices]

# Uso en el entrenamiento
for epoch in range(10):
    for X_batch, y_batch in batch_generator(X_train, y_train):
        model.train_on_batch(X_batch, y_batch)

# Generador de aumento de datos
def augment_images(images, labels):
    for img, label in zip(images, labels):
        # Original
        yield img, label
        # Volteada
        yield np.fliplr(img), label
        # Rotada
        yield np.rot90(img), label

# Generador infinito para el entrenamiento
def infinite_batch_generator(X, y, batch_size=32):
    while True:
        indices = np.random.choice(len(X), batch_size)
        yield X[indices], y[indices]

# Usar con steps_per_epoch
gen = infinite_batch_generator(X_train, y_train)
# model.fit(gen, steps_per_epoch=100, epochs=10)

Rareza: Común Dificultad: Media


Algoritmos y Teoría de ML (5 Preguntas)

6. Explica la diferencia entre bagging y boosting.

Respuesta: Ambos son métodos de conjunto, pero funcionan de manera diferente:

  • Bagging (Bootstrap Aggregating):
    • Entrenamiento paralelo en subconjuntos aleatorios
    • Reduce la varianza
    • Ejemplo: Random Forest
  • Boosting:
    • Entrenamiento secuencial, cada modelo corrige los errores anteriores
    • Reduce el sesgo
    • Ejemplos: AdaBoost, Gradient Boosting, XGBoost
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score

# Generar datos
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)

# Bagging - Random Forest
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf_scores = cross_val_score(rf, X, y, cv=5)
print(f"CV de Random Forest: {rf_scores.mean():.3f} (+/- {rf_scores.std():.3f})")

# Boosting - Gradient Boosting
gb = GradientBoostingClassifier(n_estimators=100, random_state=42)
gb_scores = cross_val_score(gb, X, y, cv=5)
print(f"CV de Gradient Boosting: {gb_scores.mean():.3f} (+/- {gb_scores.std():.3f})")

# XGBoost (boosting avanzado)
import xgboost as xgb
xgb_model = xgb.XGBClassifier(n_estimators=100, random_state=42)
xgb_scores = cross_val_score(xgb_model, X, y, cv=5)
print(f"CV de XGBoost: {xgb_scores.mean():.3f} (+/- {xgb_scores.std():.3f})")

Rareza: Muy Común Dificultad: Media


7. ¿Cómo manejas los conjuntos de datos desequilibrados?

Respuesta: Los datos desequilibrados pueden sesgar los modelos hacia la clase mayoritaria.

  • Técnicas:
    • Remuestreo: SMOTE, submuestreo
    • Pesos de clase: Penaliza la clasificación errónea
    • Métodos de conjunto: Random Forest equilibrado
    • Evaluación: Usar F1, precisión, recall (no exactitud)
    • Ajuste del umbral: Optimizar el umbral de decisión
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from collections import Counter

# Crear un conjunto de datos desequilibrado
X, y = make_classification(
    n_samples=1000, n_features=20,
    weights=[0.95, 0.05],  # 95% clase 0, 5% clase 1
    random_state=42
)

print(f"Distribución original: {Counter(y)}")

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 1. Pesos de clase
model_weighted = RandomForestClassifier(class_weight='balanced', random_state=42)
model_weighted.fit(X_train, y_train)
print("\nCon pesos de clase:")
print(classification_report(y_test, model_weighted.predict(X_test)))

# 2. SMOTE (sobremuestreo de la clase minoritaria)
smote = SMOTE(random_state=42)
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
print(f"Después de SMOTE: {Counter(y_train_smote)}")

model_smote = RandomForestClassifier(random_state=42)
model_smote.fit(X_train_smote, y_train_smote)
print("\nCon SMOTE:")
print(classification_report(y_test, model_smote.predict(X_test)))

# 3. Ajuste del umbral
y_proba = model_weighted.predict_proba(X_test)[:, 1]
threshold = 0.3  # Un umbral más bajo favorece a la clase minoritaria
y_pred_adjusted = (y_proba >= threshold).astype(int)
print("\nCon el umbral ajustado:")
print(classification_report(y_test, y_pred_adjusted))

Rareza: Muy Común Dificultad: Media


8. ¿Qué es la validación cruzada y por qué es importante?

Respuesta: La validación cruzada evalúa el rendimiento del modelo de forma más fiable que una única división de entrenamiento-prueba.

  • Tipos:
    • K-Fold: Dividir en k folds
    • Stratified K-Fold: Conserva la distribución de la clase
    • Time Series Split: Respeta el orden temporal
  • Beneficios:
    • Estimación del rendimiento más robusta
    • Utiliza todos los datos para el entrenamiento y la validación
    • Detecta el sobreajuste
from sklearn.model_selection import (
    cross_val_score, KFold, StratifiedKFold,
    TimeSeriesSplit, cross_validate
)
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# Cargar datos
data = load_iris()
X, y = data.data, data.target

model = RandomForestClassifier(random_state=42)

# K-Fold estándar
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kfold)
print(f"Puntuaciones de K-Fold: {scores}")
print(f"Media: {scores.mean():.3f} (+/- {scores.std():.3f})")

# Stratified K-Fold (conserva la distribución de la clase)
stratified_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
stratified_scores = cross_val_score(model, X, y, cv=stratified_kfold)
print(f"\nStratified K-Fold: {stratified_scores.mean():.3f}")

# Time Series Split (para datos temporales)
tscv = TimeSeriesSplit(n_splits=5)
ts_scores = cross_val_score(model, X, y, cv=tscv)
print(f"CV de Time Series: {ts_scores.mean():.3f}")

# Múltiples métricas
cv_results = cross_validate(
    model, X, y, cv=5,
    scoring=['accuracy', 'precision_macro', 'recall_macro', 'f1_macro'],
    return_train_score=True
)

print(f"\nExactitud: {cv_results['test_accuracy'].mean():.3f}")
print(f"Precisión: {cv_results['test_precision_macro'].mean():.3f}")
print(f"Recall: {cv_results['test_recall_macro'].mean():.3f}")
print(f"F1: {cv_results['test_f1_macro'].mean():.3f}")

Rareza: Muy Común Dificultad: Fácil


9. Explica precisión, recall y F1-score.

Respuesta: Métricas de clasificación para evaluar el rendimiento del modelo:

  • Precisión: De los positivos predichos, cuántos son correctos
    • Fórmula: TP / (TP + FP)
    • Usar cuando: Los falsos positivos son costosos
  • Recall: De los positivos reales, cuántos se encontraron
    • Fórmula: TP / (TP + FN)
    • Usar cuando: Los falsos negativos son costosos
  • F1-Score: Media armónica de la precisión y el recall
    • Fórmula: 2 × (Precisión × Recall) / (Precisión + Recall)
    • Usar cuando: Se necesita un equilibrio entre la precisión y el recall
from sklearn.metrics import (
    precision_score, recall_score, f1_score,
    classification_report, confusion_matrix
)
import numpy as np

# Predicciones de ejemplo
y_true = np.array([0, 1, 1, 0, 1, 1, 0, 1, 0, 0])
y_pred = np.array([0, 1, 0, 0, 1, 1, 0, 1, 1, 0])

# Calcular las métricas
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)

print(f"Precisión: {precision:.3f}")  # 0.800
print(f"Recall: {recall:.3f}")        # 0.800
print(f"F1-Score: {f1:.3f}")          # 0.800

# Matriz de confusión
cm = confusion_matrix(y_true, y_pred)
print(f"\nMatriz de confusión:\n{cm}")
# [[4 1]
#  [1 4]]

# Informe de clasificación (todas las métricas)
print("\nInforme de clasificación:")
print(classification_report(y_true, y_pred))

# Ejemplo de compensación
from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt

# Obtener las predicciones de probabilidad
y_proba = model.predict_proba(X_test)[:, 1]

# Calcular la precisión-recall en diferentes umbrales
precisions, recalls, thresholds = precision_recall_curve(y_test, y_proba)

# Encontrar el umbral óptimo (maximizar F1)
f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-10)
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]
print(f"Umbral óptimo: {optimal_threshold:.3f}")

Rareza: Muy Común Dificultad: Fácil


10. ¿Qué es la regularización y cuándo la usarías?

Respuesta: La regularización previene el sobreajuste penalizando la complejidad del modelo.

  • Tipos:
    • L1 (Lasso): Añade el valor absoluto de los coeficientes
    • L2 (Ridge): Añade los coeficientes al cuadrado
    • Elastic Net: Combina L1 y L2
  • Cuándo usar:
    • Alta varianza (sobreajuste)
    • Muchas características
    • Multicolinealidad
from sklearn.linear_model import Ridge, Lasso, ElasticNet, LinearRegression
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# Generar datos con muchas características
X, y = make_regression(
    n_samples=100, n_features=50,
    n_informative=10, noise=10, random_state=42
)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# Sin regularización
lr = LinearRegression()
lr.fit(X_train, y_train)
lr_score = r2_score(y_test, lr.predict(X_test))
print(f"R² de la regresión lineal: {lr_score:.3f}")

# Regularización L2 (Ridge)
ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
ridge_score = r2_score(y_test, ridge.predict(X_test))
print(f"R² de Ridge: {ridge_score:.3f}")

# Regularización L1 (Lasso)
lasso = Lasso(alpha=0.1)
lasso.fit(X_train, y_train)
lasso_score = r2_score(y_test, lasso.predict(X_test))
print(f"R² de Lasso: {lasso_score:.3f}")
print(f"Coeficientes distintos de cero de Lasso: {np.sum(lasso.coef_ != 0)}")

# Elastic Net (L1 + L2)
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)
elastic.fit(X_train, y_train)
elastic_score = r2_score(y_test, elastic.predict(X_test))
print(f"R² de Elastic Net: {elastic_score:.3f}")

# Ajuste de hiperparámetros para alfa
from sklearn.model_selection import GridSearchCV

param_grid = {'alpha': [0.001, 0.01, 0.1, 1.0, 10.0]}
grid = GridSearchCV(Ridge(), param_grid, cv=5)
grid.fit(X_train, y_train)
print(f"\nMejor alfa: {grid.best_params_['alpha']}")
print(f"Mejor puntuación de CV: {grid.best_score_:.3f}")

Rareza: Muy Común Dificultad: Media


Entrenamiento e Implementación de Modelos (5 Preguntas)

11. ¿Cómo guardas y cargas modelos en producción?

Respuesta: La serialización de modelos permite la implementación y la reutilización.

import joblib
import pickle
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# Entrenar el modelo
X, y = load_iris(return_X_y=True)
model = RandomForestClassifier()
model.fit(X, y)

# Método 1: Joblib (recomendado para scikit-learn)
joblib.dump(model, 'model.joblib')
loaded_model = joblib.load('model.joblib')

# Método 2: Pickle
with open('model.pkl', 'wb') as f:
    pickle.dump(model, f)

with open('model.pkl', 'rb') as f:
    loaded_model = pickle.load(f)

# Para TensorFlow/Keras
import tensorflow as tf

# Guardar el modelo completo
# keras_model.save('model.h5')
# loaded_model = tf.keras.models.load_model('model.h5')

# Guardar solo los pesos
# keras_model.save_weights('model_weights.h5')
# new_model.load_weights('model_weights.h5')

# Para PyTorch
import torch

# Guardar el diccionario de estado del modelo
# torch.save(model.state_dict(), 'model.pth')
# model.load_state_dict(torch.load('model.pth'))

# Guardar el modelo completo
# torch.save(model, 'model_complete.pth')
# model = torch.load('model_complete.pth')

# Control de versiones para los modelos
import datetime

model_version = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
model_path = f'models/model_{model_version}.joblib'
joblib.dump(model, model_path)
print(f"Modelo guardado en {model_path}")

Rareza: Muy Común Dificultad: Fácil


12. ¿Cómo creas una API REST para el servicio de modelos?

Respuesta: Las API REST hacen que los modelos sean accesibles a las aplicaciones.

from flask import Flask, request, jsonify
import joblib
import numpy as np

app = Flask(__name__)

# Cargar el modelo al inicio
model = joblib.load('model.joblib')

@app.route('/predict', methods=['POST'])
def predict():
    try:
        # Obtener los datos de la solicitud
        data = request.get_json()
        features = np.array(data['features']).reshape(1, -1)
        
        # Hacer la predicción
        prediction = model.predict(features)
        probability = model.predict_proba(features)
        
        # Devolver la respuesta
        return jsonify({
            'prediction': int(prediction[0]),
            'probability': probability[0].tolist(),
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': str(e),
            'status': 'error'
        }), 400

@app.route('/health', methods=['GET'])
def health():
    return jsonify({'status': 'healthy'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

# Alternativa FastAPI (moderna, más rápida)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class PredictionRequest(BaseModel):
    features: list

class PredictionResponse(BaseModel):
    prediction: int
    probability: list
    status: str

@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
    try:
        features = np.array(request.features).reshape(1, -1)
        prediction = model.predict(features)
        probability = model.predict_proba(features)
        
        return PredictionResponse(
            prediction=int(prediction[0]),
            probability=probability[0].tolist(),
            status="success"
        )
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

# Uso:
# curl -X POST "http://localhost:5000/predict" \
#      -H "Content-Type: application/json" \
#      -d '{"features": [5.1, 3.5, 1.4, 0.2]}'

Rareza: Muy Común Dificultad: Media


13. ¿Qué es Docker y por qué es útil para la implementación de ML?

Respuesta: Los contenedores Docker empaquetan las aplicaciones con todas las dependencias.

  • Beneficios:
    • Reproducibilidad
    • Consistencia entre entornos
    • Fácil implementación
    • Aislamiento
# Dockerfile para el modelo de ML
FROM python:3.9-slim

WORKDIR /app

# Copiar los requisitos
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar el modelo y el código
COPY model.joblib .
COPY app.py .

# Exponer el puerto
EXPOSE 5000

# Ejecutar la aplicación
CMD ["python", "app.py"]
# Construir la imagen de Docker
docker build -t ml-model:v1 .

# Ejecutar el contenedor
docker run -p 5000:5000 ml-model:v1

# Docker Compose para la configuración de varios contenedores
# docker-compose.yml
version: '3.8'
services:
  model:
    build: .
    ports:
      - "5000:5000"
    environment:
      - MODEL_PATH=/app/model.joblib
    volumes:
      - ./models:/app/models

Rareza: Común Dificultad: Media


14. ¿Cómo supervisas el rendimiento del modelo en producción?

Respuesta: La supervisión detecta la degradación del modelo y garantiza la fiabilidad.

  • Qué supervisar:
    • Métricas de predicción: Exactitud, latencia
    • Deriva de datos: Cambios en la distribución de las entradas
    • Deriva del modelo: Degradación del rendimiento
    • Métricas del sistema: CPU, memoria, errores
import logging
from datetime import datetime
import numpy as np

class ModelMonitor:
    def __init__(self, model):
        self.model = model
        self.predictions = []
        self.actuals = []
        self.latencies = []
        self.input_stats = []
        
        # Configurar el registro
        logging.basicConfig(
            filename='model_monitor.log',
            level=logging.INFO,
            format='%(asctime)s - %(message)s'
        )
    
    def predict(self, X):
        import time
        
        # Rastrear las estadísticas de las entradas
        self.input_stats.append({
            'mean': X.mean(),
            'std': X.std(),
            'min': X.min(),
            'max': X.max()
        })
        
        # Medir la latencia
        start = time.time()
        prediction = self.model.predict(X)
        latency = time.time() - start
        
        self.predictions.append(prediction)
        self.latencies.append(latency)
        
        # Registrar la predicción
        logging.info(f"Predicción: {prediction}, Latencia: {latency:.3f}s")
        
        # Alertar si la latencia es demasiado alta
        if latency > 1.0:
            logging.warning(f"Latencia alta detectada: {latency:.3f}s")
        
        return prediction
    
    def log_actual(self, y_true):
        self.actuals.append(y_true)
        
        # Calcular la exactitud si tenemos suficientes datos
        if len(self.actuals) >= 100:
            accuracy = np.mean(
                np.array(self.predictions[-100:]) == np.array(self.actuals[-100:])
            )
            logging.info(f"Exactitud rodante (últimos 100): {accuracy:.3f}")
            
            if accuracy < 0.8:
                logging.error(f"Rendimiento del modelo degradado: {accuracy:.3f}")
    
    def check_data_drift(self, reference_stats):
        if not self.input_stats:
            return
        
        current_stats = self.input_stats[-1]
        
        # Detección simple de la deriva (comparar las medias)
        mean_diff = abs(current_stats['mean'] - reference_stats['mean'])
        if mean_diff > 2 * reference_stats['std']:
            logging.warning(f"Deriva de datos detectada: diferencia de la media = {mean_diff:.3f}")

# Uso
monitor = ModelMonitor(model)
prediction = monitor.predict(X_new)
# Más tarde, cuando la etiqueta real esté disponible
monitor.log_actual(y_true)

Rareza: Común Dificultad: Media


15. ¿Qué es CI/CD para el aprendizaje automático?

Respuesta: CI/CD automatiza las pruebas y la implementación de los modelos de ML.

  • Integración Continua:
    • Pruebas automatizadas
    • Comprobaciones de la calidad del código
    • Validación del modelo
  • Implementación Continua:
    • Implementación automatizada
    • Capacidades de reversión
    • Pruebas A/B
# .github/workflows/ml-pipeline.yml
name: ML Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
Newsletter subscription

Consejos de carrera semanales que realmente funcionan

Recibe las últimas ideas directamente en tu bandeja de entrada

Decorative doodle

Deja de Postularte. Comienza a Ser Contratado.

Transforma tu currículum en un imán de entrevistas con optimización impulsada por IA confiada por buscadores de empleo en todo el mundo.

Comienza gratis

Compartir esta publicación

Consigue Empleo 50% Más Rápido

Los buscadores de empleo que usan currículums profesionales mejorados con IA consiguen puestos en un promedio de 5 semanas en comparación con las 10 estándar. Deja de esperar y empieza a entrevistar.