11. Banco de preguntas y respuestas
Preguntas por nivel, cada una con respuesta modelo. Úsalas para auto-evaluarte: intenta responder antes de leer la respuesta. Las de "defensa académica" son las que un jurado, investigador o ingeniero senior podría plantear.
Nivel básico
P1. ¿Qué hace el proyecto en una frase? R. Clasifica la foto de una hoja de maíz en una de 9 condiciones (enfermedades, plagas o deficiencias nutricionales) usando deep learning, con el objetivo final de correr offline en un teléfono de gama media para agricultores sin conectividad.
P2. ¿Cuántas clases hay y de qué tipo? R. 9 clases: 4 enfermedades (roya común, mancha gris, necrosis letal, tizón foliar del norte), 1 plaga (gusano cogollero), 3 deficiencias nutricionales (nitrógeno, fósforo, potasio) y "hoja sana". Definidas en config/dataset.yaml.
P3. ¿Qué diferencia hay entre raw/ y clean/? R. raw/ son las 8 fuentes crudas originales, inmutables. clean/ es el dataset consolidado y estandarizado (<clase>/{lab,real}/), la única fuente de verdad para entrenar. Nunca se modifica raw/.
P4. ¿Qué significa lab vs real? R. lab = imágenes de laboratorio (fondo controlado, hoja aislada). real = imágenes de campo (fondo ruidoso, iluminación variable). real representa el dominio de despliegue real.
P5. ¿Qué es un "baseline" aquí? R. A pesar del nombre, es un pipeline de deep learning completo y funcional. Se llama baseline porque su propósito es comparar varias arquitecturas rápido y barato para elegir la mejor, no porque sea simple.
P6. ¿Cómo entreno un modelo? R. make train-baselines MODELS=efficientnet_b0. Genera los splits si faltan, entrena y guarda checkpoints + métricas en outputs/baselines/efficientnet_b0/<timestamp>/.
Nivel intermedio (arquitectura)
P7. ¿Por qué src/ y scripts/ están separados? R. src/ es la librería reutilizable (componentes: dataset, modelos, transforms); scripts/ son los entrypoints ejecutables. La separación permite que la misma lógica de datos/modelos la usen ambos pipelines de entrenamiento, la explicabilidad y la inferencia, sin duplicar código. El paquete se instala editable (pip install -e .), evitando sys.path.append.
P8. ¿Cómo se construye un modelo a partir de un string? R. Con el patrón registry: MODEL_REGISTRY mapea nombre→fábrica. Cada archivo de src/models/baselines/ registra sus arquitecturas al importarse. build(name, num_classes) construye la red y reemplaza la cabeza para 9 salidas.
P9. ¿Cómo se maneja el desbalance de clases? R. Con un WeightedRandomSampler que sobremuestrea las clases minoritarias, más augmentation agresivo para ellas. La CrossEntropyLoss va sin pesos, para no compensar dos veces. El sampler se desactiva solo si el split ya está balanceado.
P10. ¿Cómo se garantiza que no haya fuga de datos entre train y test? R. Deduplicación exacta por SHA-256 del contenido (misma imagen, aunque tenga otro nombre, cuenta una vez), más split estratificado por clase+entorno para que la mezcla lab/real se preserve en los tres conjuntos.
P11. ¿Qué se guarda al terminar un entrenamiento? R. En el run_dir versionado por timestamp: best.pth (mejor por val macro-F1), last.pth, summary.json (mapeo de clases, hiperparámetros, métricas), predictions.csv, train_history.csv, matriz de confusión y reporte de clasificación. latest.json apunta al run más reciente.
P12. ¿Por qué la imagen se redimensiona distorsionando el aspecto? R. Para preservar toda la hoja, incluidos los bordes donde aparecen síntomas de deficiencias (necrosis marginal por potasio). Un CenterCrop descartaría esos bordes. Además es consistente entre train/eval/inferencia.
P13. ¿Cómo se explican las predicciones? R. Post-hoc con dos técnicas: LIME (perturba superpíxeles y ajusta un modelo lineal local) y Grad-CAM (gradientes de la clase respecto a la última conv). Se usan las dos para triangular: si coinciden, la explicación es confiable.
Nivel avanzado (técnico profundo)
P14. ¿Por qué worker_init_fn deriva la semilla de torch.initial_seed() y no usa una fija? R. Los workers del DataLoader son procesos que renacen cada época. Con una semilla fija, cada worker generaría la misma secuencia de augmentation en cada época, anulando el propósito del augmentation. torch.initial_seed() es variable por época pero reproducible globalmente, dando variación real y reproducibilidad del run.
P15. ¿Por qué Grad-CAM usa out.register_hook (hook de tensor) y no register_full_backward_hook (hook de módulo)? R. Varias arquitecturas (GhostNetV2) aplican una ReLU inplace justo después de la capa objetivo. El hook de módulo intercepta el tensor antes de esa modificación y falla con "view is being modified inplace". Enganchar el gradiente del tensor de salida directamente evita el conflicto y funciona en todas las arquitecturas.
P16. ¿Por qué las clases minoritarias se derivan del split y no se hardcodean? R. Porque el conjunto de minoritarias depende de la distribución del split concreto. En el dataset completo califican 4 clases; en un split balanceado, ninguna. Derivarlo dinámicamente (compute_minority_classes, umbral max_count/count > 4.0) hace que augmentation y sampler se activen o desactiven automáticamente sin un frozenset que se desincronice.
P17. ¿Qué previene load_run_metadata al priorizar summary.json? R. El bug de rótulos permutados. Los índices de clase no viven en el state_dict; si se reconstruyen desde baseline.classes (cuyo orden puede diferir del canónico dataset.classes), las etiquetas salen permutadas. Persistir y releer el mapeo exacto del run lo elimina.
P18. ¿Cómo evita el sistema quedarse sin memoria con modelos de mayor resolución? R. _scale_batch_size reduce el batch inversamente al área de la imagen. Para 380×380 (≈2.9× el área de 224), el batch 32 baja a ~11. Solo baja, nunca sube, con mínimo de 1.
P19. ¿Por qué se selecciona best.pth por macro-F1 de validación y no por loss? R. Con desbalance, la loss y la accuracy pueden mejorar aunque el modelo ignore clases minoritarias. Macro-F1 pesa todas las clases igual, así que seleccionar por él garantiza que el modelo elegido no sacrifica las clases raras (las deficiencias, las que más importan).
P20. ¿Qué mide explanation_dispersion y por qué importa? R. La desviación estándar de los pesos LIME normalizados. Baja = explicación concentrada en pocos superpíxeles (interpretable); alta = pesos repartidos parejo (mala, típica de fondo ruidoso). Es una métrica de calidad de la explicación, no del modelo.
Nivel defensa académica
P21. (Profesor) Justifiquen la elección de macro-F1 como métrica principal frente a otras. R. El dataset está fuertemente desbalanceado (healthy ~8.7k vs potassium ~266). La accuracy premiaría acertar la clase mayoritaria; un modelo que prediga siempre "healthy" tendría accuracy alta pero sería inútil. Macro-F1 promedia el F1 por clase con peso igual, penalizando ignorar minoritarias. Como estas son justamente las deficiencias nutricionales —las de mayor valor agronómico y las más difíciles— macro-F1 alinea la métrica con el objetivo de negocio. Reportamos también accuracy y la matriz de confusión para una imagen completa.
P22. (Jurado) ¿Cómo garantizan que los resultados no están inflados por fuga de datos? R. Tres barreras. (1) Deduplicación exacta por SHA-256 del contenido, con escaneo determinista sorted() para reproducibilidad entre máquinas, eliminando copias idénticas entre las 8 fuentes solapadas. (2) Split estratificado por clase+entorno, evitando que el test sea de un dominio distinto. (3) Selección de modelo por validación y evaluación final en un test nunca visto. Reconocemos una limitación: la dedup es exacta, no perceptual, por lo que cuasi-duplicados podrían escapar; es una mejora identificada.
P23. (Investigador) ¿Por qué transfer learning es válido aquí y no un sesgo hacia ImageNet? R. Con ~25k imágenes, dos órdenes de magnitud menos que ImageNet, entrenar desde cero sobreajustaría. Las capas tempranas de ImageNet aprenden features genéricas (bordes, texturas) transferibles a fitopatología. Solo reemplazamos la cabeza y hacemos fine-tuning con LR bajo (1e-4) para adaptar sin destruir. El sesgo de ImageNet es hacia features visuales de bajo nivel, no hacia clases; y el augmentation informado por dominio (preservando el hue diagnóstico) corrige la parte específica de nuestro problema. Un experimento --no-pretrained confirmaría empíricamente la ganancia.
P24. (Ing. senior) El pipeline principal no está implementado. ¿Por qué presentar baselines como resultado? R. Los baselines son deep learning funcional de punta a punta, no un placeholder: entrenan, evalúan y explican, alcanzando macro-F1 >0.91, superando la meta de 0.85. Su propósito metodológico es comparar arquitecturas bajo condiciones idénticas (LR constante, misma config) para seleccionar candidatas de forma justa y económica. El pipeline principal difiere en el loop (schedulers, fine-tuning por fases), no en la infraestructura de datos/modelos, que ya está compartida y probada. Presentamos los baselines porque son evidencia real y reproducible del rendimiento alcanzable.
P25. (Jurado) ¿Cómo saben que el modelo mira la lesión y no el fondo o un artefacto del dataset? R. Por la explicabilidad doble. LIME resalta los superpíxeles que sostienen la predicción y Grad-CAM muestra dónde se activa la red. Cuando ambos coinciden sobre la lesión, hay evidencia de que el modelo aprende la patología, no un atajo. Además, explain_report.py --errors-only analiza específicamente los errores, y lime_stability.py audita que las explicaciones sean estables entre seeds. La métrica explanation_dispersion cuantifica cuán concentrada está cada explicación. Reconocemos que LIME es estocástico, por eso auditamos su estabilidad en vez de confiar en una sola corrida.
P26. (Profesor) ¿Qué harían si al cuantizar a Int8 la macro-F1 cae por debajo de 0.85? R. Escalonadamente: (1) cuantización con calibración (post-training quantization con un set representativo) o quantization-aware training para recuperar precisión; (2) si no basta, elegir una arquitectura más robusta a la cuantización o más pequeña (ShuffleNetV2, EfficientNet-Lite0 ya son candidatos); (3) knowledge distillation a un estudiante ligero; (4) revisar la restricción de ≤20 MB si el caso de uso lo permite. Validaríamos la macro-F1 post-cuantización por clase, no solo global, para no sacrificar las deficiencias.
P27. (Ing. senior) ¿Cuál es el punto más frágil del sistema y cómo lo mitigan? R. El acoplamiento entre el state_dict (que no guarda nombres de clase) y el summary.json (que sí guarda el mapeo). Si se separa un checkpoint de su summary.json, o se reconstruye el mapeo con orden distinto, se obtienen etiquetas permutadas sin error visible —el bug más silencioso posible. Lo mitigamos con load_run_metadata, que prioriza summary.json, con la advertencia documentada en el código, y con el versionado íntegro del run_dir. Una mejora sería persistir el mapeo dentro del propio checkpoint.
P28. (Investigador) ¿Por qué no validación cruzada? R. Para la fase de comparación de baselines, un split fijo estratificado es suficiente y k× más barato, permitiendo comparar muchas arquitecturas rápido. K-fold sería apropiado para la evaluación final del modelo elegido, donde la robustez de la estimación importa más que la velocidad. Es una mejora planificada, no un descuido; también correr múltiples semillas y reportar media±desviación.
Siguiente
Cómo estructurar tus respuestas en una defensa real: Guía de defensa del proyecto.