tutoriales.com

Reinforcement Learning con TensorFlow y PyTorch: Aprendizaje por Refuerzo Profundo para Juegos y Control

Este tutorial exhaustivo explora el fascinante mundo del Aprendizaje por Refuerzo Profundo (DRL) utilizando los frameworks TensorFlow y PyTorch. Aprenderás los conceptos fundamentales del RL, implementarás algoritmos clave como Q-learning y DQN, y verás cómo aplicar estas técnicas para resolver problemas complejos de control y juegos.

Intermedio18 min de lectura12 views
Reportar error

🚀 Introducción al Aprendizaje por Refuerzo Profundo (DRL)

El Aprendizaje por Refuerzo (RL) es un paradigma de Machine Learning donde un agente aprende a tomar decisiones en un entorno para maximizar una recompensa acumulada. A diferencia del aprendizaje supervisado y no supervisado, el RL no requiere datos etiquetados directamente, sino que aprende a través de la interacción y el ensayo y error.

Cuando combinamos el poder del Aprendizaje por Refuerzo con las Redes Neuronales Profundas, obtenemos el Aprendizaje por Refuerzo Profundo (DRL), una técnica que ha revolucionado campos como los videojuegos (AlphaGo, AlphaStar) y la robótica.

¿Por qué DRL? 🤔

El DRL permite a los agentes aprender políticas de decisión óptimas en entornos complejos y de alta dimensionalidad, donde los métodos tradicionales de RL fallarían debido a la enorme cantidad de estados y acciones posibles. Las redes neuronales actúan como aproximadores de funciones, permitiendo al agente generalizar a partir de experiencias limitadas.

🔥 Importante: El DRL es la clave para que la IA aprenda a tomar decisiones inteligentes y secuenciales en situaciones dinámicas, adaptándose y optimizando su comportamiento a lo largo del tiempo.

📖 Conceptos Fundamentales del Aprendizaje por Refuerzo

Antes de sumergirnos en el código, es crucial entender los pilares del RL.

El Agente y el Entorno 🌐

  • Agente: El tomador de decisiones. Su objetivo es aprender una política que le permita maximizar la recompensa total.
  • Entorno: El mundo con el que interactúa el agente. Define las reglas del juego, los estados disponibles y cómo se otorgan las recompensas.

Estados, Acciones y Recompensas ✨

  • Estado (S): Una representación de la situación actual del entorno. Puede ser simple (ej. posición de un robot) o complejo (ej. píxeles de una pantalla de videojuego).
  • Acción (A): Una decisión que el agente puede tomar en un estado dado. Cada acción cambia el estado del entorno.
  • Recompensa (R): Un valor numérico que el entorno otorga al agente después de una acción. Las recompensas pueden ser positivas (deseadas) o negativas (penalizaciones).

Política y Función de Valor ✅

  • Política (π): Es el cerebro del agente. Define cómo el agente se comporta, mapeando estados a acciones (π: S → A). El objetivo es encontrar la política óptima.
  • Función de Valor (V): Estima cuánto valor (recompensa esperada a largo plazo) tiene estar en un estado particular bajo una política dada, o tomar una acción particular en un estado dado (Función Q).
📌 Nota: La *función de valor de estado* (V(s)) nos dice cuán bueno es un estado, mientras que la *función de valor de acción-estado* (Q(s,a)) nos dice cuán buena es una acción particular en un estado particular.

El Problema del Crédito y el Descuento 💰

El desafío en RL es el problema del crédito: las acciones tomadas ahora pueden tener consecuencias (recompensas o penalizaciones) mucho más tarde. Para manejar esto, usamos un factor de descuento (γ) para ponderar las recompensas futuras. Una recompensa inmediata tiene más peso que una recompensa lejana.

Exploración vs. Explotación 🗺️ vs. 🎯

Un agente debe equilibrar dos actividades clave:

  • Exploración: Probar nuevas acciones para descubrir mejores recompensas.
  • Explotación: Utilizar el conocimiento actual para elegir acciones que se sabe que dan buenas recompensas.

Sin exploración, el agente podría quedarse atrapado en un óptimo local. Sin explotación, no usaría lo que ya sabe.

