# deep-learning-en-python-y-tensorflow
Deep Learning en Python y Tensorflow
Volver a la portada del cursoTransformaciones 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
Normalizationy su métodoadapt. - Añadir el eje de canal que piden las capas convolucionales.
- Construir una tubería de
tf.dataconshuffle,batchyprefetch. - 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:
| Época | Sin normalizar | Normalizado |
|---|---|---|
| 1 | 0.7102 | 0.8438 |
| 2 | 0.7428 | 0.8528 |
| 3 | 0.7415 | 0.862 |
| 4 | 0.8133 | 0.8713 |
| 5 | 0.8243 | 0.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.