Point card at the renamed GitHub repos (crowd-nav = package, crowd-nav-legacy = research)
Browse files
README.md
CHANGED
|
@@ -1,131 +1,132 @@
|
|
| 1 |
-
---
|
| 2 |
-
license: mit
|
| 3 |
-
library_name: pytorch
|
| 4 |
-
pipeline_tag: robotics
|
| 5 |
-
tags:
|
| 6 |
-
- robotics
|
| 7 |
-
- social-navigation
|
| 8 |
-
- human-robot-interaction
|
| 9 |
-
- pedestrian-trajectory-prediction
|
| 10 |
-
- model-predictive-control
|
| 11 |
-
- convex-optimization
|
| 12 |
-
---
|
| 13 |
-
|
| 14 |
-
# crowd-nav — human-response models for the Foresight controller
|
| 15 |
-
|
| 16 |
-
Trained weights for **Foresight**, a convex receding-horizon controller that predicts how each
|
| 17 |
-
nearby pedestrian will respond to the robot's *candidate plan*, and then chooses that plan by
|
| 18 |
-
exact convex optimization. These are the response models the controller calls; the planner is
|
| 19 |
-
plain code (no learned policy, no RL).
|
| 20 |
-
|
| 21 |
-
| file | model | role |
|
| 22 |
-
|---|---|---|
|
| 23 |
-
| `linres_head.pt` | **Linear-response operator** (headline) | best closed-loop performance; makes planning an exact convex QP |
|
| 24 |
-
| `residual_predictor_k1.pt` | Residual predictor | most accurate on real data; the safest variant in closed loop |
|
| 25 |
-
| `residual_predictor.pt` | *legacy* | **deprecated** — trained with a robot-force prior of 0 but deployed at 1; superseded by `residual_predictor_k1.pt` |
|
| 26 |
-
|
| 27 |
-
## Which one should I use?
|
| 28 |
-
|
| 29 |
-
**`linres_head.pt`** unless you have a reason not to. It is a few centimetres less accurate as a
|
| 30 |
-
pure forecaster, but its prediction is *exactly affine* in the robot's planned displacement, so the
|
| 31 |
-
planner gets an exact constant Jacobian and the safety constraints stay linear — the whole plan
|
| 32 |
-
selection becomes a convex QP that solves in ~17 ms with a checkable feasibility certificate.
|
| 33 |
-
That trade wins where it counts: it leads our benchmark, and it is the only variant that crossed a
|
| 34 |
-
90-pedestrian oncoming corridor with zero contacts.
|
| 35 |
-
|
| 36 |
-
## Measured results
|
| 37 |
-
|
| 38 |
-
Isaac Sim, 16 scenarios × 10 paired seeds (160 episodes per method), identical hardware:
|
| 39 |
-
|
| 40 |
-
| model | goal reach | hard collisions / ep | plan latency (mean) |
|
| 41 |
-
|---|---|---|---|
|
| 42 |
-
| **Foresight-LinRes** (`linres_head.pt`) | **94.4 %** | 0.013 | **16.6 ms** |
|
| 43 |
-
| Foresight-Residual (`residual_predictor_k1.pt`) | 90.0 % | **0.006** | 19.7 ms |
|
| 44 |
-
| Foresight-SFM (physics only, ablation) | 93.8 % | 0.013 | 17.2 ms |
|
| 45 |
-
| Foresight-CV (straight-line, ablation) | 90.0 % | 0.019 | 16.4 ms |
|
| 46 |
-
| SICNav [T-RO '24] (nonconvex bilevel MPC) | 89.2 % | 0.042 | 1843 ms |
|
| 47 |
-
|
| 48 |
-
Stress test — a 4.5 m × 105 m corridor with 90 pedestrians *all walking against* the robot and a
|
| 49 |
-
continuous inflow so it never empties: LinRes reached the goal in 98.5 s with **zero contacts**;
|
| 50 |
-
against pedestrian models it was never tuned on (SRFM, social-force) it reached in 85.0 s and
|
| 51 |
-
91.7 s, also contact-free.
|
| 52 |
-
|
| 53 |
-
Prediction accuracy on the held-out real split (factual ADE, metres — lower is better):
|
| 54 |
-
|
| 55 |
-
| model | no robot | stationary robot | moving robot |
|
| 56 |
-
|---|---|---|---|
|
| 57 |
-
| Constant velocity | 0.240 | 0.253 | 0.354 |
|
| 58 |
-
| Social force | 0.343 | 0.358 | 0.416 |
|
| 59 |
-
| Residual (k1) | **0.192** | **0.217** | 0.285 |
|
| 60 |
-
| Linear response | 0.204 | 0.248 | 0.319 |
|
| 61 |
-
|
| 62 |
-
## Architecture
|
| 63 |
-
|
| 64 |
-
Both models are a **physics prior plus a learned correction** — small by design, because the real
|
| 65 |
-
moving-robot data is scarce.
|
| 66 |
-
|
| 67 |
-
```
|
| 68 |
-
prior (no parameters, per pedestrian per 0.25 s step)
|
| 69 |
-
f_goal = (1.3·(g−p)/‖g−p‖ − v)/0.5
|
| 70 |
-
f_ped = Σ 2.0·exp(−d/0.4)·d̂ (d < 2.0 m)
|
| 71 |
-
f_robot = k·exp(−d/0.5)·d̂ (d < 3.0 m)
|
| 72 |
-
v ← clip(v + f·Δt, 1.8); p ← p + v·Δt
|
| 73 |
-
|
| 74 |
-
residual_predictor_k1.pt ŷ = prior(k=1) + f_θ(x)
|
| 75 |
-
f_θ : MLP 22 → 128 → 128 → 16 (21,520 params)
|
| 76 |
-
|
| 77 |
-
linres_head.pt ŷ = prior(k=0) + f_θ(x) + G_φ(x)·ΔR
|
| 78 |
-
f_θ : MLP 22 → 128 → 128 → 16 (21,520 params)
|
| 79 |
-
G_φ : MLP 22 → 64 → 96 (7,712 params) ⇒ rank-3 16×16 operator
|
| 80 |
-
G = Σ_{r=1..3} a_r b_rᵀ, ΔR = (R − 1⊗r₀)/5.0
|
| 81 |
-
⇒ ∂ŷ/∂R = G/5.0 — exact, constant, no differentiation needed
|
| 82 |
-
```
|
| 83 |
-
|
| 84 |
-
Input `x` (22-d, pedestrian local frame): 1 s of past positions (4 @ 0.25 s), velocity, goal
|
| 85 |
-
direction, robot relative position, a robot-nearby flag, the two nearest neighbours, and a 3-way
|
| 86 |
-
condition one-hot. Output: 8 × 2 future displacements — 2.0 s at 0.25 s.
|
| 87 |
-
|
| 88 |
-
Training: supervised (Adam, lr 1e-3, batch 128, ~25 epochs), synthetic pre-train then fine-tune on
|
| 89 |
-
the real **PeRoI** robot–pedestrian recordings. No reinforcement learning anywhere in this stack.
|
| 90 |
-
Both run in well under 1 ms per call on CPU.
|
| 91 |
-
|
| 92 |
-
## Load
|
| 93 |
-
|
| 94 |
-
```bash
|
| 95 |
-
pip install huggingface_hub torch
|
| 96 |
-
hf download elmoghany/crowd-nav linres_head.pt residual_predictor_k1.pt --local-dir .
|
| 97 |
-
```
|
| 98 |
-
|
| 99 |
-
```python
|
| 100 |
-
import torch
|
| 101 |
-
ck = torch.load("linres_head.pt", map_location="cpu")
|
| 102 |
-
ck["config"]
|
| 103 |
-
# {'in_dim': 22, 'horizon_dim': 16, 'rank': 3, 'hidden': 64, 'dr_scale': 5.0,
|
| 104 |
-
# 'sfm_k_robot': 0.0, 'convention': 'y = sfm(k=0) + f_resid(x_ctx) + head(x_ctx, dR)'}
|
| 105 |
-
ck["f_resid"], ck["head"] # two state_dicts
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
The `config` block is authoritative: `sfm_k_robot` **must** match the robot-force strength used in
|
| 109 |
-
your prior at deployment. Mismatching them is a real bug we shipped once — the residual then
|
| 110 |
-
corrects an error that is not there. `residual_predictor.pt` is the artifact of that mistake and is
|
| 111 |
-
kept only for reproducibility.
|
| 112 |
-
|
| 113 |
-
Model classes (`ResidualPredictor`, `LinearResponseHead`) and the full controller live in the
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
pipeline_tag: robotics
|
| 5 |
+
tags:
|
| 6 |
+
- robotics
|
| 7 |
+
- social-navigation
|
| 8 |
+
- human-robot-interaction
|
| 9 |
+
- pedestrian-trajectory-prediction
|
| 10 |
+
- model-predictive-control
|
| 11 |
+
- convex-optimization
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# crowd-nav — human-response models for the Foresight controller
|
| 15 |
+
|
| 16 |
+
Trained weights for **Foresight**, a convex receding-horizon controller that predicts how each
|
| 17 |
+
nearby pedestrian will respond to the robot's *candidate plan*, and then chooses that plan by
|
| 18 |
+
exact convex optimization. These are the response models the controller calls; the planner is
|
| 19 |
+
plain code (no learned policy, no RL).
|
| 20 |
+
|
| 21 |
+
| file | model | role |
|
| 22 |
+
|---|---|---|
|
| 23 |
+
| `linres_head.pt` | **Linear-response operator** (headline) | best closed-loop performance; makes planning an exact convex QP |
|
| 24 |
+
| `residual_predictor_k1.pt` | Residual predictor | most accurate on real data; the safest variant in closed loop |
|
| 25 |
+
| `residual_predictor.pt` | *legacy* | **deprecated** — trained with a robot-force prior of 0 but deployed at 1; superseded by `residual_predictor_k1.pt` |
|
| 26 |
+
|
| 27 |
+
## Which one should I use?
|
| 28 |
+
|
| 29 |
+
**`linres_head.pt`** unless you have a reason not to. It is a few centimetres less accurate as a
|
| 30 |
+
pure forecaster, but its prediction is *exactly affine* in the robot's planned displacement, so the
|
| 31 |
+
planner gets an exact constant Jacobian and the safety constraints stay linear — the whole plan
|
| 32 |
+
selection becomes a convex QP that solves in ~17 ms with a checkable feasibility certificate.
|
| 33 |
+
That trade wins where it counts: it leads our benchmark, and it is the only variant that crossed a
|
| 34 |
+
90-pedestrian oncoming corridor with zero contacts.
|
| 35 |
+
|
| 36 |
+
## Measured results
|
| 37 |
+
|
| 38 |
+
Isaac Sim, 16 scenarios × 10 paired seeds (160 episodes per method), identical hardware:
|
| 39 |
+
|
| 40 |
+
| model | goal reach | hard collisions / ep | plan latency (mean) |
|
| 41 |
+
|---|---|---|---|
|
| 42 |
+
| **Foresight-LinRes** (`linres_head.pt`) | **94.4 %** | 0.013 | **16.6 ms** |
|
| 43 |
+
| Foresight-Residual (`residual_predictor_k1.pt`) | 90.0 % | **0.006** | 19.7 ms |
|
| 44 |
+
| Foresight-SFM (physics only, ablation) | 93.8 % | 0.013 | 17.2 ms |
|
| 45 |
+
| Foresight-CV (straight-line, ablation) | 90.0 % | 0.019 | 16.4 ms |
|
| 46 |
+
| SICNav [T-RO '24] (nonconvex bilevel MPC) | 89.2 % | 0.042 | 1843 ms |
|
| 47 |
+
|
| 48 |
+
Stress test — a 4.5 m × 105 m corridor with 90 pedestrians *all walking against* the robot and a
|
| 49 |
+
continuous inflow so it never empties: LinRes reached the goal in 98.5 s with **zero contacts**;
|
| 50 |
+
against pedestrian models it was never tuned on (SRFM, social-force) it reached in 85.0 s and
|
| 51 |
+
91.7 s, also contact-free.
|
| 52 |
+
|
| 53 |
+
Prediction accuracy on the held-out real split (factual ADE, metres — lower is better):
|
| 54 |
+
|
| 55 |
+
| model | no robot | stationary robot | moving robot |
|
| 56 |
+
|---|---|---|---|
|
| 57 |
+
| Constant velocity | 0.240 | 0.253 | 0.354 |
|
| 58 |
+
| Social force | 0.343 | 0.358 | 0.416 |
|
| 59 |
+
| Residual (k1) | **0.192** | **0.217** | 0.285 |
|
| 60 |
+
| Linear response | 0.204 | 0.248 | 0.319 |
|
| 61 |
+
|
| 62 |
+
## Architecture
|
| 63 |
+
|
| 64 |
+
Both models are a **physics prior plus a learned correction** — small by design, because the real
|
| 65 |
+
moving-robot data is scarce.
|
| 66 |
+
|
| 67 |
+
```
|
| 68 |
+
prior (no parameters, per pedestrian per 0.25 s step)
|
| 69 |
+
f_goal = (1.3·(g−p)/‖g−p‖ − v)/0.5
|
| 70 |
+
f_ped = Σ 2.0·exp(−d/0.4)·d̂ (d < 2.0 m)
|
| 71 |
+
f_robot = k·exp(−d/0.5)·d̂ (d < 3.0 m)
|
| 72 |
+
v ← clip(v + f·Δt, 1.8); p ← p + v·Δt
|
| 73 |
+
|
| 74 |
+
residual_predictor_k1.pt ŷ = prior(k=1) + f_θ(x)
|
| 75 |
+
f_θ : MLP 22 → 128 → 128 → 16 (21,520 params)
|
| 76 |
+
|
| 77 |
+
linres_head.pt ŷ = prior(k=0) + f_θ(x) + G_φ(x)·ΔR
|
| 78 |
+
f_θ : MLP 22 → 128 → 128 → 16 (21,520 params)
|
| 79 |
+
G_φ : MLP 22 → 64 → 96 (7,712 params) ⇒ rank-3 16×16 operator
|
| 80 |
+
G = Σ_{r=1..3} a_r b_rᵀ, ΔR = (R − 1⊗r₀)/5.0
|
| 81 |
+
⇒ ∂ŷ/∂R = G/5.0 — exact, constant, no differentiation needed
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
Input `x` (22-d, pedestrian local frame): 1 s of past positions (4 @ 0.25 s), velocity, goal
|
| 85 |
+
direction, robot relative position, a robot-nearby flag, the two nearest neighbours, and a 3-way
|
| 86 |
+
condition one-hot. Output: 8 × 2 future displacements — 2.0 s at 0.25 s.
|
| 87 |
+
|
| 88 |
+
Training: supervised (Adam, lr 1e-3, batch 128, ~25 epochs), synthetic pre-train then fine-tune on
|
| 89 |
+
the real **PeRoI** robot–pedestrian recordings. No reinforcement learning anywhere in this stack.
|
| 90 |
+
Both run in well under 1 ms per call on CPU.
|
| 91 |
+
|
| 92 |
+
## Load
|
| 93 |
+
|
| 94 |
+
```bash
|
| 95 |
+
pip install huggingface_hub torch
|
| 96 |
+
hf download elmoghany/crowd-nav linres_head.pt residual_predictor_k1.pt --local-dir .
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
```python
|
| 100 |
+
import torch
|
| 101 |
+
ck = torch.load("linres_head.pt", map_location="cpu")
|
| 102 |
+
ck["config"]
|
| 103 |
+
# {'in_dim': 22, 'horizon_dim': 16, 'rank': 3, 'hidden': 64, 'dr_scale': 5.0,
|
| 104 |
+
# 'sfm_k_robot': 0.0, 'convention': 'y = sfm(k=0) + f_resid(x_ctx) + head(x_ctx, dR)'}
|
| 105 |
+
ck["f_resid"], ck["head"] # two state_dicts
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
The `config` block is authoritative: `sfm_k_robot` **must** match the robot-force strength used in
|
| 109 |
+
your prior at deployment. Mismatching them is a real bug we shipped once — the residual then
|
| 110 |
+
corrects an error that is not there. `residual_predictor.pt` is the artifact of that mistake and is
|
| 111 |
+
kept only for reproducibility.
|
| 112 |
+
|
| 113 |
+
Model classes (`ResidualPredictor`, `LinearResponseHead`) and the full controller live in the
|
| 114 |
+
**[github.com/elmoghany/crowd-nav](https://github.com/elmoghany/crowd-nav)** -- a one-file,
|
| 115 |
+
simulator-free controller with a `quickstart.py` that fetches these weights and verifies your
|
| 116 |
+
install in one command. The full research stack (convex-QP planner, Isaac benchmarks, paper) is at
|
| 117 |
+
[github.com/elmoghany/crowd-nav-legacy](https://github.com/elmoghany/crowd-nav-legacy).
|
| 118 |
+
|
| 119 |
+
## Honest limitations
|
| 120 |
+
|
| 121 |
+
- **Real-data forecasting is a tie.** On four real robot–pedestrian datasets (including JRDB),
|
| 122 |
+
every learned model here ties the analytic baselines on factual accuracy. Our measured
|
| 123 |
+
explanation: the counterfactual proxy those benchmarks rely on (constant-velocity extrapolation)
|
| 124 |
+
correlates only r ≈ 0.30 with exact simulated counterfactuals — the target is mostly noise. The
|
| 125 |
+
gains above are **closed-loop** gains, not forecasting gains.
|
| 126 |
+
- **Compliance bias.** The training recordings contain cooperative pedestrians. On deliberately
|
| 127 |
+
stubborn crowds the k1 residual is the weakest learned variant; compliance-randomized training
|
| 128 |
+
data is the known fix, not yet applied.
|
| 129 |
+
- **Cross-dataset transfer.** Moving `residual_predictor_k1.pt` to JRDB over-predicts robot-induced
|
| 130 |
+
deviation by ~6× (its k=1 prior is calibrated to our robot). It makes a controller conservative,
|
| 131 |
+
not unsafe — it still threaded real JRDB crowds contact-free — but retune `sfm_k_robot` for a
|
| 132 |
+
different platform.
|