Reinforcement Learning
stable-baselines3
LunarLander-v2
deep-reinforcement-learning
Eval Results (legacy)
Instructions to use sourav6565/test123 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- stable-baselines3
How to use sourav6565/test123 with stable-baselines3:
from huggingface_sb3 import load_from_hub checkpoint = load_from_hub( repo_id="sourav6565/test123", filename="{MODEL FILENAME}.zip", ) - Notebooks
- Google Colab
- Kaggle
Add from-scratch PPO (Unit 8): from-scratch/ppo_scratch.py
Browse files- from-scratch/ppo_scratch.py +154 -0
from-scratch/ppo_scratch.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random, time
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.optim as optim
|
| 6 |
+
from torch.distributions.categorical import Categorical
|
| 7 |
+
import gymnasium as gym
|
| 8 |
+
|
| 9 |
+
ENV_ID = "LunarLander-v2"
|
| 10 |
+
NUM_ENVS = 16
|
| 11 |
+
NUM_STEPS = 1024
|
| 12 |
+
TOTAL_TIMESTEPS = 1000000
|
| 13 |
+
LR = 2.5e-4
|
| 14 |
+
GAMMA = 0.999
|
| 15 |
+
GAE_LAMBDA = 0.98
|
| 16 |
+
NUM_MINIBATCHES = 32
|
| 17 |
+
UPDATE_EPOCHS = 4
|
| 18 |
+
CLIP_COEF = 0.2
|
| 19 |
+
ENT_COEF = 0.01
|
| 20 |
+
VF_COEF = 0.5
|
| 21 |
+
MAX_GRAD_NORM = 0.5
|
| 22 |
+
SEED = 1
|
| 23 |
+
|
| 24 |
+
BATCH_SIZE = NUM_ENVS * NUM_STEPS
|
| 25 |
+
MINIBATCH_SIZE = BATCH_SIZE // NUM_MINIBATCHES
|
| 26 |
+
NUM_UPDATES = TOTAL_TIMESTEPS // BATCH_SIZE
|
| 27 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 28 |
+
print("device:", device, "updates:", NUM_UPDATES, flush=True)
|
| 29 |
+
|
| 30 |
+
random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)
|
| 31 |
+
|
| 32 |
+
def make_env(idx):
|
| 33 |
+
def thunk():
|
| 34 |
+
env = gym.make(ENV_ID)
|
| 35 |
+
env = gym.wrappers.RecordEpisodeStatistics(env)
|
| 36 |
+
env.reset(seed=SEED + idx)
|
| 37 |
+
return env
|
| 38 |
+
return thunk
|
| 39 |
+
|
| 40 |
+
envs = gym.vector.SyncVectorEnv([make_env(i) for i in range(NUM_ENVS)])
|
| 41 |
+
obs_dim = int(np.array(envs.single_observation_space.shape).prod())
|
| 42 |
+
act_dim = envs.single_action_space.n
|
| 43 |
+
|
| 44 |
+
def layer_init(layer, std=np.sqrt(2), bias=0.0):
|
| 45 |
+
nn.init.orthogonal_(layer.weight, std)
|
| 46 |
+
nn.init.constant_(layer.bias, bias)
|
| 47 |
+
return layer
|
| 48 |
+
|
| 49 |
+
class Agent(nn.Module):
|
| 50 |
+
def __init__(self):
|
| 51 |
+
super().__init__()
|
| 52 |
+
self.critic = nn.Sequential(
|
| 53 |
+
layer_init(nn.Linear(obs_dim, 64)), nn.Tanh(),
|
| 54 |
+
layer_init(nn.Linear(64, 64)), nn.Tanh(),
|
| 55 |
+
layer_init(nn.Linear(64, 1), std=1.0))
|
| 56 |
+
self.actor = nn.Sequential(
|
| 57 |
+
layer_init(nn.Linear(obs_dim, 64)), nn.Tanh(),
|
| 58 |
+
layer_init(nn.Linear(64, 64)), nn.Tanh(),
|
| 59 |
+
layer_init(nn.Linear(64, act_dim), std=0.01))
|
| 60 |
+
def get_value(self, x):
|
| 61 |
+
return self.critic(x)
|
| 62 |
+
def get_action_and_value(self, x, action=None):
|
| 63 |
+
logits = self.actor(x)
|
| 64 |
+
probs = Categorical(logits=logits)
|
| 65 |
+
if action is None:
|
| 66 |
+
action = probs.sample()
|
| 67 |
+
return action, probs.log_prob(action), probs.entropy(), self.critic(x)
|
| 68 |
+
|
| 69 |
+
agent = Agent().to(device)
|
| 70 |
+
optimizer = optim.Adam(agent.parameters(), lr=LR, eps=1e-5)
|
| 71 |
+
|
| 72 |
+
obs = torch.zeros((NUM_STEPS, NUM_ENVS, obs_dim)).to(device)
|
| 73 |
+
actions = torch.zeros((NUM_STEPS, NUM_ENVS)).to(device)
|
| 74 |
+
logprobs = torch.zeros((NUM_STEPS, NUM_ENVS)).to(device)
|
| 75 |
+
rewards = torch.zeros((NUM_STEPS, NUM_ENVS)).to(device)
|
| 76 |
+
dones = torch.zeros((NUM_STEPS, NUM_ENVS)).to(device)
|
| 77 |
+
values = torch.zeros((NUM_STEPS, NUM_ENVS)).to(device)
|
| 78 |
+
|
| 79 |
+
global_step = 0
|
| 80 |
+
start_time = time.time()
|
| 81 |
+
next_obs, _ = envs.reset(seed=SEED)
|
| 82 |
+
next_obs = torch.Tensor(next_obs).to(device)
|
| 83 |
+
next_done = torch.zeros(NUM_ENVS).to(device)
|
| 84 |
+
|
| 85 |
+
for update in range(1, NUM_UPDATES + 1):
|
| 86 |
+
frac = 1.0 - (update - 1.0) / NUM_UPDATES
|
| 87 |
+
optimizer.param_groups[0]["lr"] = frac * LR
|
| 88 |
+
ep_returns = []
|
| 89 |
+
for step in range(NUM_STEPS):
|
| 90 |
+
global_step += NUM_ENVS
|
| 91 |
+
obs[step] = next_obs
|
| 92 |
+
dones[step] = next_done
|
| 93 |
+
with torch.no_grad():
|
| 94 |
+
action, logprob, _, value = agent.get_action_and_value(next_obs)
|
| 95 |
+
values[step] = value.flatten()
|
| 96 |
+
actions[step] = action
|
| 97 |
+
logprobs[step] = logprob
|
| 98 |
+
next_obs_np, reward, term, trunc, info = envs.step(action.cpu().numpy())
|
| 99 |
+
done = np.logical_or(term, trunc)
|
| 100 |
+
rewards[step] = torch.tensor(reward).to(device).view(-1)
|
| 101 |
+
next_obs = torch.Tensor(next_obs_np).to(device)
|
| 102 |
+
next_done = torch.Tensor(done).to(device)
|
| 103 |
+
if "final_info" in info:
|
| 104 |
+
for item in info["final_info"]:
|
| 105 |
+
if item and "episode" in item:
|
| 106 |
+
ep_returns.append(item["episode"]["r"])
|
| 107 |
+
with torch.no_grad():
|
| 108 |
+
next_value = agent.get_value(next_obs).reshape(1, -1)
|
| 109 |
+
advantages = torch.zeros_like(rewards).to(device)
|
| 110 |
+
lastgaelam = 0
|
| 111 |
+
for t in reversed(range(NUM_STEPS)):
|
| 112 |
+
if t == NUM_STEPS - 1:
|
| 113 |
+
nextnonterminal = 1.0 - next_done
|
| 114 |
+
nextvalues = next_value
|
| 115 |
+
else:
|
| 116 |
+
nextnonterminal = 1.0 - dones[t + 1]
|
| 117 |
+
nextvalues = values[t + 1]
|
| 118 |
+
delta = rewards[t] + GAMMA * nextvalues * nextnonterminal - values[t]
|
| 119 |
+
advantages[t] = lastgaelam = delta + GAMMA * GAE_LAMBDA * nextnonterminal * lastgaelam
|
| 120 |
+
returns = advantages + values
|
| 121 |
+
b_obs = obs.reshape((-1, obs_dim))
|
| 122 |
+
b_logprobs = logprobs.reshape(-1)
|
| 123 |
+
b_actions = actions.reshape(-1)
|
| 124 |
+
b_advantages = advantages.reshape(-1)
|
| 125 |
+
b_returns = returns.reshape(-1)
|
| 126 |
+
b_values = values.reshape(-1)
|
| 127 |
+
b_inds = np.arange(BATCH_SIZE)
|
| 128 |
+
for epoch in range(UPDATE_EPOCHS):
|
| 129 |
+
np.random.shuffle(b_inds)
|
| 130 |
+
for start in range(0, BATCH_SIZE, MINIBATCH_SIZE):
|
| 131 |
+
mb_inds = b_inds[start:start + MINIBATCH_SIZE]
|
| 132 |
+
_, newlogprob, entropy, newvalue = agent.get_action_and_value(b_obs[mb_inds], b_actions.long()[mb_inds])
|
| 133 |
+
logratio = newlogprob - b_logprobs[mb_inds]
|
| 134 |
+
ratio = logratio.exp()
|
| 135 |
+
mb_adv = b_advantages[mb_inds]
|
| 136 |
+
mb_adv = (mb_adv - mb_adv.mean()) / (mb_adv.std() + 1e-8)
|
| 137 |
+
pg_loss1 = -mb_adv * ratio
|
| 138 |
+
pg_loss2 = -mb_adv * torch.clamp(ratio, 1 - CLIP_COEF, 1 + CLIP_COEF)
|
| 139 |
+
pg_loss = torch.max(pg_loss1, pg_loss2).mean()
|
| 140 |
+
newvalue = newvalue.view(-1)
|
| 141 |
+
v_loss = 0.5 * ((newvalue - b_returns[mb_inds]) ** 2).mean()
|
| 142 |
+
entropy_loss = entropy.mean()
|
| 143 |
+
loss = pg_loss - ENT_COEF * entropy_loss + v_loss * VF_COEF
|
| 144 |
+
optimizer.zero_grad()
|
| 145 |
+
loss.backward()
|
| 146 |
+
nn.utils.clip_grad_norm_(agent.parameters(), MAX_GRAD_NORM)
|
| 147 |
+
optimizer.step()
|
| 148 |
+
mr = np.mean(ep_returns) if ep_returns else float("nan")
|
| 149 |
+
sps = int(global_step / (time.time() - start_time))
|
| 150 |
+
print(f"update {update}/{NUM_UPDATES} step {global_step} ep_rew_mean {mr:.1f} sps {sps}", flush=True)
|
| 151 |
+
|
| 152 |
+
envs.close()
|
| 153 |
+
torch.save(agent.state_dict(), "ppo_scratch_lunarlander.pt")
|
| 154 |
+
print("=== TRAINING DONE, weights saved ===", flush=True)
|