Introducción al aprendizaje automático para ingenieros de backend
Introducción práctica a los conceptos de aprendizaje automático que un ingeniero backend necesita para integrar modelos en producción.

No necesitas un doctorado para integrar aprendizaje automático en tus aplicaciones. Lo que sí necesitas es entender qué pueden y qué no pueden hacer los modelos de ML, cómo fallan y cómo ponerlos en producción. La mayoría de los ingenieros de backend no se topan con el ML entrenando modelos, sino desplegándolos, monitoreándolos y manteniéndolos.
La brecha entre "el modelo funciona en un notebook de Jupyter" y "el modelo funciona en producción" es donde las habilidades de ingeniería de backend se vuelven críticas. Ahí es donde entras tú.
Los modelos de ML son solo funciones
Si quitas la jerga, un modelo de ML entrenado es una función: entran datos y sale una predicción. El proceso de entrenamiento encuentra los parámetros que hacen que esta función produzca resultados útiles.
# A trained model is conceptually this simple
def predict_churn(customer: dict) -> float:
"""Returns probability (0-1) that customer will cancel."""
# Internally: multiply features by learned weights,
# apply activation functions, return result
return model.predict(preprocess(customer))
# In production, you're wrapping this function in an API
@app.post("/predict/churn")
async def churn_endpoint(customer: CustomerInput):
probability = predict_churn(customer.dict())
return {"churn_probability": probability}La complejidad vive en tres áreas: llevar los datos al formato correcto, servir las predicciones con una latencia aceptable y monitorear si el modelo sigue funcionando.
Ingeniería de características: el trabajo real
Los modelos consumen números, no objetos. Convertir los datos crudos de la aplicación en las características numéricas que espera un modelo se llama ingeniería de características (feature engineering), y suele ser la parte más frágil del pipeline.
# ❌ Passing raw data directly — model can't use this
raw_customer = {
"signup_date": "2020-03-15",
"plan": "premium",
"last_login": "2021-01-20",
"support_tickets": 3,
}
# ✅ Engineered features — numeric values the model expects
def extract_features(customer: dict) -> list[float]:
now = datetime.now()
signup = datetime.fromisoformat(customer["signup_date"])
last_login = datetime.fromisoformat(customer["last_login"])
return [
(now - signup).days, # account_age_days
(now - last_login).days, # days_since_login
customer["support_tickets"], # support_ticket_count
1.0 if customer["plan"] == "premium" else 0.0, # is_premium
customer["support_tickets"] / max((now - signup).days, 1), # tickets_per_day
]La regla crítica: las características usadas en producción deben coincidir con las usadas durante el entrenamiento, en el mismo orden y con las mismas transformaciones. Un desajuste aquí produce predicciones que parecen plausibles pero que están completamente equivocadas.
// Feature pipeline validation
interface FeatureSchema {
name: string;
type: 'numeric' | 'categorical';
range?: [number, number];
required: boolean;
}
const expectedFeatures: FeatureSchema[] = [
{ name: 'account_age_days', type: 'numeric', range: [0, 3650], required: true },
{ name: 'days_since_login', type: 'numeric', range: [0, 365], required: true },
{ name: 'support_tickets', type: 'numeric', range: [0, 100], required: true },
{ name: 'is_premium', type: 'categorical', required: true },
{ name: 'tickets_per_day', type: 'numeric', range: [0, 10], required: true },
];Patrones para servir modelos
Tres patrones comunes para servir predicciones de ML, cada uno con sus propias ventajas y desventajas.
API síncrona
# Direct inference — model loaded in the API process
from fastapi import FastAPI
import joblib
app = FastAPI()
model = joblib.load("models/churn_v3.pkl")
@app.post("/predict")
async def predict(features: FeatureInput):
prediction = model.predict([features.to_array()])
return {"prediction": float(prediction[0])}Es simple, pero el modelo consume memoria en cada proceso worker de la API, y la latencia de inferencia se suma directamente al tiempo de respuesta.
Asíncrono con cola
# Decouple prediction from request/response
import aio_pika
async def request_prediction(customer_id: str, features: list[float]):
message = aio_pika.Message(
body=json.dumps({
"customer_id": customer_id,
"features": features,
"callback_url": "/webhooks/prediction-complete",
}).encode()
)
await channel.default_exchange.publish(
message,
routing_key="predictions",
)
# Worker process — separate from API
async def prediction_worker(message):
data = json.loads(message.body)
prediction = model.predict([data["features"]])
await notify_callback(data["callback_url"], {
"customer_id": data["customer_id"],
"prediction": float(prediction[0]),
})Es mejor para modelos costosos (con inferencia superior a 100 ms) o para predicciones por lotes. La API se mantiene responsiva mientras la predicción ocurre de forma asíncrona.
Predicciones precalculadas
-- Batch job runs nightly, stores predictions in database
INSERT INTO churn_predictions (customer_id, probability, computed_at)
SELECT
c.id,
predict_churn(c.features),
NOW()
FROM customers c
ON CONFLICT (customer_id)
DO UPDATE SET probability = EXCLUDED.probability,
computed_at = EXCLUDED.computed_at;// API reads pre-computed predictions — zero inference latency
async function getChurnRisk(customerId: string): Promise<number> {
const result = await db.query(
'SELECT probability FROM churn_predictions WHERE customer_id = $1',
[customerId]
);
return result?.probability ?? 0.5; // default for new customers
}Esto funciona cuando las predicciones no necesitan ser en tiempo real. La mayoría de los sistemas de recomendación, puntajes de riesgo y funciones de personalización usan predicciones precalculadas.
Monitoreo de modelos
Un modelo que fue 95 % preciso en el entrenamiento puede degradarse a un 60 % en producción sin ningún cambio de código. Esto ocurre cuando la distribución de los datos del mundo real se aleja de la distribución de los datos de entrenamiento.
interface PredictionLog {
modelVersion: string;
inputFeatures: Record<string, number>;
prediction: number;
confidence: number;
latencyMs: number;
timestamp: Date;
}
async function logPrediction(log: PredictionLog): Promise<void> {
// Store for monitoring and analysis
await db.insert('prediction_logs', log);
// Alert on anomalies
if (log.confidence < 0.3) {
await alerting.warn('low-confidence-prediction', {
model: log.modelVersion,
confidence: log.confidence,
});
}
if (log.latencyMs > 200) {
await alerting.warn('slow-prediction', {
model: log.modelVersion,
latency: log.latencyMs,
});
}
}Monitorea tres cosas:
- Distribución de las predicciones: si la distribución de salida cambia, es posible que el modelo esté viendo datos con los que no fue entrenado
- Distribución de las características: si las características de entrada se alejan de los rangos de entrenamiento, las predicciones se vuelven poco confiables
- Latencia: el tiempo de inferencia del modelo puede dispararse con ciertas formas de entrada
Pruebas A/B de modelos
Desplegar una nueva versión de un modelo requiere compararla contra la versión actual con tráfico real.
function selectModel(userId: string): ModelVersion {
// Deterministic assignment based on user ID
const hash = hashCode(userId) % 100;
if (hash < 10) {
return 'v4-candidate'; // 10% traffic
}
return 'v3-production'; // 90% traffic
}
async function predict(userId: string, features: number[]): Promise<Prediction> {
const version = selectModel(userId);
const model = models.get(version);
const result = model.predict(features);
await logPrediction({
modelVersion: version,
prediction: result,
userId,
});
return result;
}Compara el modelo candidato contra el modelo en producción usando métricas de negocio (tasa de conversión, impacto en los ingresos), no solo puntajes de precisión. Un modelo con 2 % menos de precisión pero 10 % más de conversión es la mejor opción.
Puntos clave
- Los modelos de ML son funciones: entran datos, sale una predicción. Tu trabajo es servir esa función de manera confiable.
- La paridad de características es crítica: las características de producción deben coincidir exactamente con las de entrenamiento, o las predicciones no tienen sentido
- Elige el patrón de servicio según tus requisitos de latencia: API síncrona para tiempo real, cola para modelos costosos, precalculado para lotes
- Monitorea las predicciones, no solo la infraestructura: la calidad del modelo se degrada sin cambios de código cuando cambia la distribución de los datos
- Haz pruebas A/B con métricas de negocio: la precisión aislada no te dice si el modelo mejora los resultados


