Skip to content

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

python
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=False al construir: no queremos los pesos de ImageNet, sino los del checkpoint entrenado. Se cargan con load_state_dict.
  • Prioridad de resolución del mapeo y tamaño: primero summary.json; si no existe, reconstruye desde train.csv. Misma lógica de robustez que en explicabilidad.
  • _load_state_dict flexible: acepta tanto un state_dict puro 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_map per-píxel.
python
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.0

Por 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): descarga clean/ (~25k imágenes) desde Hugging Face Datasets Hub (repo daiv05/corn-leaf-diseases-pests-and-deficiencies), con fallback a Google Drive. --source auto resuelve cuál usar.
  • upload_to_hf.py: sube clean/ 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

python
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/:

EjemploQué hizo
common_rust/move_rename_common_rust_real.pyMovió y renombró imágenes de roya de una fuente
fall_armyworm/fix_fall_armyworm_remove_damage.pyFiltró imágenes de daño no relevante
northern_corn_leaf_blight/delete_duplicates_...pyEliminó duplicados de una fuente
others/sort_maize_by_disease.pyReordenó 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 MakeScriptProduce
make splits / splits-baselinecreate_splits.pyCSVs de split
make train-baselinestrain_baselines.pyrun_dir con checkpoints + métricas
make traintrain.py(loop pendiente)
make explain-limeexplain_lime.pyPNGs LIME+Grad-CAM
make explain-report / explain-errorsexplain_report.pyfidelidad agregada / errores
make summarydataset_summary.pyconteo por clase/entorno
make test-loadersmoke_loader.pysmoke check
(sin Make) predict.pyinferencia de 1 imagen
(sin Make) lime_stability.pyauditorí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.