twanghcmut's picture
download
raw
4.58 kB
"""A dynamic-time-warping teacher for the blend weight.
`mse(a_exec, a_corr)` tells alpha how much its blend hurt on THIS chunk. It does not tell it the
thing that generalises: whether the segment being followed is the same path as the one the arm
should be on, up to how fast it is walked. Two segments that trace the same motion at different
speeds have a large pointwise error and a small warped one -- and it is the warped one that decides
whether steering towards the segment is a good idea.
So a second, cheap supervision channel: warp the reference segment the blend is about to follow
onto the true future segment, and label the pair "aligned" when the mean warped distance clears a
threshold. Offline at training time, worth nothing at deploy time, and it gives alpha a target on
rows where the regression term happens to be small for the wrong reason.
Positions only. The rotation block is measured in radians against metres and has no shared scale
with it; mixing them would make the threshold meaningless.
"""
from __future__ import annotations
from typing import Final
import torch
from torch import Tensor
__all__ = ["ALIGN_H", "dtw_cost", "alignment_label"]
# Metres. Mean warped per-step distance below which a segment counts as "the same path". Chosen at
# roughly a gripper width: closer than this and following the reference cannot put the hand
# somewhere the true segment does not also pass through.
ALIGN_H: Final[float] = 0.05
_BIG: Final[float] = 1e9
def dtw_cost(a: Tensor, b: Tensor) -> Tensor:
"""Mean-per-step dynamic-time-warping distance between two batches of position segments.
Classic |a| x |b| DP with the three standard moves, divided by the number of aligned pairs on
the realised path. The loop runs over the K x K cells, not over the batch: K is the action-chunk
length (8), so 64 vectorised [B] updates cost less than one Python pass over a batch.
Args:
a: [B, Ka, 3] positions, metres.
b: [B, Kb, 3] positions, metres.
Returns:
[B] mean warped distance, metres. Path length is used as the normaliser, so the value is
comparable across different K.
Raises:
ValueError: Either input is not [B, K, 3] with K >= 1, or the batch sizes disagree.
"""
for name, tensor in (("a", a), ("b", b)):
if tensor.dim() != 3 or tensor.shape[-1] != 3 or tensor.shape[1] < 1:
raise ValueError(f"{name} must be [B, K, 3] with K >= 1, got {tuple(tensor.shape)}")
if a.shape[0] != b.shape[0]:
raise ValueError(f"batch mismatch: a has {a.shape[0]} rows, b has {b.shape[0]}")
local = torch.cdist(a, b) # [B, Ka, Kb]
n_b, k_a, k_b = local.shape
big = torch.full((n_b,), _BIG, device=local.device, dtype=local.dtype)
# (accumulated cost, path length) per cell; only the previous row is ever read.
prev_cost = [big] * k_b
prev_len = [torch.zeros_like(big)] * k_b
for i in range(k_a):
row_cost: list[Tensor] = []
row_len: list[Tensor] = []
for j in range(k_b):
if i == 0 and j == 0:
best_cost, best_len = torch.zeros_like(big), torch.zeros_like(big)
else:
cands = [(prev_cost[j], prev_len[j])] # from above
if j > 0:
cands.append((row_cost[j - 1], row_len[j - 1])) # from the left
cands.append((prev_cost[j - 1], prev_len[j - 1])) # diagonal
stacked = torch.stack([c for c, _ in cands])
lengths = torch.stack([n for _, n in cands])
pick = stacked.argmin(dim=0)
best_cost = stacked.gather(0, pick[None])[0]
best_len = lengths.gather(0, pick[None])[0]
row_cost.append(best_cost + local[:, i, j])
row_len.append(best_len + 1.0)
prev_cost, prev_len = row_cost, row_len
return prev_cost[-1] / prev_len[-1].clamp_min(1.0)
def alignment_label(ee_used: Tensor, ee_true: Tensor, h: float = ALIGN_H) -> Tensor:
"""Whether the segment the blend will follow traces the same path as the true future one.
Args:
ee_used: [B, K, 6] reference segment the blend is built from, concat(position, axis-angle).
ee_true: [B, K, 6] the label node's own segment, same layout.
h: Threshold on the mean warped distance, metres.
Returns:
[B] float in {0, 1}, ready as a BCE target.
"""
return (dtw_cost(ee_used[..., :3], ee_true[..., :3]) < h).to(ee_used.dtype)

Xet Storage Details

Size:
4.58 kB
·
Xet hash:
48dbc87fb7196f07a9f0fd1f08d2f3dc6d332b17d665e93d29f6fbf06d58ebab

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.