Skip to content

GRU pose encoder + avg-pool ablation arm - #3

Open
bkabbarah wants to merge 4 commits into
OpenTouch-MIT:mainfrom
bkabbarah:gru-pose-encoder
Open

GRU pose encoder + avg-pool ablation arm#3
bkabbarah wants to merge 4 commits into
OpenTouch-MIT:mainfrom
bkabbarah:gru-pose-encoder

Conversation

@bkabbarah

@bkabbarah bkabbarah commented Aug 5, 2026

Copy link
Copy Markdown

Replaces average pooling in PoseEncoder with a 2-layer bidirectional GRU (readout = concat of final forward and backward hidden states, 240-d).

Motivation — a structural asymmetry, not a capacity shortfall. The tactile encoder already used a GRU; the pose encoder averaged all T=20 frames, discarding temporal order. InfoNCE was being asked to align a temporally-aware embedding against a temporally-flat one. This gives both encoders matching temporal structure.

Retrieval results

TEST split, matched 3-seed comparison (seeds 42/0/1 on both arms, identical hyperparameters, --split-group-by clip stated explicitly):

split direction avg-pool (3 seeds) GRU (3 seeds) ratio
clip-disjoint T→P 16.46 ± 0.36 46.17 ± 0.54 2.80×
clip-disjoint P→T 16.07 ± 0.29 45.63 ± 0.96 2.84×
scene-disjoint T→P 6.46 [5.45, 7.59] 28.31 [25.59, 31.01] ~4.4×
scene-disjoint P→T 6.38 [5.24, 7.61] 27.95 [25.55, 30.36] ~4.4×

Clip CIs (1000 clip-clustered bootstrap draws, fixed gallery): GRU 45.50 [42.09, 48.65] vs avg-pool per-seed lows ≥ 14.2 — no overlap in any direction on either split. Scene-disjoint holds participants out entirely, so the gain is not clip memorisation; it is larger there. (Scene rows are single-run per arm; clip rows are the seeded headline.)

Two caveats

  • Both arms are trained and evaluated identically in this repo; no comparison against the paper's published figure is made or implied.
  • --split-group-by is not recorded in checkpoints and defaults to clip. A scene-trained model evaluated without it explicitly set scores 72.09 mAP instead of 28.31. Always pass it explicitly when reproducing.

What's in the diff

temporal_mode={gru,mean} keeps both arms in one class so the ablation runs from this repo. mean is architecturally faithful to the original — 128-wide projection, no ReLU — so historical avg-pool checkpoints load unchanged and reproduce their numbers. Getting either detail wrong scores an upstream checkpoint at ~10 mAP instead of 16.8.

15 tests, including guards that the two modes' state dicts are not interchangeable, that mean mode stays order-invariant while GRU mode does not, and that an upstream-shaped avg-pool state_dict loads strictly.

3 files, +237/−5.

Bashar Kabbarah and others added 4 commits August 4, 2026 21:47
(cherry picked from commit 73dc799)
…one repo

The avg-pool baseline was previously only runnable from the upstream
`opentouch` repo while the GRU arm ran from `opentouch-gru`, which made the
published 13.43-vs-45.46 comparison a cross-codebase one -- and the repo's
own matched re-run of that baseline reads 16.76, not 13.43.

"mean" pools over time and zero-pads 128->240 so both modes share one
projection of identical shape; the GRU is not constructed in mean mode, so a
state_dict from either arm fails loudly rather than half-loading into the
other. Adds OpenTouch-DINOv3-B16-Retrieval-AvgPool.json to select it.

Tests pin that gru remains the default, that mean is order-invariant while
gru is not, and that the zero-pad region is exactly zero.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit f147054)
The mean arm zero-padded its pooled 128-dim vector to 240 so both modes could
share a projection shape. Functionally equivalent, but it carried 7,168 dead
input columns and, more importantly, made every historical avg-pool checkpoint
fail to load with a size mismatch on pose.projection.weight -- which is how it
was found, when evaluating the upstream avg-pool run through this codebase.

Mean mode now projects from 128 directly, matching upstream, so those
checkpoints load unchanged and the same-codebase comparison can be made
without retraining. A test builds an upstream-shaped state_dict and asserts a
strict load; the obsolete padding test is removed.

Note the overnight p2t_scene_avgpool checkpoint was trained under the padded
variant and needs commit 618eba4 or earlier to load. Its eval results are
already saved to results_retrieval_scene_p2t_scene_avgpool_{val,test}.json.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit f71a25e)
Upstream avg-pool projects the pooled vector directly; the ReLU arrived with
the GRU in 73dc799 and was never part of that architecture. Mean mode had
inherited it, which both diverged from the published model and handicapped
the baseline by zeroing every negative pooled feature.

Found by evaluating the upstream avg-pool checkpoint through this codebase:
it scored 10.25 mAP against the 16.76 that run originally reported. Two
things had to match to reproduce it, the projection width and the absence of
this ReLU.

This matters beyond checkpoint loading: the overnight scene-disjoint avg-pool
arm was trained WITH the ReLU, so that baseline was handicapped and the 5.36x
participant-disjoint ratio is measured against too weak an opponent. That arm
needs retraining before the ratio is quoted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 1ab43d4)
bkabbarah pushed a commit to bkabbarah/opentouch that referenced this pull request Aug 5, 2026
…os final

All three clip-split avg-pool runs (seeds 42/0/1, matched to the GRU arm's
seeds, full provenance) trained to epoch 300 exit 0 and bootstrap-evaluated:
T->P 16.46 +/- 0.36, P->T 16.07 +/- 0.29. Consistent with the unprovenanced
single-run 16.76 it replaces. Final matched 3v3: 2.80x (T->P), 2.84x (P->T).

PR OpenTouch-MIT#3 body updated to the seeded table; 2.20's D6 caveat marked resolved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant