En 07-01 dejamos una cifra sobre la mesa — 82-85 % de exactitud del clasificador de categorías — y una promesa: batirla sin escribir apenas más código. Este proyecto final la cumple con el transfer learning de 05-03: en lugar de entrenar una CNN desde cero, tomaremos MobileNetV2 preentrenada en ImageNet y la adaptaremos a nuestro problema en dos fases (extracción de características y fine-tuning). Al terminar compararás ambos enfoques con números en la mano, decidirás cuál despliega TecnoMarket y cerrarás el módulo con los cinco proyectos entregados.

Contenido

  1. Enunciado: rehacer 07-01 y batirlo
  2. Fase 1: datos — redimensionado y preprocess_input correcto
  3. Fase 2: el modelo con MobileNetV2 congelada
  4. Fase 3: entrenamiento en dos fases (extracción → fine-tuning)
  5. Fase 4: comparación de curvas entre fases
  6. Fase 5: comparación final honesta contra la CNN de 07-01
  7. Fase 6: cuándo compensa el transfer learning (la tabla 2×2, revisada)
  8. Fase 7: empaquetado final y decisión de negocio
  9. Cierre del módulo: el portfolio de TecnoMarket

Enunciado: rehacer 07-01 y batirlo

Mismo problema, mismos datos, misma vara de medir: CIFAR-10 como stand-in de las categorías de producto de TecnoMarket, el mismo conjunto de test congelado de 07-01 y la misma partición train/validación. Solo cambia la estrategia: en vez de aprender a ver desde cero con 45 000 imágenes, partimos de una red que ya aprendió a ver con 1.4 millones (ImageNet) y le enseñamos únicamente nuestras categorías. Es la apuesta central de 05-03; hoy la medimos contra una alternativa real construida por nosotros mismos — la comparación más honesta posible.

Objetivo cuantitativo: superar claramente el 85 % de 07-01. Resultado típico esperable: 90-93 %.

Fase 1: datos — redimensionado y preprocess_input correcto

MobileNetV2 no acepta 32×32: fue preentrenada con imágenes grandes y su cascada de reducciones dejaría mapas vacíos. Redimensionamos a 160×160 (compromiso entre fidelidad y coste; 05-03). Y el punto que más proyectos rompe: cada red preentrenada exige su propio preprocesado. MobileNetV2 espera píxeles en [-1, 1], no el /255 que usamos en 07-01:

import tensorflow as tf
import numpy as np

tf.random.set_seed(42)

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
x_val, y_val = x_train[45000:], y_train[45000:]      # misma particion que 07-01
x_train, y_train = x_train[:45000], y_train[:45000]

TAM = 160
BATCH = 64
AUTOTUNE = tf.data.AUTOTUNE

aumento = tf.keras.Sequential([                       # mismo aumento que 07-01 (05-04)
    tf.keras.layers.RandomFlip("horizontal"),
    tf.keras.layers.RandomTranslation(0.1, 0.1),
    tf.keras.layers.RandomZoom(0.1),
])

def preparar(x, y):
    x = tf.image.resize(tf.cast(x, tf.float32), (TAM, TAM))
    x = tf.keras.applications.mobilenet_v2.preprocess_input(x)  # -> [-1, 1]
    return x, y

def dataset(x, y, entrenar=False):
    ds = tf.data.Dataset.from_tensor_slices((x, y)).map(preparar, AUTOTUNE)
    if entrenar:
        ds = ds.shuffle(10_000).batch(BATCH).map(
            lambda a, b: (aumento(a, training=True), b), AUTOTUNE)
    else:
        ds = ds.batch(BATCH)
    return ds.prefetch(AUTOTUNE)

train_ds = dataset(x_train, y_train, entrenar=True)
val_ds = dataset(x_val, y_val)
test_ds = dataset(x_test, y_test)

Usar preprocess_input de la misma familia de modelo no es opcional: con el /255 de 07-01, MobileNetV2 recibiría entradas fuera de la distribución con la que se preentrenó y rendiría varios puntos por debajo sin dar ningún error visible.

Fase 2: el modelo con MobileNetV2 congelada

La receta de 05-03: base preentrenada sin cabeza (include_top=False), congelada, más una cabeza nueva mínima:

from tensorflow.keras import layers, models

base = tf.keras.applications.MobileNetV2(
    input_shape=(TAM, TAM, 3),
    include_top=False,           # sin la cabeza de las 1000 clases de ImageNet
    weights="imagenet",
)
base.trainable = False           # fase 1: congelada por completo

entradas = layers.Input(shape=(TAM, TAM, 3))
x = base(entradas, training=False)   # training=False: BN en modo inferencia (05-03)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.3)(x)
salidas = layers.Dense(10, activation="softmax")(x)
modelo = models.Model(entradas, salidas, name="tecnomarket_tl")

modelo.summary()
# Total: ~2.3 M de parametros; entrenables: ~13 000 (solo la cabeza)

Fíjate en la desproporción: la base aporta ~2.26 M de parámetros ya aprendidos; nosotros solo entrenamos ~13 000. El training=False en la llamada a la base es el detalle sutil de 05-03: mantiene las BatchNormalization en modo inferencia incluso durante el fine-tuning posterior, protegiendo las estadísticas de ImageNet.

Fase 3: entrenamiento en dos fases (extracción → fine-tuning)

Fase de extracción de características — solo la cabeza, lr normal:

modelo.compile(optimizer=tf.keras.optimizers.Adam(1e-3),
               loss="sparse_categorical_crossentropy", metrics=["accuracy"])

callbacks = [
    tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", patience=5,
                                     restore_best_weights=True),
    tf.keras.callbacks.ModelCheckpoint("models/tl_extraccion.keras",
                                       monitor="val_accuracy", save_best_only=True),
    tf.keras.callbacks.TensorBoard(log_dir="logs/tl_fase1"),
]
hist1 = modelo.fit(train_ds, validation_data=val_ds, epochs=15,
                   callbacks=callbacks)
# Resultado tipico: val_accuracy ~86-88 % en 8-12 epocas

Ya en esta fase — entrenando el 0.6 % de los parámetros — se supera a la CNN de 07-01. Las características de ImageNet (bordes, texturas, formas de objetos) transfieren bien a CIFAR-10.

Fase de fine-tuning — descongelar el bloque final, lr bajo:

base.trainable = True
for capa in base.layers[:100]:       # MobileNetV2 tiene 154 capas: liberamos ~1/3 final
    capa.trainable = False

modelo.compile(optimizer=tf.keras.optimizers.Adam(1e-5),   # lr 100x mas bajo
               loss="sparse_categorical_crossentropy", metrics=["accuracy"])

callbacks[1] = tf.keras.callbacks.ModelCheckpoint("models/tl_finetuning.keras",
               monitor="val_accuracy", save_best_only=True)
callbacks[2] = tf.keras.callbacks.TensorBoard(log_dir="logs/tl_fase2")

hist2 = modelo.fit(train_ds, validation_data=val_ds, epochs=15,
                   callbacks=callbacks)
# Resultado tipico: val_accuracy ~90-93 %

Las dos reglas de oro de 05-03, aplicadas: recompilar tras cambiar trainable (si no, el cambio no surte efecto) y learning rate muy bajo (1e-5): las capas descongeladas ya contienen conocimiento valioso; un lr alto lo destruiría en las primeras iteraciones (el llamado catastrophic forgetting). Descongelamos solo el bloque final porque las capas tempranas (bordes, colores) son universales y las tardías son las que conviene especializar en nuestras categorías.

Fase 4: comparación de curvas entre fases

Concatena ambos historiales y pinta la exactitud de validación con una línea vertical en el cambio de fase:

import matplotlib.pyplot as plt

acc = hist1.history["val_accuracy"] + hist2.history["val_accuracy"]
plt.plot(acc, label="val_accuracy")
plt.axvline(len(hist1.history["val_accuracy"]) - 1, ls="--", c="gray",
            label="inicio fine-tuning")
plt.xlabel("epoca"); plt.legend(); plt.grid(True)

Lectura típica de la curva:

  • Fase 1: subida rápida las primeras épocas y meseta en ~87 % — la cabeza exprime todo lo que dan unas características genéricas congeladas.
  • Cambio de fase: un salto visible de 2-4 puntos en las primeras épocas de fine-tuning — las capas finales se adaptan a texturas de CIFAR — y meseta nueva en ~91-92 %.
  • Si en la fase 2 la exactitud cae de golpe, el lr era demasiado alto: es el síntoma clásico.

Fase 5: comparación final honesta contra la CNN de 07-01

