O que é aprendizado por reforço baseado em política
O aprendizado por reforço treina agentes através de tentativas. Diferentemente de métodos baseados em valor, a política é aprendida diretamente. Primeiramente, a política é uma função que mapeia estados para ações. O agente não precisa estimar valores para cada ação possível. Em vez disso, ele aprende a probabilidade de executar cada ação. Este tipo de algoritmo é chamado de método baseado em política. Por exemplo, um robô pode aprender a andar sem um mapa de valores. A política é representada por uma rede neural profunda. Os parâmetros da rede são ajustados durante o treinamento. Assim, o agente melhora sua estratégia gradualmente.
Uma vantagem importante é lidar com ações contínuas. Os métodos baseados em política são mais estáveis que os baseados em valor. Eles convergem para políticas ótimas mesmo com aproximação de funções. Contudo, esses métodos podem ter alta variância nos gradientes. Para resolver isso, usa-se a linha de base (baseline). A arquitetura REINFORCE é o algoritmo mais simples desta classe. Ela foi proposta por Williams em 1992. O método usa amostragem Monte Carlo para estimar retornos. Cada episódio completo é usado para atualizar a política. Este é um algoritmo do tipo “episódio completo”.
Arquitetura do modelo reinforce (monte carlo policy gradient)
A arquitetura REINFORCE possui uma rede neural como aproximador. A entrada da rede representa o estado atual do ambiente. A saída fornece probabilidades para cada ação possível. Primeiramente, usa-se uma camada softmax na saída. Esta camada transforma os valores em probabilidades. Entre as camadas, funções de ativação ReLU são aplicadas. A arquitetura clássica contém duas ou três camadas escondidas. Cada camada escondida possui 64 ou 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.
O algoritmo REINFORCE funciona de forma iterativa. Primeiro, um episódio completo é executado com a política atual. Todas as transições (estado, ação, recompensa) são armazenadas. Depois, o retorno descontado é calculado para cada passo. A fórmula do retorno é: \(G_t = \sum_{k=0}^{T-t-1} \gamma^k R_{t+k+1}\). Em seguida, o gradiente da política é estimado. A função objetivo é maximizar a recompensa esperada. O gradiente é calculado usando a regra da cadeia. A atualização dos parâmetros segue a direção do gradiente. Este processo é repetido até a convergência da política.
Hiperparâmetros e fórmulas matemáticas
A taxa de aprendizado (alpha) controla as atualizações 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 número máximo de passos por episódio é um hiperparâmetro. A arquitetura da rede (número de neurônios) também é importante. O tamanho do lote (batch size) afeta a estabilidade. Para REINFORCE, um episódio inteiro é um lote. A inicialização dos pesos da rede é crucial. Usa-se inicialização uniforme ou normal pequena.
A função de perda para REINFORCE é o log-probabilidade ponderada: \(L(\theta) = -\sum_{t=0}^{T-1} \log \pi_\theta(a_t|s_t) G_t\). O gradiente da política é dado por: \(\nabla_\theta J(\theta) = \mathbb{E}\left[\sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) G_t\right]\). A atualização dos parâmetros segue a regra: \(\theta \leftarrow \theta + \alpha \nabla_\theta J(\theta)\). Uma variante inclui uma linha de base para reduzir variância: \(\nabla_\theta J(\theta) = \mathbb{E}\left[\sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) (G_t – b)\right]\). A linha de base b geralmente é a média dos retornos. Esta técnica melhora significativamente a convergência. O algoritmo REINFORCE é simples, mas poderoso. Ele resolve problemas onde ações contínuas são necessárias. Muitos jogos e robóticas usam esta abordagem.
Enunciado do exemplo clássico: o ambiente cartpole
Você deve equilibrar um poste sobre um carrinho móvel. O ambiente se chama CartPole-v1 na 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 REINFORCE deve aprender a política ótima. Uma rede neural representará a política diretamente. A saída da rede será uma distribuição de probabilidade sobre ações.
Primeiramente, implemente a rede neural com duas camadas. A entrada terá 4 neurônios (observações do ambiente). A saída terá 2 neurônios com ativação softmax. Em segundo lugar, execute episódios completos para coleta de dados. Depois, calcule os retornos descontados para cada passo. Normalize os retornos para reduzir a variância. Treine o agente por 1000 episódios no Google Colab. Ao final, exiba um gráfico das recompensas acumuladas. Mostre também a política aprendida pelo agente. O código abaixo resolve completamente este enunciado.
|
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 |
# -*- coding: utf-8 -*- """REINFORCE_CartPole.ipynb""" import numpy as np import matplotlib.pyplot as plt from collections import deque import gymnasium as gym # Instalar PyTorch !pip install torch -q import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F print("✅ Bibliotecas carregadas com sucesso!") print(f"PyTorch versão: {torch.__version__}") # Dispositivo device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Dispositivo: {device}") # Ambiente env = gym.make('CartPole-v1') # Hiperparâmetros GAMMA = 0.99 LEARNING_RATE = 0.001 EPISODES = 1000 MAX_STEPS = 500 print(f"\n📊 Informações do ambiente:") print(f" - Estado: {env.observation_space.shape[0]} dimensões") print(f" - Ações: {env.action_space.n} (0=esquerda, 1=direita)") # Rede Neural Policy (REINFORCE) class PolicyNetwork(nn.Module): def __init__(self, state_size, action_size): super(PolicyNetwork, self).__init__() self.fc1 = nn.Linear(state_size, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, action_size) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = F.softmax(self.fc3(x), dim=-1) return x # Inicializar rede state_size = env.observation_space.shape[0] action_size = env.action_space.n policy = PolicyNetwork(state_size, action_size).to(device) optimizer = optim.Adam(policy.parameters(), lr=LEARNING_RATE) print("\n🔧 Rede Policy criada com sucesso!") print(f" - Camada 1: {state_size} -> 128 neurônios") print(f" - Camada 2: 128 -> 128 neurônios") print(f" - Camada 3: 128 -> {action_size} neurônios (softmax)") # Função para escolher ação def select_action(state): state = torch.FloatTensor(state).unsqueeze(0).to(device) probs = policy(state) m = torch.distributions.Categorical(probs) action = m.sample() log_prob = m.log_prob(action) return action.item(), log_prob # Função para calcular retornos descontados def calculate_returns(rewards, gamma=GAMMA): returns = [] R = 0 for r in reversed(rewards): R = r + gamma * R returns.insert(0, R) # Normalizar retornos para reduzir variância returns = torch.FloatTensor(returns) returns = (returns - returns.mean()) / (returns.std() + 1e-8) return returns # Métricas episode_rewards = [] episode_lengths = [] print("\n" + "="*60) print("🎯 INICIANDO TREINAMENTO REINFORCE") print("="*60 + "\n") # Treinamento for episode in range(EPISODES): state, _ = env.reset() log_probs = [] rewards = [] total_reward = 0 # Coletar trajetória for step in range(MAX_STEPS): action, log_prob = select_action(state) next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated log_probs.append(log_prob) rewards.append(reward) state = next_state total_reward += reward if done: break # Calcular retornos returns = calculate_returns(rewards) # Calcular loss e atualizar política loss = [] for log_prob, R in zip(log_probs, returns): loss.append(-log_prob * R) optimizer.zero_grad() policy_loss = torch.cat(loss).sum() policy_loss.backward() optimizer.step() # Salvar métricas episode_rewards.append(total_reward) episode_lengths.append(step + 1) # Mostrar progresso if (episode + 1) % 50 == 0: avg_reward = np.mean(episode_rewards[-50:]) print(f"📈 Episódio {episode + 1:4d}/{EPISODES} | " f"Recompensa média: {avg_reward:6.1f} | " f"Passos: {step + 1:3d}") # Avaliação do agente print("\n" + "="*60) print("🏆 AVALIANDO O AGENTE TREINADO") print("="*60) test_episodes = 10 test_rewards = [] test_lengths = [] policy.eval() for episode in range(test_episodes): state, _ = env.reset() total_reward = 0 steps = 0 for step in range(MAX_STEPS): with torch.no_grad(): state_tensor = torch.FloatTensor(state).unsqueeze(0).to(device) probs = policy(state_tensor) action = torch.argmax(probs).item() next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated state = next_state total_reward += reward steps += 1 if done: break test_rewards.append(total_reward) test_lengths.append(steps) status = "✅" if total_reward >= 475 else "⚠️" print(f"{status} Teste {episode + 1:2d}: {int(total_reward):3d} passos") # Resultados print("\n" + "="*60) print("📊 RESULTADOS FINAIS") print("="*60) print(f"🎯 Melhor recompensa no treino: {int(max(episode_rewards))} 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") # Gráficos fig, axes = plt.subplots(2, 2, figsize=(14, 10)) # Gráfico 1: Evolução das recompensas axes[0, 0].plot(episode_rewards, alpha=0.5, color='blue', linewidth=0.5, label='Por episódio') if len(episode_rewards) >= 50: moving_avg = np.convolve(episode_rewards, np.ones(50)/50, mode='valid') axes[0, 0].plot(range(49, len(episode_rewards)), moving_avg, 'r-', linewidth=2, label='Média móvel (50)') axes[0, 0].set_xlabel('Episódio', fontsize=12) axes[0, 0].set_ylabel('Recompensa (passos)', fontsize=12) axes[0, 0].set_title('🎯 Evolução do Aprendizado REINFORCE', fontsize=14) axes[0, 0].legend() axes[0, 0].grid(True, alpha=0.3) axes[0, 0].axhline(y=500, color='green', linestyle='--', alpha=0.5, label='Meta (500)') axes[0, 0].set_ylim([0, 520]) # Gráfico 2: Distribuição das recompensas axes[0, 1].hist(episode_rewards, bins=30, color='skyblue', edgecolor='black', alpha=0.7) axes[0, 1].axvline(x=np.mean(episode_rewards[-100:]), color='red', linestyle='--', linewidth=2, label=f'Média final: {np.mean(episode_rewards[-100:]):.1f}') axes[0, 1].set_xlabel('Recompensa', fontsize=12) axes[0, 1].set_ylabel('Frequência', fontsize=12) axes[0, 1].set_title('📊 Distribuição das Recompensas', fontsize=14) axes[0, 1].legend() axes[0, 1].grid(True, alpha=0.3) # Gráfico 3: Desempenho nos testes colors = ['green' if r >= 475 else 'orange' for r in test_rewards] bars = axes[1, 0].bar(range(1, test_episodes + 1), test_rewards, color=colors, edgecolor='black') axes[1, 0].axhline(y=np.mean(test_rewards), color='red', linestyle='--', linewidth=2, label=f'Média: {np.mean(test_rewards):.1f}') axes[1, 0].axhline(y=475, color='blue', linestyle=':', linewidth=1.5, label='Sucesso (475)') axes[1, 0].set_xlabel('Episódio de teste', fontsize=12) axes[1, 0].set_ylabel('Passos', fontsize=12) axes[1, 0].set_title('🏅 Desempenho Final do Agente', fontsize=14) axes[1, 0].legend() axes[1, 0].grid(True, alpha=0.3) axes[1, 0].set_ylim([0, 520]) # Adicionar valores for bar, valor in zip(bars, test_rewards): axes[1, 0].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5, f'{int(valor)}', ha='center', va='bottom', fontsize=9) # Gráfico 4: Comprimento dos episódios axes[1, 1].plot(episode_lengths, alpha=0.5, color='purple', linewidth=0.5) axes[1, 1].set_xlabel('Episódio', fontsize=12) axes[1, 1].set_ylabel('Passos por episódio', fontsize=12) axes[1, 1].set_title('⏱️ Sobrevivência do Poste', fontsize=14) axes[1, 1].grid(True, alpha=0.3) axes[1, 1].axhline(y=500, color='green', linestyle='--', alpha=0.5, label='Meta (500)') axes[1, 1].legend() plt.tight_layout() plt.show() # Análise do aprendizado print("\n" + "="*60) print("📈 ANÁLISE DO APRENDIZADO") print("="*60) initial_avg = np.mean(episode_rewards[:100]) final_avg = np.mean(episode_rewards[-100:]) improvement = final_avg - initial_avg print(f"📊 Média inicial (100 episódios): {initial_avg:.1f} passos") print(f"📊 Média final (100 episódios): {final_avg:.1f} passos") print(f"📈 Melhoria: {improvement:.1f} passos") if improvement > 100: print("✅ O agente aprendeu significativamente!") elif improvement > 50: print("⚠️ O agente aprendeu moderadamente") else: print("❌ O agente aprendeu pouco. Aumente os episódios para 2000") # Explicação final print("\n" + "="*60) print("📚 RESUMO DO QUE APRENDEMOS:") print("="*60) print("✅ REINFORCE: Algoritmo de gradiente de política Monte Carlo") print("✅ Policy Gradient: A política é aprendida diretamente") print("✅ Retornos descontados: [latex]G_t = \\sum \\gamma^k R_{t+k+1}[/latex]") print("✅ Função de perda: [latex]L = -\\sum \\log \\pi(a_t|s_t) G_t[/latex]") print("✅ Normalização: Reduz variância e estabiliza treinamento") 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 REINFORCE!") |
O código implementa o algoritmo REINFORCE completo. A rede neural representa diretamente a política do agente. Primeiramente, cada episódio é executado com a política atual. Todas as ações e recompensas são armazenadas durante o episódio. Depois, os retornos descontados são calculados para cada passo. Os retornos são normalizados para reduzir a variância. Em seguida, o gradiente da política é estimado e aplicado. Este processo é repetido até a política convergir. O agente aprende a equilibrar o poste por centenas de passos. Parabéns! Você implementou seu primeiro Policy Gradient.