AGENTE ENTORNO Acción (A) Estado (S) Recompensa (R) Ciclo de Aprendizaje por Refuerzo

🛠️ Q-Learning Profundo (DQN): Un Vistazo en TensorFlow y PyTorch

El Q-Learning es un algoritmo fundamental en RL. Su objetivo es aprender la función Q óptima, Q*(s,a), que representa la máxima recompensa futura esperada al tomar la acción 'a' en el estado 's' y seguir la política óptima a partir de entonces.

En DRL, las Redes Neuronales Profundas se utilizan para aproximar la función Q(s,a), de ahí el término Deep Q-Network (DQN).

Arquitectura Básica de DQN 🏗️

Una DQN es típicamente una red neuronal feedforward que toma un estado como entrada y produce un valor Q para cada acción posible como salida.

Poder de DQN

Algoritmo DQN Paso a Paso

  1. Inicialización: Crea dos redes neuronales idénticas: la red de predicción (Q-Network) y la red objetivo (Target Q-Network). Inicializa un búfer de repetición (replay buffer).
  2. Interacción: El agente interactúa con el entorno, eligiendo acciones mediante una política ε-greedy (exploración vs. explotación).
  3. Almacenamiento: Cada transición (s, a, r, s') se almacena en el búfer de repetición.
  4. Muestreo: Para el entrenamiento, se muestrea un minibatch aleatorio de transiciones del búfer.
  5. Cálculo del Valor Objetivo (Target Q): Se calcula el Q-value objetivo para cada transición en el minibatch usando la red objetivo: Y = r + γ * max_a' Q_target(s', a')
  6. Cálculo del Valor Predicho (Predicted Q): Se calcula el Q-value predicho para la acción tomada en el estado 's' usando la red de predicción: Q_pred = Q_prediction(s, a)
  7. Actualización de Pesos: Se calcula la pérdida (por ejemplo, MSE) entre Y y Q_pred, y se actualizan los pesos de la red de predicción mediante descenso de gradiente.
  8. Actualización de la Red Objetivo: Periódicamente, los pesos de la red objetivo se actualizan copiando los pesos de la red de predicción.

Implementación con TensorFlow 🧠

Vamos a crear una red DQN simple usando tf.keras.

import tensorflow as tf
from tensorflow.keras import layers, models
import numpy as np
import random
from collections import deque

class DQNAgentTF:
    def __init__(self, state_size, action_size, learning_rate=0.001, gamma=0.99, epsilon=1.0, epsilon_min=0.01, epsilon_decay=0.995, replay_buffer_size=2000):
        self.state_size = state_size
        self.action_size = action_size
        self.learning_rate = learning_rate
        self.gamma = gamma
        self.epsilon = epsilon
        self.epsilon_min = epsilon_min
        self.epsilon_decay = epsilon_decay
        self.replay_buffer = deque(maxlen=replay_buffer_size)
        self.model = self._build_model()
        self.target_model = self._build_model()
        self.update_target_model()

    def _build_model(self):
        model = models.Sequential()
        model.add(layers.Dense(24, input_dim=self.state_size, activation='relu'))
        model.add(layers.Dense(24, activation='relu'))
        model.add(layers.Dense(self.action_size, activation='linear'))
        model.compile(loss='mse', optimizer=tf.keras.optimizers.Adam(learning_rate=self.learning_rate))
        return model

    def update_target_model(self):
        self.target_model.set_weights(self.model.get_weights())

    def remember(self, state, action, reward, next_state, done):
        self.replay_buffer.append((state, action, reward, next_state, done))

    def act(self, state):
        if np.random.rand() <= self.epsilon:
            return random.randrange(self.action_size)
        q_values = self.model.predict(state)
        return np.argmax(q_values[0])

    def replay(self, batch_size):
        if len(self.replay_buffer) < batch_size:
            return
        minibatch = random.sample(self.replay_buffer, batch_size)
        
        states = np.array([t[0] for t in minibatch]).reshape(batch_size, self.state_size)
        actions = np.array([t[1] for t in minibatch])
        rewards = np.array([t[2] for t in minibatch])
        next_states = np.array([t[3] for t in minibatch]).reshape(batch_size, self.state_size)
        dones = np.array([t[4] for t in minibatch])

        target = self.model.predict(states)
        target_next = self.target_model.predict(next_states)
        
        for i in range(batch_size):
            if dones[i]:
                target[i][actions[i]] = rewards[i]
            else:
                target[i][actions[i]] = rewards[i] + self.gamma * np.amax(target_next[i])
        
        self.model.fit(states, target, epochs=1, verbose=0)
        if self.epsilon > self.epsilon_min:
            self.epsilon *= self.epsilon_decay

# Ejemplo de uso (simulado, requiere un entorno real como gym)
# state_size = 4  # Por ejemplo, para CartPole
# action_size = 2 # Por ejemplo, para CartPole
# agent = DQNAgentTF(state_size, action_size)
# state = np.random.rand(1, state_size) # Estado inicial simulado
# action = agent.act(state)
# print(f"Acción tomada: {action}")

Implementación con PyTorch 🧠

Ahora veamos una implementación similar con PyTorch.

import torch
import torch.nn as nn
import torch.optim as optim
import random
from collections import deque
import numpy as np

class DQN(nn.Module):
    def __init__(self, input_size, output_size):
        super(DQN, self).__init__()
        self.fc1 = nn.Linear(input_size, 24)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(24, 24)
        self.fc3 = nn.Linear(24, output_size)

    def forward(self, x):
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        return self.fc3(x)

class DQNAgentPT:
    def __init__(self, state_size, action_size, learning_rate=0.001, gamma=0.99, epsilon=1.0, epsilon_min=0.01, epsilon_decay=0.995, replay_buffer_size=2000):
        self.state_size = state_size
        self.action_size = action_size
        self.gamma = gamma
        self.epsilon = epsilon
        self.epsilon_min = epsilon_min
        self.epsilon_decay = epsilon_decay
        self.replay_buffer = deque(maxlen=replay_buffer_size)
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

        self.policy_net = DQN(state_size, action_size).to(self.device)
        self.target_net = DQN(state_size, action_size).to(self.device)
        self.target_net.load_state_dict(self.policy_net.state_dict())
        self.target_net.eval() # Poner en modo evaluación para target_net

        self.optimizer = optim.Adam(self.policy_net.parameters(), lr=learning_rate)
        self.criterion = nn.MSELoss()

    def remember(self, state, action, reward, next_state, done):
        self.replay_buffer.append((state, action, reward, next_state, done))

    def act(self, state):
        if random.random() <= self.epsilon:
            return random.randrange(self.action_size)
        state = torch.from_numpy(state).float().unsqueeze(0).to(self.device)
        with torch.no_grad():
            q_values = self.policy_net(state)
        return torch.argmax(q_values).item()

    def optimize_model(self, batch_size):
        if len(self.replay_buffer) < batch_size:
            return
        minibatch = random.sample(self.replay_buffer, batch_size)

        states = torch.from_numpy(np.vstack([e[0] for e in minibatch])).float().to(self.device)
        actions = torch.from_numpy(np.vstack([e[1] for e in minibatch])).long().to(self.device)
        rewards = torch.from_numpy(np.vstack([e[2] for e in minibatch])).float().to(self.device)
        next_states = torch.from_numpy(np.vstack([e[3] for e in minibatch])).float().to(self.device)
        dones = torch.from_numpy(np.vstack([e[4] for e in minibatch]).astype(np.uint8)).float().to(self.device)

        # Predicted Q-values
        current_q_values = self.policy_net(states).gather(1, actions)

        # Target Q-values
        next_q_values = self.target_net(next_states).max(1)[0].detach().unsqueeze(1)
        target_q_values = rewards + (self.gamma * next_q_values * (1 - dones))

        loss = self.criterion(current_q_values, target_q_values)

        self.optimizer.zero_grad()
        loss.backward()
        for param in self.policy_net.parameters():
            param.grad.data.clamp_(-1, 1) # Clipping de gradientes
        self.optimizer.step()

        if self.epsilon > self.epsilon_min:
            self.epsilon *= self.epsilon_decay

    def update_target_net(self):
        self.target_net.load_state_dict(self.policy_net.state_dict())

# Ejemplo de uso (simulado, requiere un entorno real como gym)
# state_size = 4
# action_size = 2
# agent = DQNAgentPT(state_size, action_size)
# state = np.random.rand(state_size)
# action = agent.act(state)
# print(f"Acción tomada: {action}")
💡 Consejo: Para entornos complejos con observaciones de imágenes (como videojuegos), las redes neuronales convolucionales (CNNs) son una elección excelente para la DQN.

📈 Policy Gradients: Otra Perspectiva en DRL

Mientras que DQN aprende la función de valor para luego derivar una política, los métodos de Policy Gradients aprenden directamente una política que mapea estados a probabilidades de acciones. El objetivo es ajustar los parámetros de la política (generalmente una red neuronal) para que las acciones que llevan a recompensas altas sean más probables.

Ventajas de Policy Gradients ➕

  • Políticas estocásticas: Pueden aprender políticas estocásticas (probabilísticas), lo cual es crucial en entornos donde una acción determinista podría ser subóptima. DQN siempre elige la acción con el Q-value más alto.
  • Espacios de acción continuos: Se adaptan mejor a espacios de acción continuos, ya que la red neuronal puede parametrizar directamente las distribuciones de acciones (ej. la media y desviación estándar de una gaussiana).

El Algoritmo REINFORCE (Monte Carlo Policy Gradient) 🏛️

REINFORCE es uno de los algoritmos de Policy Gradients más fundamentales. Se basa en la idea de que si una secuencia de acciones llevó a una recompensa alta, esas acciones deberían hacerse más probables. Si llevó a una recompensa baja, menos probables.

  1. Inicialización: Inicializa una red neuronal para la política (Policy Network).
  2. Generación de Episodios: El agente interactúa con el entorno, siguiendo la política actual, hasta que el episodio termina. Se registra cada paso (s, a, r).
  3. Cálculo de Recompensas Acumuladas: Para cada paso t en el episodio, calcula la recompensa futura descontada G_t (el retorno).
  4. Actualización de Pesos: Los parámetros de la política se actualizan en la dirección del gradiente de la recompensa esperada. La regla de actualización se basa en la idea de que el gradiente se puede estimar promediando sobre los episodios: ∇J(θ) ≈ Σ_t (G_t * ∇log π_θ(a_t|s_t)) Donde π_θ(a_t|s_t) es la probabilidad de tomar la acción a_t en el estado s_t según la política con parámetros θ.

Implementación REINFORCE con PyTorch 🐍

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

class PolicyNetwork(nn.Module):
    def __init__(self, input_size, output_size):
        super(PolicyNetwork, self).__init__()
        self.fc1 = nn.Linear(input_size, 128)
        self.fc2 = nn.Linear(128, output_size)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        return F.softmax(self.fc2(x), dim=1) # Salida de probabilidades de acciones

class REINFORCEAgentPT:
    def __init__(self, state_size, action_size, learning_rate=0.01, gamma=0.99):
        self.gamma = gamma
        self.policy_net = PolicyNetwork(state_size, action_size)
        self.optimizer = optim.Adam(self.policy_net.parameters(), lr=learning_rate)
        self.log_probs = [] # Almacenar log probabilidades de acciones tomadas
        self.rewards = []   # Almacenar recompensas del episodio

    def act(self, state):
        state = torch.from_numpy(state).float().unsqueeze(0)
        probs = self.policy_net(state)
        m = torch.distributions.Categorical(probs) # Distribución categórica para muestrear acciones
        action = m.sample()
        self.log_probs.append(m.log_prob(action)) # Guardar log-probabilidad para backprop
        return action.item()

    def store_reward(self, reward):
        self.rewards.append(reward)

    def update_policy(self):
        G = 0 # Recompensa acumulada descontada
        returns = []
        
        # Calcular recompensas acumuladas descontadas al revés
        for r in reversed(self.rewards):
            G = r + self.gamma * G
            returns.insert(0, G)
        
        returns = torch.tensor(returns)
        # Normalización de retornos (opcional pero ayuda a la estabilidad)
        returns = (returns - returns.mean()) / (returns.std() + 1e-9)

        policy_loss = []
        for log_prob, G_t in zip(self.log_probs, returns):
            policy_loss.append(-log_prob * G_t) # Multiplicar por -1 para gradiente ascendente

        self.optimizer.zero_grad()
        loss = torch.cat(policy_loss).sum()
        loss.backward()
        self.optimizer.step()

        self.log_probs = [] # Limpiar para el siguiente episodio
        self.rewards = []

# Ejemplo de uso (simulado, requiere un entorno real como gym)
# state_size = 4
# action_size = 2
# agent = REINFORCEAgentPT(state_size, action_size)

# # Simular un episodio
# state = np.random.rand(state_size)
# done = False
# episode_reward = 0
# while not done:
#     action = agent.act(state)
#     next_state = np.random.rand(state_size) # Simular siguiente estado
#     reward = 1 if action == 0 else -1 # Simular recompensa
#     done = np.random.rand() > 0.95 # Simular fin de episodio
#     agent.store_reward(reward)
#     state = next_state
#     episode_reward += reward

# print(f"Recompensa del episodio simulado: {episode_reward}")
# agent.update_policy()
Ventajas y Desventajas de Policy Gradients

Ventajas:

  • Puede aprender políticas estocásticas.
  • Adecuado para espacios de acción continuos.
  • Converge a un óptimo local (o global).

Desventajas:

  • Alta varianza en los gradientes (los retornos pueden ser muy ruidosos).
  • Puede requerir muchos episodios para converger.
  • Sensible a la inicialización y parámetros.

🎮 Aplicaciones Prácticas y Entornos de Entrenamiento

El DRL brilla en una multitud de aplicaciones. Aquí hay algunas notables:

  • Juegos: Desde Atari y Go hasta StarCraft y Dota 2, los agentes de DRL han superado a los humanos. Plataformas como OpenAI Gym proporcionan entornos estandarizados para probar algoritmos.
  • Robótica: Control de manipuladores, robots móviles, tareas de agarre y manipulación.
  • Finanzas: Optimización de estrategias de trading, gestión de carteras.
  • Sistemas de Recomendación: Personalización de contenido y productos.
  • Control Industrial: Optimización de procesos, gestión de energía.

OpenAI Gym: Tu Patio de Juegos de RL 🕹️

gym es una toolkit de OpenAI que proporciona una amplia gama de entornos de RL para desarrollar y comparar algoritmos. Es la forma más común de empezar a aplicar tus agentes DRL.

💡 Consejo: Empieza con entornos simples como `CartPole-v1` o `LunarLander-v2` para familiarizarte con la interacción agente-entorno antes de pasar a problemas más complejos.
| Entorno Común | Descripción | Tipo de Espacio de Acción | Dificultad | | :------------------- | :----------------------------------------------- | :------------------------ | :------------------ | | --- | --- | --- | --- | | `CartPole-v1` | Equilibra un poste en un carro que se mueve | Discreto (2 acciones) | Fácil | | `LunarLander-v2` | Aterriza un módulo lunar de forma segura | Discreto (4 acciones) | Intermedio | | --- | --- | --- | --- | | `Acrobot-v1` | Mueve un robot de dos brazos a una posición objetivo | Discreto (3 acciones) | Intermedio | | `MountainCar-v0` | Sube una montaña con un coche poco potente | Discreto (3 acciones) | Intermedio | | --- | --- | --- | --- | | `BipedalWalker-v3` | Agente bípedo que camina por un terreno irregular | Continuo | Avanzado |

🔮 Más Allá de DQN y REINFORCE: Otros Algoritmos Clave

El campo del DRL es vasto y en constante evolución. DQN y REINFORCE son puntos de partida excelentes, pero existen algoritmos más avanzados que abordan sus limitaciones.

Actor-Critic Methods 🎭

Combinan lo mejor de ambos mundos: un Actor (que es la Policy Network) y un Crítico (que es la Value Network). El crítico evalúa las acciones del actor, guiando al actor para mejorar su política. Esto reduce la varianza de los Policy Gradients.

Ejemplos: A2C (Advantage Actor-Critic), A3C (Asynchronous Advantage Actor-Critic), DDPG (Deep Deterministic Policy Gradient), SAC (Soft Actor-Critic).

Proximal Policy Optimization (PPO) 🛡️

PPO es uno de los algoritmos de Policy Gradients más populares y robustos. Introduce un clip en la función de pérdida para evitar actualizaciones de política demasiado grandes, lo que mejora la estabilidad y el rendimiento. Es un algoritmo de referencia para muchos problemas de RL.

Prioritized Experience Replay (PER) ✨

Una mejora para DQN y otros algoritmos basados en replay buffer. En lugar de muestrear transiciones uniformemente, PER da prioridad a las transiciones donde el agente tuvo un error de aprendizaje grande (grandes errores TD). Esto acelera el aprendizaje al centrarse en lo que es más informativo.

Multi-Agent Reinforcement Learning (MARL) 🤝

Cuando múltiples agentes interactúan en el mismo entorno, surge el MARL. Aquí, los agentes deben considerar no solo el entorno, sino también el comportamiento de otros agentes, lo que introduce complejidades adicionales como la coordinación y la competencia.

Agente Actor (Red de Política) Crítico (Red de Valor) Entorno Acción Recompensa Estado (s) Evaluación (TD Error) Actualización ↺ Actualización ↺
⚠️ Advertencia: Implementar algoritmos DRL complejos desde cero puede ser un desafío. Considera usar librerías como `Stable-Baselines3` (para PyTorch) o `RLlib` (para TensorFlow y PyTorch) que proporcionan implementaciones robustas y optimizadas.

📝 Consejos para el Éxito en DRL

Entrenar agentes de DRL puede ser un arte. Aquí tienes algunos consejos prácticos:

  • Normalización de Estados y Recompensas: Escala tus observaciones y recompensas. Esto ayuda a la estabilidad del entrenamiento de la red neuronal.
  • Ajuste de Hiperparámetros: Los algoritmos DRL son muy sensibles a los hiperparámetros (learning rate, factor de descuento, tamaño del batch, etc.). Usa herramientas como Ray Tune o Optuna para la optimización.
  • Tamaño del Replay Buffer: Un búfer grande ayuda a decorrelacionar las muestras y a estabilizar el entrenamiento.
  • Actualización de la Red Objetivo: Actualizar la red objetivo con poca frecuencia (ej. cada N pasos) es crucial para la estabilidad en DQN.
  • Clipping de Gradientes: Limitar la magnitud de los gradientes previene explosiones de gradientes y ayuda a la estabilidad del entrenamiento.
  • Arquitectura de Red: Empieza con arquitecturas simples y aumenta la complejidad si es necesario. Para entradas de imágenes, las CNNs son esenciales.
  • Monitoreo: Usa TensorBoard o Weights & Biases para monitorear métricas clave como la recompensa por episodio, la pérdida de la red, y el valor epsilon.
  • Entornos de Prueba: Prueba tu agente en entornos simples primero. Asegúrate de que funciona bien antes de pasar a problemas más desafiantes.
Paso 1: Entender el Entorno: Familiarízate con los estados, acciones y recompensas.
Paso 2: Elegir un Algoritmo Base: DQN para entornos discretos, Policy Gradients/Actor-Critic para continuos.
Paso 3: Implementación Inicial: Escribe el código básico del agente y la red.
Paso 4: Debugging y Monitoreo: Asegúrate de que las pérdidas disminuyen y las recompensas aumentan.
Paso 5: Ajuste de Hiperparámetros: Optimiza para un mejor rendimiento y estabilidad.
Paso 6: Experimentación Avanzada: Prueba con mejoras (PER, doble DQN, etc.).

Conclusión ✨

El Aprendizaje por Refuerzo Profundo es un campo apasionante y con un potencial enorme. Hemos cubierto los fundamentos del RL, explorado las implementaciones de DQN en TensorFlow y PyTorch, y visto el funcionamiento de REINFORCE. Además, discutimos algoritmos más avanzados y ofrecimos consejos prácticos para tus propias implementaciones.

Al dominar estas técnicas, estarás bien equipado para abordar una amplia gama de problemas donde la toma de decisiones secuenciales bajo incertidumbre es clave. La clave es la práctica: ¡experimenta con diferentes entornos y algoritmos!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!

Reinforcement Learning con TensorFlow y PyTorch: Aprendizaje por Refuerzo Profundo para Juegos y Control | tutoriales.com