Evalúa sobre el mismo test congelado de 07-01 y pon las cifras frente a frente. Valores típicos (los tuyos variarán algo; usa los que obtengas de verdad):

test_loss, test_acc = modelo.evaluate(test_ds)
print(f"Exactitud test transfer learning: {test_acc:.3f}")
Criterio CNN propia (07-01) MobileNetV2 + fine-tuning
Exactitud en test ~83 % ~91 %
Parámetros totales ~1.2 M ~2.3 M
Parámetros entrenados ~1.2 M (todos) ~13 K + ~0.9 M (fase 2)
Épocas hasta el mejor modelo 40-50 10 + 8
Tiempo de entrenamiento (GPU) ~20 min ~25-35 min*
Tamaño en disco ~14 MB ~28 MB
Coste de inferencia Menor (entrada 32×32) Mayor (entrada 160×160)

* Cada época del transfer learning es más cara (imágenes 160×160), pero necesita muchas menos.

Conclusiones honestas: +8 puntos de exactitud es una mejora enorme (los errores bajan de ~17 % a ~9 %: casi la mitad); el precio es un modelo mayor y una inferencia más cara. Repite también la matriz de confusión de 07-01: verás que las parejas conflictivas (gato↔perro) siguen siendo las peores, pero con la mitad de confusiones — con el umbral de 0.80 de 03-04, la cobertura automática típica sube del ~70 % al ~85 %.

Fase 6: cuándo compensa el transfer learning (la tabla 2×2, revisada)

Retomamos la tabla 2×2 de 05-03 y situamos este proyecto en ella:

Datos parecidos a ImageNet Datos muy distintos
Pocos datos Extracción de características (solo cabeza) Fine-tuning profundo con cuidado, o buscar otra base
Muchos datos Fine-tuning parcial ← nuestro caso Considerar entrenar desde cero

CIFAR-10 cae en «muchos datos (45 K), parecidos a ImageNet (objetos naturales)»: la casilla del fine-tuning parcial, y los resultados lo confirman. La CNN desde cero de 07-01 solo habría sido competitiva en la casilla inferior derecha — por ejemplo, imágenes médicas o espectrogramas con millones de ejemplos. Para las fotos de producto reales de TecnoMarket (objetos cotidianos, decenas de miles de fotos), la receta de hoy es la correcta casi con seguridad.

Fase 7: empaquetado final y decisión de negocio

Decisión: TecnoMarket despliega el modelo de transfer learning. La mejora de exactitud reduce a la mitad la carga de la cola de revisión (03-04), lo que compensa con creces el mayor coste de inferencia. La CNN de 07-01 queda documentada como baseline en el repositorio (git, 06-04) — todo modelo en producción necesita un baseline contra el que justificarse.

Empaquetado según 06-05, con el preprocesado dentro del modelo:

entrada = tf.keras.layers.Input(shape=(32, 32, 3), dtype=tf.uint8)  # imagen cruda
x = tf.cast(entrada, tf.float32)
x = tf.keras.layers.Resizing(TAM, TAM)(x)
x = tf.keras.applications.mobilenet_v2.preprocess_input(x)
prob = modelo(x, training=False)
modelo_servicio = tf.keras.Model(entrada, prob)
modelo_servicio.save("models/clasificador_categorias_v2.keras")

Redimensionado y preprocess_input viajan dentro del .keras: la API de FastAPI de 06-05 recibe la imagen cruda y no puede equivocarse de preprocesado. Antes de sustituir a v1, el protocolo de 06-05: validar v2 sobre el conjunto congelado, comparar contra v1 con las mismas imágenes, desplegar con posibilidad de vuelta atrás y recalibrar el umbral de confianza sobre validación.

Cierre del módulo: el portfolio de TecnoMarket

Con este quinto proyecto, el equipo de TecnoMarket (es decir, tú) entrega su portfolio completo:

Proyecto Problema de negocio Técnica principal Estado
07-01 Alta automática de artículos CNN propia + umbral y cola de revisión Baseline documentado
07-02 Borradores de descripciones LSTM carácter a carácter + temperatura Prototipo con revisión humana
07-03 Detección de fraude Autoencoder + error de reconstrucción En producción con recalibrado periódico
07-04 Creatividades generativas DCGAN con bucle GradientTape Prototipo educativo; producción requiere difusión
07-05 Alta automática, v2 Transfer learning MobileNetV2 Desplegado, sustituye a 07-01

