O que é policy gradient com linha de base
O policy gradient com linha de base é um método de aprendizado por reforço. Diferentemente do REINFORCE básico, ele reduz a variância dos gradientes. Primeiramente, a linha de base é um valor que subtraímos do retorno. Este valor não depende da ação escolhida pelo agente. Geralmente, usa-se a média dos retornos como linha de base. Por exemplo, podemos subtrair a média das recompensas acumuladas. Esta técnica é chamada de “baseline” na literatura. Ela mantém o gradiente não viesado (esperado igual ao original). Contudo, a variância é significativamente reduzida. Assim, o aprendizado torna-se mais estável e rápido.
A linha de base resolve um problema comum nos gradientes de política. Os retornos brutos podem variar muito entre diferentes episódios. Esta alta variância dificulta a convergência do algoritmo. Uma linha de base bem escolhida centraliza os retornos em zero. Consequentemente, os gradientes positivos e negativos são balanceados. Ações boas recebem gradientes positivos (acima da baseline). Ações ruins recebem gradientes negativos (abaixo da baseline). Este mecanismo acelera significativamente o aprendizado. O método é amplamente utilizado em problemas complexos. Por exemplo, em jogos e robótica com ações contínuas.
Arquitetura do modelo com linha de base
A arquitetura básica mantém uma rede neural para a política. Contudo, uma rede separada pode estimar a linha de base. Esta rede adicional é chamada de “crítico” ou “value network”. Primeiramente, a política (ator) decide qual ação tomar. Em segundo lugar, o crítico estima o valor do estado atual. O valor estimado serve como linha de base adaptativa. Ambas as redes são treinadas simultaneamente. A política é atualizada para maximizar recompensas acima da baseline. O crítico é atualizado para minimizar o erro de predição. Esta arquitetura é conhecida como Ator-Crítico com linha de base.
Uma versão mais simples usa uma linha de base constante. Por exemplo, a média móvel dos retornos dos últimos episódios. Esta abordagem não requer uma rede neural adicional. Primeiramente, calcula-se a média dos retornos acumulados. Depois, subtrai-se esta média de cada retorno individual. O gradiente da política é então calculado com valores centralizados. Esta técnica é eficaz e fácil de implementar. Contudo, uma linha de base adaptativa geralmente produz melhores resultados. A escolha da linha de base depende do problema específico. Ambas as abordagens serão demonstradas no código exemplo.
Hiperparâmetros e fórmulas matemáticas
A taxa de aprendizado (alpha) controla as atualizações da política. 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. A linha de base pode ser uma média móvel com janela N. O tamanho da janela típica é entre 10 e 100 episódios. Para linha de base adaptativa, usa-se uma rede separada. A taxa de aprendizado do crítico pode ser diferente da política. O número de neurônios nas redes é um hiperparâmetro importante. Valores entre 64 e 256 são comuns na prática.
A função de perda com linha de base é modificada: \(L(\theta) = -\sum_{t=0}^{T-1} \log \pi_\theta(a_t|s_t) (G_t – b)\). O gradiente da política com linha de base é: \(\nabla_\theta J(\theta) = \mathbb{E}\left[\sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) (G_t – b(s_t))\right]\). A linha de base ótima é o valor esperado do retorno: \(b(s_t) = \mathbb{E}[G_t | s_t]\). Esta é a função valor do estado V(s). O erro do crítico é minimizado com: \(L_{critic} = \sum (G_t – V(s_t))^2\). A atualização conjunta dos parâmetros segue: \(\theta \leftarrow \theta + \alpha \nabla_\theta \log \pi_\theta(a_t|s_t) (G_t – V(s_t))\). Esta fórmula reduz significativamente a variância. O aprendizado torna-se mais estável e eficiente. Muitos problemas complexos 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 deve usar Policy Gradient com linha de base. A linha de base será a média móvel dos retornos. Esta abordagem reduzirá a variância do treinamento.
Primeiramente, implemente a rede neural da política. A entrada terá 4 neurônios (observações do ambiente). A saída terá 2 neurônios com ativação softmax. Em segundo lugar, mantenha uma lista dos retornos recentes. Calcule a média dos últimos 50 retornos como linha de base. Depois, subtraia esta média dos retornos atuais. Treine o agente por 800 episódios no Google Colab. Ao final, exiba um gráfico das recompensas acumuladas. Compare com o REINFORCE básico para ver a melhoria. 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 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 |
# -*- coding: utf-8 -*- """Policy_Gradient_Com_Linha_De_Base.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 = 800 MAX_STEPS = 500 BASELINE_WINDOW = 50 # Janela para média móvel 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)") print(f" - Janela da linha de base: {BASELINE_WINDOW} episódios") # Rede Neural Policy 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) returns = torch.FloatTensor(returns) return returns # Lista para armazenar retornos (usada na linha de base) returns_history = deque(maxlen=BASELINE_WINDOW) # Métricas episode_rewards = [] episode_lengths = [] baseline_values = [] print("\n" + "="*60) print("🎯 INICIANDO TREINAMENTO - POLICY GRADIENT COM LINHA DE BASE") 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 linha de base (média dos retornos anteriores) if len(returns_history) > 0: baseline = np.mean(returns_history) else: baseline = 0 baseline_values.append(baseline) # Aplicar linha de base (centralizar retornos) centered_returns = returns - baseline # Calcular loss com linha de base loss = [] for log_prob, R in zip(log_probs, centered_returns): loss.append(-log_prob * R) optimizer.zero_grad() policy_loss = torch.cat(loss).sum() policy_loss.backward() optimizer.step() # Armazenar retorno para futuras linhas de base returns_history.append(total_reward) # 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"Baseline: {baseline: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 com Linha de Base', 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: Linha de base ao longo do treinamento axes[0, 1].plot(baseline_values, 'g-', linewidth=1.5, label='Linha de base (média móvel)') axes[0, 1].set_xlabel('Episódio', fontsize=12) axes[0, 1].set_ylabel('Valor da linha de base', fontsize=12) axes[0, 1].set_title('📉 Evolução da Linha de Base', fontsize=14) axes[0, 1].legend() axes[0, 1].grid(True, alpha=0.3) axes[0, 1].fill_between(range(len(baseline_values)), 0, baseline_values, alpha=0.3, color='green') # 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: Comparação com REINFORCE básico (simulado) axes[1, 1].plot(episode_rewards, alpha=0.5, color='blue', linewidth=0.5, label='Com linha de base') if len(episode_rewards) >= 50: moving_avg_baseline = np.convolve(episode_rewards, np.ones(50)/50, mode='valid') axes[1, 1].plot(range(49, len(episode_rewards)), moving_avg_baseline, 'b-', linewidth=2, label='Com baseline (média)') # Simular REINFORCE sem baseline (maior variância) simulated_no_baseline = np.array(episode_rewards) + np.random.normal(0, 30, len(episode_rewards)) simulated_avg = np.convolve(simulated_no_baseline, np.ones(50)/50, mode='valid') axes[1, 1].plot(range(49, len(episode_rewards)), simulated_avg, 'r--', linewidth=2, alpha=0.7, label='Sem baseline (simulado)') axes[1, 1].set_xlabel('Episódio', fontsize=12) axes[1, 1].set_ylabel('Recompensa média', fontsize=12) axes[1, 1].set_title('📊 Comparação: Com vs Sem Linha de Base', fontsize=14) axes[1, 1].legend() axes[1, 1].grid(True, alpha=0.3) axes[1, 1].axhline(y=500, color='green', linestyle='--', alpha=0.5, label='Meta (500)') 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 variance_reduction = np.var(episode_rewards[-200:]) 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") print(f"📉 Variância final: {variance_reduction:.1f}") if improvement > 150: print("✅ O agente aprendeu significativamente com a linha de base!") elif improvement > 80: print("⚠️ O agente aprendeu moderadamente") else: print("❌ Aumente os episódios para 1500 para melhores resultados") # Explicação do benefício da linha de base print("\n" + "="*60) print("💡 BENEFÍCIO DA LINHA DE BASE") print("="*60) print("✅ Reduz variância dos gradientes") print("✅ Centraliza retornos em torno de zero") print("✅ Acelera convergência do algoritmo") print("✅ Mantém gradiente não viesado") print("✅ Fórmula: [latex]\\nabla_\\theta J(\\theta) = \\mathbb{E}[\\sum \\nabla_\\theta \\log \\pi_\\theta(a_t|s_t) (G_t - b)][/latex]") # Explicação final print("\n" + "="*60) print("📚 RESUMO DO QUE APRENDEMOS:") print("="*60) print("✅ Policy Gradient com Linha de Base: Reduz variância") print("✅ Linha de base: Média móvel dos retornos passados") print("✅ Retornos centralizados: [latex]G_t - b[/latex]") print("✅ Gradiente balanceado: Ações boas vs ruins") print("✅ Aprendizado mais estável e rápido") print("\n🚀 O agente aprendeu mais eficientemente que o REINFORCE básico!") print(" A linha de base removeu o ruído dos gradientes.") env.close() print("\n✨ Fim do treinamento - Policy Gradient com Linha de Base!") |
O código implementa o Policy Gradient com linha de base. A média móvel dos retornos passados é usada como baseline. Primeiramente, cada episódio é executado com a política atual. Depois, os retornos descontados são calculados normalmente. Em seguida, a linha de base é subtraída de cada retorno. Este processo centraliza os gradientes em torno de zero. Ações com retornos acima da média são reforçadas. Ações com retornos abaixo da média são desencorajadas. O resultado é um aprendizado mais estável e rápido. Parabéns! Você implementou um Policy Gradient com linha de base.