En mantenimiento predictivo, distinguir “comportamiento normal” de “comportamiento anómalo” con pocos falsos positivos es clave para intervenir a tiempo sin disparar alarmas innecesarias. Los Modelos Ocultos de Markov (HMM) son una opción elegante cuando trabajamos con señales temporales multivariantes y queremos modelar la dinámica (cómo cambian los estados internos del sistema a lo largo del tiempo) en lugar de solo mirar instantáneas estáticas.
Este artículo resume un flujo de trabajo práctico con HMM aplicado a CMAPSS (el dataset clásico de NASA para turbofanes), siguiendo dos fases: entrenamiento del comportamiento normal y despliegue del detector (scoring por verosimilitud y decisión). El enfoque se implementa con hmmlearn
y un preprocesado sencillo con ventanas deslizantes.
¿Por qué HMM para anomalías?
Un HMM asume que existe una cadena de estados latentes (p. ej., “régimen estable”, “transitorio”, “pre-fallo”) que evoluciona con dinámica Markoviana. Cada estado emite observaciones según una distribución (en regresión de equipos, suele bastar una Gaussiana por estado o un GMM si la distribución es multimodal). Dos ventajas:
- Estructura temporal: capta transiciones entre regímenes operativos (no solo medias/varianzas).
- Probabilidad explícita: el log-verosímil de una secuencia dada es un score natural: cuanto menos probable sea la secuencia bajo el “modelo de normalidad”, más sospechosa.
Datos y preprocesado (CMAPSS)
- Datos: series temporales multivariantes de sensores de motores (subconjuntos FD00x).
- Ventaneo: para convertir trayectorias largas en muestras comparables, se usan ventanas deslizantes de longitud fija L (p. ej., 5–20 pasos).
- Estandarización:
StandardScaler
por característica, ajustado sobre las ventanas de entrenamiento (solo “normal”). - Etiquetas: en un setting no supervisado, basta entrenar con ejemplos normales y, en test, puntuar con el HMM.
Entrenamiento: “aprender la normalidad”
Se entrena un GaussianHMM con pocas componentes (p. ej., 2–4 estados) y una covarianza sencilla (p. ej., spherical
o diag
) para robustez y eficiencia.
from hmmlearn.hmm import GaussianHMM
from sklearn.preprocessing import StandardScaler
import numpy as np
# X_train: (N, L, F) con ventanas "normales"
N, L, F = X_train.shape
scaler = StandardScaler().fit(X_train.reshape(-1, F))
Xtr = scaler.transform(X_train.reshape(-1, F))
lengths = [L] * N
hmm = GaussianHMM(n_components=2, covariance_type='spherical', n_iter=200, random_state=0)
hmm.fit(Xtr, lengths=lengths)
Score y umbral
El score de cada ventana es su log-verosimilitud bajo el HMM. Como anomaly score usamos -loglik
(más alto ⇒ más anómalo). Para decidir, se fija un umbral desde el propio entrenamiento (percentil alto de los -loglik
normales, p. ej., P93–P99):
def hmm_scores(model, X3, scaler):
N, L, F = X3.shape
X = scaler.transform(X3.reshape(-1, F)).reshape(N, L, F)
return np.array([model.score(X[i]) for i in range(N)]) # loglik por ventana
loglik_train = hmm_scores(hmm, X_train, scaler)
threshold = np.percentile(-loglik_train, 93) # ejemplo
Inferencia: del score a la alarma
En despliegue, dadas ventanas de test:
loglik_test = hmm_scores(hmm, X_test, scaler)
anom_score = -loglik_test
y_pred = (anom_score > threshold).astype(int)
Para suavizar y reducir falsos positivos debidos a ruido, es muy eficaz exigir k consecutivas:
def flag_k_consecutive(flags, k=3):
run = 0
out = np.zeros_like(flags, dtype=bool)
for i, a in enumerate(flags.astype(bool)):
run = run + 1 if a else 0
out[i] = run >= k
return out
y_alarm = flag_k_consecutive(y_pred, k=3)
Este “regla k-consecutivas” simula la lógica operativa de muchos equipos: una única ventana sospechosa no dispara mantenimiento, pero persistencia sí.
Cómo leer las métricas
En tus notebooks se calculan accuracy, precision, recall, specificity, F1 y AUC. En predictive maintenance conviene priorizar:
- Recall (sensibilidad): no perdernos anomalías reales.
- Specificity: no saturar con falsas alarmas.
- F1 como equilibrio y, sobre todo, evaluar horizonte de detección temprana (cuántas horas/ciclos antes del fallo detectamos la deriva).
La matriz de confusión por ventanas ofrece una lectura rápida; adicionalmente, visualizar anom_score
a lo largo del tiempo (o por unidad) ayuda a ver derivas que preceden el fallo.
Consejos prácticos (lo que funcionó bien)
- Escalado: imprescindible; sin él, sensores con distinta escala dominan el ajuste.
- Estados (n_components): empezar con 2–3; si ves multimodalidad clara en residuos o regímenes, subir a 4–5. Más estados ⇒ más capacidad pero mayor riesgo de sobreajuste.
- Covarianza:
spherical
/diag
suelen ser más estables quefull
con pocos datos por estado. - Longitud de ventana (L): pequeñas capturan eventos locales; grandes capturan dinámica más lenta. Prueba L∈[5, 20].
- Umbral por percentil: fácil de calibrar con datos normales; si hay etiquetas, afina con curva Precision–Recall.
- Regla k-consecutivas: reduce picos espurios; k∈[3, 5] suele ser un buen comienzo.
- Persistencia del pipeline: guarda modelo + scaler + metadatos (forma de entrenamiento, umbral, fecha). Facilita reproducibilidad y auditoría.
¿Qué dice el HMM “por dentro”?
Además del score, el HMM permite etiquetar cada ventana con su estado más probable (Viterbi). Esto abre diagnósticos útiles:
- ¿Qué sensores distinguen más los estados?
- ¿Aumenta la tasa de transición hacia un estado particular antes del fallo?
- ¿Se alarga la duración esperada de un estado “inestable” con la degradación?
Esta interpretabilidad operativa es un plus frente a modelos puramente discriminativos.
Limitaciones y extensiones
Limitaciones:
- Supone Markov y emisores (Gaussians/GMM) relativamente simples.
- No siempre captura no linealidades complejas entre sensores.
Extensiones:
- GMM-HMM si cada estado es claramente multimodal.
- HDP-HMM (no paramétrico) cuando el nº de estados es incierto.
- HSMM (duraciones explícitas) para equipos con permanencias de estado características.
- HMM profundo (emisiones neurales) si hay mucha señal no lineal.
Checklist para producción
- Drift de datos: monitoriza medias/varianzas y distribución del score en operación para recalibrar umbrales.
- Ventaneo online: genera ventanas en streaming y puntúa en caliente.
- Alarmas operativas: integra la regla k-consecutivas y cooldowns para evitar “flapping”.
- Explainability: registra estado Viterbi y top sensores por contribución.
- Retraining: ciclo de realimentación con nuevas campañas y degradaciones reales etiquetadas.
Conclusión
Los HMM son una línea base potente y transparente para detección de anomalías en mantenimiento predictivo con datos temporales multivariantes. Con un pipeline sencillo (escalado, HMM Gaussiano, umbral por percentil y regla de k-consecutivas) se obtiene un detector estable, interpretable y rápido de desplegar. A partir de ahí, puedes iterar hacia variantes (GMM-HMM, HSMM) o combinarlos con modelos más sofisticados cuando la complejidad de la planta lo justifique.
Apéndice: plantilla mínima (entrenamiento e inferencia)
Entrenamiento
# X_train: (N, L, F) con ventanas normales
scaler = StandardScaler().fit(X_train.reshape(-1, F))
Xtr = scaler.transform(X_train.reshape(-1, F))
lengths = [L]*N
hmm = GaussianHMM(n_components=3, covariance_type='diag', n_iter=200, random_state=0)
hmm.fit(Xtr, lengths=lengths)
loglik_train = np.array([hmm.score(Xtr[i*L:(i+1)*L]) for i in range(N)])
threshold = np.percentile(-loglik_train, 95)
# Serializa modelo, scaler y metadatos
Inferencia
def score_windows(hmm, scaler, X):
N, L, F = X.shape
Xs = scaler.transform(X.reshape(-1, F)).reshape(N, L, F)
return np.array([hmm.score(Xs[i]) for i in range(N)])
loglik = score_windows(hmm, scaler, X_test)
y_raw = (-loglik > threshold).astype(int)
y_alarm = flag_k_consecutive(y_raw, k=3)