sourav6565 commited on
Commit
06e653b
·
verified ·
1 Parent(s): dc87230

Add from-scratch PPO (Unit 8): from-scratch/ppo_scratch.py

Browse files
Files changed (1) hide show
  1. 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)