Detección de anomalías con Modelos Ocultos de Markov (HMM) en mantenimiento predictivo


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:

  1. Estructura temporal: capta transiciones entre regímenes operativos (no solo medias/varianzas).
  2. 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 que full 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

  1. Drift de datos: monitoriza medias/varianzas y distribución del score en operación para recalibrar umbrales.
  2. Ventaneo online: genera ventanas en streaming y puntúa en caliente.
  3. Alarmas operativas: integra la regla k-consecutivas y cooldowns para evitar “flapping”.
  4. Explainability: registra estado Viterbi y top sensores por contribución.
  5. 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)