Saltar al contenido

# deep-learning-en-python-y-tensorflow

Deep Learning en Python y Tensorflow

Volver a la portada del curso
Notebook

Transformaciones y Normalización

Sección 3 · Manejo de Datos en Tensorflow

Notebook: transformaciones y normalización

En el video anterior viste cómo llegan los datos a una red: tensores, píxeles normalizados, lotes y aumento de datos. En este notebook lo haces tú con las fotos del catálogo de moda de PyShop (Fashion-MNIST) y compruebas con números por qué cada paso importa. Esta guía acompaña al notebook transformaciones-y-normalizacion: te dice qué buscar en cada sección y qué resultados debes obtener.

Objetivos

  • Pasar los píxeles de 0-255 a 0-1 y medir el efecto en el entrenamiento.
  • Estandarizar con la capa Normalization y su método adapt.
  • Añadir el eje de canal que piden las capas convolucionales.
  • Construir una tubería de tf.data con shuffle, batch y prefetch.
  • Generar variantes de las fotos con capas de aumento de datos.
  • Distinguir etiquetas enteras de etiquetas one-hot y elegir la pérdida que corresponde.

1. Datos del curso

La primera celda copia las funciones de los datos del curso (cargar_moda, semillas, modelo_mlp, CLASES); el notebook es autocontenido. Keras descarga Fashion-MNIST la primera vez (unos 30 MB) y la guarda en ~/.keras/datasets; las siguientes corridas la leen del disco.

2. Píxeles crudos y normalizados

(x_r, y_tr), (x_rte, y_te) = cargar_moda(normalizar=False)
print(x_r.shape, x_r.dtype, x_r.min(), x_r.max())   # (60000, 28, 28) uint8 0 255
print(round(float(x_r.mean()), 2))                  # 72.94
(x_tr, _), (x_te, _) = cargar_moda()                # divide entre 255
print(round(float(x_tr.mean()), 4))                 # 0.286

Dividir entre 255 deja los píxeles en $[0, 1]$ y los convierte a float32. Ojo con la memoria: las 60,000 fotos ocupan 47 MB en uint8 y 188 MB en float32, cuatro veces más.

3. Entrenar sin normalizar y normalizado

El notebook entrena el mismo MLP dos veces, 5 épocas cada una, con semillas(42) antes de cada modelo, y grafica la val_accuracy de las dos corridas:

ÉpocaSin normalizarNormalizado
10.71020.8438
20.74280.8528
30.74150.862
40.81330.8713
50.82430.8758

Con píxeles de hasta 255, las sumas ponderadas de la primera capa son enormes y los primeros pasos del optimizador mueven los pesos de forma brusca: la curva arranca 13 puntos abajo y sube con tropiezos (baja en la época 3). Normalizado, el mismo modelo arranca en 0.8438 y sube de forma pareja.

4. La capa Normalization

Otra opción es estandarizar: restar la media y dividir entre la desviación estándar, $z = (x - \mu) / \sigma$. La capa Normalization calcula $\mu$ y $\sigma$ con adapt y luego los aplica:

norm = keras.layers.Normalization(axis=None)
norm.adapt(x_tr)
print(norm.mean.numpy().ravel(), norm.variance.numpy().ravel())
# [0.2860406] [0.12462611]
z = norm(x_tr).numpy()
print(round(float(z.mean()), 4), round(float(z.std()), 4))   # -0.0 1.0

Con axis=None usa una sola media y una sola varianza para todos los píxeles: $\mu = 0.286$ y $\sigma = \sqrt{0.1246} \approx 0.353$ (el -0.0 de la media es cero con signo por redondeo). Un píxel negro (0) pasa a −0.81 y uno blanco (1) a 2.022. La ventaja de la capa es que viaja dentro del modelo: al guardarlo, la transformación se guarda con él. Llama a adapt solo con datos de entrenamiento.

5. Añadir el canal

x_c = x_tr[..., None]                 # o tf.expand_dims(x_tr, -1)
print(x_c.shape)                      # (60000, 28, 28, 1)

Conv2D y las capas de aumento esperan (lote, alto, ancho, canales). La foto no cambia: solo se agrega un eje de tamaño 1.

6. Tubería con tf.data

ds = (tf.data.Dataset.from_tensor_slices((x_tr, y_tr))
      .shuffle(10000, seed=42)
      .batch(64)
      .prefetch(tf.data.AUTOTUNE))
xb, yb = next(iter(ds))
print(xb.shape, yb.shape, len(ds))    # (64, 28, 28) (64,) 938
  • shuffle(10000) baraja con un búfer de 10,000 ejemplos; la semilla hace que el orden se repita.
  • batch(64) arma lotes: 60,000 entre 64 da 937 lotes completos y uno final de 32, en total 938.
  • prefetch(AUTOTUNE) prepara el siguiente lote mientras la red entrena con el actual.

El notebook también muestra map, que aplica una función a cada elemento: con tf.cast(x, tf.float32) / 255.0 la normalización ocurre dentro de la tubería y el lote sale en float32 con máximo 1.0.

7. Aumento de datos

aumento = keras.Sequential([
    keras.layers.RandomFlip("horizontal", seed=42),
    keras.layers.RandomTranslation(0.1, 0.1, seed=42)])
print(aumento(x_tr[:8, :, :, None], training=True).shape)   # (8, 28, 28, 1)

Cada llamada con training=True voltea y desplaza las fotos al azar, hasta un 10 % de alto y ancho; la forma no cambia. El notebook dibuja las 8 fotos originales y sus variantes. Con training=False las capas devuelven las fotos sin tocar: el aumento solo actúa al entrenar. Voltear en horizontal es seguro con ropa; voltear en vertical no lo sería, porque nadie fotografía un botín de cabeza.

8. Etiquetas enteras y one-hot

print(y_tr[0])                                     # 9 (botin)
print(keras.utils.to_categorical(y_tr[:1], 10))    # [[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]

Las etiquetas de Fashion-MNIST son enteros de 0 a 9 y van con sparse_categorical_crossentropy. Si las conviertes a one-hot, la forma pasa a (60000, 10) y la pérdida correcta es categorical_crossentropy. Las dos dan el mismo número: con probabilidades [0.1, 0.7, 0.2] y clase correcta 1, ambas dan 0.3567, que es $-\ln 0.7$. Mezclarlas (one-hot con la versión sparse) produce un error de formas.

Qué debes obtener

Media de píxeles 72.94 cruda y 0.286 normalizada; val_accuracy en la época 1 de 0.7102 sin normalizar contra 0.8438 normalizado; media 0 y desviación 1 tras Normalization; lotes de (64, 28, 28) y 938 por época; aumento con forma (8, 28, 28, 1); la misma pérdida 0.3567 con etiquetas enteras y one-hot. Si tus números de entrenamiento difieren, revisa que llamaste a semillas(42) antes de crear cada modelo. Los ejercicios del final traen assert para que compruebes tus respuestas.

Recursos