4.6. Scripts de soporte
El resto de entrypoints y utilidades: inferencia, checks, hosting del dataset, cómputo en la nube y limpieza histórica.
scripts/pipeline/predict.py — inferencia de una imagen
Propósito
Clasificar una imagen desde la línea de comandos, replicando exactamente el preprocesamiento del entrenamiento. Es el prototipo de lo que hará la app Android.
Flujo
checkpoint_path = resolve_run_dir(output_root/"baselines", args.model, args.run) / "best.pth"
summary = _load_summary(checkpoint_path) # lee summary.json del run
class_to_idx, idx_to_class = _resolve_class_mapping(summary, splits_dir, cfg)
target_size = _resolve_target_size(args, summary, cfg)
model = build_model(args.model, num_classes=..., pretrained=False)
model.load_state_dict(_load_state_dict(checkpoint_path, device))
image = load_and_normalize_image(args.image)
tensor = factory.get_pipeline("inference")(image).unsqueeze(0)
probabilities = softmax(model(tensor))
values, indices = torch.topk(probabilities, k=top_k)Puntos clave:
pretrained=Falseal construir: no queremos los pesos de ImageNet, sino los del checkpoint entrenado. Se cargan conload_state_dict.- Prioridad de resolución del mapeo y tamaño: primero
summary.json; si no existe, reconstruye desdetrain.csv. Misma lógica de robustez que en explicabilidad. _load_state_dictflexible: acepta tanto unstate_dictpuro como un checkpoint envuelto en{"model_state_dict": ...}— compatibilidad hacia adelante.get_pipeline("inference")= las transforms deterministas de validación. Cero augmentation.- Salida: top-k clases con probabilidades (default k=3), para que el usuario vea la confianza y las alternativas.
Por qué reutiliza todo: loader, transforms, registry son los mismos del entrenamiento. Esto elimina el train/serve skew por construcción: es imposible que una imagen se procese distinto al entrenar vs al predecir, porque es el mismo código.
scripts/checks/ — verificación
smoke_loader.py
make test-loader. Prueba de humo del pipeline de carga: construye un CornDataset, saca unos batches y verifica que las formas y tipos son correctos. Es la comprobación rápida de que "los datos fluyen" antes de lanzar un entrenamiento largo.
lime_stability.py
Diagnóstico manual (sin target Make). Corre render_visual_explanation N veces con seeds distintas sobre la misma imagen y compara, entre corridas consecutivas:
- IoU de las máscaras de superpíxeles positivos.
- Correlación de Pearson entre los
weight_mapper-píxel.
def _mask_iou(mask_a, mask_b):
intersection = np.logical_and(mask_a, mask_b).sum()
union = np.logical_or(mask_a, mask_b).sum()
return intersection / union if union > 0 else 1.0Por qué existe: LIME es estocástico (perturba aleatoriamente). Si la explicación cambia mucho entre seeds, no es confiable. Esta auditoría cuantifica esa estabilidad. Reutiliza los artefactos .json/.npy persistidos, así que no re-ejecuta LIME para comparar — reconstruye las máscaras desde disco. Ver riesgos: inestabilidad de LIME.
scripts/dataset/ — hosting del dataset limpio
download_dataset.py(make download-dataset): descargaclean/(~25k imágenes) desde Hugging Face Datasets Hub (repodaiv05/corn-leaf-diseases-pests-and-deficiencies), con fallback a Google Drive.--source autoresuelve cuál usar.upload_to_hf.py: subeclean/al Hub (usado por los mantenedores al actualizar el dataset).
No confundir con download_datasets.sh
scripts/download_datasets.sh (plural, en la raíz de scripts) es un flujo distinto: ingesta de fuentes crudas nuevas (Kaggle/Mendeley/Roboflow) hacia raw/. No toca clean/. Es parte de la fase de construcción del dataset, no del uso diario.
scripts/modal/ — cómputo en GPU en la nube
Modal permite ejecutar los pipelines en GPU sin gestionar servidores.
_common.py — imagen y volumes compartidos
dataset_vol = modal.Volume.from_name("corn-clean", create_if_missing=True)
outputs_vol = modal.Volume.from_name("corn-outputs", create_if_missing=True)
image = (modal.Image.debian_slim(python_version="3.11")
.pip_install("torch==2.12.1", "torchvision==0.27.1", index_url=".../cu126")
.pip_install_from_pyproject("pyproject.toml", optional_dependencies=["cloud","xai"])
.env({"DATASET_ROOT": "/data", "OUTPUT_ROOT": "/outputs", ...}))Diseño clave: una única definición de imagen + volumes, compartida por train.py y explain.py. Si divergieran, un checkpoint generado por uno no sería consumible por el otro. Nota cómo DATASET_ROOT=/data (solo lectura, el Volume corn-clean) y OUTPUT_ROOT=/outputs (Volume persistente corn-outputs) — exactamente la dicotomía consume/produce de config.py.
train.py / explain.py (Modal)
Wrappers que invocan los scripts locales dentro del contenedor. La CLI es idéntica a la local: cualquier flag que funcione en make train-baselines funciona en make modal-train-baselines. make modal-pull trae los outputs/ remotos de vuelta.
SPLITS_INDEX_WORKERS=24 en el entorno de Modal: fuerza el nº de hilos del indexado de splits, porque en contenedores con cuota os.cpu_count() reporta los cores del host, no la asignación del contenedor — sin override, create_splits.py lanzaría demasiados hilos a ciegas.
scripts/cleanup/ — limpieza histórica (un solo uso)
~25 scripts organizados por clase. Cada uno resolvió un problema puntual al construir clean/:
| Ejemplo | Qué hizo |
|---|---|
common_rust/move_rename_common_rust_real.py | Movió y renombró imágenes de roya de una fuente |
fall_armyworm/fix_fall_armyworm_remove_damage.py | Filtró imágenes de daño no relevante |
northern_corn_leaf_blight/delete_duplicates_...py | Eliminó duplicados de una fuente |
others/sort_maize_by_disease.py | Reordenó por enfermedad |
No forman parte del pipeline de entrenamiento. Ya cumplieron su función. Su valor hoy es de trazabilidad: documentan, script a script, cómo se transformó cada fuente cruda hasta la estructura clean/<clase>/{lab,real}/. Si tuvieras que reconstruir el dataset desde raw/, estos scripts son la receta.
src/analysis/dataset_summary.py
make summary. Recorre clean/ y cuenta imágenes por clase y entorno (lab/real). Es la forma rápida de ver la distribución real del dataset y detectar el desbalance. Escribe su reporte vía get_output_root().
Resumen de entrypoints y sus comandos Make
| Comando Make | Script | Produce |
|---|---|---|
make splits / splits-baseline | create_splits.py | CSVs de split |
make train-baselines | train_baselines.py | run_dir con checkpoints + métricas |
make train | train.py | (loop pendiente) |
make explain-lime | explain_lime.py | PNGs LIME+Grad-CAM |
make explain-report / explain-errors | explain_report.py | fidelidad agregada / errores |
make summary | dataset_summary.py | conteo por clase/entorno |
make test-loader | smoke_loader.py | smoke check |
(sin Make) predict.py | inferencia de 1 imagen | |
(sin Make) lime_stability.py | auditoría de estabilidad LIME |
Fin del análisis de código
Con esto tienes el sistema completo a nivel de implementación. El siguiente bloque conecta el código con la teoría que lo sustenta: Fundamentos teóricos.