O que é aprendizado por reforço baseado em valor
O aprendizado por reforço ensina um agente a tomar decisões. O agente recebe recompensas por ações corretas no ambiente. Primeiramente, ele aprende uma função chamada valor Q. Esta função estima a recompensa futura para cada ação. O problema baseado em valor busca maximizar essas recompensas. Por exemplo, um robô aprende a andar sem quedas. O agente não precisa de exemplos prontos para aprender. Ele descobre as melhores ações por tentativa e erro. Assim, o aprendizado ocorre através da interação direta. Esta abordagem é fundamental para inteligência artificial moderna.
Uma tabela simples guardaria os valores Q para cada estado. Contudo, essa tabela fica enorme em problemas complexos. Por exemplo, um jogo de video game tem milhões de estados. A tabela não conseguiria armazenar todos esses valores. É aí que entra a aproximação de funções. Uma rede neural é usada para estimar os valores Q. A rede neural generaliza entre estados semelhantes. Consequentemente, o agente aprende mesmo com muitos estados. Esta técnica permite resolver problemas do mundo real. Ela é chamada de Deep Q-Network (DQN).
Arquitetura do modelo DQN com experience replay
A DQN utiliza uma rede neural profunda como aproximador. A entrada da rede representa o estado atual do ambiente. A saída fornece os valores Q para todas as ações possíveis. Primeiramente, a rede possui camadas totalmente conectadas. Entre as camadas, funções de ativação ReLU são aplicadas. A camada final não possui ativação para valores livres. A arquitetura clássica contém três camadas escondidas. Cada camada escondida possui 128 neurônios tipicamente. O tamanho da entrada varia conforme o problema resolvido. Por fim, o tamanho da saída iguala-se ao número de ações.
A Experience Replay guarda as experiências do agente. Cada experiência é armazenada como uma tupla (s, a, r, s’). O buffer de replay contém as últimas N experiências. Durante o treinamento, um lote aleatório é amostrado. Este processo quebra a correlação entre experiências sequenciais. Primeiramente, o aprendizado torna-se mais estável. Em segundo lugar, as experiências podem ser reutilizadas. Por conseguinte, a amostragem aleatória melhora a eficiência. O buffer típico armazena entre 10.000 e 1.000.000 transições. Este mecanismo foi proposto por Mnih et al. (2015).
Hiperparâmetros e fórmulas matemáticas
A taxa de aprendizado (alpha) controla a atualização da rede. Valores típicos variam entre 0.0001 e 0.001. O fator de desconto (gamma) prioriza recompensas imediatas. Geralmente, gamma entre 0.9 e 0.99 é utilizado. O epsilon para exploração decai ao longo do treinamento. Começamos com epsilon=1.0 e terminamos com 0.01. O tamanho do lote (batch size) afeta a estabilidade. Valores comuns são 32, 64 ou 128 transições. A frequência de atualização da rede alvo é crucial. Atualizamos a cada C passos (geralmente C=1000).
A fórmula central do DQN é a equação de Bellman: \(Q(s,a) = r + \gamma \max_{a’} Q(s’, a’)\). O erro quadrático médio (MSE) é usado como função de perda: \(L = \mathbb{E}\left[\left(r + \gamma \max_{a’} Q_{target}(s’,a’) – Q(s,a)\right)^2\right]\). A rede alvo (Q_target) é uma cópia congelada da rede principal. Esta arquitetura reduz a correlação entre predição e alvo. O gradiente descendente atualiza os pesos da rede principal. Após cada C passos, os pesos são copiados para a rede alvo. O algoritmo completo converge para políticas ótimas. Vários jogos da Atari foram vencidos com esta técnica.
Enunciado do exemplo clássico: o ambiente cartpole
Você deve equilibrar um poste sobre um carrinho móvel. O ambiente se chama CartPole-v1 no biblioteca Gymnasium. O carrinho pode mover-se para esquerda ou direita. O objetivo é manter o poste na posição vertical. Cada passo sem queda gera uma recompensa de +1. O episódio termina quando o poste cai mais de 15 graus. Ou quando o carrinho sai dos limites da pista. Seu agente DQN deve aprender a política ótima. A rede neural aproximará os valores Q para cada ação. Experience Replay deve ser usado para estabilizar o treinamento.
Primeiramente, implemente o buffer de replay circular. Em segundo lugar, crie uma rede neural com 3 camadas. A entrada terá 4 neurônios (observações do ambiente). A saída terá 2 neurônios (esquerda ou direita). Treine o agente por 500 episódios no Google Colab. Ao final, exiba um gráfico das recompensas acumuladas. Mostre também o desempenho antes e depois do treinamento. O código abaixo resolve completamente este enunciado. Ele roda sem necessidade de ajustes adicionais. A política treinada equilibra o poste por centenas de passos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# -*- coding: utf-8 -*- """DQN_CartPole_PyTorch_FIXED.ipynb""" import numpy as np import matplotlib.pyplot as plt from collections import deque import random import gymnasium as gym # Instalar PyTorch no Colab !pip install torch -q import torch import torch.nn as nn import torch.optim as optim print("✅ PyTorch instalado com sucesso!") print(f"PyTorch versão: {torch.__version__}") # Verificar GPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Dispositivo: {device}") # Criar ambiente env = gym.make('CartPole-v1') # Hiperparâmetros GAMMA = 0.99 LEARNING_RATE = 0.001 EPSILON = 1.0 EPSILON_MIN = 0.01 EPSILON_DECAY = 0.995 BATCH_SIZE = 64 BUFFER_SIZE = 10000 TARGET_UPDATE = 10 EPISODES = 500 # Aumentei para 500 para melhor aprendizado # Buffer de replay class ReplayBuffer: def __init__(self, capacity): self.buffer = deque(maxlen=capacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch = random.sample(self.buffer, batch_size) states, actions, rewards, next_states, dones = zip(*batch) return (np.array(states), np.array(actions), np.array(rewards), np.array(next_states), np.array(dones)) def __len__(self): return len(self.buffer) # Rede Neural DQN em PyTorch class DQN(nn.Module): def __init__(self, state_size, action_size): super(DQN, self).__init__() self.fc1 = nn.Linear(state_size, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, 128) self.fc4 = nn.Linear(128, action_size) self.relu = nn.ReLU() def forward(self, x): x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.relu(self.fc3(x)) x = self.fc4(x) return x # Função para criar modelo def create_dqn_model(state_size, action_size): model = DQN(state_size, action_size).to(device) optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE) return model, optimizer # Parâmetros do ambiente state_size = env.observation_space.shape[0] action_size = env.action_space.n print(f"\n📊 Informações do ambiente:") print(f" - Estado: {state_size} dimensões") print(f" - Ações: {action_size} (0=esquerda, 1=direita)") print(f" - Objetivo: Equilibrar o poste por 500 passos") # Inicializar redes print("\n🔧 Criando redes neurais...") model, optimizer = create_dqn_model(state_size, action_size) target_model, _ = create_dqn_model(state_size, action_size) target_model.load_state_dict(model.state_dict()) print("✅ Redes criadas com sucesso!") # Inicializar buffer memory = ReplayBuffer(BUFFER_SIZE) # Métricas rewards_per_episode = [] epsilon_history = [] print("\n" + "="*60) print("🎯 INICIANDO TREINAMENTO DO DQN") print("="*60 + "\n") # Função de treinamento def train_model(states, actions, rewards, next_states, dones): # Converter para tensores PyTorch states = torch.FloatTensor(states).to(device) actions = torch.LongTensor(actions).to(device) rewards = torch.FloatTensor(rewards).to(device) next_states = torch.FloatTensor(next_states).to(device) dones = torch.FloatTensor(dones).to(device) # Q-values atuais current_q = model(states).gather(1, actions.unsqueeze(1)) # Q-values alvo with torch.no_grad(): next_q = target_model(next_states).max(1)[0] target_q = rewards + (GAMMA * next_q * (1 - dones)) # Calcular loss loss = nn.MSELoss()(current_q.squeeze(), target_q) # Backpropagation optimizer.zero_grad() loss.backward() optimizer.step() return loss.item() # Treinamento for episode in range(EPISODES): state, _ = env.reset() total_reward = 0 episode_losses = [] while True: # Escolher ação (epsilon-greedy) if np.random.random() < EPSILON: action = env.action_space.sample() # Explorar else: with torch.no_grad(): state_tensor = torch.FloatTensor(state).unsqueeze(0).to(device) q_values = model(state_tensor) action = q_values.argmax().item() # Executar ação next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated # Guardar experiência memory.push(state, action, reward, next_state, done) # Atualizar estado state = next_state total_reward += reward # Treinar com Experience Replay if len(memory) > BATCH_SIZE: states, actions, rewards, next_states, dones = memory.sample(BATCH_SIZE) loss = train_model(states, actions, rewards, next_states, dones) episode_losses.append(loss) if done: break # Decair epsilon EPSILON = max(EPSILON_MIN, EPSILON * EPSILON_DECAY) # Atualizar rede alvo if episode % TARGET_UPDATE == 0: target_model.load_state_dict(model.state_dict()) # Salvar métricas rewards_per_episode.append(total_reward) epsilon_history.append(EPSILON) # Mostrar progresso if (episode + 1) % 50 == 0: avg_reward = np.mean(rewards_per_episode[-50:]) avg_loss = np.mean(episode_losses) if episode_losses else 0 print(f"📈 Episódio {episode + 1:3d}/{EPISODES} | " f"Recompensa média: {avg_reward:6.1f} | " f"Exploração (ε): {EPSILON:.3f} | " f"Memória: {len(memory)}/{BUFFER_SIZE}") # Avaliação do agente treinado print("\n" + "="*60) print("🏆 AVALIANDO O AGENTE TREINADO") print("="*60) test_episodes = 10 test_rewards = [] # Modo de avaliação (sem exploração) model.eval() for episode in range(test_episodes): state, _ = env.reset() total_reward = 0 while True: with torch.no_grad(): state_tensor = torch.FloatTensor(state).unsqueeze(0).to(device) q_values = model(state_tensor) action = q_values.argmax().item() next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated state = next_state total_reward += reward if done: break test_rewards.append(total_reward) # CONVERTENDO PARA INTEIRO PARA EVITAR ERRO DE FORMATAÇÃO status = "✅" if total_reward >= 475 else "⚠️" print(f"{status} Teste {episode + 1:2d}: {int(total_reward):3d} passos equilibrando o poste") # Resultados estatísticos print("\n" + "="*60) print("📊 RESULTADOS FINAIS") print("="*60) print(f"🎯 Melhor desempenho no treino: {int(max(rewards_per_episode))} passos") print(f"📈 Média nos testes: {np.mean(test_rewards):.1f} ± {np.std(test_rewards):.1f} passos") print(f"⭐ Maior pontuação nos testes: {int(max(test_rewards))} passos") if np.mean(test_rewards) >= 475: print("\n🎉 SUCESSO! O agente aprendeu a equilibrar o poste!") print("✅ O algoritmo DQN com Experience Replay funcionou perfeitamente!") print("✅ O agente consegue manter o poste equilibrado por centenas de passos!") else: print(f"\n⚠️ Média de {np.mean(test_rewards):.1f} passos") print("💡 Para melhorar, tente:") print(" - Aumentar EPISODES para 1000") print(" - Ajustar a taxa de aprendizado (LEARNING_RATE)") print(" - Modificar a arquitetura da rede neural") # Gráficos fig, axes = plt.subplots(1, 3, figsize=(15, 5)) # Gráfico 1: Evolução do aprendizado axes[0].plot(rewards_per_episode, alpha=0.5, color='blue', linewidth=0.5, label='Por episódio') if len(rewards_per_episode) >= 50: moving_avg = np.convolve(rewards_per_episode, np.ones(50)/50, mode='valid') axes[0].plot(range(49, len(rewards_per_episode)), moving_avg, 'r-', linewidth=2, label='Média móvel (50 episódios)') axes[0].set_xlabel('Episódio', fontsize=12) axes[0].set_ylabel('Passos (recompensa)', fontsize=12) axes[0].set_title('🎯 Evolução do Aprendizado DQN', fontsize=14) axes[0].legend() axes[0].grid(True, alpha=0.3) axes[0].axhline(y=500, color='green', linestyle='--', alpha=0.5, label='Meta (500 passos)') axes[0].set_ylim([0, 520]) # Gráfico 2: Decaimento da exploração axes[1].plot(epsilon_history, 'g-', linewidth=2) axes[1].set_xlabel('Episódio', fontsize=12) axes[1].set_ylabel('Valor de ε (epsilon)', fontsize=12) axes[1].set_title('🔄 Decaimento da Exploração', fontsize=14) axes[1].grid(True, alpha=0.3) axes[1].fill_between(range(len(epsilon_history)), 0, epsilon_history, alpha=0.3, color='green') axes[1].set_ylim([0, 1.1]) # Gráfico 3: Desempenho final colors = ['green' if r >= 475 else 'orange' for r in test_rewards] bars = axes[2].bar(range(1, test_episodes + 1), test_rewards, color=colors, edgecolor='black') axes[2].axhline(y=np.mean(test_rewards), color='red', linestyle='--', linewidth=2, label=f'Média: {np.mean(test_rewards):.1f}') axes[2].axhline(y=475, color='blue', linestyle=':', linewidth=1.5, label='Sucesso (475 passos)') axes[2].set_xlabel('Episódio de teste', fontsize=12) axes[2].set_ylabel('Passos', fontsize=12) axes[2].set_title('🏅 Desempenho Final do Agente', fontsize=14) axes[2].legend() axes[2].grid(True, alpha=0.3) axes[2].set_ylim([0, 520]) # Adicionar valores nas barras for bar, valor in zip(bars, test_rewards): axes[2].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5, f'{int(valor)}', ha='center', va='bottom', fontsize=9) plt.tight_layout() plt.show() # Análise do aprendizado print("\n" + "="*60) print("📈 ANÁLISE DO APRENDIZADO") print("="*60) # Verificar tendência de aprendizado initial_avg = np.mean(rewards_per_episode[:50]) final_avg = np.mean(rewards_per_episode[-50:]) improvement = final_avg - initial_avg print(f"📊 Média inicial (primeiros 50 episódios): {initial_avg:.1f} passos") print(f"📊 Média final (últimos 50 episódios): {final_avg:.1f} passos") print(f"📈 Melhoria: {improvement:.1f} passos") if improvement > 50: print("✅ O agente aprendeu significativamente!") elif improvement > 20: print("⚠️ O agente aprendeu moderadamente") else: print("❌ O agente aprendeu pouco. Aumente os episódios para 1000") # Explicação final print("\n" + "="*60) print("📚 RESUMO DO QUE APRENDEMOS:") print("="*60) print("✅ DQN (Deep Q-Network): Rede neural que aproxima valores Q") print("✅ Experience Replay: Buffer que armazena e reutiliza experiências") print("✅ Target Network: Rede separada para estabilizar o treinamento") print("✅ Epsilon-greedy: Equilíbrio entre exploração e explotação") print("\n🚀 O agente aprendeu sem receber nenhum exemplo prévio!") print(" Ele descobriu sozinho a estratégia para equilibrar o poste!") env.close() print("\n✨ Fim do treinamento!") # Sugestão para melhorar if np.mean(test_rewards) < 475: print("\n" + "="*60) print("💡 DICA PARA MELHORAR O RESULTADO:") print("="*60) print("Execute o código novamente com EPISODES = 1000") print("Ou aumente a rede neural para 256 neurônios por camada") print("Isso permitirá que o agente aprenda melhor a estratégia!") |
O código implementa um agente DQN completo com Experience Replay. A rede neural aproxima os valores Q para cada ação possível. O buffer replay armazena as últimas 10.000 experiências. Primeiramente, o agente explora o ambiente aleatoriamente. Com o tempo, ele reduz a exploração e explora o conhecimento. As recompensas acumuladas aumentam significativamente. O poste permanece equilibrado por centenas de passos. Esta solução foi amplamente validada na literatura. O agente aprende sem nenhum conhecimento prévio do ambiente. Parabéns! Você implementou seu primeiro DQN funcional.