El 94% que nunca existió

¡Hola Devs! Todavía me acuerdo de la salida del terminal: 94.3% de precisión. Revisé la matriz de confusión dos veces. El número se fue al README y al CV. También estaba mal — no por un error de redondeo, sino por 25 puntos completos. La cifra honesta era 69%.

Nadie lo detectó por mí. Solo salió a la luz porque decidí construir herramientas capaces de verificar mis propios resultados. Corregirlo me enseñó más sobre ML en producción que construir el sistema original. Si evalúas modelos con datos secuenciales — video, audio, sensores vestibles, series temporales — hay buena probabilidad de que el mismo bug esté en tu pipeline ahora mismo.

Fíjate en esto: este es un post-mortem de un data leakage aparentemente banal, y de la reconstrucción de la evaluación que vino después. Para ver cómo la ingeniería asistida por IA está cambiando el flujo de MVP, checa nuestro análisis sobre Go como la lengua inesperada de MVP para ingeniería asistida por IA.

Data scientist analyzing confusion matrix and accuracy metrics on a laptop screen for fall detection model System Abstract Visual

El leakage: splits aleatorios sobre muestras dependientes

El sistema era un detector de caídas en tiempo real para monitoreo asistivo. Una webcam corre por MediaPipe Pose para extraer landmarks esqueléticos. A partir de ellos calculo features biomecánicas — ángulo del torso, alturas normalizadas de cadera y hombro, geometría del bounding box. Un Random Forest clasifica cada frame, y una ventana de suavizado sobre las últimas predicciones decide si dispara la alarma. Todo corre en CPU. Ningún video sale del dispositivo.

Entrené con el dataset Le2i de detección de caídas y usé la línea más inocente del mundo:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

El problema: ese split es aleatorio sobre frames individuales, y los frames del mismo video no son independientes. Dos frames capturados con 80 milisegundos de diferencia son casi idénticos — misma persona, misma pose, misma iluminación. Cuando mezclas todo y separas 20% para test, casi cada frame de test tiene un gemelo cercano en el set de entrenamiento.

O sea: el test no mide generalización a personas y cuartos nuevos. Mide memorización, y un Random Forest memoriza casi todo. Este es el data leakage en su forma más común: las muestras no son independientes, así que el split aleatorio le entrega las respuestas al set de test por adelantado.

La corrección es simple de enunciar: divide por lo que hace dependientes a tus muestras. Para video, divide por video.

from sklearn.model_selection import GroupKFold

# Agrupa por video_id para que ninguna grabación aparezca en ambos lados del fold
gkf = GroupKFold(n_splits=5)
for train_idx, test_idx in gkf.split(X, y, groups=video_ids):
    model.fit(X[train_idx], y[train_idx])
    score = model.score(X[test_idx], y[test_idx])

El score agrupado volvió en 69.1%. Uno de los folds, con el mismo código dividido honestamente, cayó a 31%. Ese es el número real.

Wearable sensor and webcam setup for real-time fall detection using MediaPipe pose estimation Developer Related Image

Las etiquetas eran peores que el split

Después de auditar el split, empecé a auditar todo lo demás. Mis etiquetas de entrenamiento venían de nombres de carpeta — si el video estaba en falls/, cada frame se etiquetaba fall. Pero una caída escenificada dura uno o dos segundos; el resto del clip es actividad normal. El dataset sí provee anotaciones frame a frame marcando el inicio y fin exactos de cada caída, así que las crucé. El resultado fue vergonzoso:

  • 76% de los frames con etiqueta fall mostraban a la persona solo caminando normal.
  • Solo 6% capturaban una caída real.
  • El 18% restante mostraba a alguien ya tirado en el piso después.
  • Un video de 78 segundos aportaba 44% de toda la clase positiva, y 1.5 segundos de él eran caída.

Mis etiquetas habían cambiado la tarea sin que me diera cuenta. El modelo resolvía el problema más fácil que ellas describían, no el que yo quería plantear. Y ese es justo el tipo de falla que el leakage esconde: una evaluación con leakage no puede decirte que tus etiquetas están mal, porque solo mide memorización — y un modelo memoriza etiquetas malas con la misma facilidad que las buenas.

Reconstruyendo desde el ground truth

Reconstruí el set de entrenamiento a partir de las anotaciones:

  • Un frame es positivo si está dentro de la ventana anotada de caída o en los dos segundos tirado que vienen después.
  • Todo lo anterior a la caída es negativo.
  • Frames ambiguos tarde después de la caída (la persona puede seguir tirada o estar levantándose, sin ground truth) se excluyeron, no se adivinaron.

