1.4.4 – Metodos Actor-Critic
1.4.4.1 – Arquiteturas Basicas
1.4.4.1.2 – Advantage Actor-Critic – A2C
LEGENDA
Principal
Ramo
Metodo
Problemas
Modelo
Arquitetura
O que é o advantage actor-critic (a2c)
O A2C é uma evolução do método Actor-Critic tradicional. Ele introduz o conceito de “vantagem” (advantage) para melhorar o aprendizado. Primeiramente, a vantagem mede o quanto uma ação é melhor que a média. Ela é calculada como o TD Error tradicional. Diferentemente do TD Error simples, a vantagem é usada diretamente. Por exemplo, uma ação com vantagem positiva é reforçada. Uma ação com vantagem negativa é desencorajada. Este método reduz ainda mais a variância dos gradientes. Consequentemente, o aprendizado torna-se mais estável. O A2C é amplamente usado em problemas complexos. Uma característica importante é o uso de paralelismo. Vários ambientes são executados simultaneamente. Primeiramente, cada ambiente coleta experiências independentemente. Depois, as experiências são reunidas para atualizar a rede. Este processo é chamado de “sincronizado” ou “síncrono”. Por isso, o nome é Advantage Actor-Critic (A2C). O paralelismo reduz a correlação entre as amostras. Também acelera significativamente o treinamento. Contudo, a versão básica pode rodar com um único ambiente. O código exemplo usará um ambiente para simplificar.Arquitetura do modelo a2c
A arquitetura A2C possui duas redes neurais principais. O ator (policy network) mapeia estados para distribuições de ações. O crítico (value network) mapeia estados para valores. Primeiramente, o ator produz probabilidades usando softmax. Em segundo lugar, o crítico produz um único valor escalar. Ambas as redes podem compartilhar camadas iniciais. Isso reduz o número total de parâmetros. Contudo, para iniciantes, redes separadas são mais claras. A entrada das redes são as observações do ambiente. A saída do ator é o número de ações possíveis. A vantagem (advantage) é calculada de duas formas principais. A primeira usa o TD Error: \(A(s,a) = r + \gamma V(s’) – V(s)\). A segunda usa o retorno descontado menos o valor do estado. Ambas as formas são equivalentes em expectativa. A função de perda do ator é ponderada pela vantagem: \(L_{actor} = -\log \pi_\theta(a_t|s_t) A(s_t,a_t)\). O crítico é atualizado para minimizar o erro quadrático. O erro quadrático é \(L_{critic} = (r + \gamma V(s’) – V(s))^2\). A entropia da política pode ser adicionada como regularizador. Isso incentiva a exploração e evita convergência prematura.Hiperparâmetros e fórmulas matemáticas
A taxa de aprendizado do ator (alpha) é um hiperparâmetro chave. Valores típicos variam entre 0.0001 e 0.001. A taxa de aprendizado do crítico (beta) pode ser diferente. Geralmente, beta é igual ou ligeiramente maior que alpha. O fator de desconto (gamma) prioriza recompensas futuras. Gamma é tipicamente 0.99 para problemas contínuos. O coeficiente de entropia (entropy_coef) incentiva exploração. Valores típicos variam entre 0.01 e 0.001. O número de passos por atualização (n_steps) é importante. Valores entre 5 e 20 são comuns na prática. A função de perda total do A2C é a soma de três termos: \(L_{total} = L_{actor} + c_1 L_{critic} – c_2 H(\pi)\). O termo de entropia é definido como: \(H(\pi) = -\sum_a \pi_\theta(a|s) \log \pi_\theta(a|s)\). O gradiente do ator com vantagem é: \(\nabla_\theta J(\theta) = \mathbb{E}[\nabla_\theta \log \pi_\theta(a|s) A(s,a)]\). O gradiente do crítico minimiza o erro quadrático: \(\nabla_\omega L = -2(r + \gamma V(s’) – V(s)) \nabla_\omega V(s)\). A vantagem também pode ser calculada como: \(A(s,a) = \sum_{k=0}^{n-1} \gamma^k r_{t+k} + \gamma^n V(s_{t+n}) – V(s_t)\). Esta é a vantagem de n-passos (n-step advantage). Ela balanceia viés e variância do estimador. O A2C clássico usa vantagem de 1-passo (TD Error). Estas fórmulas são implementadas no código exemplo.Enunciado do exemplo clássico: 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. Seu agente A2C deve aprender a política ótima. Use a vantagem (advantage) para guiar o aprendizado. A entropia deve ser adicionada para incentivar exploração. Treine o agente por 500 episódios no Google Colab. Primeiramente, implemente o ator com saída softmax. A entrada do ator são as 4 observações do ambiente. A saída são 2 ações (esquerda ou direita). Em segundo lugar, implemente o crítico com saída linear. A entrada do crítico também são as 4 observações. A saída é o valor esperado do estado. Depois, calcule a vantagem usando o TD Error. Adicione a entropia à função de perda do ator. Ao final, exiba gráficos das recompensas e da vantagem. 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 313 314 315 316 317 |
# -*- coding: utf-8 -*- """A2C_Advantage_Actor_Critic_CartPole_Corrected.ipynb""" import numpy as np import matplotlib.pyplot as plt import gymnasium as gym from collections import deque # 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 A2C GAMMA = 0.99 LR_ACTOR = 0.0003 LR_CRITIC = 0.0005 ENTROPY_COEF = 0.01 # Coeficiente da entropia EPISODES = 500 MAX_STEPS = 500 print(f"\n📊 Ambiente CartPole-v1:") print(f" - Estado: {env.observation_space.shape[0]} dimensões") print(f" - Ações: {env.action_space.n} (0=esquerda, 1=direita)") print(f" - Gamma: {GAMMA}") print(f" - Entropy Coef: {ENTROPY_COEF}") # Rede do Ator (Policy) class ActorNetwork(nn.Module): def __init__(self, state_size, action_size): super(ActorNetwork, 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 # Rede do Crítico (Value) class CriticNetwork(nn.Module): def __init__(self, state_size): super(CriticNetwork, self).__init__() self.fc1 = nn.Linear(state_size, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, 1) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x # Parâmetros state_size = env.observation_space.shape[0] action_size = env.action_space.n # Inicializar redes actor = ActorNetwork(state_size, action_size).to(device) critic = CriticNetwork(state_size).to(device) # Otimizadores actor_optimizer = optim.Adam(actor.parameters(), lr=LR_ACTOR) critic_optimizer = optim.Adam(critic.parameters(), lr=LR_CRITIC) print("\n🔧 Redes A2C criadas:") print(f" - Ator: {state_size} -> 128 -> 128 -> {action_size} (softmax)") print(f" - Crítico: {state_size} -> 128 -> 128 -> 1 (valor)") # Função para escolher ação def select_action(state): state = torch.FloatTensor(state).unsqueeze(0).to(device) probs = actor(state) m = torch.distributions.Categorical(probs) action = m.sample() log_prob = m.log_prob(action) entropy = m.entropy() # Entropia para exploração return action.item(), log_prob, entropy # Métricas episode_rewards = [] episode_lengths = [] advantage_history = [] print("\n" + "="*60) print("🎯 TREINANDO ADVANTAGE ACTOR-CRITIC (A2C)") print("="*60 + "\n") # Treinamento for episode in range(EPISODES): state, _ = env.reset() total_reward = 0 episode_advantages = [] for step in range(MAX_STEPS): # Ator escolhe ação action, log_prob, entropy = select_action(state) # Executa ação next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated # Crítico avalia estados state_tensor = torch.FloatTensor(state).unsqueeze(0).to(device) next_state_tensor = torch.FloatTensor(next_state).unsqueeze(0).to(device) value = critic(state_tensor) next_value = critic(next_state_tensor) if not done else torch.tensor([[0.0]]).to(device) # Cálculo da Vantagem (Advantage) - sem detach aqui para o crítico advantage = reward + GAMMA * next_value - value # A = r + γV(s') - V(s) episode_advantages.append(advantage.item()) # CORREÇÃO: Atualizar Ator (usando advantage.detach()) actor_loss = -(log_prob * advantage.detach() + ENTROPY_COEF * entropy) actor_optimizer.zero_grad() actor_loss.backward() actor_optimizer.step() # CORREÇÃO: Atualizar Crítico (criar loss específica) # Usamos o mesmo advantage, mas agora sem detach e com pow(2) critic_loss = advantage.pow(2) critic_optimizer.zero_grad() critic_loss.backward() critic_optimizer.step() state = next_state total_reward += reward if done: break # Armazenar métricas episode_rewards.append(total_reward) episode_lengths.append(step + 1) advantage_history.append(np.mean(episode_advantages) if episode_advantages else 0) # Progresso if (episode + 1) % 50 == 0: avg_reward = np.mean(episode_rewards[-50:]) avg_adv = np.mean(advantage_history[-50:]) print(f"📈 Episódio {episode + 1:4d}/{EPISODES} | " f"Recompensa média: {avg_reward:6.1f} | " f"Vantagem média: {avg_adv:.4f} | " f"Passos: {step + 1:3d}") # Avaliação print("\n" + "="*60) print("🏆 AVALIANDO O AGENTE A2C TREINADO") print("="*60) test_episodes = 10 test_rewards = [] test_lengths = [] actor.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 = actor(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 A2C com Vantagem', 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: Evolução da Vantagem (Advantage) axes[0, 1].plot(advantage_history, 'g-', linewidth=1.5, label='Vantagem média') axes[0, 1].axhline(y=0, color='red', linestyle='--', linewidth=1, label='Neutro (zero)') axes[0, 1].set_xlabel('Episódio', fontsize=12) axes[0, 1].set_ylabel('Valor da Vantagem', fontsize=12) axes[0, 1].set_title('📈 Evolução da Vantagem (Advantage)', fontsize=14) axes[0, 1].legend() axes[0, 1].grid(True, alpha=0.3) axes[0, 1].fill_between(range(len(advantage_history)), 0, advantage_history, alpha=0.3, color='green', where=np.array(advantage_history) > 0, label='Vantagem positiva') axes[0, 1].fill_between(range(len(advantage_history)), 0, advantage_history, alpha=0.3, color='red', where=np.array(advantage_history) < 0, label='Vantagem negativa') axes[0, 1].legend() # 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 A2C', fontsize=14) axes[1, 0].legend() axes[1, 0].grid(True, alpha=0.3) axes[1, 0].set_ylim([0, 520]) 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: Distribuição das recompensas finais axes[1, 1].hist(episode_rewards[-100:], bins=20, color='skyblue', edgecolor='black', alpha=0.7) axes[1, 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[1, 1].axvline(x=475, color='blue', linestyle=':', linewidth=1.5, label='Sucesso (475)') axes[1, 1].set_xlabel('Recompensa (passos)', fontsize=12) axes[1, 1].set_ylabel('Frequência', fontsize=12) axes[1, 1].set_title('📊 Distribuição das Recompensas (últimos 100)', fontsize=14) axes[1, 1].legend() axes[1, 1].grid(True, alpha=0.3) plt.tight_layout() plt.show() # Análise do aprendizado print("\n" + "="*60) print("📈 ANÁLISE DO APRENDIZADO A2C") print("="*60) initial_avg = np.mean(episode_rewards[:100]) final_avg = np.mean(episode_rewards[-100:]) improvement = final_avg - initial_avg final_advantage = np.mean(advantage_history[-100:]) print(f"📊 Recompensa inicial (100 episódios): {initial_avg:.1f} passos") print(f"📊 Recompensa final (100 episódios): {final_avg:.1f} passos") print(f"📈 Melhoria: {improvement:.1f} passos") print(f"📉 Vantagem média final: {final_advantage:.4f}") if improvement > 200: print("✅ Aprendizado excelente! A vantagem funcionou perfeitamente!") elif improvement > 100: print("✅ Bom aprendizado! O A2C convergiu satisfatoriamente.") else: print("⚠️ Considere aumentar os episódios para 800 ou ajustar a entropia") # Explicação da Vantagem (Advantage) print("\n" + "="*60) print("💡 O QUE É A VANTAGEM (ADVANTAGE)?") print("="*60) print("✅ Mede o quanto uma ação é melhor que a média") print("✅ Fórmula: A(s,a) = r + γV(s') - V(s)") print("✅ Positiva: ação melhor que o esperado") print("✅ Negativa: ação pior que o esperado") print("✅ Reduz variância comparado ao REINFORCE") # Explicação do A2C print("\n" + "="*60) print("📚 RESUMO DO ADVANTAGE ACTOR-CRITIC (A2C)") print("="*60) print("✅ Ator: aprende política usando vantagem") print("✅ Crítico: aprende valor dos estados") print("✅ Vantagem: guia o aprendizado do ator") print("✅ Entropia: incentiva exploração") print("✅ Aprendizado online e estável") print("\n🚀 O agente A2C aprendeu a equilibrar o poste!") print(" A vantagem permitiu aprendizado mais eficiente.") env.close() print("\n✨ Fim do treinamento Advantage Actor-Critic (A2C)!") |