Primeros pasos con TensorFlow
Construiremos una red neuronal desde cero con TensorFlow y Keras: cargar datos, diseñar un MLP, entrenarlo, guardarlo y usarlo para predecir. Cada línea de código está explicada.
Requisitos previos
- Python 3.8 o superior instalado
- Conocimientos básicos de Python (funciones, listas, bucles)
- Saber qué es una red neuronal (consulta la teoría del perceptrón y el MLP)
Instalación e imports
Lo primero es instalar TensorFlow. Desde tu terminal:
pip install tensorflow
Ahora importamos las librerías que vamos a usar. TensorFlow incluye Keras como su API de alto nivel — es con la que trabajaremos:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
print(f"TensorFlow version: {tf.__version__}")
print(f"GPU disponible: {len(tf.config.list_physical_devices('GPU')) > 0}")
tensorflow — el framework completo. Se importa como tf por convención.keras — la API de alto nivel de TensorFlow. Nos permite definir modelos y capas de forma simple.layers — contiene las capas predefinidas: Dense, Dropout, Conv2D, etc.numpy — para manipular arrays. TensorFlow trabaja con tensores, pero NumPy es útil para preparar datos.False, TensorFlow usará la CPU.
Funciona igualmente, pero será más lento para modelos grandes. Para este tutorial no importa.
Cargar y preparar los datos
Usaremos el dataset MNIST: 70,000 imágenes de dígitos manuscritos (0-9), cada una de 28×28 píxeles en escala de grises. Es el dataset clásico para empezar con redes neuronales.
# Cargar MNIST (viene incluido en Keras)
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# Ver las dimensiones
print(f"Train: {x_train.shape}, Labels: {y_train.shape}")
print(f"Test: {x_test.shape}, Labels: {y_test.shape}")
print(f"Rango de píxeles: [{x_train.min()}, {x_train.max()}]")
print(f"Ejemplo de label: {y_train[0]}")
Ahora necesitamos preprocesar los datos. Para un MLP necesitamos dos cosas:
- Aplanar las imágenes: de (28, 28) a (784,) — un MLP espera vectores, no matrices
- Normalizar los píxeles: de [0, 255] a [0, 1] — dividiendo entre 255
# Aplanar: (60000, 28, 28) → (60000, 784)
x_train = x_train.reshape(-1, 784).astype("float32")
x_test = x_test.reshape(-1, 784).astype("float32")
# Normalizar a [0, 1]
x_train /= 255.0
x_test /= 255.0
print(f"Shape tras preprocesado: {x_train.shape}")
print(f"Nuevo rango: [{x_train.min():.1f}, {x_train.max():.1f}]")
reshape(-1, 784) — el -1 deja que NumPy calcule esa dimensión automáticamente (será 60000). 28×28 = 784.astype("float32") — convierte de enteros (uint8) a decimales. Las redes necesitan números decimales.La normalización es fundamental en deep learning por varias razones:
- Los pesos de la red se inicializan como valores pequeños (~0.01). Si los inputs son grandes (0-255), las multiplicaciones producen valores enormes → gradientes inestables.
- Con datos en [0, 1], el optimizador puede usar learning rates estándar (como 0.001) sin problemas.
- Diferentes features con diferentes rangos harían que la red preste más atención a las de mayor magnitud, sesgando el aprendizaje.
Regla: normaliza siempre tus datos antes de entrenar. Las técnicas más comunes son dividir entre el máximo (Min-Max) o estandarizar (media 0, std 1).
Diseñar el modelo (MLP)
Ahora viene lo más interesante: definir la arquitectura de nuestra red neuronal. Usaremos un MLP (Multi-Layer Perceptron) sencillo con dos capas ocultas.
En Keras, la forma más fácil de construir un modelo es con Sequential:
defines las capas en orden, como una lista.
model = keras.Sequential([
layers.Dense(128, activation="relu", input_shape=(784,)),
layers.Dropout(0.2),
layers.Dense(64, activation="relu"),
layers.Dropout(0.2),
layers.Dense(10, activation="softmax"),
])
model.summary()
Sequential — modelo secuencial: la salida de cada capa es la entrada de la siguiente.Dense(128, activation="relu") — capa densa (fully connected) con 128 neuronas y activación ReLU. input_shape=(784,) indica que la entrada tiene 784 features.Dropout(0.2) — durante el entrenamiento, «apaga» aleatoriamente el 20% de las neuronas en cada paso. Es una técnica de regularización que previene el overfitting.Dense(10, activation="softmax") — capa de salida. 10 neuronas (una por dígito 0-9). softmax convierte las salidas en probabilidades que suman 1.Cada capa Dense tiene pesos + biases:
- Dense(128): 784 × 128 pesos + 128 biases = 100,480
- Dense(64): 128 × 64 + 64 = 8,256
- Dense(10): 64 × 10 + 10 = 650
Dropout no tiene parámetros: solo «apaga» neuronas al azar.
Total: 100,480 + 8,256 + 650 = 109,386 parámetros. Esto es un modelo pequeño — GPT-4 tiene cientos de miles de millones.
Compilar el modelo
Antes de entrenar, necesitamos compilar el modelo — esto significa decirle cómo aprender. Tres decisiones:
- Optimizador: el algoritmo que ajusta los pesos (Adam es lo estándar)
- Función de pérdida: cómo medir el error del modelo
- Métricas: qué queremos monitorizar durante el entrenamiento
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
"adam" — Adam optimizer. Combina momentum y learning rates adaptativos. Es el optimizador por defecto en la mayoría de proyectos."sparse_categorical_crossentropy" — pérdida para clasificación multiclase cuando las labels son enteros (0, 1, ..., 9). Si las labels fueran one-hot, usaríamos "categorical_crossentropy"."accuracy" — porcentaje de predicciones correctas. Se calcula en cada epoch para train y validación.model.compile() no entrena nada todavía — solo configura el
optimizador y la loss. Es como «preparar la cocina antes de cocinar». El entrenamiento real
ocurre en el siguiente paso con model.fit().
Entrenar el modelo
¡Hora de entrenar! Con model.fit(), Keras se encarga de todo el loop de entrenamiento:
forward pass, cálculo de la loss, backward pass, actualización de pesos. Todo automático.
history = model.fit(
x_train, y_train,
epochs=10,
batch_size=32,
validation_split=0.1,
verbose=1,
)
epochs=10 — el modelo verá el dataset completo 10 veces. Más epochs = más aprendizaje (hasta cierto punto).batch_size=32 — en cada paso, procesa 32 imágenes a la vez. Más grande = más rápido pero necesita más memoria.validation_split=0.1 — reserva el 10% de los datos de train para validación. Así vemos si el modelo generaliza bien o hace overfitting.history — guarda las métricas de cada epoch (loss, accuracy) para poder graficarlas después.- accuracy: 0.9812 — el modelo acierta el 98.12% de los datos de entrenamiento.
- loss: 0.0598 — la función de pérdida (cross-entropy). Más bajo = mejor.
- val_accuracy: 0.9778 — accuracy en los datos de validación (datos que el modelo NO ha visto durante el entrenamiento).
- val_loss: 0.0742 — loss de validación. Si esta sube mientras train baja → overfitting.
En este caso, val_accuracy ≈ train_accuracy, así que no hay overfitting significativo. ¡Buen modelo!
Evaluar el modelo
Evaluamos el modelo con los datos de test — datos completamente nuevos que el modelo nunca ha visto, ni siquiera durante la validación. Esto nos da la métrica real de rendimiento:
test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=0)
print(f"Test loss: {test_loss:.4f}")
print(f"Test accuracy: {test_accuracy:.4f}")
print(f"Acierta {test_accuracy*100:.1f}% de los dígitos que nunca ha visto")
¡97.8% de accuracy con un MLP tan sencillo! No está mal para un modelo con apenas 109K parámetros. Con una CNN podríamos superar el 99%.
Guardar el modelo
El modelo entrenado vive en memoria RAM. Si cierras Python, se pierde todo. Necesitamos guardarlo en disco para poder reutilizarlo después:
# Opción 1: Formato Keras nativo (.keras) — RECOMENDADO
model.save("mi_modelo_mnist.keras")
print("✅ Modelo guardado como mi_modelo_mnist.keras")
# Opción 2: Solo los pesos (sin la arquitectura)
model.save_weights("pesos_mnist.weights.h5")
print("✅ Pesos guardados como pesos_mnist.weights.h5")
.save("archivo.keras") — guarda todo: arquitectura + pesos + configuración del optimizador. Puedes cargar y seguir entrenando..save_weights() — solo guarda los valores numéricos de los pesos. Necesitarás volver a definir la arquitectura para cargarlos..keras es el recomendado desde TensorFlow 2.16+.
Anteriormente se usaba .h5 (HDF5) o SavedModel (directorio).
Los tres siguen funcionando, pero .keras es el estándar actual.
Cargar y usar el modelo guardado
En un escenario real, guardarías el modelo al final del entrenamiento y lo cargarías en un script (o API) separado para hacer predicciones. Así se carga:
# En un nuevo script o sesión de Python:
import tensorflow as tf
from tensorflow import keras
# Cargar el modelo completo
loaded_model = keras.models.load_model("mi_modelo_mnist.keras")
# Verificar que funciona
loaded_model.summary()
# Evaluar con los datos de test (misma accuracy que antes)
test_loss, test_acc = loaded_model.evaluate(x_test, y_test, verbose=0)
print(f"Modelo cargado → accuracy: {test_acc:.4f}")
La misma accuracy. El modelo cargado es idéntico al original.
Puedes distribuir el archivo .keras a tus compañeros, subirlo a producción, etc.
Hacer predicciones
Ya tenemos nuestro modelo entrenado y cargado. Vamos a usarlo para clasificar dígitos nuevos:
# Predecir las primeras 5 imágenes del test set
predictions = loaded_model.predict(x_test[:5], verbose=0)
# 'predictions' tiene shape (5, 10) — 10 probabilidades por imagen
for i in range(5):
predicted_digit = np.argmax(predictions[i])
confidence = predictions[i][predicted_digit] * 100
real_digit = y_test[i]
status = "✅" if predicted_digit == real_digit else "❌"
print(f"{status} Imagen {i}: predicho={predicted_digit} "
f"(confianza: {confidence:.1f}%) | real={real_digit}")
.predict() devuelve las probabilidades para cada clase. Shape: (n_samples, n_classes).np.argmax() — devuelve el índice de la probabilidad más alta → el dígito predicho.¡Todas correctas y con alta confianza! Nuestro MLP reconoce dígitos manuscritos con un 97-98% de precisión general.
Script completo
Aquí tienes el script completo que puedes copiar y ejecutar directamente. Incluye todo lo que hemos visto: imports → datos → modelo → entrenamiento → guardado → carga → predicción.
📄 Script: mnist_tensorflow.py
"""
Primeros pasos con TensorFlow: MLP para clasificar dígitos MNIST.
"""
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
# ── 1. Cargar datos ──────────────────────────────────────
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# ── 2. Preprocesar ──────────────────────────────────────
x_train = x_train.reshape(-1, 784).astype("float32") / 255.0
x_test = x_test.reshape(-1, 784).astype("float32") / 255.0
# ── 3. Definir modelo ───────────────────────────────────
model = keras.Sequential([
layers.Dense(128, activation="relu", input_shape=(784,)),
layers.Dropout(0.2),
layers.Dense(64, activation="relu"),
layers.Dropout(0.2),
layers.Dense(10, activation="softmax"),
])
# ── 4. Compilar ─────────────────────────────────────────
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
# ── 5. Entrenar ─────────────────────────────────────────
history = model.fit(
x_train, y_train,
epochs=10,
batch_size=32,
validation_split=0.1,
verbose=1,
)
# ── 6. Evaluar ──────────────────────────────────────────
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
print(f"\n🎯 Test accuracy: {test_acc:.4f}")
# ── 7. Guardar ──────────────────────────────────────────
model.save("mi_modelo_mnist.keras")
print("💾 Modelo guardado")
# ── 8. Cargar ───────────────────────────────────────────
loaded = keras.models.load_model("mi_modelo_mnist.keras")
# ── 9. Predecir ─────────────────────────────────────────
preds = loaded.predict(x_test[:5], verbose=0)
for i in range(5):
digit = np.argmax(preds[i])
conf = preds[i][digit] * 100
print(f"{'✅' if digit == y_test[i] else '❌'} "
f"Predicho: {digit} ({conf:.1f}%) | Real: {y_test[i]}")