Módulo 3.1: Algoritmos de IA Avançados (PPO, SAC)
🧠 Módulo 3.1: Algoritmos de IA Avançados (PPO, SAC)
Domine os algoritmos state-of-the-art de Aprendizado por Reforço!
📋 Informações do Módulo
| Informação | Detalhes |
|---|---|
| Duração estimada | 15-20 horas |
| Nível | Avançado |
| Pré-requisitos | Nível 2 concluído, conhecimento de RL básico |
| Ferramentas | Python, Stable-Baselines3, PyTorch, TensorBoard |
🎯 Objetivos de Aprendizado
Ao completar este módulo, você será capaz de:
- Compreender profundamente PPO (Proximal Policy Optimization)
- Implementar e otimizar SAC (Soft Actor-Critic)
- Comparar e escolher algoritmos apropriados para diferentes tarefas
- Realizar tuning avançado de hiperparâmetros
- Treinar políticas com ambientes vetorizados
- Monitorar e debugar treinamento com TensorBoard
- Analisar convergência e performance
📚 Conteúdo Teórico
1. Revisão: Fundamentos de Reinforcement Learning
Antes de mergulharmos nos algoritmos avançados, vamos revisar conceitos essenciais:
Estado, Ação e Recompensa
# Estrutura básica de um ambiente de RL
class RobotEnvironment:
def __init__(self):
self.state = None
def reset(self):
"""Retorna estado inicial"""
return initial_state
def step(self, action):
"""Executa ação e retorna (próximo_estado, recompensa, done, info)"""
next_state = self.transition(self.state, action)
reward = self.compute_reward(next_state)
done = self.is_terminal(next_state)
return next_state, reward, done, {}
Política vs. Value Function
- Política (π): Mapeia estados para ações
-
Estocástica: π(a s) = probabilidade de ação a dado estado s - Determinística: a = π(s)
-
- Value Function (V): Valor esperado de um estado
-
V^π(s) = E[∑γ^t * r_t s_0=s, π]
-
- Q-Function: Valor esperado de ação em estado
-
Q^π(s,a) = E[∑γ^t * r_t s_0=s, a_0=a, π]
-
Exploration vs. Exploitation
Problema fundamental do RL: balancear exploração (tentar coisas novas) vs. exploração (usar o que já funciona).
Estratégias comuns:
- ε-greedy (usado em DQN)
- Entropy regularization (usado em SAC)
- Noise no espaço de ações (usado em DDPG)
Reward Shaping
Projetar boas funções de recompensa é crítico para o sucesso do RL.
Princípios:
- Densas: Feedback frequente
- Normalizadas: Recompensas na mesma escala
- Sem contradições: Não incentivar comportamentos opostos
- Alinhadas: Refletir objetivo real
Exemplo prático - Robô andando:
def compute_reward_walking(state):
"""Função de recompensa bem projetada para caminhar"""
# 1. Recompensa principal: velocidade para frente
forward_velocity = state['base_velocity_x']
reward = forward_velocity * 1.0 # Peso principal
# 2. Penalidade por queda
base_height = state['base_height']
if base_height < 0.3: # Muito baixo = queda
reward -= 10.0
# 3. Incentivo por manter corpo reto
body_orientation = state['base_roll'] # Ângulo de inclinação
reward -= abs(body_orientation) * 0.5
# 4. Penalidade leve por uso de energia
torques = state['joint_torques']
energy_cost = np.sum(np.square(torques)) * 0.001
reward -= energy_cost
# 5. Bonus por estabilidade
if state['steps_without_falling'] > 100:
reward += 2.0
return reward
Armadilhas comuns:
- Recompensa muito esparsa (robô nunca aprende)
- Recompensa densa demais (overfitting a comportamentos locais)
- Recompensas conflitantes (ex: maximizar velocidade E minimizar movimento)
2. PPO (Proximal Policy Optimization)
O que é PPO?
PPO é o algoritmo de RL mais popular para robótica, desenvolvido pela OpenAI em 2017. Ele resolve um problema fundamental: como atualizar a política de forma eficiente sem causar mudanças drásticas que desestabilizem o treinamento.
Paper original: Proximal Policy Optimization Algorithms (Schulman et al., 2017)
Intuição: Por que PPO?
Imagine que você está treinando um robô para andar:
- Iteração 1: Robô aprende a dar pequenos passos
- Iteração 2: Você atualiza a política de forma agressiva
- Resultado: Robô esquece tudo e volta a cair
Problema: Policy Gradient tradicional pode fazer updates muito grandes, destruindo o que foi aprendido.
Solução do PPO: Limitar o tamanho do update para garantir melhorias incrementais e estáveis.
Matemática do PPO (Simplificada)
O PPO otimiza uma função objetivo que limita quanto a nova política pode diferir da antiga:
L^CLIP(θ) = E[min(r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1+ε) * A_t)]
Onde:
- r_t(θ) = π_θ(a|s) / π_θ_old(a|s) [razão entre política nova e antiga]
- A_t = vantagem (quão melhor que média é tomar ação a)
- ε = limite de clipping (tipicamente 0.2)
Interpretação:
- Se
r_testiver entre[1-ε, 1+ε], aceita o update - Se
r_ttentar ir muito além, clippa (limita) o valor
Isso garante que a nova política não se afasta muito da antiga.
Componentes do PPO
1. Actor-Critic Architecture
import torch
import torch.nn as nn
class PPOActorCritic(nn.Module):
def __init__(self, obs_dim, action_dim):
super().__init__()
# Feature extractor compartilhado
self.shared = nn.Sequential(
nn.Linear(obs_dim, 256),
nn.ReLU(),
nn.Linear(256, 256),
nn.ReLU()
)
# Actor: gera distribuição de ações
self.actor_mean = nn.Linear(256, action_dim)
self.actor_logstd = nn.Parameter(torch.zeros(action_dim))
# Critic: estima value function
self.critic = nn.Linear(256, 1)
def forward(self, obs):
features = self.shared(obs)
# Política (Gaussian para ações contínuas)
action_mean = self.actor_mean(features)
action_std = torch.exp(self.actor_logstd)
# Value function
value = self.critic(features)
return action_mean, action_std, value
def get_action(self, obs, deterministic=False):
"""Amostra ação da política"""
action_mean, action_std, value = self.forward(obs)
if deterministic:
return action_mean, value
# Amostra de distribuição Gaussian
dist = torch.distributions.Normal(action_mean, action_std)
action = dist.sample()
log_prob = dist.log_prob(action).sum(dim=-1)
return action, log_prob, value
2. Generalized Advantage Estimation (GAE)
GAE é usado para calcular vantagens de forma mais estável:
def compute_gae(rewards, values, dones, gamma=0.99, lambda_=0.95):
"""
Calcula Generalized Advantage Estimation
GAE balanceia bias vs. variance no cálculo de vantagens:
- λ=0: baixa variance, alto bias (TD(0))
- λ=1: alta variance, baixo bias (Monte Carlo)
- λ=0.95: sweet spot empírico
"""
advantages = []
gae = 0
for t in reversed(range(len(rewards))):
if t == len(rewards) - 1:
next_value = 0
else:
next_value = values[t + 1]
# TD error: δ = r + γV(s') - V(s)
delta = rewards[t] + gamma * next_value * (1 - dones[t]) - values[t]
# GAE: A = δ + γλδ' + (γλ)²δ'' + ...
gae = delta + gamma * lambda_ * (1 - dones[t]) * gae
advantages.insert(0, gae)
return torch.tensor(advantages)
3. PPO Update Loop
def ppo_update(policy, optimizer, states, actions, old_log_probs,
advantages, returns, clip_epsilon=0.2, epochs=10):
"""
Atualiza política usando PPO
"""
for epoch in range(epochs):
# Forward pass
action_mean, action_std, values = policy(states)
dist = torch.distributions.Normal(action_mean, action_std)
new_log_probs = dist.log_prob(actions).sum(dim=-1)
# Ratio r_t = π_new / π_old
ratio = torch.exp(new_log_probs - old_log_probs)
# PPO clipped objective
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1-clip_epsilon, 1+clip_epsilon) * advantages
actor_loss = -torch.min(surr1, surr2).mean()
# Value function loss
value_loss = F.mse_loss(values.squeeze(), returns)
# Entropy bonus (incentiva exploração)
entropy = dist.entropy().mean()
# Loss total
loss = actor_loss + 0.5 * value_loss - 0.01 * entropy
# Backward pass
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(policy.parameters(), max_norm=0.5)
optimizer.step()
Implementação Completa com Stable-Baselines3
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv
from stable_baselines3.common.callbacks import EvalCallback
import gymnasium as gym
# 1. Criar ambiente
def make_env():
def _init():
env = gym.make('HumanoidStandup-v4')
return env
return _init
# 2. Ambientes vetorizados (paralelo)
n_envs = 8
env = SubprocVecEnv([make_env() for _ in range(n_envs)])
# 3. Configurar PPO
model = PPO(
policy="MlpPolicy",
env=env,
# Hiperparâmetros core
learning_rate=3e-4,
n_steps=2048, # Steps por rollout por env
batch_size=64, # Minibatch size
n_epochs=10, # Epochs por update
gamma=0.99, # Discount factor
gae_lambda=0.95, # GAE lambda
# PPO específico
clip_range=0.2, # Epsilon de clipping
clip_range_vf=None, # Clipping no value function
# Regularização
ent_coef=0.01, # Entropy bonus
vf_coef=0.5, # Value function coef
max_grad_norm=0.5, # Gradient clipping
# Network
policy_kwargs=dict(
net_arch=[dict(pi=[256, 256], vf=[256, 256])]
),
# Logging
verbose=1,
tensorboard_log="./ppo_humanoid_tensorboard/"
)
# 4. Callback para avaliação
eval_callback = EvalCallback(
eval_env=gym.make('HumanoidStandup-v4'),
best_model_save_path='./best_model/',
log_path='./eval_logs/',
eval_freq=10000,
deterministic=True,
render=False
)
# 5. Treinar
model.learn(
total_timesteps=10_000_000, # 10M steps
callback=eval_callback
)
# 6. Salvar
model.save("ppo_humanoid_final")
# 7. Testar
obs, info = env.reset()
for _ in range(1000):
action, _states = model.predict(obs, deterministic=True)
obs, rewards, dones, truncated, infos = env.step(action)
env.render()
Quando Usar PPO?
PPO é ideal para:
- ✅ Tarefas de locomoção (andar, correr, pular)
- ✅ Manipulação robótica
- ✅ Controle contínuo multi-dimensional
- ✅ Quando você precisa de estabilidade no treinamento
- ✅ Quando você tem muitos cores (paraleliza bem)
PPO não é ideal para:
- ❌ Tarefas que exigem exploração agressiva
- ❌ Ambientes muito esparsos
- ❌ Quando você precisa de sample efficiency extrema
3. SAC (Soft Actor-Critic)
O que é SAC?
SAC (Soft Actor-Critic) é um algoritmo de RL off-policy que maximiza tanto a recompensa quanto a entropia da política. Foi desenvolvido pela UC Berkeley em 2018.
Paper original: Soft Actor-Critic: Off-Policy Maximum Entropy Deep RL (Haarnoja et al., 2018)
Intuição: Maximum Entropy RL
SAC resolve um problema diferente do PPO:
Objetivo tradicional de RL:
Maximize: E[∑ γ^t * r_t]
Objetivo do SAC (Maximum Entropy):
Maximize: E[∑ γ^t * (r_t + α * H(π(·|s_t)))]
Onde H(π) = entropia da política
Por que adicionar entropia?
- Exploração automática: Política naturalmente explora
- Robustez: Aprende múltiplas soluções
- Transfer: Generaliza melhor para novos ambientes
Analogia: Ao invés de aprender “uma única forma certa” de pegar um objeto, o robô aprende “várias formas que funcionam”, tornando-o mais adaptável.
Arquitetura do SAC
SAC usa três redes neurais:
import torch
import torch.nn as nn
import torch.nn.functional as F
class SACActorCritic(nn.Module):
def __init__(self, obs_dim, action_dim):
super().__init__()
# Actor: Política estocástica (Gaussian Squashed)
self.actor = nn.Sequential(
nn.Linear(obs_dim, 256),
nn.ReLU(),
nn.Linear(256, 256),
nn.ReLU()
)
self.mean = nn.Linear(256, action_dim)
self.log_std = nn.Linear(256, action_dim)
# Critic 1: Q-function
self.q1 = nn.Sequential(
nn.Linear(obs_dim + action_dim, 256),
nn.ReLU(),
nn.Linear(256, 256),
nn.ReLU(),
nn.Linear(256, 1)
)
# Critic 2: Q-function (double Q-learning)
self.q2 = nn.Sequential(
nn.Linear(obs_dim + action_dim, 256),
nn.ReLU(),
nn.Linear(256, 256),
nn.ReLU(),
nn.Linear(256, 1)
)
def get_action(self, obs, deterministic=False):
"""Amostra ação usando reparameterization trick"""
features = self.actor(obs)
mean = self.mean(features)
log_std = self.log_std(features)
log_std = torch.clamp(log_std, -20, 2) # Estabilidade
std = torch.exp(log_std)
if deterministic:
action = mean
else:
# Reparameterization trick
normal = torch.distributions.Normal(mean, std)
z = normal.rsample() # Permite backprop
action = torch.tanh(z) # Squash para [-1, 1]
# Log probability (com correção para tanh)
log_prob = normal.log_prob(z) - torch.log(1 - action.pow(2) + 1e-6)
log_prob = log_prob.sum(dim=-1, keepdim=True)
return action, log_prob
def get_q_values(self, obs, action):
"""Retorna Q-values das duas redes"""
x = torch.cat([obs, action], dim=-1)
q1 = self.q1(x)
q2 = self.q2(x)
return q1, q2
Componentes Chave do SAC
1. Replay Buffer (Experience Replay)
SAC é off-policy: aprende de transições antigas armazenadas:
import numpy as np
class ReplayBuffer:
def __init__(self, obs_dim, action_dim, capacity=1_000_000):
self.capacity = capacity
self.ptr = 0
self.size = 0
self.states = np.zeros((capacity, obs_dim), dtype=np.float32)
self.actions = np.zeros((capacity, action_dim), dtype=np.float32)
self.rewards = np.zeros((capacity, 1), dtype=np.float32)
self.next_states = np.zeros((capacity, obs_dim), dtype=np.float32)
self.dones = np.zeros((capacity, 1), dtype=np.float32)
def add(self, state, action, reward, next_state, done):
self.states[self.ptr] = state
self.actions[self.ptr] = action
self.rewards[self.ptr] = reward
self.next_states[self.ptr] = next_state
self.dones[self.ptr] = done
self.ptr = (self.ptr + 1) % self.capacity
self.size = min(self.size + 1, self.capacity)
def sample(self, batch_size):
idx = np.random.randint(0, self.size, size=batch_size)
return (
torch.FloatTensor(self.states[idx]),
torch.FloatTensor(self.actions[idx]),
torch.FloatTensor(self.rewards[idx]),
torch.FloatTensor(self.next_states[idx]),
torch.FloatTensor(self.dones[idx])
)
2. Target Networks
SAC usa redes target para estabilizar treinamento:
import copy
# Criar redes target
target_critic = copy.deepcopy(critic)
# Soft update (Polyak averaging)
def soft_update(target, source, tau=0.005):
"""
Target: θ' ← τθ + (1-τ)θ'
"""
for target_param, param in zip(target.parameters(), source.parameters()):
target_param.data.copy_(
tau * param.data + (1.0 - tau) * target_param.data
)
3. Automatic Entropy Tuning
SAC ajusta automaticamente o coeficiente de entropia α:
# Entropia alvo (heurística: -dim(A))
target_entropy = -action_dim
# α como variável treinável (em log-space para estabilidade)
log_alpha = torch.zeros(1, requires_grad=True)
alpha_optimizer = torch.optim.Adam([log_alpha], lr=3e-4)
# Atualizar α
def update_alpha(log_prob):
alpha = log_alpha.exp()
alpha_loss = -(log_alpha * (log_prob + target_entropy).detach()).mean()
alpha_optimizer.zero_grad()
alpha_loss.backward()
alpha_optimizer.step()
return alpha.detach()
4. SAC Update Loop
def sac_update(actor, critic, target_critic, replay_buffer,
alpha, batch_size=256):
# Amostra batch
states, actions, rewards, next_states, dones = replay_buffer.sample(batch_size)
# --- Atualizar Critic ---
with torch.no_grad():
# Amostra próxima ação da política atual
next_actions, next_log_probs = actor.get_action(next_states)
# Q-target usando min de duas redes (Clipped Double Q-learning)
q1_next, q2_next = target_critic.get_q_values(next_states, next_actions)
q_next = torch.min(q1_next, q2_next) - alpha * next_log_probs
q_target = rewards + gamma * (1 - dones) * q_next
# Q atual
q1, q2 = critic.get_q_values(states, actions)
# Loss de critic (MSE)
critic_loss = F.mse_loss(q1, q_target) + F.mse_loss(q2, q_target)
critic_optimizer.zero_grad()
critic_loss.backward()
critic_optimizer.step()
# --- Atualizar Actor ---
new_actions, log_probs = actor.get_action(states)
q1_new, q2_new = critic.get_q_values(states, new_actions)
q_new = torch.min(q1_new, q2_new)
# Maximizar Q - entropia (equivalent: minimizar -Q + α*log_prob)
actor_loss = (alpha * log_probs - q_new).mean()
actor_optimizer.zero_grad()
actor_loss.backward()
actor_optimizer.step()
# --- Atualizar Alpha ---
alpha = update_alpha(log_probs.detach())
# --- Soft update de target networks ---
soft_update(target_critic, critic, tau=0.005)
return actor_loss.item(), critic_loss.item()
Implementação Completa com Stable-Baselines3
from stable_baselines3 import SAC
from stable_baselines3.common.noise import NormalActionNoise
import gymnasium as gym
import numpy as np
# 1. Criar ambiente
env = gym.make('Humanoid-v4')
# 2. Configurar SAC
model = SAC(
policy="MlpPolicy",
env=env,
# Hiperparâmetros core
learning_rate=3e-4,
buffer_size=1_000_000, # Replay buffer size
learning_starts=10000, # Steps antes de começar treino
batch_size=256, # Batch size
tau=0.005, # Soft update coef
gamma=0.99, # Discount factor
# SAC específico
train_freq=1, # Atualiza a cada step
gradient_steps=1, # Gradient steps por update
ent_coef='auto', # Auto-tune entropy coef
target_entropy='auto', # -dim(A)
# Network architecture
policy_kwargs=dict(
net_arch=[256, 256],
log_std_init=-3
),
# Logging
verbose=1,
tensorboard_log="./sac_humanoid_tensorboard/"
)
# 3. Treinar
model.learn(
total_timesteps=3_000_000,
log_interval=10
)
# 4. Salvar e testar
model.save("sac_humanoid_final")
obs, info = env.reset()
for _ in range(1000):
action, _states = model.predict(obs, deterministic=True)
obs, reward, done, truncated, info = env.step(action)
env.render()
Quando Usar SAC?
SAC é ideal para:
- ✅ Tarefas que exigem exploração intensa
- ✅ Manipulação de objetos complexa
- ✅ Ambientes contínuos com recompensas esparsas
- ✅ Quando você precisa de sample efficiency (aprende rápido)
- ✅ Quando você quer robustez e generalização
SAC não é ideal para:
- ❌ Ações discretas (use DQN ou PPO)
- ❌ Quando você tem muitos cores ociosos (não paraleliza como PPO)
- ❌ Ambientes muito simples (overhead de replay buffer)
4. PPO vs SAC: Comparação Detalhada
| Aspecto | PPO | SAC |
|---|---|---|
| Tipo | On-policy | Off-policy |
| Paralelização | Excelente (8-32 envs) | Limitada (1 env) |
| Sample Efficiency | Baixa-Média | Alta |
| Estabilidade | Muito alta | Alta |
| Exploração | ε-decay ou noise | Automatic (entropy) |
| Memória | Baixa | Alta (replay buffer) |
| Velocidade por step | Rápida | Mais lenta |
| Hiperparâmetros | Sensível | Mais robusto |
| Convergência | Gradual | Rápida (se convergir) |
Regra de ouro:
- Locomoção/Controle simples: PPO
- Manipulação/Exploração: SAC
- Muitos cores disponíveis: PPO
- Sample efficiency crítica: SAC
- Não sabe qual usar: Comece com PPO
5. Tuning de Hiperparâmetros
Hiperparâmetros Críticos do PPO
Learning Rate (lr)
- Range:
1e-5a1e-3 - Padrão:
3e-4 - Como ajustar: Se treinamento oscilar muito, diminua. Se muito lento, aumente.
# Decaying learning rate
from torch.optim.lr_scheduler import LinearLR
scheduler = LinearLR(
optimizer,
start_factor=1.0,
end_factor=0.1,
total_iters=1000
)
n_steps (Rollout Length)
- Range:
512a4096 - Padrão:
2048 - Trade-off: Maior = mais estável, mas menos updates
Clip Range (ε)
- Range:
0.1a0.3 - Padrão:
0.2 - Como ajustar: Se updates muito conservadores, aumente. Se instável, diminua.
GAE Lambda (λ)
- Range:
0.9a0.99 - Padrão:
0.95 - Trade-off: Menor = menos variance, mais bias. Maior = mais variance, menos bias.
Hiperparâmetros Críticos do SAC
Learning Rate
- Range:
1e-5a1e-3 - Padrão:
3e-4 - Dica: SAC geralmente tolera LR maiores que PPO
Batch Size
- Range:
64a512 - Padrão:
256 - Trade-off: Maior = mais estável, mas mais lento
Buffer Size
- Range:
100ka10M - Padrão:
1M - Limite: RAM disponível (1M steps ≈ 2-4 GB)
Tau (Soft Update)
- Range:
0.001a0.01 - Padrão:
0.005 - Como ajustar: Menor = mais estável, mas mais lento
Grid Search Automatizado
from stable_baselines3 import PPO
import optuna
def objective(trial):
# Hiperparâmetros a otimizar
lr = trial.suggest_loguniform('lr', 1e-5, 1e-3)
n_steps = trial.suggest_categorical('n_steps', [512, 1024, 2048, 4096])
clip_range = trial.suggest_uniform('clip_range', 0.1, 0.3)
gae_lambda = trial.suggest_uniform('gae_lambda', 0.9, 0.99)
# Criar modelo
model = PPO(
'MlpPolicy',
env='Humanoid-v4',
learning_rate=lr,
n_steps=n_steps,
clip_range=clip_range,
gae_lambda=gae_lambda,
verbose=0
)
# Treinar por um período curto
model.learn(total_timesteps=100_000)
# Avaliar performance
mean_reward = evaluate_policy(model, env, n_eval_episodes=10)
return mean_reward
# Otimizar com Optuna
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50)
print(f"Melhores hiperparâmetros: {study.best_params}")
💻 Prática Hands-On
Projeto 1: Treinar Humanoid com PPO
Objetivo: Treinar um robô humanoide para ficar em pé usando PPO.
# train_humanoid_ppo.py
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import SubprocVecEnv
from stable_baselines3.common.callbacks import CheckpointCallback
import gymnasium as gym
# Criar 8 ambientes paralelos
def make_env():
def _init():
return gym.make('HumanoidStandup-v4')
return _init
env = SubprocVecEnv([make_env() for _ in range(8)])
# Callback para salvar checkpoints
checkpoint_callback = CheckpointCallback(
save_freq=100_000,
save_path='./checkpoints/',
name_prefix='humanoid_ppo'
)
# Configurar PPO otimizado
model = PPO(
'MlpPolicy',
env,
learning_rate=3e-4,
n_steps=2048,
batch_size=64,
n_epochs=10,
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2,
ent_coef=0.01,
verbose=1,
tensorboard_log='./ppo_humanoid/'
)
# Treinar por 10M steps (~3-4 horas em CPU moderno)
model.learn(
total_timesteps=10_000_000,
callback=checkpoint_callback
)
model.save('humanoid_ppo_final')
Executar:
python train_humanoid_ppo.py
# Monitorar com TensorBoard
tensorboard --logdir ./ppo_humanoid/
Métricas esperadas:
- Ep Reward Mean: 0 → 5000+ em 5-10M steps
- Ep Length Mean: ~500 (máximo do ambiente)
- Value Loss: Deve estabilizar em ~100-200
Projeto 2: Comparar PPO vs SAC
Objetivo: Treinar o mesmo ambiente com ambos e comparar.
# compare_algorithms.py
import gymnasium as gym
from stable_baselines3 import PPO, SAC
import matplotlib.pyplot as plt
import pandas as pd
env_name = 'Ant-v4' # Formiga quadrúpede
# 1. Treinar PPO
print("Treinando PPO...")
ppo_model = PPO('MlpPolicy', env_name, verbose=0)
ppo_rewards = []
for i in range(100):
ppo_model.learn(total_timesteps=10_000, reset_num_timesteps=False)
mean_reward = evaluate_policy(ppo_model, env_name, n_eval_episodes=5)
ppo_rewards.append(mean_reward)
print(f"PPO Step {i*10000}: {mean_reward}")
# 2. Treinar SAC
print("Treinando SAC...")
sac_model = SAC('MlpPolicy', env_name, verbose=0)
sac_rewards = []
for i in range(100):
sac_model.learn(total_timesteps=10_000, reset_num_timesteps=False)
mean_reward = evaluate_policy(sac_model, env_name, n_eval_episodes=5)
sac_rewards.append(mean_reward)
print(f"SAC Step {i*10000}: {mean_reward}")
# 3. Plotar comparação
plt.figure(figsize=(10, 6))
plt.plot(ppo_rewards, label='PPO', linewidth=2)
plt.plot(sac_rewards, label='SAC', linewidth=2)
plt.xlabel('Training Steps (x10k)')
plt.ylabel('Mean Episode Reward')
plt.title(f'PPO vs SAC on {env_name}')
plt.legend()
plt.grid(True)
plt.savefig('ppo_vs_sac_comparison.png')
plt.show()
# 4. Estatísticas finais
print("\n=== Resultados Finais ===")
print(f"PPO Final Reward: {ppo_rewards[-1]:.2f}")
print(f"SAC Final Reward: {sac_rewards[-1]:.2f}")
print(f"PPO converge em ~{len([r for r in ppo_rewards if r > 0.8*max(ppo_rewards)])}k steps")
print(f"SAC converge em ~{len([r for r in sac_rewards if r > 0.8*max(sac_rewards)])}k steps")
Projeto 3: Custom Reward Function
Objetivo: Criar função de recompensa customizada para tarefa específica.
Tarefa: Robô deve pegar um cubo e colocá-lo em uma caixa.
import gymnasium as gym
from gymnasium import spaces
import numpy as np
class PickAndPlaceEnv(gym.Env):
"""Ambiente customizado: pegar e colocar objeto"""
def __init__(self):
super().__init__()
# Estado: [posição_robô(3), vel_robô(3), posição_cubo(3), posição_alvo(3)]
self.observation_space = spaces.Box(
low=-np.inf, high=np.inf, shape=(12,), dtype=np.float32
)
# Ação: [delta_x, delta_y, delta_z, gripper_force]
self.action_space = spaces.Box(
low=-1, high=1, shape=(4,), dtype=np.float32
)
self.reset()
def reset(self, seed=None):
super().reset(seed=seed)
# Posição inicial aleatória
self.robot_pos = np.array([0.0, 0.0, 0.5])
self.robot_vel = np.zeros(3)
self.cube_pos = np.random.uniform([-0.3, -0.3, 0.0], [0.3, 0.3, 0.0])
self.target_pos = np.array([0.5, 0.5, 0.2])
self.cube_grasped = False
self.steps = 0
return self._get_obs(), {}
def _get_obs(self):
return np.concatenate([
self.robot_pos,
self.robot_vel,
self.cube_pos,
self.target_pos
])
def step(self, action):
# Mover robô
self.robot_pos += action[:3] * 0.05
self.robot_vel = action[:3]
# Verificar se pegou cubo
dist_to_cube = np.linalg.norm(self.robot_pos - self.cube_pos)
if dist_to_cube < 0.05 and action[3] > 0.5: # Gripper fechado
self.cube_grasped = True
# Se pegou, cubo move com robô
if self.cube_grasped:
self.cube_pos = self.robot_pos.copy()
# Calcular recompensa (aqui está a mágica!)
reward = self._compute_reward()
# Termina se alcançou objetivo ou timeout
dist_to_target = np.linalg.norm(self.cube_pos - self.target_pos)
done = (dist_to_target < 0.1 and self.cube_grasped) or self.steps > 500
self.steps += 1
return self._get_obs(), reward, done, False, {}
def _compute_reward(self):
"""
Design de recompensa multi-stage para pick-and-place
"""
reward = 0.0
dist_robot_to_cube = np.linalg.norm(self.robot_pos - self.cube_pos)
dist_cube_to_target = np.linalg.norm(self.cube_pos - self.target_pos)
# FASE 1: Alcançar cubo
if not self.cube_grasped:
# Recompensa densa por aproximar do cubo
reward += 1.0 / (1.0 + dist_robot_to_cube)
# Bonus por tocar no cubo
if dist_robot_to_cube < 0.05:
reward += 2.0
# FASE 2: Pegar cubo
if self.cube_grasped:
reward += 5.0 # Bonus por pegar (one-time)
# Recompensa densa por levar ao alvo
reward += 2.0 / (1.0 + dist_cube_to_target)
# Bonus enorme por colocar no lugar
if dist_cube_to_target < 0.1:
reward += 20.0
# PENALIDADES
# - Penalidade leve por movimentos bruscos (energia)
velocity_penalty = np.sum(np.square(self.robot_vel)) * 0.01
reward -= velocity_penalty
# - Penalidade por sair dos limites
if np.any(np.abs(self.robot_pos[:2]) > 1.0):
reward -= 1.0
return reward
# Treinar com PPO
env = PickAndPlaceEnv()
model = PPO('MlpPolicy', env, verbose=1)
model.learn(total_timesteps=500_000)
model.save('pick_and_place_ppo')
📊 Monitoramento com TensorBoard
TensorBoard é essencial para debugar treinamento de RL:
# Instalar
pip install tensorboard
# Executar
tensorboard --logdir ./logs/
Métricas importantes:
- Episode Reward Mean: Recompensa média por episódio
- Deve aumentar consistentemente
- Se oscila muito: reward shaping ruim ou lr muito alta
- Episode Length Mean: Duração dos episódios
- Se sempre max_steps: robô não está terminando tarefa
- Se muito curto: robô está falhando rápido
- Policy Loss: Loss do actor
- Deve diminuir e estabilizar
- Se aumenta: treinamento instável
- Value Loss: Loss do critic
- Deve diminuir
- Se muito alta: critic não consegue estimar valores
- Explained Variance: Quão bem critic prevê retornos
- Ideal: > 0.7
- Baixa: critic ruim, vai prejudicar actor
- Entropy: Aleatoriedade da política
- Deve diminuir gradualmente
- Se cai rápido demais: exploração insuficiente
Exemplo de análise:
# Carregar logs do TensorBoard
from tensorboard.backend.event_processing import event_accumulator
import matplotlib.pyplot as plt
ea = event_accumulator.EventAccumulator('logs/PPO_1/')
ea.Reload()
# Extrair métricas
rewards = [(s.step, s.value) for s in ea.Scalars('rollout/ep_rew_mean')]
steps, values = zip(*rewards)
# Plotar
plt.figure(figsize=(12, 6))
plt.plot(steps, values)
plt.xlabel('Steps')
plt.ylabel('Mean Episode Reward')
plt.title('Learning Curve')
plt.grid(True)
plt.show()
🏆 Projeto Final
Sistema de Locomoção Bípede Avançado
Objetivo: Treinar um robô humanoide para:
- Andar para frente
- Andar para trás
- Virar esquerda/direita
- Manter equilíbrio em terrenos irregulares
Especificações:
- Ambiente:
Humanoid-v4ou Isaac Sim - Algoritmo: PPO ou SAC (sua escolha)
- Critério de sucesso: Andar 10m sem cair
# advanced_humanoid_locomotion.py
import gymnasium as gym
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import SubprocVecEnv, VecNormalize
from stable_baselines3.common.callbacks import EvalCallback, CheckpointCallback
def make_env():
def _init():
env = gym.make('Humanoid-v4')
return env
return _init
# 1. Criar ambientes paralelos
n_envs = 16
env = SubprocVecEnv([make_env() for _ in range(n_envs)])
# 2. Normalização de observações e recompensas
env = VecNormalize(
env,
norm_obs=True,
norm_reward=True,
clip_obs=10.0,
clip_reward=10.0
)
# 3. Callbacks
checkpoint_callback = CheckpointCallback(
save_freq=50_000,
save_path='./checkpoints/',
name_prefix='humanoid_advanced'
)
eval_callback = EvalCallback(
eval_env=gym.make('Humanoid-v4'),
best_model_save_path='./best_model/',
log_path='./eval_logs/',
eval_freq=20_000,
deterministic=True
)
# 4. Configurar modelo com hiperparâmetros otimizados
model = PPO(
'MlpPolicy',
env,
learning_rate=3e-4,
n_steps=2048,
batch_size=64,
n_epochs=10,
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2,
ent_coef=0.005, # Menos entropia para comportamento mais consistente
vf_coef=0.5,
max_grad_norm=0.5,
policy_kwargs=dict(
net_arch=[dict(pi=[256, 256], vf=[256, 256])],
activation_fn=torch.nn.ReLU
),
verbose=1,
tensorboard_log='./logs/'
)
# 5. Treinar
print("Iniciando treinamento...")
model.learn(
total_timesteps=20_000_000, # 20M steps
callback=[checkpoint_callback, eval_callback]
)
# 6. Salvar modelo final
model.save('humanoid_advanced_final')
env.save('vec_normalize.pkl')
# 7. Testar
print("Testando modelo treinado...")
obs = env.reset()
for _ in range(5000):
action, _states = model.predict(obs, deterministic=True)
obs, rewards, dones, info = env.step(action)
env.render()
print("Treinamento completo!")
Critérios de avaliação:
- Robô anda 10m sem cair
- Velocidade média > 0.5 m/s
- Mantém equilíbrio (corpo reto)
- Código documentado e limpo
- Logs e gráficos de treinamento
- Análise de performance
📚 Recursos Adicionais
Papers Fundamentais
- PPO: Proximal Policy Optimization Algorithms - Schulman et al., 2017
- SAC: Soft Actor-Critic: Off-Policy Maximum Entropy Deep RL - Haarnoja et al., 2018
- GAE: High-Dimensional Continuous Control Using Generalized Advantage Estimation - Schulman et al., 2015
Tutoriais e Cursos
Bibliotecas
pip install stable-baselines3[extra]
pip install gymnasium[mujoco]
pip install tensorboard
pip install optuna # Hyperparameter tuning
Comunidades
✅ Checklist de Conclusão
- Entendo a diferença entre on-policy e off-policy
- Implementei PPO do zero (ou com SB3)
- Implementei SAC do zero (ou com SB3)
- Comparei PPO vs SAC em um ambiente
- Projetei uma reward function customizada
- Fiz tuning de hiperparâmetros
- Treinei com ambientes vetorizados
- Analisei logs no TensorBoard
- Completei o projeto final
- Publiquei código no GitHub
🚀 Próximos Passos
Parabéns por dominar algoritmos avançados de RL! Agora você está pronto para:
- Módulo 3.2: Visão Computacional para Robótica
- Módulo 3.3: Large Behavior Models (LBMs)
- Nível 4: Aplicações Profissionais
→ Iniciar Módulo 3.2: Visão Computacional
Última atualização: 2025-10-29 Autor: Programa FTH Licença: MIT