Curso / fast.ai / Lección 2
● piloto de formato

fast.ai · Practical Deep Learning for Coders · Lección 2

Deployment: de un dataset propio a una app desplegada

La lección que cumple la promesa de la Lección 1: armar tu propio dataset desde cero, entrenar, interpretar errores, limpiar datos con ayuda del propio modelo, y desplegar. El proceso completo, de punta a punta.

1. El pipeline completo: de la búsqueda de imágenes al modelo

El ejemplo del libro es un clasificador de osos (grizzly / negro / teddy), armado con imágenes buscadas y descargadas por código — no un dataset ya empaquetado y limpio como el de la Lección 1. El pipeline:

# 1. Buscar y descargar (150 imágenes por clase alcanza)
urls = search_images_bing(key, 'grizzly bear')
download_images(dest, urls=urls)

# 2. Verificar y descartar archivos corruptos
fns = get_image_files(path)
failed = verify_images(fns)
failed.map(Path.unlink)

Con menos de 150 imágenes por clase, sin dataset curado de antemano, ya alcanza para entrenar algo útil — el punto es que armar tu propio dataset desde cero no es el cuello de botella que parece.

2. La DataBlock API, pieza por pieza

DataBlock es la pieza central de fastai: una plantilla que obliga a responder cuatro preguntas explícitas sobre cualquier dataset, en vez de escribir un Dataset/DataLoader de PyTorch a mano cada vez:

bears = DataBlock(
    blocks=(ImageBlock, CategoryBlock),
    get_items=get_image_files,
    splitter=RandomSplitter(valid_pct=0.2, seed=42),
    get_y=parent_label,
    item_tfms=Resize(128))
dls = bears.dataloaders(path)
  • blocks: tipos de la variable independiente y dependiente (acá, imagen → categoría).
  • get_items: cómo listar los ítems del dataset.
  • splitter: cómo separar train/validation (semilla fija, para que el split sea reproducible).
  • get_y: cómo etiquetar cada ítem — acá, el nombre de la carpeta que lo contiene.

Sobre item_tfms (transforms por ítem individual, como Resize) vs. batch_tfms (transforms sobre el batch ya armado, ejecutados en GPU — típicamente data augmentation: rotación, flip, warping de perspectiva, brillo/contraste, vía aug_transforms()): la variación aleatoria no cambia el significado de la imagen, y le da al modelo más diversidad efectiva sin necesitar más datos reales. RandomResizedCrop combina resize + recorte aleatorio distinto en cada época, mejor que un crop/pad fijo.

3. Limpiar datos usando el propio modelo

La secuencia del libro invierte el orden intuitivo: en vez de limpiar todos los datos a mano antes de entrenar, entrená un modelo simple primero y usalo para encontrar los problemas del dataset.

learn = vision_learner(dls, resnet18, metrics=error_rate)
learn.fine_tune(4)

interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()
interp.plot_top_losses(5, nrows=1)

plot_top_losses muestra los ejemplos donde el modelo tuvo mayor error — junto con predicción, etiqueta real, loss y probabilidad. Es la herramienta clave para distinguir un error del modelo (imagen bien etiquetada, el modelo se equivocó) de un error del dataset (la imagen está mal etiquetada, o no es realmente un oso). ImageClassifierCleaner(learn) es un widget que muestra las imágenes de mayor loss por categoría y deja marcarlas para borrar o re-etiquetar — sin ejecutar nada directamente, solo devuelve los índices marcados (cleaner.delete(), cleaner.change()) para aplicarlos después.

4. Out-of-domain data y domain shift

Una sección entera del capítulo se llama "How to Avoid Disaster", y el argumento central es incómodo: un modelo entrenado con fotos prolijas de internet va a fallar sistemáticamente frente a datos de producción distintos — video en vez de fotos fijas, tomas nocturnas, cámaras de baja resolución, un oso visto de espaldas. Esto es out-of-domain data: la red nunca vio ese tipo de input durante el entrenamiento, y no existe una forma técnica completa de detectarlo de antemano. Domain shift es la versión temporal del mismo problema: la distribución de datos en producción cambia con el tiempo (el ejemplo del libro es una aseguradora cuyo perfil de clientes evoluciona y va dejando obsoletos los datos con los que se entrenó el modelo de riesgo). Ambos son manifestaciones de algo más grande: nunca se puede entender completamente el comportamiento de una red con millones de parámetros con solo mirarla.

La receta que propone el libro para desplegar sin desastre: rollout en fases (proceso 100% manual con el modelo corriendo en paralelo solo como apoyo visual, después rollout limitado geográfica/temporalmente con supervisión humana de cada alerta, recién después expansión gradual con buen monitoreo). Y un ejercicio mental recomendado antes de lanzar cualquier cosa: "¿qué pasaría si el modelo funcionara extremadamente bien?" — para anticipar impactos extremos, no solo fallas.

5. Cómo se servía en 2020 — y cómo se sirve hoy

El libro exporta el modelo completo (arquitectura + pesos + la definición del DataLoaders) a un único archivo con learn.export(), y para inferencia usa load_learner(...) + learn_inf.predict('imagen.jpg'). Hasta ahí, el flujo sigue vigente sin cambios. Donde el capítulo envejeció mal es en cómo se sirve: la idea de 2020 era que un notebook Jupyter ya es una aplicación web — se arma una GUI con ipywidgets (FileUpload, Output, Button) dentro del propio notebook, y Voilà lo convierte en una web app "limpia" que oculta el código, desplegada gratis en Binder.

🟢 Esto es justo lo que reemplaza la Lección 2 real del curso

Bing Image Search API (la que usa el libro para descargar imágenes) ya no existe como producto standalone — Microsoft la discontinuó. Y el stack de deployment completo (ipywidgets + Voilà + Binder) es lento y quedó abandonado. La página web actual de la Lección 2 lo reemplaza por exactamente lo que ya usaste en el Capítulo 9 de HF: Hugging Face Spaces + Gradio. Se sube el modelo exportado a un Space, se escribe una app.py de pocas líneas con gr.Interface, y HF se encarga de hosting, URL pública y cómputo gratis — sin notebooks, sin configurar un servidor, con mejor UX. El resto del contenido conceptual de este capítulo (DataBlock, out-of-domain data, domain shift, rollout gradual) no cambió en absoluto.

Sobre infraestructura de inferencia, el libro hace una observación que sigue vigente: casi nunca hace falta GPU en producción para servir un modelo — se procesa un ítem a la vez (o batches chicos), sin el paralelismo masivo que justifica una GPU en entrenamiento. Un servidor CPU barato alcanza para la enorme mayoría de casos de uso reales.

6. Resumen

  1. Armar tu propio dataset desde cero (buscar, descargar, verificar) no es el cuello de botella que parece — 150 imágenes por clase ya alcanzan para un modelo útil.
  2. DataBlock formaliza cuatro preguntas (blocks, get_items, splitter, get_y) en vez de escribir Dataset/DataLoader a mano.
  3. La secuencia correcta es entrenar primero, después usar plot_top_losses y el cleaner para encontrar problemas en los datos — no limpiar todo a ciegas antes.
  4. Out-of-domain data y domain shift son la razón por la que ningún modelo se despliega de golpe a producción completa — rollout gradual con supervisión humana.
  5. El stack de deployment del libro (Voilà/Binder) es historia; hoy es Hugging Face Spaces + Gradio, exactamente lo que ya viste en el Capítulo 9 de HF.