Tensores con TensorFlow
Tutorial completo de tensores en TensorFlow: creación, operaciones, GradientTape, Keras, clasificación MNIST y uso de GPU.
🔶 Tensores con TensorFlow
Submódulo: Tensores en Deep Learning · Fundamentos de Deep Learning
TensorFlow es la biblioteca de deep learning desarrollada por Google. Su nombre viene literalmente de tensor + flow: datos tensoriales fluyendo por un grafo de operaciones.
En este notebook recorremos las operaciones fundamentales con tensores en TensorFlow:
- Creación de tensores (escalares, vectores, matrices, tensores ND)
- Operaciones básicas (suma, multiplicación, matmul)
- Auto-diferenciación con
GradientTape - Regresión lineal desde cero
- Regresión con la API Keras
- Clasificación MNIST con una red neuronal
- Uso de GPU
1. Creación de Tensores
En TensorFlow, los tensores son objetos tf.Tensor inmutables. Se crean con tf.constant() o se generan con funciones como tf.zeros(), tf.ones(), tf.random.uniform().
import tensorflow as tf
import numpy as np
# ── Escalares, vectores, matrices, tensores ─────────────
escalar = tf.constant(7)
vector = tf.constant([1.0, 2.0, 3.0])
matriz = tf.constant([[1, 2], [3, 4]])
tensor = tf.constant([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
for name, t in [('Escalar', escalar), ('Vector', vector), ('Matriz', matriz), ('Tensor 3D', tensor)]:
print(f"{name:10s} | shape: {str(t.shape):12s} | dtype: {t.dtype.name:8s} | rango: {len(t.shape)}")
# ── Tensores especiales ─────────────────────────────────
print(f"\ntf.zeros((2,3)):\n{tf.zeros((2,3)).numpy()}")
print(f"\ntf.ones((2,3)):\n{tf.ones((2,3)).numpy()}")
print(f"\ntf.random.uniform((2,3)):\n{tf.random.uniform((2,3)).numpy().round(3)}")
Escalar: tf.Tensor(7, shape=(), dtype=int32) Vector: tf.Tensor([1. 2. 3.], shape=(3,), dtype=float32) Matriz: tf.Tensor( [[1 2] [3 4]], shape=(2, 2), dtype=int32) Tensor: tf.Tensor( [[[1 2] [3 4]] [[5 6] [7 8]]], shape=(2, 2, 2), dtype=int32)
2. Operaciones Básicas
TensorFlow soporta las mismas operaciones que NumPy, con funciones como tf.add(), tf.multiply(), tf.matmul(), o directamente con operadores +, *, @.
# ── Operaciones con tensores ──────────────────────────────
a = tf.constant([1, 2, 3])
b = tf.constant([4, 5, 6])
print(f"a = {a.numpy()}")
print(f"b = {b.numpy()}")
# Suma elemento a elemento
print(f"\nSuma (a + b): {tf.add(a, b).numpy()}")
# Multiplicación elemento a elemento
print(f"Producto Hadamard (a*b): {tf.multiply(a, b).numpy()}")
# Producto matricial
M1 = tf.constant([[1, 2], [3, 4]])
M2 = tf.constant([[5, 6], [7, 8]])
print(f"\nProducto matricial M1 @ M2:")
print(tf.matmul(M1, M2).numpy())
# Producto escalar
dot = tf.tensordot(tf.constant([1., 2., 3., 4.]), tf.constant([5., 6., 7., 8.]), axes=1)
print(f"\nProducto escalar: {dot.numpy()}")
Suma: tf.Tensor([5 7 9], shape=(3,), dtype=int32) Multiplicación: tf.Tensor([ 4 10 18], shape=(3,), dtype=int32) Producto Matricial: tf.Tensor( [[19 22] [43 50]], shape=(2, 2), dtype=int32)
3. Auto-Diferenciación: GradientTape
TensorFlow calcula gradientes automáticamente con tf.GradientTape. Esto es la base del backpropagation en redes neuronales.
El paradigma es:
- Crear una
tf.Variable(parámetro a optimizar) - Dentro de
with tf.GradientTape() as tape:computar el forward pass - Llamar
tape.gradient(loss, variable)para obtener $\frac{\partial \text{loss}}{\partial \text{variable}}$
# ── GradientTape ─────────────────────────────────────────
x = tf.Variable(3.0)
# Ejemplo 1: y = x² → dy/dx = 2x = 6.0
with tf.GradientTape() as tape:
y = x ** 2
grad = tape.gradient(y, x)
print(f"y = x² en x=3.0")
print(f"dy/dx = 2x = {grad.numpy()}")
# Ejemplo 2: función más compleja y = x³ + 2x² - 5x + 3
with tf.GradientTape() as tape:
y = x**3 + 2*x**2 - 5*x + 3
grad = tape.gradient(y, x)
print(f"\ny = x³ + 2x² - 5x + 3 en x=3.0")
print(f"dy/dx = 3x² + 4x - 5 = {grad.numpy()} (esperado: 34.0)")
El gradiente de y respecto a x en x=3.0 es: 6.0
4. Regresión Lineal desde Cero
Implementamos el modelo $y = Wx + b$ y lo entrenamos con descenso del gradiente manual, usando GradientTape para calcular los gradientes automáticamente.
# ── Regresión lineal con GradientTape ────────────────────
np.random.seed(42)
# Datos: y = 2x + 1 + ruido
X = np.linspace(0, 10, 100).astype(np.float32)
Y = (2 * X + 1 + np.random.randn(100).astype(np.float32) * 0.5)
X = tf.constant(X)
Y = tf.constant(Y)
# Parámetros a aprender
W = tf.Variable(tf.random.normal([1]))
b = tf.Variable(tf.random.normal([1]))
lr = 0.01
for epoch in range(100):
with tf.GradientTape() as tape:
Y_pred = W * X + b
loss = tf.reduce_mean(tf.square(Y - Y_pred))
grads = tape.gradient(loss, [W, b])
W.assign_sub(lr * grads[0])
b.assign_sub(lr * grads[1])
if (epoch + 1) % 20 == 0:
print(f"Epoch {epoch+1:>3}: loss={loss.numpy():.4f}, W={W.numpy()[0]:.3f}, b={b.numpy()[0]:.3f}")
print(f"\n✅ Modelo final: y = {W.numpy()[0]:.3f}·x + {b.numpy()[0]:.3f} (real: y = 2x + 1)")
Epoch 10: pérdida = 0.2812432050704956, W = [2.0178018], b = [0.9052763] Epoch 20: pérdida = 0.28030240535736084, W = [2.0163846], b = [0.9148339] Epoch 30: pérdida = 0.27945074439048767, W = [2.0150177], b = [0.9239248] Epoch 40: pérdida = 0.278679758310318, W = [2.0137172], b = [0.93257433] Epoch 50: pérdida = 0.27798184752464294, W = [2.0124798], b = [0.9408039] Epoch 60: pérdida = 0.2773500382900238, W = [2.0113022], b = [0.948634] Epoch 70: pérdida = 0.2767781913280487, W = [2.0101821], b = [0.956084] Epoch 80: pérdida = 0.27626028656959534, W = [2.0091164], b = [0.96317244] Epoch 90: pérdida = 0.27579158544540405, W = [2.0081022], b = [0.9699167] Epoch 100: pérdida = 0.27536728978157043, W = [2.0071375], b = [0.9763336] Modelo entrenado: Y = 2.0071375370025635 * X + 0.9763336181640625
5. Lo Mismo con la API Keras
tf.keras abstrae todo el bucle anterior en unas pocas líneas. Es la forma estándar de usar TensorFlow en proyectos reales.
# ── Regresión con Keras ───────────────────────────────────
X_np = np.linspace(0, 10, 100)
Y_np = 2 * X_np + 1 + np.random.randn(100) * 0.5
model = tf.keras.Sequential([
tf.keras.layers.Dense(1, input_shape=(1,))
])
model.compile(optimizer='sgd', loss='mse')
# Entrenar (verbose=0 para no llenar la salida)
model.fit(X_np, Y_np, epochs=100, verbose=0)
W_k, b_k = model.layers[0].get_weights()
print(f"Modelo Keras: y = {W_k[0][0]:.3f}·x + {b_k[0]:.3f} (real: y = 2x + 1)")
C:\Users\juano\AppData\Roaming\Python\Python310\site-packages\keras\src\layers\core\dense.py:87: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Modelo entrenado: Y = 2.034008264541626 * X + 0.8488515019416809
6. Red Neuronal para Clasificación (MNIST)
Un ejemplo completo de clasificación de dígitos manuscritos con un MLP de una capa oculta (128 neuronas, ReLU).
import matplotlib.pyplot as plt
# ── Cargar MNIST ──────────────────────────────────────────
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
X_train, X_test = X_train / 255.0, X_test / 255.0 # Normalizar [0, 1]
print(f"Train: {X_train.shape} | Test: {X_test.shape}")
# Visualizar ejemplos
fig, axes = plt.subplots(2, 5, figsize=(12, 5))
for i, ax in enumerate(axes.flat):
ax.imshow(X_train[i], cmap='gray')
ax.set_title(f"Label: {y_train[i]}")
ax.axis('off')
plt.suptitle('Ejemplos de MNIST', fontsize=14)
plt.tight_layout()
plt.show()
Tamaño del conjunto de entrenamiento: (60000, 28, 28) Tamaño del conjunto de prueba: (10000, 28, 28)
# ── Modelo MLP ────────────────────────────────────────────
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Entrenar
model.fit(X_train, y_train, epochs=5, batch_size=64)
# Evaluar
loss, acc = model.evaluate(X_test, y_test, verbose=0)
print(f"\n✅ Test accuracy: {acc*100:.1f}%")
Epoch 1/5
C:\Users\juano\AppData\Roaming\Python\Python310\site-packages\keras\src\layers\reshaping\flatten.py:37: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
[1m1875/1875[0m [32m━━━━━━━━━━━━━━━━━━━━[0m[37m[0m [1m2s[0m 879us/step - accuracy: 0.8800 - loss: 0.4273 Epoch 2/5 [1m1875/1875[0m [32m━━━━━━━━━━━━━━━━━━━━[0m[37m[0m [1m2s[0m 849us/step - accuracy: 0.9629 - loss: 0.1223 Epoch 3/5 [1m1875/1875[0m [32m━━━━━━━━━━━━━━━━━━━━[0m[37m[0m [1m2s[0m 844us/step - accuracy: 0.9769 - loss: 0.0765 Epoch 4/5 [1m1875/1875[0m [32m━━━━━━━━━━━━━━━━━━━━[0m[37m[0m [1m2s[0m 852us/step - accuracy: 0.9836 - loss: 0.0526 Epoch 5/5 [1m1875/1875[0m [32m━━━━━━━━━━━━━━━━━━━━[0m[37m[0m [1m2s[0m 859us/step - accuracy: 0.9877 - loss: 0.0417 [1m313/313[0m [32m━━━━━━━━━━━━━━━━━━━━[0m[37m[0m [1m0s[0m 595us/step - accuracy: 0.9722 - loss: 0.0889 Pérdida en test: 0.07669886201620102 Precisión en test: 0.9761999845504761
7. Uso de GPU
TensorFlow detecta automáticamente las GPUs disponibles y mueve los tensores al dispositivo óptimo. Con tf.device('/GPU:0') se fuerza la ejecución en GPU.
# ── Verificar GPU ────────────────────────────────────────
gpus = tf.config.list_physical_devices('GPU')
print(f"GPUs disponibles: {len(gpus)}")
for gpu in gpus:
print(f" {gpu.name}")
# Operación en GPU (si disponible)
device = '/GPU:0' if gpus else '/CPU:0'
with tf.device(device):
A = tf.random.uniform([1000, 1000])
B = tf.random.uniform([1000, 1000])
C = tf.matmul(A, B)
print(f"\nMultiplicación 1000×1000 ejecutada en {device}")
print(f"Resultado shape: {C.shape}")
8. Resumen
| Concepto | TensorFlow | Equivalente NumPy |
|---|---|---|
| Crear tensor | tf.constant() |
np.array() |
| Suma | tf.add(a, b) o a + b |
a + b |
| Matmul | tf.matmul(A, B) o A @ B |
A @ B |
| Gradientes | tf.GradientTape |
❌ (manual) |
| GPU | tf.device('/GPU:0') |
❌ |
| Modelos | tf.keras.Sequential |
❌ |
💡 TensorFlow añade sobre NumPy dos capacidades clave: auto-diferenciación (
GradientTape) y aceleración GPU — imprescindibles para entrenar redes neuronales de forma eficiente.