🛠️ Unidad 8. Frameworks y Práctica con Deep Learning
Esta unidad cubre los principales frameworks de Deep Learning, herramientas de desarrollo, y mejores prácticas para proyectos de producción.
8.1. Comparación de Frameworks
TensorFlow vs PyTorch
| Característica | TensorFlow | PyTorch |
|---|---|---|
| Desarrollador | Facebook (Meta) | |
| Paradigma | Grafo estático → Eager execution | Eager execution (dinámico) |
| API de Alto Nivel | Keras (integrado) | torch.nn |
| Debugging | TensorBoard, tf.debugging | PyDB, hooks |
| Producción | TF Serving, TF Lite, TF.js | TorchServe, ONNX |
| Comunidad | Industria, producción | Investigación, academia |
| Curva de Aprendizaje | Moderada | Más intuitivo |
Recomendación
- TensorFlow/Keras: Producción, despliegue móvil, principiantes.
- PyTorch: Investigación, prototipado rápido, flexibilidad.
8.2. TensorFlow y Keras
Arquitectura de TensorFlow 2.x
┌────────────────────────────────────────────┐
│ Tu Código Python │
├────────────────────────────────────────────┤
│ Keras API │
├────────────────────────────────────────────┤
│ TensorFlow Core (Operaciones) │
├────────────────────────────────────────────┤
│ Hardware: CPU / GPU (CUDA) / TPU / Edge │
└────────────────────────────────────────────┘
Formas de Crear Modelos en Keras
Sequential API (más simple)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Dropout, Input
model = Sequential([
Input(shape=(784,)),
Dense(256, activation='relu'),
Dropout(0.3),
Dense(128, activation='relu'),
Dense(10, activation='softmax')
])
Functional API (más flexible)
from tensorflow.keras import Model
from tensorflow.keras.layers import Input, Dense, Concatenate
# Múltiples entradas
input_a = Input(shape=(32,), name='input_a')
input_b = Input(shape=(64,), name='input_b')
x_a = Dense(16, activation='relu')(input_a)
x_b = Dense(32, activation='relu')(input_b)
merged = Concatenate()([x_a, x_b])
x = Dense(64, activation='relu')(merged)
output = Dense(10, activation='softmax')(x)
model = Model(inputs=[input_a, input_b], outputs=output)
Subclassing (máxima flexibilidad)
class MiModelo(Model):
def __init__(self):
super().__init__()
self.dense1 = Dense(256, activation='relu')
self.dense2 = Dense(128, activation='relu')
self.dense3 = Dense(10, activation='softmax')
def call(self, inputs, training=False):
x = self.dense1(inputs)
if training:
x = tf.nn.dropout(x, rate=0.3)
x = self.dense2(x)
return self.dense3(x)
model = MiModelo()
Entrenamiento Personalizado
# Training loop personalizado
optimizer = tf.keras.optimizers.Adam()
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy()
train_acc_metric = tf.keras.metrics.SparseCategoricalAccuracy()
@tf.function # Compilar para mayor velocidad
def train_step(x, y):
with tf.GradientTape() as tape:
predictions = model(x, training=True)
loss = loss_fn(y, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
train_acc_metric.update_state(y, predictions)
return loss
# Loop de entrenamiento
for epoch in range(epochs):
for x_batch, y_batch in train_dataset:
loss = train_step(x_batch, y_batch)
train_acc = train_acc_metric.result()
print(f"Epoch {epoch}: Loss = {loss:.4f}, Accuracy = {train_acc:.4f}")
train_acc_metric.reset_states()
8.3. PyTorch
Estructura Básica
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
# Definir modelo
class MiRed(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super().__init__()
self.layer1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.3)
self.layer2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
x = self.layer1(x)
x = self.relu(x)
x = self.dropout(x)
x = self.layer2(x)
return x
# Instanciar
model = MiRed(784, 256, 10)
# Mover a GPU si está disponible
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
Entrenamiento en PyTorch
# Configurar
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Crear DataLoader
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
# Entrenar
model.train() # Modo entrenamiento
for epoch in range(epochs):
running_loss = 0.0
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
# Zero gradients
optimizer.zero_grad()
# Forward
outputs = model(inputs)
loss = criterion(outputs, labels)
# Backward
loss.backward()
# Actualizar pesos
optimizer.step()
running_loss += loss.item()
print(f"Epoch {epoch}: Loss = {running_loss/len(train_loader):.4f}")
Evaluación
model.eval() # Modo evaluación
correct = 0
total = 0
with torch.no_grad(): # No calcular gradientes
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f"Accuracy: {100 * correct / total:.2f}%")
8.4. Callbacks y Monitorización
Callbacks en Keras
from tensorflow.keras.callbacks import (
ModelCheckpoint,
EarlyStopping,
ReduceLROnPlateau,
TensorBoard,
CSVLogger
)
callbacks = [
# Guardar el mejor modelo
ModelCheckpoint(
'best_model.keras',
monitor='val_loss',
save_best_only=True
),
# Early stopping
EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True
),
# Reducir learning rate
ReduceLROnPlateau(
monitor='val_loss',
factor=0.2,
patience=5,
min_lr=1e-7
),
# TensorBoard
TensorBoard(log_dir='./logs'),
# Log a CSV
CSVLogger('training_log.csv')
]
model.fit(X_train, y_train, callbacks=callbacks)
TensorBoard
# Logging personalizado
import tensorflow as tf
log_dir = "logs/custom"
summary_writer = tf.summary.create_file_writer(log_dir)
with summary_writer.as_default():
tf.summary.scalar('custom_metric', value, step=epoch)
tf.summary.image('generated', images, step=epoch)
tf.summary.histogram('weights', model.layers[0].weights[0], step=epoch)
8.5. GPU y Aceleración
Verificar GPU
# TensorFlow
import tensorflow as tf
print("GPUs disponibles:", tf.config.list_physical_devices('GPU'))
# PyTorch
import torch
print("CUDA disponible:", torch.cuda.is_available())
print("GPU:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "N/A")
Configurar Memoria GPU (TensorFlow)
# Crecimiento dinámico de memoria
gpus = tf.config.experimental.list_physical_devices('GPU')
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu, True)
# O limitar memoria máxima
tf.config.set_logical_device_configuration(
gpus[0],
[tf.config.LogicalDeviceConfiguration(memory_limit=4096)] # 4GB
)
Entrenamiento Multi-GPU
# TensorFlow
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
model = create_model()
model.compile(...)
model.fit(...)
# PyTorch
model = nn.DataParallel(model) # Simple
# O usar DistributedDataParallel para mejor rendimiento
8.6. Optimización del Pipeline de Datos
tf.data API
import tensorflow as tf
# Crear dataset
dataset = tf.data.Dataset.from_tensor_slices((X, y))
# Pipeline optimizado
dataset = (dataset
.cache() # Cachear en memoria
.shuffle(buffer_size=10000) # Mezclar
.batch(32) # Crear batches
.prefetch(tf.data.AUTOTUNE) # Cargar siguiente batch mientras se entrena
)
# Para imágenes desde directorio
dataset = tf.keras.utils.image_dataset_from_directory(
'data/train',
image_size=(224, 224),
batch_size=32
)
dataset = dataset.prefetch(tf.data.AUTOTUNE)
DataLoader en PyTorch
from torch.utils.data import DataLoader
train_loader = DataLoader(
dataset,
batch_size=32,
shuffle=True,
num_workers=4, # Carga paralela
pin_memory=True, # Acelera transferencia a GPU
persistent_workers=True
)
8.7. Guardado y Carga de Modelos
TensorFlow/Keras
# Guardar modelo completo
model.save('modelo_completo.keras')
# Cargar
model = tf.keras.models.load_model('modelo_completo.keras')
# Solo pesos
model.save_weights('pesos.weights.h5')
model.load_weights('pesos.weights.h5')
# SavedModel (para producción)
model.save('modelo_savedmodel', save_format='tf')
PyTorch
# Solo pesos (recomendado)
torch.save(model.state_dict(), 'modelo_pesos.pth')
# Cargar
model = MiRed(784, 256, 10)
model.load_state_dict(torch.load('modelo_pesos.pth'))
model.eval()
# Checkpoint completo (para continuar entrenamiento)
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss,
}, 'checkpoint.pth')
# Cargar checkpoint
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
8.8. Debugging y Profiling
Debugging en TensorFlow
# Eager execution por defecto permite debugging normal
tf.config.run_functions_eagerly(True)
# Verificar valores
@tf.function
def mi_funcion(x):
tf.debugging.assert_non_negative(x, message="x debe ser positivo")
tf.print("Valor de x:", x)
return x ** 2
Profiling
# TensorFlow Profiler
import tensorflow as tf
# En el callback de TensorBoard
tensorboard_callback = tf.keras.callbacks.TensorBoard(
log_dir='logs',
profile_batch=(10, 20) # Perfilar batches 10-20
)
# PyTorch Profiler
with torch.profiler.profile(
activities=[
torch.profiler.ProfilerActivity.CPU,
torch.profiler.ProfilerActivity.CUDA
],
on_trace_ready=torch.profiler.tensorboard_trace_handler('./logs'),
) as prof:
for step, (inputs, labels) in enumerate(train_loader):
outputs = model(inputs)
loss.backward()
prof.step()
8.9. Despliegue a Producción
TensorFlow Serving
# Exportar modelo
model.save('modelo_produccion/1')
# Docker
# docker pull tensorflow/serving
# docker run -p 8501:8501 --mount type=bind,source=/path/modelo_produccion,target=/models/mi_modelo -e MODEL_NAME=mi_modelo -t tensorflow/serving
# Hacer predicción vía REST
import requests
import json
data = json.dumps({"instances": X_test[:5].tolist()})
response = requests.post(
'http://localhost:8501/v1/models/mi_modelo:predict',
data=data,
headers={"content-type": "application/json"}
)
predictions = response.json()['predictions']
TensorFlow Lite (Móvil)
# Convertir a TFLite
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT] # Cuantización
tflite_model = converter.convert()
# Guardar
with open('modelo.tflite', 'wb') as f:
f.write(tflite_model)
ONNX (Interoperabilidad)
# PyTorch a ONNX
import torch.onnx
dummy_input = torch.randn(1, 784)
torch.onnx.export(
model,
dummy_input,
"modelo.onnx",
export_params=True,
opset_version=11,
input_names=['input'],
output_names=['output']
)
# TensorFlow a ONNX
import tf2onnx
model_proto, _ = tf2onnx.convert.from_keras(model)
8.10. Mejores Prácticas
Estructura de Proyecto
mi_proyecto/
├── data/
│ ├── raw/
│ ├── processed/
│ └── external/
├── models/
│ ├── checkpoints/
│ └── saved/
├── src/
│ ├── data/
│ │ └── dataset.py
│ ├── models/
│ │ ├── architectures.py
│ │ └── losses.py
│ ├── training/
│ │ ├── train.py
│ │ └── callbacks.py
│ └── utils/
│ └── helpers.py
├── notebooks/
│ └── exploration.ipynb
├── configs/
│ └── config.yaml
├── tests/
├── requirements.txt
└── README.md
Reproducibilidad
import random
import numpy as np
import tensorflow as tf
def set_seeds(seed=42):
random.seed(seed)
np.random.seed(seed)
tf.random.set_seed(seed)
# PyTorch
# torch.manual_seed(seed)
# torch.cuda.manual_seed_all(seed)
set_seeds(42)
Configuración con YAML
# config.yaml
model:
architecture: resnet50
num_classes: 10
dropout: 0.3
training:
batch_size: 32
epochs: 100
learning_rate: 0.001
data:
train_dir: "data/train"
val_dir: "data/val"
image_size: [224, 224]
import yaml
with open('configs/config.yaml', 'r') as f:
config = yaml.safe_load(f)
batch_size = config['training']['batch_size']
Logging con Weights & Biases
import wandb
wandb.init(project="mi-proyecto", config=config)
# Durante entrenamiento
wandb.log({
"loss": loss,
"accuracy": accuracy,
"epoch": epoch
})
# Al final
wandb.finish()
8.11. Recursos Adicionales
Documentación Oficial
Cursos Recomendados
- Deep Learning Specialization (Coursera/Andrew Ng).
- Fast.ai (Practical Deep Learning).
- Stanford CS231n (Computer Vision).
- Stanford CS224n (NLP).
Libros
- "Deep Learning" - Goodfellow, Bengio, Courville.
- "Hands-On Machine Learning" - Aurélien Géron.
- "Deep Learning with Python" - François Chollet.
📅 Fecha de creación: Enero 2026
✍️ Autor: Fran García