# ๐ฆพ Pusher-v5 PPO // ์ธ๊ณต์ง๋ฅ ๋ก๋ณดํฑ์ค ๊ด์ ํ๋ธ & ์ค์๊ฐ ์ ์ด ์ฝํ
[](README.md)
[](README_KR.md)
[](https://huggingface.co/hwihwalab/pusher-v5-ppo)
[](https://github.com/Hwihwa-Lab/pusher-v5-ppo)
[](https://github.com/Hwihwa-Lab/pusher-v5-ppo/blob/main/LICENSE)
[](https://gymnasium.farama.org/environments/mujoco/pusher/)
[](https://pytorch.org)
[](https://stable-baselines3.readthedocs.io)
> **Gymnasium MuJoCo 7์์ ๋ ๋ก๋ด ํ ์ฐ์ ์ ์ด ํ
๋ ๋ฉํธ๋ฆฌ & PPO ์ฌ์ธต ๊ฐํํ์ต ํตํฉ ํ๋ซํผ**
> *[ ๐ English Documentation ](README.md) | [ ๐ฐ๐ท ํ๊ตญ์ด ๋งค๋ด์ผ ](README_KR.md)*
๋ณธ ๋ฆฌํฌ์งํ ๋ฆฌ๋ [Gymnasium](https://gymnasium.farama.org/environments/mujoco/pusher/)์ ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
ํ๊ฒฝ์ธ `Pusher-v5`์์ 7์์ ๋(7-DOF) ๋ก๋ด ํ์ด ์ํตํ ๋ฌผ์ฒด๋ฅผ ๋ชฉํ ์ง์ ๊น์ง ์ ํํ๊ฒ ๋ฐ์ด ๋ฃ๋๋ก ํ์ต์ํค๋ **Stable-Baselines3 PPO ๊ฐํํ์ต ์์คํ
** ๋ฐ **์ค์๊ฐ 30 FPS ์น ๊ด์ ์ฝํ(FastAPI + WebSocket)**์ ์ ๊ณตํฉ๋๋ค.
---
## ๐ ํต์ฌ ์ฑ๋ฅ ๋ฐ ๋ชจ๋ธ ์คํ
| ํญ๋ชฉ | ์์ธ ๊ท๊ฒฉ ๋ฐ ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ |
| :--- | :--- |
| **ํ์ต ํ๊ฒฝ** | Gymnasium MuJoCo `Pusher-v5` (7์์ ๋ ๋ก๋ด ๋งค๋ํฐ๋ ์ดํฐ) |
| **๊ด์ธก ๊ณต๊ฐ (Observation)** | 23์ฐจ์ ์ฐ์ ๋ฒกํฐ (๊ด์ ๊ฐ๋ 7, ๊ด์ ๊ฐ์๋ 7, ํ ์์น 3, ๋ฌผ์ฒด ์์น 3, ๊ณจ ์์น 3) |
| **ํ๋ ๊ณต๊ฐ (Action)** | 7์ฐจ์ ์ฐ์ ๋ชจํฐ ํ ํฌ ์ ์ด๊ฐ (`Box[-2.0, 2.0]`, float32) |
| **ํ์ต ์๊ณ ๋ฆฌ์ฆ** | Proximal Policy Optimization (PPO, `MlpPolicy`) |
| **๋ฐฑ์๋ ํ๋ ์์ํฌ** | Stable-Baselines3 / PyTorch / FastAPI / Starlette WebSockets |
| **์ด๊ธฐ ๊ธฐ์ด ์ ์ (Step 0)** | **`-57.51 pts`** (๋ฌด์์ ๊ด์ ํ์, ํ-๋ฌผ์ฒด ๊ฑฐ๋ฆฌ 0.215m) |
| **์ต์ข
์๋ ด ์ ์ (Step 300k+)**| **`-32.42 ยฑ 4.30 pts`** *(์ต๊ณ ์ํผ์๋: **`-26.15 pts`**)* |
| **ํ-๋ฌผ์ฒด ์ ์ด ์ ๋ฐ๋** | **`0.028 m (2.8cm)`** (์ํต ๋ฌผ์ฒด ์๋ฒฝ ํฌ์ฐฉ ๋ฐ ๋ฐ์ฐฉ) |
| **๋ชฉํ ์ง์ ๊ทผ์ ์ ๋ฐ๋** | **`0.054 m`** (๋ชฉํ ๊ณจ๋ ์์ฐฉ ๋ฐ ํธ์ฑ ์๋ฃ) |
---
## ๐๏ธ ์์คํ
์ํคํ
์ฒ ๋ฐ ๋ฐ์ดํฐ ํ๋ฆ
```mermaid
flowchart TD
subgraph Live_Web_Cockpit ["1-ํ๋ฉด ์ ๋ก ์คํฌ๋กค ๋ก๋ณดํฑ์ค ๊ด์ ์ฝํ"]
W1["HTML5 / CSS3 / Vanilla JS ํด๋ผ์ด์ธํธ"] <-->|"WebSocket /ws/simulation @ 30 FPS"| S1["FastAPI ๊ณ ์ฑ๋ฅ ์๋ฒ"]
S1 -->|"Base64 JPEG ๋ฌผ๋ฆฌ ์คํธ๋ฆผ"| W1
S1 -->|"7-DOF ๋ฐ์ดํด๋ผ ๋ชจํฐ ํ ํฌ (-2.0 ~ +2.0 Nm)"| W1
S1 -->|"3D ๋ฐ์นด๋ฅดํธ ์ขํ: ํ, ๋ฌผ์ฒด, ๊ณจ๋"| W1
W1 -->|"์ ์ด ๋ช
๋ น: Start, Pause, Step, Reset, Policy"| S1
end
subgraph Analytics_Deck ["4-ํญ ๋ถ์ ๋ฐ ๋ฆฌํ๋ ์ด ๋ฑ"]
T1["1ํญ: ์ค์๊ฐ ํ
๋ ๋ฉํธ๋ฆฌ ์ฐจํธ (Raw ๋ณด์ & 20-Ep ์ด๋ํ๊ท ์ )"]
T2["2ํญ: ๋ง์ผ์คํค ๋ฆฌํ๋ ์ด ๊ฐค๋ฌ๋ฆฌ (16:9 ์์ด๋ ๋น๋์ค ์นด๋)"]
T3["3ํญ: ์ค์๊ฐ PPO ํฐ๋ฏธ๋ ๋ก๊ทธ (ํ์ต ์งํ๋ฅ & FPS ์ฝ์)"]
T4["4ํญ: ํ๊ฒฝ ์คํ & ๋ณด์ ํจ์ ์ํ ๊ณต์"]
end
subgraph Deep_RL_Pipeline ["PPO ๊ฐํํ์ต ํ์ดํ๋ผ์ธ"]
TR1["train.py / ๋ฐฑ๊ทธ๋ผ์ด๋ ํ์ต ์ค๋ ๋"] --> TR2["Pusher-v5 ๋ฌผ๋ฆฌ ์์ง"]
TR2 --> TR3["VisualProgressCallback"]
TR3 --> TR4["Step 0๋ถํฐ 300k๊น์ง์ ๋น๋์ค(MP4) ๋ฐ GIF"]
TR3 --> TR5["4๋ถํ ํ์ต ๋ถ์ ๊ทธ๋ํ & metrics.json"]
TR4 & TR5 --> TR6["์ํด๋ฆญ ์์ถ ํจํค์ง: ppo_pusher_bundle.zip"]
end
```
---
## ๐ฎ ๊ด์ ์ผํฐ ์ฃผ์ ๊ธฐ๋ฅ
1. **์ด์ ์ง์ฐ 30 FPS ์ค์๊ฐ ๋ฌผ๋ฆฌ ์บ๋ฒ์ค**:
- ์น์์ผ์ ํตํ ๊ณ ์ ๋ ๋๋ง ์คํธ๋ฆผ ๋ฐ ์ค์๊ฐ FPS ์นด์ดํฐ.
- 7๊ฐ ๊ด์ ๋ชจํฐ ํ ํฌ๋ฅผ ์ค์ 0.0 ๊ธฐ์ค์ผ๋ก ์์(์ฒญ๋ก์, Cyan)์ ์์(์ฅ๋ฏธ์, Rose)๋ก ์ค์๊ฐ ์๊ฐํํ๋ **7-DOF ๋ฐ์ดํด๋ผ ํ ํฌ ๊ฒ์ด์ง**.
- ๋ก๋ด ์๊ฐ๋ฝ ๋(Tip), ๋ฌผ์ฒด(Object), ๊ณจ๋(Goal)์ 3D ๊ณต๊ฐ ์ขํ X, Y, Z ๋ฏธํฐ ๋จ์ ์ ๋ฐ ์ถ์ .
- ํค๋ณด๋ ๋จ์ถํค ์ง์ (Space ์์/์ผ์์ ์ง, R ๋ฆฌ์
, S 1์คํ
์ ์ง, H HUD ์จ์คํ).
2. **๊ฐํํ์ต ์์ฐ ํ๋ฆฌ์
์
๋ ํฐ**:
- `500 Ep (50k Steps โข ~12s) - ๋น ๋ฅธ ํ
์คํธ`
- `2,000 Ep (200k Steps โข ~45s) - ๊ธฐ๋ณธ ํธ์ฑ ํ์ต`
- `5,000 Ep (500k Steps โข ~1.8m) โ
์ถ์ฒ ์์ฑํ ์ ์ฑ
`
- `10,000 Ep (1M Steps โข ~3.5m) - ์ด์ ๋ฐ ์๋ ด`
- `โ๏ธ ์ฌ์ฉ์ ์ ์(Custom) ์คํ
์ค์ `
3. **16:9 ์์ด๋ ๋ฉํฐ ๋น๋์ค ์นด๋ ๊ฐค๋ฌ๋ฆฌ**:
- ์คํ
0๋ถํฐ ์ต์ข
์คํ
๊น์ง AI์ ์ฑ์ฅ ๊ณผ์ ์ ๋ทํ๋ฆญ์ค ์ธ๋ค์ผ์ฒ๋ผ ํ๋์ ๊ฐ๋ก๋ก ๋น๊ตํ๋ ๋น๋์ค ์นด๋ ๋ฆด.
- ์นด๋๋ณ ๋
๋ฆฝ **`MP4 ๋น๋์ค`** ๋ฐ **`GIF ์ ๋๋ฉ์ด์
`** ์ฆ์ ๋ค์ด๋ก๋ ๊ธฐ๋ฅ.
4. **๋จ์ผ ZIP ํ์ผ ์๋ ํจํค์ง**:
- ์๋จ `Download Bundle` ๋ฒํผ์ ๋๋ฅด๋ฉด ํ์ต๋ ๋ชจ๋ธ ๊ฐ์ค์น(`ppo_pusher.zip`), ๋น๋์ค, ์ฐจํธ, ๋ฉํธ๋ฆญ์ด ํ๋์ ์์ถ ํ์ผ(`ppo_pusher_bundle.zip`)๋ก ์ฆ์ ๋ค์ด๋ก๋๋ฉ๋๋ค.
---
## ๐ ๋น ๋ฅธ ์์ ๊ฐ์ด๋ (Quickstart)
### 1. ํ๊ฒฝ ์ค์น
```bash
git clone https://github.com/Hwihwa-Lab/pusher-v5-ppo.git
cd pusher-v5-ppo
pip install -r requirements.txt
```
### 2. ์ค์๊ฐ ์น ๊ด์ ์ผํฐ ์คํ
```bash
python app.py
```
๋ธ๋ผ์ฐ์ ์์ **`http://localhost:8000`** ์ ์.
### 3. ํ๊น
ํ์ด์ค ์ํด๋ฆญ ์๋ ๋ฐฐํฌ
```bash
python deploy_to_hf.py
```
### 4. CLI ๊ธฐ๋ฐ ๋
๋ฆฝ ํ์ต ๋ฐ ๋ชจ๋ธ ํ๊ฐ
```bash
# PPO ์์ด์ ํธ ํ์ต ์คํ
python train.py --timesteps 300000 --eval_freq 30000
# ํ์ต ์๋ฃ๋ ๋ชจ๋ธ ๋
๋ฆฝ ํ๊ฐ ๋ฐ ๋น๋์ค ์ถ์ถ
python evaluate.py --model_path ./results/ppo_pusher.zip --episodes 5
```
---
## ๐ 5์ค ํ์ด์ฌ ๋น ๋ฅธ ํ๊ฐ ์ค๋ํซ (Quick Evaluation)
๋ณธ ๋ฆฌํฌ์งํ ๋ฆฌ์ ํ์ต ์๋ฃ ๊ฐ์ค์น๋ฅผ ๋ถ๋ฌ์ 5์ค์ ํ์ด์ฌ ์ฝ๋๋ก ์ฆ์ ์๋ฎฌ๋ ์ด์
์ ์คํํ ์ ์์ต๋๋ค:
```python
import gymnasium as gym
from stable_baselines3 import PPO
# 1. Pusher-v5 ํ๊ฒฝ ์ด๊ธฐํ ๋ฐ ์์ฑ ๊ฐ์ค์น ๋ก๋
env = gym.make("Pusher-v5", render_mode="human")
model = PPO.load("results/ppo_pusher.zip")
# 2. ๊ฒฐ์ ๋ก ์ ํธ์ฑ ์ ์ด ๋กค์์ ์คํ
obs, _ = env.reset()
done = False
while not done:
action, _ = model.predict(obs, deterministic=True)
obs, reward, terminated, truncated, _ = env.step(action)
done = terminated or truncated
env.close()
```
---
## โจ๏ธ ํค๋ณด๋ ๋จ์ถํค ์๋ด (Keyboard Shortcuts)
| ๋จ์ถํค | ์กฐ์ ๊ธฐ๋ฅ | ์ค๋ช
|
| :---: | :--- | :--- |
| **`Space`** | **์์ / ์ผ์์ ์ง** | ์ค์๊ฐ 30 FPS MuJoCo ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
ํ ๊ธ |
| **`R`** | **ํ๊ฒฝ ์ด๊ธฐํ (Reset)** | ๋ก๋ด ํ, ์ํต ๋ฌผ์ฒด, ๋ชฉํ ๊ณจ๋๋ฅผ ์๋ก์ด ๋๋ค ์์น๋ก ์ฌ๋ฐฐ์น |
| **`S`** | **1์คํ
์ ์ง (Step Once)** | ๋ฌผ๋ฆฌ ์์ง์ 1๋จ์ ํ์์คํ
(0.05์ด) ์ ์ง |
| **`H`** | **HUD ์จ์คํ ํ ๊ธ** | ์บ๋ฒ์ค ํ๋ฉด ์ ํ
๋ ๋ฉํธ๋ฆฌ ์ค๋ฒ๋ ์ด ํ์/์จ๊น |
---
## ๐ก๏ธ AI ์์ง๋์ด๋ง ๊ฑฐ๋ฒ๋์ค ๋ฐ ๋ฌธ์ ์ฒด๊ณ
๋ณธ ์์คํ
์ ๊ฐํํ์ต ์๋ฎฌ๋ ์ด์
์ ๋ฌผ๋ฆฌ์ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์กดํ๊ณ ๋ฐ์ด๋ธ-์ฝ๋ฉ ๋๋ฆฌํํธ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ ๋ฐํ ์์ง๋์ด๋ง ๋ฌธ์ ํ๋กํ ์ฝ(GitHub ์ ๊ณต)์ ์ค์ํฉ๋๋ค:
- **[`.cursorrules`](https://github.com/Hwihwa-Lab/pusher-v5-ppo/blob/main/.cursorrules)**: AI ์ฝ๋ฉ ๋ฐฉ์ด ๋ฐ ๊ท์น ๋ง์คํฐ ํ๋ฒ
- **[`DOCS_AI_CODING_PROTOCOL.md`](https://github.com/Hwihwa-Lab/pusher-v5-ppo/blob/main/DOCS_AI_CODING_PROTOCOL.md)**: ์ฝ๋ฉ ํ์ค ๋ฐ ์ ์ฒด ๋ฌธ์ ๋งต
- **[`DOCS_SYSTEM_ARCHITECTURE.md`](https://github.com/Hwihwa-Lab/pusher-v5-ppo/blob/main/DOCS_SYSTEM_ARCHITECTURE.md)**: ํ์คํ ์์คํ
๋ฐ WebSocket ์ํคํ
์ฒ ๋ช
์ธ์
- **[`DOCS_DATA_SCHEMA.md`](https://github.com/Hwihwa-Lab/pusher-v5-ppo/blob/main/DOCS_DATA_SCHEMA.md)**: ํ
๋ ๋ฉํธ๋ฆฌ ํจํท ํ๋กํ ์ฝ ๋ฐ REST ๋ฐ์ดํฐ ์คํค๋ง
- **[`DOCS_MODEL_EVALUATION_AND_HF_DEPLOY.md`](https://github.com/Hwihwa-Lab/pusher-v5-ppo/blob/main/DOCS_MODEL_EVALUATION_AND_HF_DEPLOY.md)**: ๋ฒค์น๋งํฌ ํ๊ฐ ๋ฐ ํ๊น
ํ์ด์ค ๋ฐฐํฌ ๊ท๊ฒฉ์
## ๐ ๋ฆฌํฌ์งํ ๋ฆฌ ํ์ผ ๊ตฌ์ฑ (Repository Contents)
* `README.md`: ์๋ฌธ ๊ธ๋ก๋ฒ ๋ชจ๋ธ ์นด๋ ๋ฐ ๋ฒค์น๋งํฌ ๊ฐ์ด๋.
* `README_KR.md`: ํ๊ตญ์ด ์ข
ํฉ ๊ธฐ์ ๋งค๋ด์ผ ([ํ๊ตญ์ด ๋งค๋ด์ผ](README_KR.md)).
* `app.py`: FastAPI ๊ณ ์ฑ๋ฅ ๋ฐฑ์๋ ๋ฐ 30 FPS WebSocket ๋ฌผ๋ฆฌ ์คํธ๋ฆฌ๋ฐ ์๋ฒ.
* `train.py`: Stable-Baselines3 PPO 7์์ ๋ ๋ก๋ด ํ ๊ฐํํ์ต ์์ง (`VisualProgressCallback` ๋ด์ฅ).
* `evaluate.py`: ๋
๋ฆฝ 5ํ ์ฐ์ ๋กค์์ ์ฑ๋ฅ ํ๊ฐ๊ธฐ ๋ฐ ๋น๋์ค ๋
นํ๊ธฐ.
* `visualizer.py`: ๋
๋ฆฝ Matplotlib ํ
๋ ๋ฉํธ๋ฆฌ ์๊ฐํ ๋ฐ ๊ทธ๋ํ ์์ฑ ๋ชจ๋.
* `web/`: 1-ํ๋ฉด ์ ๋ก ์คํฌ๋กค ์น ๊ด์ ์ฝํ ํ๋ก ํธ์๋ (`app.js`, `index.html`, `style.css`).
* `results/ppo_pusher.zip`: 300,000 ์คํ
์์ฑํ PPO ์ ๊ฒฝ๋ง ๊ฐ์ค์น (ํ๊ท -32.4 pts).
* `ppo_pusher_bundle.zip`: ๊ฐ์ค์น, 12๊ฐ ์ฒดํฌํฌ์ธํธ ๋น๋์ค, ๋ถ์ ์ฐจํธ๊ฐ ํฌํจ๋ ๋จ์ผ ํ๋ก๋์
๋ฐฐํฌ ์์ถ๋ณธ.
* `deploy_to_hf.py`: ํ๊น
ํ์ด์ค ๋ชจ๋ธ ํ๋ธ ์ํด๋ฆญ ์๋ ๋ฐฐํฌ ์คํฌ๋ฆฝํธ.
* `requirements.txt` & `packages.txt`: ํ์ด์ฌ ํจํค์ง ๋ฐ OS ์์กด์ฑ ๋ช
์ธ์.
---
## ๐ ์คํ์์ค ๊ณต์ ๋งํฌ (Open Source Hubs)
- ๐ **GitHub ์ ์ฅ์**: [https://github.com/Hwihwa-Lab/pusher-v5-ppo](https://github.com/Hwihwa-Lab/pusher-v5-ppo)
- ๐ค **Hugging Face ๋ชจ๋ธ ํ๋ธ**: [https://huggingface.co/hwihwalab/pusher-v5-ppo](https://huggingface.co/hwihwalab/pusher-v5-ppo)
---
## ๐ ๋ผ์ด์ ์ค
๋ณธ ํ๋ก์ ํธ๋ [MIT License](https://github.com/Hwihwa-Lab/pusher-v5-ppo/blob/main/LICENSE)๋ฅผ ๋ฐ๋ฆ
๋๋ค.
---
*Trained and deployed with [Pusher AI Hub](https://huggingface.co/hwihwalab/pusher-v5-ppo) by **hwihwalab**.*