10. Riesgos técnicos
Cuellos de botella, deuda técnica, suposiciones ocultas y componentes frágiles. Ser honesto sobre las limitaciones es parte de una buena defensa: un jurado valora que conozcas los puntos débiles y tengas plan para ellos.
10.1. Cuellos de botella (rendimiento)
E/S de imágenes
El pipeline lee decenas de miles de imágenes de disco. En almacenamiento lento (o volúmenes remotos como Modal), la E/S puede dominar el tiempo, dejando la GPU ociosa esperando datos. Mitigaciones ya presentes: carga perezosa, num_workers paralelos, pin_memory en CUDA, indexado paralelo en create_splits.py (ThreadPoolExecutor), SPLITS_INDEX_WORKERS para ajustar en contenedores. Riesgo residual: con num_workers mal configurado (o 0), la GPU se subutiliza.
LIME es caro
Cada explicación LIME hace num_samples (=1000) forwards. Explicar muchas imágenes es lento. Mitigación: muestreo balanceado pequeño para reportes visuales; persistencia .json/.npy para reanálisis sin re-ejecutar. Riesgo: correr explain-report sobre todo el test es costoso.
Reproducibilidad cuesta velocidad
cudnn.benchmark=False (para determinismo) desactiva el autotuning de convoluciones → algo más lento. Es un trade-off consciente, no un bug.
10.2. Suposiciones ocultas
| Suposición | Riesgo si se rompe |
|---|---|
DATASET_ROOT apunta a un clean/ completo y actualizado | create_splits.py falla (bien: falla ruidoso) o entrena con datos viejos |
El orden de dataset.classes nunca cambia tras entrenar | Rótulos permutados; checkpoints inutilizables |
Los índices de clase viven en summary.json, no en el state_dict | Cargar un checkpoint sin su summary.json → mapeo reconstruido, posible desalineación |
| Las imágenes de test representan el dominio de despliegue (campo real) | Si el test es mayormente lab, las métricas son optimistas |
| La dedup exacta basta | Cuasi-duplicados (recorte/recompresión) escapan → leakage parcial |
El punto más frágil: el mapeo de clases
El acoplamiento entre el state_dict (que no guarda los nombres de clase) y el summary.json (que sí) es una suposición estructural. Si alguien mueve un best.pth sin su summary.json, o reconstruye el mapeo desde una lista con orden distinto, obtiene etiquetas equivocadas sin ningún error visible. El código lo mitiga con load_run_metadata (prioriza summary.json) y la advertencia documentada, pero sigue siendo el punto donde un descuido causa el bug más silencioso del proyecto.
Deduplicación solo exacta
create_splits.py deduplica por SHA-256 (bit-a-bit). Dos versiones de la misma foto (recortada, recomprimida a JPEG, redimensionada) tienen hashes distintos y pasan como imágenes diferentes → posible fuga entre train y test. El grupo analysis incluye imagededup (perceptual) para la fase de construcción, pero el pipeline de splits no lo aplica. Riesgo conocido; mejora futura: dedup perceptual en el split.
10.3. Deuda técnica
train.py es un esqueleto
El pipeline "principal" no tiene loop de entrenamiento (solo un TODO). Hoy el único entrenamiento funcional es train_baselines.py. Riesgo: confusión para nuevos integrantes que esperen que make train entrene algo. Documentado claramente, pero es deuda pendiente. Al implementarlo, debe reusar _run_epoch y llamar update_latest_pointer.
Sin tests automatizados formales
Hay smoke checks (smoke_loader.py) y auditorías manuales (lime_stability.py), pero no una suite de tests unitarios (pytest) que corra en CI. Riesgo: una regresión en CornDataset o el splitter podría pasar desapercibida. Mejora recomendada: tests unitarios de compute_minority_classes, build_weighted_sampler, el splitter y el mapeo de clases.
Scripts de cleanup no reproducibles como conjunto
Los ~25 scripts de scripts/cleanup/ construyeron clean/ pero no hay un orquestador único que los ejecute en orden. Reconstruir clean/ desde raw/ requeriría correrlos manualmente en la secuencia correcta. Mitigación real: clean/ está versionado en HF Hub, así que la reconstrucción rara vez se necesita.
Exportación a TFLite pendiente
Todo el objetivo de despliegue (cuantización Int8, app Android) está por hacer. Riesgo: el modelo elegido podría degradarse al cuantizar, o no cumplir ≤20 MB/≤300 ms, y eso no se sabrá hasta implementarlo. Recomendación: validar la cuantización temprano sobre el líder actual.
10.4. Componentes frágiles
GRADCAM_TARGET_LAYERS debe mantenerse sincronizado con el registry
Añadir un modelo sin su entrada aquí → Grad-CAM se omite silenciosamente (warning, 3 paneles). No es un crash, pero degrada el reporte sin que se note. Frágil por omisión.
Hooks de Grad-CAM y modificaciones inplace
El uso de out.register_hook (en vez de register_full_backward_hook) resuelve el conflicto con ReLU inplace, pero es un detalle sutil: un refactor ingenuo del hook podría reintroducir el error "view is being modified inplace" en modelos como GhostNetV2. Documentado en el código, pero frágil ante cambios descuidados.
Estratificación exige ≥7 imágenes por estrato
Si una fuente nueva aporta una clase con muy pocas imágenes en un entorno (p.ej. 5 lab de una clase rara), create_splits.py falla (bien: ruidoso) exigiendo agregar imágenes o excluir la clase. Es una restricción dura del método de split.
LIME es estocástico
Las explicaciones varían entre seeds. Si se presentan como "la" explicación sin auditar estabilidad, pueden ser engañosas. lime_stability.py existe justo para cuantificar esto, pero requiere ejecutarlo conscientemente.
10.5. Posibles fallos futuros
| Escenario | Síntoma | Prevención |
|---|---|---|
| Actualizar torch sin torchvision | Errores de carga de modelos | Actualizar en pareja; validar con smoke test |
| pandas mayor con cambio de groupby | Splits corruptos (columna perdida) | Rango acotado; el bug ya se corrigió una vez |
| Nueva clase con estrato < 7 | create_splits.py aborta | Agregar imágenes o excluir |
| Mover checkpoint sin summary.json | Rótulos permutados silenciosos | Mantener el run_dir íntegro |
| Cuantización degrada macro-F1 | Modelo móvil peor que el reportado | Validar Int8 temprano |
10.6. Resumen de salud del proyecto
Fortalezas: pipeline de datos robusto y determinista; separación limpia src/scripts; balanceo bien pensado; explicabilidad doble; versionado de runs; guardas que fallan ruidoso.
Debilidades a comunicar en una defensa: loop principal pendiente; dedup solo exacta; sin tests formales; despliegue TFLite no validado; dependencia estructural en el mapeo de clases.
Siguiente
Con los riesgos claros, prepárate para las preguntas: Banco de preguntas y respuestas.