Cinco proyectos, cuatro modalidades (imágenes, texto, datos tabulares, generación) y un método común: fases claras, particiones honestas, métricas adecuadas al problema, humanos en el circuito y entrega empaquetada.

Errores Comunes y Consejos

  • Usar /255 en lugar del preprocess_input de la familia del modelo: el error silencioso más común del transfer learning; cuesta varios puntos de exactitud sin lanzar ninguna excepción.
  • Olvidar recompilar tras cambiar trainable: Keras fija qué variables se entrenan al compilar; sin recompilar, tu «fine-tuning» sigue entrenando solo la cabeza.
  • Fine-tuning con el mismo lr de la fase 1: destruye los pesos preentrenados en pocas iteraciones y la exactitud se desploma. Baja el lr dos órdenes de magnitud.
  • Descongelar toda la base con pocos datos: más parámetros libres de los que tus datos pueden gobernar; sobreajuste casi garantizado. Descongela por bloques, del final hacia atrás.
  • Comparar modelos con particiones distintas: la comparación de la fase 5 solo vale porque el test congelado y la partición train/val son idénticos a los de 07-01. Cambiar los datos entre comparaciones invalida la tabla.

Ejercicios

  1. Repite el proyecto entrenando solo la fase de extracción (sin fine-tuning) y compara la tabla de la fase 5 con tres columnas: CNN propia, extracción sola, extracción + fine-tuning. ¿Cuánto aporta cada escalón?
  2. Prueba TAM = 96 en lugar de 160 y mide exactitud y tiempo por época. ¿Dónde queda en la comparación coste/beneficio?
  3. Descongela solo las últimas 20 capas (en vez de ~54) y, por separado, la base entera. Compara la exactitud final y el comportamiento de las curvas en los tres escenarios de descongelación.

Soluciones

  1. Basta saltarse la fase 2. Tabla típica: CNN propia ~83 %, extracción ~87 %, extracción+fine-tuning ~91 %. Lectura: el grueso del salto lo da el preentrenamiento (características de ImageNet); el fine-tuning añade el refinamiento final. Si el presupuesto de cómputo es mínimo, la extracción sola ya bate al modelo propio.
  2. Con 96×96 el tiempo por época cae aproximadamente a la mitad y la exactitud típica baja 1-2 puntos (~89-90 %): menos resolución, menos detalle para las capas profundas. Es un punto intermedio razonable para iterar rápido y entrenar la versión final a 160.
  3. Con 20 capas el resultado se queda ~1 punto por debajo del escenario base (menos capacidad de adaptación); con la base entera y lr=1e-5 puede igualar o mejorar décimas, pero las curvas son más frágiles (más riesgo de sobreajuste y de caída inicial) y el entrenamiento es más lento. El tercio final es el compromiso robusto: suficiente adaptación sin poner en riesgo las capas universales.

Conclusión

Proyecto final entregado y promesa cumplida: el clasificador v2 con MobileNetV2 bate a la CNN propia de 07-01 por unos 8 puntos, con una comparación honesta de costes y una decisión de despliegue justificada. Más importante que la cifra es el criterio que te llevas: la tabla 2×2 para decidir cuándo transferir, las dos fases con sus dos learning rates, y la disciplina de comparar siempre contra un baseline sobre el mismo test congelado. Con esto se cierra el módulo 7: cinco proyectos de principio a fin en el portfolio de TecnoMarket, integrando todo lo aprendido desde la primera red densa de 02-05 hasta el despliegue de 06-05. Pero un profesional no termina cuando el modelo funciona: lo que hemos construido — modelos que clasifican productos, generan textos e imágenes y señalan clientes como sospechosos — tiene consecuencias sobre personas reales. El módulo 8 aborda exactamente eso: la ética, el impacto y el futuro del Deep Learning que todo practicante debe dominar.

Curso de Deep Learning

Módulo 1: Introducción a Deep Learning

Módulo 2: Fundamentos de Redes Neuronales

Módulo 3: Redes Neuronales Convolucionales (CNN)

Módulo 4: Redes Neuronales Recurrentes (RNN)

Módulo 5: Técnicas Avanzadas en Deep Learning

Módulo 6: Herramientas y Frameworks

Módulo 7: Proyectos Prácticos

Módulo 8: Consideraciones Éticas y Futuro del Deep Learning

© Copyright 2026. Todos los derechos reservados