También usé el dataset completo — 186 de 190 videos, en vez de los 28 originales. Dos escenas no tenían archivo de anotación, así que vi 59 videos frame a frame y anoté 29 ventanas de caída a mano, con un buffer de seguridad alrededor de mis estimaciones para que mi propia imprecisión no contaminara la clase negativa.

Mide lo que el usuario vive

Un cuidador nunca ve un frame. O recibe una alerta o no. Así que la evaluación ahora simula la lógica exacta de la alarma en producción — una ventana de votación sobre probabilidades por frame — en videos reservados, y reporta resultados a nivel de evento:

MétricaValor
Caídas detectadas121 / 126 (96.0%)
Latencia mediana de la alarma0.58 s desde el inicio de la caída
Videos con falsa alarma3–7 de 60, según el modelo

Cada fallo tuvo la misma causa: el estimador de pose perdía a la persona a mitad de la caída — oclusión por mueble, motion blur o imagen de resolución demasiado baja para rastrear. Si el estimador de pose no ve la caída, el clasificador nunca tiene su turno. Solo lo descubres evaluando eventos completos.

El trade-off que una sola métrica esconde

La evaluación honesta reveló una decisión que nunca había notado que estaba tomando:

ModeloCaídas detectadasVideos con falsa alarmaAcostarse a propósito
etiquetas "person down"96.0%7/60alarma
etiquetas "fall motion"89.7%3/60silencio

Ninguno está mal. En cuidado de adultos mayores, probablemente quieres una alarma cada vez que alguien está en el piso. Si el objetivo es distinguir caída de acostarse, el modelo de movimiento gana. El comportamiento lo controla la política de etiquetas, no la arquitectura — y una sola precisión habría escondido todo el trade-off. Entrego los dos y documento la diferencia.

Para ver cómo las herramientas de feature flag están evolucionando junto con este tipo de ML en producción, checa Vercel Flags GA: un análisis profundo de la gestión de feature flags.

Cloud backend architecture diagram with FastAPI and PostgreSQL for fall detection alert system Dev Environment Setup

El checklist

Si te llevas una sola cosa de mis 25 puntos desperdiciados, llévate el primer punto:

  1. Agrupa tus splits por lo que hace dependientes a las muestras — video, paciente, sujeto, sesión, dispositivo. Si cambiar a split aleatorio hace que tu score salte, el número más alto es el que miente.
  2. Compara tus etiquetas con las anotaciones reales en vez de confiar en los nombres de carpeta. Luego mira dentro de tu clase positiva y ve qué contiene de verdad.
  3. Mide eventos completos, como lo haría el usuario: cuántos incidentes atrapó, con qué rapidez, y con qué frecuencia se disparó cuando no pasó nada.
  4. Mantén un test de comportamiento reservado para el modo de falla que más te asusta. El mío es un acostarse a propósito que no debe disparar alarma.
  5. Publica la corrección. Un sistema corregido en el que la gente puede creer vale más que uno impresionante en el que no.

Solo grabé el demo en vivo de 55 segundos — caída real, pipeline corriendo, dashboard en la nube poniéndose rojo, alerta reconocida — porque por fin confiaba en el sistema detrás. Nunca me habría atrevido a filmar la versión de 94.3%.

Limitaciones y advertencias

  • El dataset Le2i tiene caídas escenificadas en cuartos tipo hogar, no emergencias reales. El shift de distribución en el mundo real será mayor que cualquier cosa medida aquí.
  • La tasa de 96% es en videos reservados del mismo dataset. La generalización entre datasets no está probada.
  • La estimación de pose falla en oclusión, motion blur y baja resolución — ese es el modo de falla dominante que queda, y es un problema de percepción, no de clasificador.
  • Una sola métrica nunca es suficiente para sistemas que rozan la seguridad. Reporta métricas a nivel de evento, latencia y tasa de falsa alarma juntas.

Próximos pasos

Si estás construyendo algo parecido, la lista de prioridades es: (1) arregla tus splits, (2) arregla tus etiquetas, (3) migra a evaluación a nivel de evento, (4) y solo entonces — solo entonces — ajusta el modelo. La mayor parte de la ganancia está en los pasos 1–3. Mi siguiente ronda de trabajo es en percepción, no en tuning de clasificador.

Fuente: My Fall-Detection Model Scored 94%, and It Was Lying to Me — solo métricas agregadas, dataset usado con atribución a Charfi et al. (2013).

Este contenido fue redactado con la asistencia de herramientas de IA, basándose en fuentes confiables, y fue revisado por nuestro equipo editorial antes de su publicación. No reemplaza el asesoramiento de un profesional especializado.