ezppo 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ezppo-0.0.0/.github/workflows/python-publish.yml +70 -0
- ezppo-0.0.0/.gitignore +27 -0
- ezppo-0.0.0/PKG-INFO +149 -0
- ezppo-0.0.0/README.md +137 -0
- ezppo-0.0.0/examples/continuous_pendulum.py +98 -0
- ezppo-0.0.0/examples/discrete_cartpole.py +94 -0
- ezppo-0.0.0/examples/eval_cartpole.py +86 -0
- ezppo-0.0.0/examples/eval_pendulum.py +83 -0
- ezppo-0.0.0/ezppo/__init__.py +29 -0
- ezppo-0.0.0/ezppo/agents.py +475 -0
- ezppo-0.0.0/ezppo/networks.py +145 -0
- ezppo-0.0.0/ezppo/normalizer.py +105 -0
- ezppo-0.0.0/ezppo.egg-info/PKG-INFO +149 -0
- ezppo-0.0.0/ezppo.egg-info/SOURCES.txt +18 -0
- ezppo-0.0.0/ezppo.egg-info/dependency_links.txt +1 -0
- ezppo-0.0.0/ezppo.egg-info/requires.txt +6 -0
- ezppo-0.0.0/ezppo.egg-info/top_level.txt +1 -0
- ezppo-0.0.0/pyproject.toml +22 -0
- ezppo-0.0.0/setup.cfg +4 -0
- ezppo-0.0.0/test_ezppo.py +107 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# This workflow will upload a Python Package to PyPI when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
|
3
|
+
|
|
4
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
5
|
+
# They are provided by a third-party and are governed by
|
|
6
|
+
# separate terms of service, privacy policy, and support
|
|
7
|
+
# documentation.
|
|
8
|
+
|
|
9
|
+
name: Upload Python Package
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
release-build:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.x"
|
|
28
|
+
|
|
29
|
+
- name: Build release distributions
|
|
30
|
+
run: |
|
|
31
|
+
# NOTE: put your own distribution build steps here.
|
|
32
|
+
python -m pip install build
|
|
33
|
+
python -m build
|
|
34
|
+
|
|
35
|
+
- name: Upload distributions
|
|
36
|
+
uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: release-dists
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
pypi-publish:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
needs:
|
|
44
|
+
- release-build
|
|
45
|
+
permissions:
|
|
46
|
+
# IMPORTANT: this permission is mandatory for trusted publishing
|
|
47
|
+
id-token: write
|
|
48
|
+
|
|
49
|
+
# Dedicated environments with protections for publishing are strongly recommended.
|
|
50
|
+
# For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
|
|
51
|
+
environment:
|
|
52
|
+
name: pypi
|
|
53
|
+
# OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
|
|
54
|
+
# url: https://pypi.org/p/YOURPROJECT
|
|
55
|
+
#
|
|
56
|
+
# ALTERNATIVE: if your GitHub Release name is the PyPI project version string
|
|
57
|
+
# ALTERNATIVE: exactly, uncomment the following line instead:
|
|
58
|
+
url: https://pypi.org/project/ezppo//${{ github.event.release.name }}
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Retrieve release distributions
|
|
62
|
+
uses: actions/download-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: release-dists
|
|
65
|
+
path: dist/
|
|
66
|
+
|
|
67
|
+
- name: Publish release distributions to PyPI
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
69
|
+
with:
|
|
70
|
+
packages-dir: dist/
|
ezppo-0.0.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Virtual environments
|
|
2
|
+
.venv/
|
|
3
|
+
venv/
|
|
4
|
+
env/
|
|
5
|
+
ppoVenv/
|
|
6
|
+
testingVenv/
|
|
7
|
+
|
|
8
|
+
# Python
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
*.egg-info/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
|
|
15
|
+
# IDE
|
|
16
|
+
.vscode/
|
|
17
|
+
.idea/
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# Checkpoints
|
|
24
|
+
checkpoints/
|
|
25
|
+
|
|
26
|
+
# Old files backup
|
|
27
|
+
old/
|
ezppo-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ezppo
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: A simple PPO library for Discrete and Continuous environments
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: torch
|
|
8
|
+
Requires-Dist: gymnasium
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# EzPPO
|
|
14
|
+
|
|
15
|
+
A simple, unified PPO (Proximal Policy Optimization) library for PyTorch. Supports both **discrete** and **continuous** action spaces.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Unified API**: Same training loop for discrete and continuous
|
|
20
|
+
- **Observation normalization**: Online running statistics
|
|
21
|
+
- **GAE (Generalized Advantage Estimation)**: For stable advantage estimation
|
|
22
|
+
- **Clipped surrogate objective**: Standard PPO clipping
|
|
23
|
+
- **Mini-batch training**: With random shuffling
|
|
24
|
+
- **Gradient clipping**: Prevents exploding gradients
|
|
25
|
+
- **Checkpoint save/load**: Resume training easily
|
|
26
|
+
- **Multi-environment support**: Parallel environments built-in
|
|
27
|
+
- **GPU support**: Automatic device management
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Discrete Actions (CartPole)
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from ezppo import DiscretePPOAgent
|
|
35
|
+
import gymnasium as gym
|
|
36
|
+
|
|
37
|
+
agent = DiscretePPOAgent(state_dim=4, action_dim=2)
|
|
38
|
+
env = gym.make("CartPole-v1")
|
|
39
|
+
|
|
40
|
+
state, _ = env.reset()
|
|
41
|
+
action, log_prob = agent.act(state) # Stochastic action
|
|
42
|
+
# action = agent.act_deterministic(state) # Greedy action (for eval)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Continuous Actions (Pendulum)
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from ezppo import ContinuousPPOAgent
|
|
49
|
+
import gymnasium as gym
|
|
50
|
+
|
|
51
|
+
agent = ContinuousPPOAgent(state_dim=3, action_dim=1)
|
|
52
|
+
env = gym.make("Pendulum-v1")
|
|
53
|
+
|
|
54
|
+
state, _ = env.reset()
|
|
55
|
+
action, log_prob, raw_action = agent.act(state)
|
|
56
|
+
# action = agent.act_deterministic(state)[0] # Mean action (for eval)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install torch gymnasium numpy
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Architecture
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
ezppo/
|
|
69
|
+
├── __init__.py # Public API
|
|
70
|
+
├── networks.py # DiscretePPO, ContinuousPPO, BasePPO
|
|
71
|
+
├── normalizer.py # RunningNormalizer
|
|
72
|
+
└── agents.py # DiscretePPOAgent, ContinuousPPOAgent
|
|
73
|
+
|
|
74
|
+
examples/
|
|
75
|
+
├── discrete_cartpole.py
|
|
76
|
+
└── continuous_pendulum.py
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Training Loop Template
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
trajectory = []
|
|
83
|
+
max_trajectory = 2048
|
|
84
|
+
|
|
85
|
+
for episode in range(num_episodes):
|
|
86
|
+
state, _ = env.reset()
|
|
87
|
+
done = False
|
|
88
|
+
|
|
89
|
+
while not done:
|
|
90
|
+
action, log_prob = agent.act(state)
|
|
91
|
+
next_state, reward, terminated, truncated, _ = env.step(action.item())
|
|
92
|
+
done = terminated or truncated
|
|
93
|
+
|
|
94
|
+
trajectory.append((state, action.item(), reward, log_prob.item(),
|
|
95
|
+
next_state, terminated))
|
|
96
|
+
state = next_state
|
|
97
|
+
|
|
98
|
+
if len(trajectory) >= max_trajectory:
|
|
99
|
+
agent.train(num_epochs=4, batch_size=64)
|
|
100
|
+
trajectory.clear()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API Reference
|
|
104
|
+
|
|
105
|
+
### Networks
|
|
106
|
+
|
|
107
|
+
| Class | Purpose |
|
|
108
|
+
|-------|---------|
|
|
109
|
+
| `BasePPO` | Base class with configurable architecture |
|
|
110
|
+
| `DiscretePPO` | Discrete actions → logits output |
|
|
111
|
+
| `ContinuousPPO` | Continuous actions → mean + learnable log_std |
|
|
112
|
+
|
|
113
|
+
### Agents
|
|
114
|
+
|
|
115
|
+
| Class | Purpose |
|
|
116
|
+
|-------|---------|
|
|
117
|
+
| `DiscretePPOAgent` | Discrete action space |
|
|
118
|
+
| `ContinuousPPOAgent` | Continuous action space |
|
|
119
|
+
|
|
120
|
+
### Key Methods
|
|
121
|
+
|
|
122
|
+
**Agent.act(state)** → `(action, log_prob)` or `(action, log_prob, raw_action)`
|
|
123
|
+
**Agent.act_deterministic(state)** → Deterministic action (for evaluation)
|
|
124
|
+
**Agent.train(num_epochs=4, batch_size=64)** → Train on trajectory
|
|
125
|
+
**Agent.store(packet)** → Add transition to trajectory
|
|
126
|
+
**Agent.clear()** → Clear trajectory buffer
|
|
127
|
+
**Agent.save(path)** → Save checkpoint
|
|
128
|
+
**Agent.load(path)** → Load checkpoint
|
|
129
|
+
|
|
130
|
+
## Configuration
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
agent = DiscretePPOAgent(
|
|
134
|
+
state_dim=4, action_dim=2,
|
|
135
|
+
architecture={ # Custom network architecture
|
|
136
|
+
"actor": [state_dim, 64, 64, action_dim],
|
|
137
|
+
"critic": [state_dim, 64, 64, 1]
|
|
138
|
+
},
|
|
139
|
+
lr=3e-4, gamma=0.99,
|
|
140
|
+
gae_lambda=0.95, epsilon=0.2,
|
|
141
|
+
critic_coef=0.5, entropy_coef=0.01,
|
|
142
|
+
num_envs=1, trajectory_size=2048,
|
|
143
|
+
device="cpu"
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
ezppo-0.0.0/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# EzPPO
|
|
2
|
+
|
|
3
|
+
A simple, unified PPO (Proximal Policy Optimization) library for PyTorch. Supports both **discrete** and **continuous** action spaces.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Unified API**: Same training loop for discrete and continuous
|
|
8
|
+
- **Observation normalization**: Online running statistics
|
|
9
|
+
- **GAE (Generalized Advantage Estimation)**: For stable advantage estimation
|
|
10
|
+
- **Clipped surrogate objective**: Standard PPO clipping
|
|
11
|
+
- **Mini-batch training**: With random shuffling
|
|
12
|
+
- **Gradient clipping**: Prevents exploding gradients
|
|
13
|
+
- **Checkpoint save/load**: Resume training easily
|
|
14
|
+
- **Multi-environment support**: Parallel environments built-in
|
|
15
|
+
- **GPU support**: Automatic device management
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Discrete Actions (CartPole)
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from ezppo import DiscretePPOAgent
|
|
23
|
+
import gymnasium as gym
|
|
24
|
+
|
|
25
|
+
agent = DiscretePPOAgent(state_dim=4, action_dim=2)
|
|
26
|
+
env = gym.make("CartPole-v1")
|
|
27
|
+
|
|
28
|
+
state, _ = env.reset()
|
|
29
|
+
action, log_prob = agent.act(state) # Stochastic action
|
|
30
|
+
# action = agent.act_deterministic(state) # Greedy action (for eval)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Continuous Actions (Pendulum)
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from ezppo import ContinuousPPOAgent
|
|
37
|
+
import gymnasium as gym
|
|
38
|
+
|
|
39
|
+
agent = ContinuousPPOAgent(state_dim=3, action_dim=1)
|
|
40
|
+
env = gym.make("Pendulum-v1")
|
|
41
|
+
|
|
42
|
+
state, _ = env.reset()
|
|
43
|
+
action, log_prob, raw_action = agent.act(state)
|
|
44
|
+
# action = agent.act_deterministic(state)[0] # Mean action (for eval)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install torch gymnasium numpy
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ezppo/
|
|
57
|
+
├── __init__.py # Public API
|
|
58
|
+
├── networks.py # DiscretePPO, ContinuousPPO, BasePPO
|
|
59
|
+
├── normalizer.py # RunningNormalizer
|
|
60
|
+
└── agents.py # DiscretePPOAgent, ContinuousPPOAgent
|
|
61
|
+
|
|
62
|
+
examples/
|
|
63
|
+
├── discrete_cartpole.py
|
|
64
|
+
└── continuous_pendulum.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Training Loop Template
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
trajectory = []
|
|
71
|
+
max_trajectory = 2048
|
|
72
|
+
|
|
73
|
+
for episode in range(num_episodes):
|
|
74
|
+
state, _ = env.reset()
|
|
75
|
+
done = False
|
|
76
|
+
|
|
77
|
+
while not done:
|
|
78
|
+
action, log_prob = agent.act(state)
|
|
79
|
+
next_state, reward, terminated, truncated, _ = env.step(action.item())
|
|
80
|
+
done = terminated or truncated
|
|
81
|
+
|
|
82
|
+
trajectory.append((state, action.item(), reward, log_prob.item(),
|
|
83
|
+
next_state, terminated))
|
|
84
|
+
state = next_state
|
|
85
|
+
|
|
86
|
+
if len(trajectory) >= max_trajectory:
|
|
87
|
+
agent.train(num_epochs=4, batch_size=64)
|
|
88
|
+
trajectory.clear()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API Reference
|
|
92
|
+
|
|
93
|
+
### Networks
|
|
94
|
+
|
|
95
|
+
| Class | Purpose |
|
|
96
|
+
|-------|---------|
|
|
97
|
+
| `BasePPO` | Base class with configurable architecture |
|
|
98
|
+
| `DiscretePPO` | Discrete actions → logits output |
|
|
99
|
+
| `ContinuousPPO` | Continuous actions → mean + learnable log_std |
|
|
100
|
+
|
|
101
|
+
### Agents
|
|
102
|
+
|
|
103
|
+
| Class | Purpose |
|
|
104
|
+
|-------|---------|
|
|
105
|
+
| `DiscretePPOAgent` | Discrete action space |
|
|
106
|
+
| `ContinuousPPOAgent` | Continuous action space |
|
|
107
|
+
|
|
108
|
+
### Key Methods
|
|
109
|
+
|
|
110
|
+
**Agent.act(state)** → `(action, log_prob)` or `(action, log_prob, raw_action)`
|
|
111
|
+
**Agent.act_deterministic(state)** → Deterministic action (for evaluation)
|
|
112
|
+
**Agent.train(num_epochs=4, batch_size=64)** → Train on trajectory
|
|
113
|
+
**Agent.store(packet)** → Add transition to trajectory
|
|
114
|
+
**Agent.clear()** → Clear trajectory buffer
|
|
115
|
+
**Agent.save(path)** → Save checkpoint
|
|
116
|
+
**Agent.load(path)** → Load checkpoint
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
agent = DiscretePPOAgent(
|
|
122
|
+
state_dim=4, action_dim=2,
|
|
123
|
+
architecture={ # Custom network architecture
|
|
124
|
+
"actor": [state_dim, 64, 64, action_dim],
|
|
125
|
+
"critic": [state_dim, 64, 64, 1]
|
|
126
|
+
},
|
|
127
|
+
lr=3e-4, gamma=0.99,
|
|
128
|
+
gae_lambda=0.95, epsilon=0.2,
|
|
129
|
+
critic_coef=0.5, entropy_coef=0.01,
|
|
130
|
+
num_envs=1, trajectory_size=2048,
|
|
131
|
+
device="cpu"
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Continuous PPO example: Pendulum-v1
|
|
3
|
+
|
|
4
|
+
Trains a PPO agent to swing up and balance the pendulum.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import gymnasium as gym
|
|
8
|
+
import numpy as np
|
|
9
|
+
from ezppo import ContinuousPPOAgent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
# Environment setup
|
|
14
|
+
env_id = "Pendulum-v1"
|
|
15
|
+
env = gym.make(env_id, render_mode="rgb_array")
|
|
16
|
+
|
|
17
|
+
state_dim = env.observation_space.shape[0]
|
|
18
|
+
action_dim = env.action_space.shape[0]
|
|
19
|
+
|
|
20
|
+
print(f"Training on {env_id}")
|
|
21
|
+
print(f"State dim: {state_dim}, Action dim: {action_dim}")
|
|
22
|
+
print(f"Action range: [{env.action_space.low[0]}, {env.action_space.high[0]}]")
|
|
23
|
+
|
|
24
|
+
# Create agent — performant hyperparams, normalization off for stability
|
|
25
|
+
agent = ContinuousPPOAgent(
|
|
26
|
+
state_dim=state_dim,
|
|
27
|
+
action_dim=action_dim,
|
|
28
|
+
architecture={
|
|
29
|
+
"actor": [state_dim, 256, 256, action_dim],
|
|
30
|
+
"critic": [state_dim, 256, 256, 1]
|
|
31
|
+
},
|
|
32
|
+
lr=3e-4,
|
|
33
|
+
gamma=0.99,
|
|
34
|
+
gae_lambda=0.95,
|
|
35
|
+
epsilon=0.2,
|
|
36
|
+
critic_coef=0.5,
|
|
37
|
+
entropy_coef=0.01,
|
|
38
|
+
num_envs=1,
|
|
39
|
+
trajectory_size=512,
|
|
40
|
+
device="cpu",
|
|
41
|
+
normalize_observations=False,
|
|
42
|
+
default_num_epochs=10,
|
|
43
|
+
default_batch_size=64
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Training loop
|
|
47
|
+
max_episodes = 5000
|
|
48
|
+
best_reward = -float("inf")
|
|
49
|
+
|
|
50
|
+
for episode in range(max_episodes):
|
|
51
|
+
state, info = env.reset()
|
|
52
|
+
total_reward = 0
|
|
53
|
+
done = False
|
|
54
|
+
|
|
55
|
+
while not done:
|
|
56
|
+
action, log_prob, raw_action = agent.act(state)
|
|
57
|
+
# Scale action to env range: tanh gives [-1,1], scale to action space
|
|
58
|
+
action_scaled = action * env.action_space.high[0]
|
|
59
|
+
next_state, reward, terminated, truncated, info = env.step(action_scaled)
|
|
60
|
+
done = terminated or truncated
|
|
61
|
+
total_reward += reward
|
|
62
|
+
|
|
63
|
+
agent.store((state, raw_action, reward, log_prob, next_state, terminated))
|
|
64
|
+
state = next_state
|
|
65
|
+
|
|
66
|
+
# Train when trajectory is full
|
|
67
|
+
if len(agent.trajectory) >= agent._trajectory_size:
|
|
68
|
+
agent.train()
|
|
69
|
+
agent.clear()
|
|
70
|
+
|
|
71
|
+
if total_reward > best_reward:
|
|
72
|
+
best_reward = total_reward
|
|
73
|
+
|
|
74
|
+
if (episode + 1) % 100 == 0:
|
|
75
|
+
print(f"Episode {episode+1}/{max_episodes} | Reward: {total_reward:.1f} | Best: {best_reward:.1f}")
|
|
76
|
+
|
|
77
|
+
# Save checkpoint
|
|
78
|
+
agent.save("checkpoints/pendulum_ppo", None)
|
|
79
|
+
print("Checkpoint saved!")
|
|
80
|
+
|
|
81
|
+
# Evaluation
|
|
82
|
+
print("\n--- Evaluation ---")
|
|
83
|
+
eval_env = gym.make(env_id, render_mode="human")
|
|
84
|
+
for eval_ep in range(5):
|
|
85
|
+
state, info = eval_env.reset()
|
|
86
|
+
total_reward = 0
|
|
87
|
+
done = False
|
|
88
|
+
while not done:
|
|
89
|
+
action = agent.act_deterministic(state)[0] # Get deterministic action
|
|
90
|
+
action_scaled = action * eval_env.action_space.high[0]
|
|
91
|
+
state, reward, terminated, truncated, info = eval_env.step(action_scaled)
|
|
92
|
+
done = terminated or truncated
|
|
93
|
+
total_reward += reward
|
|
94
|
+
print(f"Eval episode: {total_reward}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Discrete PPO example: CartPole-v1
|
|
3
|
+
|
|
4
|
+
Trains a PPO agent to balance the pole on a cart.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import gymnasium as gym
|
|
8
|
+
from ezppo import DiscretePPOAgent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
# Environment setup
|
|
13
|
+
env_id = "CartPole-v1"
|
|
14
|
+
env = gym.make(env_id, render_mode="rgb_array")
|
|
15
|
+
|
|
16
|
+
state_dim = env.observation_space.shape[0]
|
|
17
|
+
action_dim = env.action_space.n
|
|
18
|
+
|
|
19
|
+
print(f"Training on {env_id}")
|
|
20
|
+
print(f"State dim: {state_dim}, Action dim: {action_dim}")
|
|
21
|
+
|
|
22
|
+
# Create agent — performant hyperparams based on old reference impl
|
|
23
|
+
agent = DiscretePPOAgent(
|
|
24
|
+
state_dim=state_dim,
|
|
25
|
+
action_dim=action_dim,
|
|
26
|
+
architecture={
|
|
27
|
+
"actor": [state_dim, 256, 256, action_dim],
|
|
28
|
+
"critic": [state_dim, 256, 256, 1]
|
|
29
|
+
},
|
|
30
|
+
lr=3e-4,
|
|
31
|
+
gamma=0.99,
|
|
32
|
+
gae_lambda=0.95,
|
|
33
|
+
epsilon=0.2,
|
|
34
|
+
critic_coef=0.5,
|
|
35
|
+
entropy_coef=0.001,
|
|
36
|
+
num_envs=1,
|
|
37
|
+
trajectory_size=512,
|
|
38
|
+
device="cpu",
|
|
39
|
+
normalize_observations=False,
|
|
40
|
+
default_num_epochs=10,
|
|
41
|
+
default_batch_size=64
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Training loop
|
|
45
|
+
max_episodes = 2000
|
|
46
|
+
best_reward = -float("inf")
|
|
47
|
+
|
|
48
|
+
for episode in range(max_episodes):
|
|
49
|
+
state, info = env.reset()
|
|
50
|
+
total_reward = 0
|
|
51
|
+
done = False
|
|
52
|
+
|
|
53
|
+
while not done:
|
|
54
|
+
action, log_prob = agent.act(state)
|
|
55
|
+
next_state, reward, terminated, truncated, info = env.step(action.item())
|
|
56
|
+
done = terminated or truncated
|
|
57
|
+
total_reward += reward
|
|
58
|
+
|
|
59
|
+
agent.store((state, action.item(), reward, log_prob.item(), next_state, terminated))
|
|
60
|
+
state = next_state
|
|
61
|
+
|
|
62
|
+
# Train when trajectory is full
|
|
63
|
+
if len(agent.trajectory) >= agent._trajectory_size:
|
|
64
|
+
agent.train()
|
|
65
|
+
agent.clear()
|
|
66
|
+
|
|
67
|
+
# Track best
|
|
68
|
+
if total_reward > best_reward:
|
|
69
|
+
best_reward = total_reward
|
|
70
|
+
|
|
71
|
+
if (episode + 1) % 100 == 0:
|
|
72
|
+
print(f"Episode {episode+1}/{max_episodes} | Reward: {total_reward:.1f} | Best: {best_reward:.1f}")
|
|
73
|
+
|
|
74
|
+
# Save checkpoint
|
|
75
|
+
agent.save("checkpoints/cartpole_ppo", "checkpoints/cartpole_ppo_norm")
|
|
76
|
+
print("Checkpoint saved!")
|
|
77
|
+
|
|
78
|
+
# Evaluation
|
|
79
|
+
print("\n--- Evaluation ---")
|
|
80
|
+
eval_env = gym.make(env_id, render_mode="human")
|
|
81
|
+
for eval_ep in range(5):
|
|
82
|
+
state, info = eval_env.reset()
|
|
83
|
+
total_reward = 0
|
|
84
|
+
done = False
|
|
85
|
+
while not done:
|
|
86
|
+
action = agent.act_deterministic(state)
|
|
87
|
+
state, reward, terminated, truncated, info = eval_env.step(action)
|
|
88
|
+
done = terminated or truncated
|
|
89
|
+
total_reward += reward
|
|
90
|
+
print(f"Eval episode: {total_reward}")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Evaluate a trained Discrete PPO checkpoint on CartPole-v1.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python eval_cartpole.py --checkpoint checkpoints/cartpole_ppo
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import gymnasium as gym
|
|
10
|
+
import numpy as np
|
|
11
|
+
from ezppo import DiscretePPOAgent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
parser = argparse.ArgumentParser(description="Evaluate a trained PPO checkpoint on CartPole-v1")
|
|
16
|
+
parser.add_argument("--checkpoint", type=str, default="checkpoints/cartpole_ppo",
|
|
17
|
+
help="Path to the saved model checkpoint (without extension)")
|
|
18
|
+
parser.add_argument("--episodes", type=int, default=10,
|
|
19
|
+
help="Number of evaluation episodes")
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
# Environment setup
|
|
23
|
+
env_id = "CartPole-v1"
|
|
24
|
+
env = gym.make(env_id, render_mode="human")
|
|
25
|
+
|
|
26
|
+
state_dim = env.observation_space.shape[0]
|
|
27
|
+
action_dim = env.action_space.n
|
|
28
|
+
|
|
29
|
+
print(f"Evaluating on {env_id}")
|
|
30
|
+
print(f"State dim: {state_dim}, Action dim: {action_dim}")
|
|
31
|
+
|
|
32
|
+
# Create agent — matches the hyperparams used in training
|
|
33
|
+
agent = DiscretePPOAgent(
|
|
34
|
+
state_dim=state_dim,
|
|
35
|
+
action_dim=action_dim,
|
|
36
|
+
architecture={
|
|
37
|
+
"actor": [state_dim, 256, 256, action_dim],
|
|
38
|
+
"critic": [state_dim, 256, 256, 1]
|
|
39
|
+
},
|
|
40
|
+
lr=3e-4,
|
|
41
|
+
gamma=0.99,
|
|
42
|
+
gae_lambda=0.95,
|
|
43
|
+
epsilon=0.2,
|
|
44
|
+
critic_coef=0.5,
|
|
45
|
+
entropy_coef=0.001,
|
|
46
|
+
num_envs=1,
|
|
47
|
+
trajectory_size=512,
|
|
48
|
+
device="cpu",
|
|
49
|
+
normalize_observations=False,
|
|
50
|
+
default_num_epochs=10,
|
|
51
|
+
default_batch_size=64
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Load checkpoint — normalizer path only needed if normalization was enabled during training
|
|
55
|
+
agent.load(args.checkpoint)
|
|
56
|
+
print(f"Loaded checkpoint from: {args.checkpoint}\n")
|
|
57
|
+
|
|
58
|
+
# Evaluation
|
|
59
|
+
rewards = []
|
|
60
|
+
lengths = []
|
|
61
|
+
print(f"--- Evaluation ({args.episodes} episodes) ---")
|
|
62
|
+
for ep in range(args.episodes):
|
|
63
|
+
state, info = env.reset()
|
|
64
|
+
total_reward = 0
|
|
65
|
+
done = False
|
|
66
|
+
steps = 0
|
|
67
|
+
while not done:
|
|
68
|
+
action = agent.act_deterministic(state)
|
|
69
|
+
state, reward, terminated, truncated, info = env.step(action)
|
|
70
|
+
done = terminated or truncated
|
|
71
|
+
total_reward += reward
|
|
72
|
+
steps += 1
|
|
73
|
+
rewards.append(total_reward)
|
|
74
|
+
lengths.append(steps)
|
|
75
|
+
print(f" Episode {ep+1}/{args.episodes}: reward={total_reward:.1f}, steps={steps}")
|
|
76
|
+
|
|
77
|
+
print(f"\nAverage reward: {np.mean(rewards):.1f} +/- {np.std(rewards):.1f}")
|
|
78
|
+
print(f"Average length: {np.mean(lengths):.1f} +/- {np.std(lengths):.1f}")
|
|
79
|
+
print(f"Best reward: {np.max(rewards):.1f}")
|
|
80
|
+
print(f"Worst reward: {np.min(rewards):.1f}")
|
|
81
|
+
success_rate = np.mean([r >= 500 for r in rewards]) * 100
|
|
82
|
+
print(f"Success rate (reward >= 500): {success_rate:.0f}%")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
main()
|