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
- 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.
- DataBlock formaliza cuatro preguntas (blocks, get_items, splitter, get_y) en vez de escribir Dataset/DataLoader a mano.
- La secuencia correcta es entrenar primero, después usar
plot_top_lossesy el cleaner para encontrar problemas en los datos — no limpiar todo a ciegas antes. - 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.
- 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.