# -*- 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)!")