Introducción

Las Unidades Recurrentes con Puerta (GRUs, por sus siglas en inglés) son una variante de las Redes Neuronales Recurrentes (RNNs) diseñadas para abordar el problema del desvanecimiento del gradiente y mejorar la capacidad de las RNNs para capturar dependencias a largo plazo en secuencias de datos. Las GRUs son similares a las Redes de Memoria a Largo Plazo (LSTM), pero con una estructura más simple y menos parámetros.

Conceptos Clave

  1. Puerta de Actualización: Controla cuánto de la información pasada debe ser llevada al futuro.
  2. Puerta de Reinicio: Decide cuánto de la información pasada debe ser olvidada.
  3. Estado Oculto: Representa la memoria de la red en un momento dado.

Estructura de una GRU

Una GRU tiene dos puertas principales: la puerta de actualización y la puerta de reinicio. A continuación se muestra la estructura matemática de una GRU:

  1. Puerta de Actualización: \[ z_t = \sigma(W_z \cdot [h_{t-1}, x_t]) \] Donde:

    • \( z_t \) es el vector de la puerta de actualización.
    • \( W_z \) son los pesos de la puerta de actualización.
    • \( h_{t-1} \) es el estado oculto anterior.
    • \( x_t \) es la entrada en el tiempo \( t \).
    • \( \sigma \) es la función sigmoide.
  2. Puerta de Reinicio: \[ r_t = \sigma(W_r \cdot [h_{t-1}, x_t]) \] Donde:

    • \( r_t \) es el vector de la puerta de reinicio.
    • \( W_r \) son los pesos de la puerta de reinicio.
  3. Estado Candidato: \[ \tilde{h}t = \tanh(W \cdot [r_t * h{t-1}, x_t]) \] Donde:

    • \( \tilde{h}_t \) es el estado candidato.
    • \( W \) son los pesos de la red.
    • \( * \) denota la multiplicación elemento a elemento.
  4. Estado Oculto: \[ h_t = (1 - z_t) * h_{t-1} + z_t * \tilde{h}_t \]

Implementación en PyTorch

Ejemplo Práctico

Vamos a implementar una GRU simple en PyTorch para predecir una secuencia de números.

Paso 1: Importar Librerías

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np

Paso 2: Definir la Clase de la GRU

class GRUNet(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers):
        super(GRUNet, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        out, _ = self.gru(x, h0)
        out = self.fc(out[:, -1, :])
        return out

Paso 3: Preparar los Datos

# Generar datos de ejemplo
def create_inout_sequences(input_data, tw):
    inout_seq = []
    L = len(input_data)
    for i in range(L-tw):
        train_seq = input_data[i:i+tw]
        train_label = input_data[i+tw:i+tw+1]
        inout_seq.append((train_seq, train_label))
    return inout_seq

data = np.sin(np.linspace(0, 100, 1000))
train_window = 10
train_inout_seq = create_inout_sequences(data, train_window)

# Convertir a tensores
train_inout_seq = [(torch.tensor(seq, dtype=torch.float32).unsqueeze(0), torch.tensor(label, dtype=torch.float32)) for seq, label in train_inout_seq]

Paso 4: Entrenar el Modelo

# Hiperparámetros
input_size = 1
hidden_size = 50
output_size = 1
num_layers = 1
num_epochs = 100
learning_rate = 0.01

# Inicializar el modelo, la función de pérdida y el optimizador
model = GRUNet(input_size, hidden_size, output_size, num_layers)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# Entrenamiento
for epoch in range(num_epochs):
    for seq, labels in train_inout_seq:
        optimizer.zero_grad()
        y_pred = model(seq.unsqueeze(-1))
        loss = criterion(y_pred, labels)
        loss.backward()
        optimizer.step()
    
    if epoch % 10 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item()}')

Paso 5: Evaluar el Modelo

# Evaluar el modelo
model.eval()
test_inputs = data[:train_window].tolist()
for i in range(100):
    seq = torch.tensor(test_inputs[-train_window:], dtype=torch.float32).unsqueeze(0).unsqueeze(-1)
    with torch.no_grad():
        test_inputs.append(model(seq).item())

# Visualizar los resultados
import matplotlib.pyplot as plt

plt.plot(data, label='Original Data')
plt.plot(range(train_window, train_window+100), test_inputs[train_window:], label='Predicted Data')
plt.legend()
plt.show()

Ejercicio Práctico

Ejercicio

Implementa una GRU para predecir la serie temporal de la función coseno en lugar de la función seno. Sigue los mismos pasos que en el ejemplo anterior, pero cambia la generación de datos para usar la función coseno.

Solución

# Generar datos de ejemplo con la función coseno
data = np.cos(np.linspace(0, 100, 1000))
train_window = 10
train_inout_seq = create_inout_sequences(data, train_window)

# Convertir a tensores
train_inout_seq = [(torch.tensor(seq, dtype=torch.float32).unsqueeze(0), torch.tensor(label, dtype=torch.float32)) for seq, label in train_inout_seq]

# Inicializar el modelo, la función de pérdida y el optimizador
model = GRUNet(input_size, hidden_size, output_size, num_layers)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# Entrenamiento
for epoch in range(num_epochs):
    for seq, labels in train_inout_seq:
        optimizer.zero_grad()
        y_pred = model(seq.unsqueeze(-1))
        loss = criterion(y_pred, labels)
        loss.backward()
        optimizer.step()
    
    if epoch % 10 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item()}')

# Evaluar el modelo
model.eval()
test_inputs = data[:train_window].tolist()
for i in range(100):
    seq = torch.tensor(test_inputs[-train_window:], dtype=torch.float32).unsqueeze(0).unsqueeze(-1)
    with torch.no_grad():
        test_inputs.append(model(seq).item())

# Visualizar los resultados
plt.plot(data, label='Original Data')
plt.plot(range(train_window, train_window+100), test_inputs[train_window:], label='Predicted Data')
plt.legend()
plt.show()

Conclusión

En esta sección, hemos aprendido sobre las Unidades Recurrentes con Puerta (GRUs), su estructura y cómo implementarlas en PyTorch. Las GRUs son una herramienta poderosa para trabajar con secuencias de datos y pueden ser utilizadas en una variedad de aplicaciones, desde la predicción de series temporales hasta el procesamiento de lenguaje natural. En el próximo módulo, exploraremos temas avanzados como las Redes Generativas Antagónicas (GANs) y el Aprendizaje por Refuerzo con PyTorch.

© Copyright 2024. Todos los derechos reservados