Aprendizaje por refuerzo#
El aprendizaje por refuerzo (en inglés Reinforcement Learning — RL) es un tipo de aprendizaje automático en el que un agente aprende a tomar decisiones a través de la interacción con un ambiente. El agente recibe recompensas o penalidades con base en sus acciones y aprende a maximizar la recompenas acumulada a lo largo del tiempo.
Los componentes clave del RL son:
Agente: aprendiz o tomador de decisiones.
Ambiente: todo con lo que agente puede interactuar.
Acción: las elecciones que el agente puede hacer.
Estado: la situación actual del agente.
Recompensa: la realimentación que recibe el agente por parte del ambiente.
Proceso de Decisión de Markov#
Una de las formas clásicas de construir un algoritmo de RL es modelar el proceso de decisión y los elementos enumerados antes, usando un proceso de decisión markoviano (en inglés Markov Decision Process — MDP), que se define como:
\(S\): conjunto finito de estados
\(A\): conjunto finito de acciones
\(P(s' \mid s, a)\): probabilidad de transición de moverse al estado \(s'\) a partir del estado \(s\) debido a la acción \(a\).
\(R(s, a)\): recompensa recibida después de tomar la acción \(a\) en el estado \(s\).
\(\gamma\in[0,1]\): factor de descuento que representa la importancia de recompensas futuras.
El objetivo es encontrar una política \(\pi: S \rightarrow A\) que maximice la recompensa acumulada esperada: $\( V^\pi(s) = \mathbb{E}\left[\sum_{t=0}^\infty \gamma^t R(s_t, a_t) \mid s_0 = s, a_t = \pi(s_t) \right] \)$
Q-learning#
Tabla Q es simplemente un nombre elegante para una tabla de consulta simple donde calculamos las recompensas futuras máximas esperadas por la acción en cada estado. Básicamente, esta tabla nos guiará hacia la mejor acción en cada estado.
#Example: A Simple Environment with Q-Learning
import numpy as np
import random
# Define environment
states = [0, 1, 2, 3] # simple environment with 4 states
actions = [0, 1] # 0 = left, 1 = right
# Transition and rewards matrix (deterministic)
rewards = np.array([
[-1, 0], # From state 0: left -> -1, right -> 0
[-1, 0], # From state 1
[-1, 10], # From state 2: right -> terminal state with reward 10
[0, 0] # Terminal state
])
# Initialize Q-table
Q = np.zeros((len(states), len(actions)))
def choose_action(state, epsilon):
if random.uniform(0, 1) < epsilon:
return random.choice(actions) # Explore
return np.argmax(Q[state]) # Exploit
def update_q_table(state, action, reward, next_state, alpha, gamma):
best_next_action = np.max(Q[next_state])
Q[state, action] += alpha * (reward + gamma * best_next_action - Q[state, action])
Actualización de la tabla Q a partir de las recompensas obtenidas:
Definiendo \(\beta_s = \max_a Q(s,:)\), como la máxima recompensa esperada dado un estado \(s\), la tabla Q se actualiza a partir de la regla:
donde \(s'\) es el estado al que se llega cuando se toma la acción \(a\) en el estado \(s\).
# Training loop
num_episodes = 100
alpha = 0.1 # Learning rate
gamma = 0.9 # Discount factor
epsilon = 0.2 # Exploration rate
for episode in range(num_episodes):
state = 0
while state != 3:
action = choose_action(state, epsilon)
reward = rewards[state, action]
next_state = state + 1 if action == 1 else max(0, state - 1)
update_q_table(state, action, reward, next_state, alpha, gamma)
state = next_state
print("Trained Q-table:")
print(Q)
Trained Q-table:
[[1.87490677 8.09171078]
[2.76375056 8.99866509]
[5.56084835 9.99973439]
[0. 0. ]]
def extract_policy(Q):
return [np.argmax(Q[state]) for state in range(len(Q))]
policy = extract_policy(Q)
print("Extracted Policy (0=left, 1=right):", policy)
Extracted Policy (0=left, 1=right): [np.int64(1), np.int64(1), np.int64(1), np.int64(0)]
Deep Q-learning#
Q-learning con una tabla no es viable para espacios de estado grandes o continuos. Deep Q-Learning (DQN) usa una red neuronal para aproximar una Q-function que predice la recomensa esperada.
Los principales componentes de una DQN son:
Una red neuronal artificial que toma un estado como entrada y retorna los valores Q para cada acción.
Repetición de la experiencia: un espacio de memoria para almacenar experiencias pasadas \((s, a, r, s')\).
Red objetivo: una red separada usada para calcular valores Q objetivo actualizada periodicamente.
La función de costo usada para entrenar la DQN es: $\( L(\theta) = \mathbb{E}_{(s,a,r,s')\sim D}\left[\left(r + \gamma \max_{a'} Q_{\text{target}}(s', a'; \theta^-) - Q(s,a; \theta)\right)^2\right] \)$
donde:
\(r = R(s,a)\), este valor lo provee el ambiente.
\(\theta\) son los parámetros de la red Q.
\(\theta^-\) son los parámetros de la red objetivo.
\(D\) es la memoria que almacena las experiencias previas.
Durante el entrenamiento, la función de costo se calcula usando:
en lugar de:
Luego, cada \(n\) pasos o episodios, se copian los pesos de la red de políticas a la red objetivo. Se puede pensar en la red objetivo, como un punto de referencia que se mueve más lentamente y permite a la red principal perseguir una señal de entrenamiento más consistente en lugar de perseguir sus propias y rápidamente cambiantes predicciones.
## Basic Python Example of Deep Q-Learning
import torch
import torch.nn as nn
import torch.optim as optim
import random
from collections import deque
# Simple environment
state_size = 4
action_size = 2
# Define neural network
class DQN(nn.Module):
def __init__(self):
super(DQN, self).__init__()
self.fc = nn.Sequential(
nn.Linear(state_size, 24),
nn.ReLU(),
nn.Linear(24, 24),
nn.ReLU(),
nn.Linear(24, action_size)
)
def forward(self, x):
return self.fc(x)
# Initialize DQN and optimizer
policy_net = DQN()
target_net = DQN()
target_net.load_state_dict(policy_net.state_dict())
target_net.eval()
optimizer = optim.Adam(policy_net.parameters(), lr=0.001)
criterion = nn.MSELoss()
# Experience replay buffer
memory = deque(maxlen=1000)
def remember(state, action, reward, next_state, done):
memory.append((state, action, reward, next_state, done))
def act(state, epsilon):
if random.random() < epsilon:
return random.randrange(action_size)
state = torch.FloatTensor(state).unsqueeze(0)
with torch.no_grad():
return policy_net(state).argmax().item()
def replay(batch_size, gamma):
if len(memory) < batch_size:
return
batch = random.sample(memory, batch_size)
states, actions, rewards, next_states, dones = zip(*batch)
states = torch.FloatTensor(np.array(states))
actions = torch.LongTensor(actions).unsqueeze(1)
rewards = torch.FloatTensor(rewards)
next_states = torch.FloatTensor(next_states)
dones = torch.BoolTensor(dones)
q_values = policy_net(states).gather(1, actions).squeeze()
next_q_values = target_net(next_states).max(1)[0]
expected_q_values = rewards + (gamma * next_q_values * (~dones))
loss = criterion(q_values, expected_q_values.detach())
optimizer.zero_grad()
loss.backward()
optimizer.step()
def update_target_network():
target_net.load_state_dict(policy_net.state_dict())
# Example training loop (hypothetical)
for episode in range(10):
state = np.random.rand(state_size) # Random state for example
for t in range(10):
action = act(state, epsilon=0.1)
next_state = np.random.rand(state_size)
#Real environment must be integrated here
reward = np.random.rand()
done = t == 9
remember(state, action, reward, next_state, done)
state = next_state
replay(batch_size=8, gamma=0.95)
#Update the target network weights periodically by copying weights from the policy network:
if episode % 5 == 0:
update_target_network()
Deep Q-Learning es fundamental para los sistemas de RL modernos y es lo que ha permitido aplicar RL a problemas de alta dimensión como jugar juegos de Atari o el control robótico. El entrenamiento de un modelo de RL, requiere la interacción con un ambiente, por lo que existen algunos desarrollos que permiten acceder a problemas y ambientes típicos. Se recomienda al lector interesado revisar la documentación de Gymnasium.