Robotics
LeRobot
Safetensors
act
ur7e
imitation-learning
act_banana_in_pot / README.md
Bigenlight's picture
Update repo names to *_lerobot_v3 in cross-links
2c732f9 verified
|
Raw
History Blame Contribute Delete
7.83 kB
---
license: apache-2.0
library_name: lerobot
tags:
- act
- robotics
- lerobot
- ur7e
- imitation-learning
pipeline_tag: robotics
datasets:
- Bigenlight/banana_in_pot_lerobot_v3
---
# ACT β€” Put the right banana in the pot (UR7e)
An **Action Chunking Transformer (ACT)** policy trained by imitation learning to perform
the manipulation task *"put the right banana in the pot"* on a **Universal Robots UR7e**
arm with two RGB cameras.
- **Policy:** ACT (Transformer encoder–decoder with a CVAE), **ResNet18** vision backbone
(ImageNet-pretrained), `chunk_size = 100`, `n_action_steps = 100`.
- **Trained on:** [`Bigenlight/banana_in_pot_lerobot_v3`](https://huggingface.co/datasets/Bigenlight/banana_in_pot_lerobot_v3)
(51 teleoperated episodes / 21,524 frames, UR7e follower + GELLO leader, 2 cameras).
- **Training length:** 50,000 steps, batch size 8, AdamW `lr = 1e-5` (constant).
- **Framework:** [LeRobot](https://github.com/huggingface/lerobot) v0.6.1.
## What the policy does
| I/O | Spec |
|---|---|
| `observation.state` | `(7,)` β€” UR joints `q1..q6` (radians) + gripper position |
| `observation.images.cam1` / `cam2` | RGB, trained at **360Γ—640** |
| `action` | `(7,)` β€” `[cmd1..cmd6, grip_cmd]`, **absolute** joint targets (radians) + ~binary gripper |
Images were captured at 720p and **resized on-the-fly to 360Γ—640** during training
(aspect-preserving half-resolution; no dataset re-encode). **You must resize every live
camera frame to 360Γ—640** at inference β€” a different aspect ratio or interpolation degrades
the policy.
## Data & hardware setup
| Component | Detail |
|---|---|
| Robot | **Universal Robots UR7e** β€” 6-DOF collaborative arm, joints in radians. Inference uses the UR7e follower only. |
| Teleoperation (data collection) | **GELLO** low-cost 3D-printed leader arm. Leader signals are recorded but are **not** policy inputs at inference. |
| Camera 1 | **Intel RealSense D435** β€” RGB only |
| Camera 2 | **Intel RealSense D435if** β€” RGB only |
| Camera streams | 1280Γ—720 (720p) @ **30 fps**, color only (**no depth / IR** recorded). ACT is trained on the two RGB views **resized to 360Γ—640**. |
| Task | *"put the right banana in the pot"* β€” table with distractor objects (2 bananas, apple, carrots/peppers, watermelon slice) and a silver pot; success = the correct banana placed in the pot. |
| Dataset scale | 51 episodes / 21,524 frames / ~12 min @ 30 fps. |
## Training configuration
| Item | Value |
|---|---|
| Policy | ACT (ResNet18 backbone, CVAE, `chunk_size=100`, `n_action_steps=100`) |
| `dim_model` / heads | 512 / 8 |
| Encoder / decoder layers | 4 / 1 |
| Normalization | MEAN_STD for state, action, and visual |
| Batch / steps | 8 / 50,000 |
| Optimizer | AdamW, `lr = 1e-5` (constant), backbone `lr = 1e-5` |
| Backbone | `ResNet18_Weights.IMAGENET1K_V1` (ImageNet-pretrained) |
| GPU | RTX 3060 12GB (~4.7 GB used, ~3.9 step/s) |
### Training loss
![training loss](assets/loss_curve.png)
Final training loss β‰ˆ **0.065**.
## Offline evaluation (open-loop: predicted actions vs. ground truth)
Evaluated on held-out episodes (train vs. held-out gap is negligible β†’ generalizes, no
overfitting). The **step 50,000** checkpoint (this model) is the best by held-out L1.
| step | joints MAE (rad) | overall L1 | gripper acc | train L1 | held-out L1 |
|---|---|---|---|---|---|
| 10000 | 0.0392 | 0.0395 | 98.0% | 0.0392 | 0.0406 |
| 20000 | 0.0354 | 0.0345 | 98.8% | 0.0324 | 0.0356 |
| 30000 | 0.0267 | 0.0265 | 98.8% | 0.0258 | 0.0269 |
| 40000 | 0.0256 | 0.0245 | 99.2% | 0.0222 | 0.0256 |
| **50000** ⭐ | **0.0237** | **0.0225** | **99.2%** | 0.0205 | **0.0235** |
![evaluation trend](assets/eval_trend.png)
**Best checkpoint (step 50,000):**
- Held-out overall **L1 = 0.0235 rad** (β‰ˆ 1.34Β°); train L1 = 0.0205 β†’ near-zero gap.
- Joints **MAE = 0.0237 rad** (β‰ˆ 1.36Β°).
- Gripper accuracy **99.2%**.
![per-joint MAE](assets/perjoint_best.png)
The wrist joint (`cmd6`) carries the largest error β€” it is the axis with the most natural
variation. The gripper channel is close to binary.
## How to use
### Load the policy (LeRobot)
```python
from lerobot.policies.act import ACTPolicy
from lerobot.policies import make_pre_post_processors
policy = ACTPolicy.from_pretrained("Bigenlight/act_banana_in_pot")
policy.eval()
# Normalization is NOT baked into forward() in lerobot 0.6.1 β€” it lives in the
# processor pipeline saved alongside the checkpoint. select_action returns a
# NORMALIZED action; the post-processor converts it back to radians.
preprocessor, postprocessor = make_pre_post_processors(
policy_cfg=policy.config,
pretrained_path="Bigenlight/act_banana_in_pot",
)
```
Per control tick:
```python
policy.reset() # once at the start of each rollout
obs = preprocessor(obs) # normalize + batch + move to device
action = policy.select_action(obs) # normalized
action = postprocessor(action) # radians, numpy (7,)
```
`select_action` returns one action per call from an internal queue; on an empty queue it
predicts a full 100-step chunk and replans after 100 executed actions (temporal ensembling
is **off** by default in this config).
### Deploy on a real UR7e
LeRobot ships **no UR robot class** β€” you build the observation dict yourself and stream
joint targets with [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/) at **30 Hz**
(33.3 ms control period). Loop: read `getActualQ()` + gripper → grab both cameras, BGR→RGB,
resize to 360Γ—640 β†’ build `observation.state` / `observation.images.cam1` / `cam2` β†’
preprocess β†’ `select_action` β†’ postprocess β†’ `servoJ(q_target[:6])` + drive gripper from
`grip_cmd`.
**⚠️ Safety β€” actions are ABSOLUTE joint positions (the single biggest safety driver):**
1. **Start near the dataset initial pose** `q1..q6 β‰ˆ [2.84, -1.41, 1.78, -2.01, -1.66, -3.42]`
rad before enabling the policy, or the first absolute command is a large jump.
2. **First-command jump guard:** if `max(|q_target βˆ’ getActualQ()|) > ~0.15 rad`, **abort**.
3. **Clamp per-tick joint change** (e.g. ≀ 0.05–0.1 rad / 33 ms early on) and clamp to UR
software joint limits.
4. **Reduced speed for first trials**; keep a hand on the **E-stop** dead-man switch.
5. **Camera mapping matters:** the policy learned a fixed `cam1`/`cam2` β†’ physical-viewpoint
mapping. Swap them and it fails silently. Verify wiring every session.
6. **Gripper** `grip_cmd` is ~binary β€” threshold (e.g. `>0.5 β†’ close`) and map to your driver.
A full deployment guide (with a reference `ur_rtde` skeleton and all the citations to the
lerobot inference/normalization code paths) is in the project's `DEPLOY_UR.md`.
## Related repositories
- **Dataset:** [`Bigenlight/banana_in_pot_lerobot_v3`](https://huggingface.co/datasets/Bigenlight/banana_in_pot_lerobot_v3)
- **HIL-SERL prep bundle** (reward classifier, SAC config, runbook) for taking this task
online with reinforcement learning:
[`Bigenlight/banana_in_pot_hilserl`](https://huggingface.co/Bigenlight/banana_in_pot_hilserl)
## Limitations
- **Small dataset** (51 demos). Metrics reflect an easy, temporally-correlated task; expect
reduced robustness to novel object positions, lighting, or camera placement.
- **Offline metrics only.** All numbers above are open-loop (predicted vs. ground-truth
actions). Real closed-loop task success on the arm has not been measured here β€” a physical
UR7e deployment is required for the final verdict.
- **Absolute-joint action space** demands the safety guards above; the policy was only ever
conditioned on states near the data-collection start pose.
- The ResNet18 backbone is **ImageNet-pretrained** (not robotics-pretrained); the CVAE and
transformer are trained from scratch on this task.