Buckets:
| """cwm3/models/overlap_head.py — F8 (R2 WP4 batch 2, 2026-09-17; plan 'F8 驾驭员状态 × 道路需求的时间重叠预测'; pre-registration docs/decisions.md | |
| 2026-09-17T19:32:07Z): a DETACHED, horizon-conditioned JOINT head for P(driver unmet state U_{t+k}, road demand D_{t+k} | H_t) on the frozen ECAN joint model. | |
| Variables per horizon k = 1..K (0.2 s steps, K = 15 = 3 s), all label-free target states of the existing packs (nothing after t enters the inputs): | |
| hands_off_{t+k} = S_c[:, k-1, 0] == 0 (results/targets_kin_causal_routes_v2; NaN = unobserved) | |
| head_turned_{t+k} = S_c[:, k-1, 1] == 1 | |
| D_{t+k} = any road-pressure family (P ∧ Pvalid)[:, k-1] (results/targets_pressure, whitelist CAN margins); observed where any family is valid ∧ M | |
| The head emits an 8-way categorical over (hands_off, head_turned, D) per horizon: state index s = 4·hands_off + 2·head_turned + D. The four (U, D) | |
| joint states and q_k = P(U = 1, D = 1) are marginalisations (overlap_from_joint): U = hands_off ∨ head_turned (manual, paper-facing) or U = head_turned | |
| only (engaged paper-facing: the engaged hand cells are out of the paper and masked from every paper-facing endpoint). | |
| Inputs: z [B, d_z] = LN([h_u ; z_e ; z_vf]) of the frozen joint model (detached cache), anchor [B, d_a] = label-free current states with validity | |
| (hands_on0, head0, 6 P0 families + their validity bits), mode [B] regime, optional zk [B, K, d_k] per-horizon features (the `margonly` attribution arm: | |
| the frozen model's own marginals, WITHOUT trunk features; use_z=False). | |
| Baseline (pre-registered): the PRODUCT of the frozen model's own marginals — joint_from_marginals(p_hands_off, p_head, p_D) is the independent 8-way | |
| distribution; overlap_from_joint of it equals p_U · p_D EXACTLY (identity unit test, tests/test_overlap_head.py). | |
| Loss: masked joint NLL = −log Σ_{s consistent with the observed variables} p_s (the categorical form of a masked BCE: unobserved variables are | |
| marginalised; a (row, k) with nothing observed contributes 0). J_t = Δt Σ_k w_k q_k (w_k = 1 pre-fixed). ≈ 0.06 M parameters (n_params()). | |
| No openpilot DMS field anywhere (states = our own pose chains and CAN margins). | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| K = 15 # horizons (0.2 s steps) = 3 s | |
| DT = 0.2 | |
| VARS = ("hands_off", "head_turned", "D") # bit order of the 8-way state index: s = 4*hands_off + 2*head_turned + D | |
| N_STATES = 8 | |
| REPORT_STEPS = {"0.6s": 3, "1s": 5, "2s": 10, "3s": 15} # 0.5 s is not on the grid: step 3 (0.6 s) = nearest step >= 0.5 s | |
| D_ANCHOR = 2 + 2 + 6 + 6 # hands_on0, head0, their validity, P0 x6, P0valid x6 | |
| # state bits [8, 3]: bit_v(s) | |
| _BITS = torch.tensor([[(s >> 2) & 1, (s >> 1) & 1, s & 1] for s in range(N_STATES)], dtype=torch.float32) | |
| def state_bits(device=None, dtype=torch.float32): | |
| b = _BITS.to(dtype) | |
| return b.to(device) if device is not None else b | |
| class OverlapHead(nn.Module): | |
| def __init__(self, d_z=384, d_anchor=D_ANCHOR, d=96, d_h=16, n_h=K, d_k=0, use_z=True, dropout=0.0): | |
| super().__init__() | |
| self.use_z, self.n_h, self.d_k = bool(use_z), int(n_h), int(d_k) | |
| self.z_ln = nn.LayerNorm(d_z) if use_z else None | |
| self.z_in = nn.Linear(d_z, d) if use_z else None | |
| self.a_in = nn.Linear(d_anchor, d) | |
| self.mode = nn.Embedding(2, d) | |
| self.hor = nn.Embedding(n_h, d_h) | |
| self.net = nn.Sequential(nn.Linear(d + d_h + self.d_k, d), nn.GELU(), nn.Dropout(dropout), nn.Linear(d, d), nn.GELU(), nn.Linear(d, N_STATES)) | |
| def forward(self, z, anchor, mode, zk=None): | |
| """-> logits [B, n_h, 8].""" | |
| x = self.a_in(anchor.float()) + self.mode(mode.long()) | |
| if self.use_z: | |
| x = x + self.z_in(self.z_ln(z.float())) | |
| x = F.gelu(x) # [B, d] | |
| B = x.shape[0] | |
| h = self.hor.weight[None].expand(B, -1, -1) # [B, n_h, d_h] | |
| parts = [x[:, None].expand(-1, self.n_h, -1), h] | |
| if self.d_k: | |
| assert zk is not None and zk.shape[1] == self.n_h and zk.shape[2] == self.d_k, (None if zk is None else zk.shape) | |
| parts.append(zk.float()) | |
| return self.net(torch.cat(parts, -1)) | |
| def n_params(m): | |
| return sum(p.numel() for p in m.parameters() if p.requires_grad) | |
| # ----------------------------------------------------------------------------- distributions | |
| def joint_from_marginals(p_hands_off, p_head, p_D): | |
| """independent product distribution over the 8 states: [...] x3 -> [..., 8] (the pre-registered product baseline).""" | |
| bits = state_bits(p_hands_off.device, torch.float64) | |
| ps = torch.stack([p_hands_off.double(), p_head.double(), p_D.double()], -1) # [..., 3] | |
| ps = ps.clamp(0.0, 1.0) | |
| P = torch.ones(*ps.shape[:-1], N_STATES, dtype=torch.float64, device=ps.device) | |
| for v in range(3): | |
| pv = ps[..., v:v + 1] | |
| P = P * torch.where(bits[:, v] > 0.5, pv, 1.0 - pv) | |
| return P | |
| def overlap_from_joint(P8, use_hands=True): | |
| """q = P(U = 1, D = 1); U = hands_off ∨ head_turned (use_hands) or head_turned only (engaged paper-facing). P8 [..., 8] -> [...]""" | |
| bits = state_bits(P8.device, P8.dtype) | |
| U = (bits[:, 0] + bits[:, 1] > 0.5) if use_hands else (bits[:, 1] > 0.5) | |
| sel = (U & (bits[:, 2] > 0.5)).to(P8.dtype) | |
| return (P8 * sel).sum(-1) | |
| def marginals_from_joint(P8): | |
| """-> dict p_hands_off, p_head, p_D, p_U (hands ∨ head), p_U_head (head only).""" | |
| bits = state_bits(P8.device, P8.dtype) | |
| out = {} | |
| for i, v in enumerate(VARS): | |
| out["p_" + v] = (P8 * bits[:, i]).sum(-1) | |
| out["p_U"] = (P8 * ((bits[:, 0] + bits[:, 1]) > 0.5).to(P8.dtype)).sum(-1) | |
| out["p_U_head"] = out["p_head_turned"] | |
| return out | |
| def J_from_q(q, dt=DT, w=None): | |
| """J_t = dt * sum_k w_k q_k; q [..., K].""" | |
| q = q.float() | |
| if w is None: | |
| return dt * q.sum(-1) | |
| return dt * (q * torch.as_tensor(w, dtype=q.dtype, device=q.device)).sum(-1) | |
| # ----------------------------------------------------------------------------- loss | |
| def masked_joint_nll(logits, y3, obs3): | |
| """logits [B, K, 8]; y3 [B, K, 3] in {0, 1} (any value where unobserved); obs3 [B, K, 3] bool. | |
| -> nll [B, K] (0 where nothing observed), mask [B, K] (any variable observed).""" | |
| logp = F.log_softmax(logits.float(), -1) # [B, K, 8] | |
| bits = state_bits(logits.device) # [8, 3] | |
| y = y3.float()[..., None, :] # [B, K, 1, 3] | |
| o = obs3.bool()[..., None, :] # [B, K, 1, 3] | |
| consistent = ((bits[None, None] == y) | ~o).all(-1) # [B, K, 8] | |
| mask = obs3.bool().any(-1) # [B, K] | |
| lp = torch.where(consistent, logp, torch.full_like(logp, float("-inf"))) | |
| ll = torch.logsumexp(lp, -1) # log P(observed configuration) | |
| nll = torch.where(mask, -ll, torch.zeros_like(ll)) | |
| return nll, mask | |
| def overlap_loss(logits, y3, obs3): | |
| nll, mask = masked_joint_nll(logits, y3, obs3) | |
| m = mask.float() | |
| total = (nll * m).sum() / m.sum().clamp_min(1.0) | |
| return total, {"joint_nll": total.detach(), "n_obs": m.sum().detach()} | |
| # ----------------------------------------------------------------------------- targets (numpy, strict future: horizon k -> index k-1) | |
| def build_targets(S, P, Pvalid, M, row_ok, n_h=K): | |
| """S [N, >=K, 2] float (NaN unobserved; hands_on, head_turned), P / Pvalid [N, >=K, 6] bool, M [N, >=K] bool, row_ok [N] bool | |
| -> y3 [N, K, 3] uint8, obs3 [N, K, 3] bool. y3[:, k-1] refers to the state at t + k dt (k >= 1) — nothing at or before t.""" | |
| import numpy as np | |
| S = np.asarray(S, np.float32)[:, :n_h] | |
| hands_obs = np.isfinite(S[:, :, 0]) & row_ok[:, None] & M[:, :n_h] | |
| head_obs = np.isfinite(S[:, :, 1]) & row_ok[:, None] & M[:, :n_h] | |
| Pv = np.asarray(Pvalid, bool)[:, :n_h] | |
| Pb = np.asarray(P, bool)[:, :n_h] & Pv | |
| D = Pb.any(2) | |
| D_obs = Pv.any(2) & M[:, :n_h] | |
| y3 = np.stack([np.nan_to_num(S[:, :, 0]) < 0.5, np.nan_to_num(S[:, :, 1]) > 0.5, D], -1).astype(np.uint8) | |
| obs3 = np.stack([hands_obs, head_obs, D_obs], -1) | |
| return y3, obs3 | |
| def realised_overlap(y3, obs3, use_hands=True): | |
| """y = any_k (U_{t+k} ∧ D_{t+k}) over the K steps; full = every needed variable observed at every step (the pre-registered ranking rows).""" | |
| import numpy as np | |
| U = (y3[:, :, 0] > 0) | (y3[:, :, 1] > 0) if use_hands else (y3[:, :, 1] > 0) | |
| D = y3[:, :, 2] > 0 | |
| need = (obs3[:, :, 0] & obs3[:, :, 1] & obs3[:, :, 2]) if use_hands else (obs3[:, :, 1] & obs3[:, :, 2]) | |
| y = (U & D & need).any(1) | |
| full = need.all(1) | |
| return y, full | |
Xet Storage Details
- Size:
- 9.14 kB
- Xet hash:
- 4a102ff98bdee7bb3d6ae17572dc5947ef74378c658c9807327b39ff3cbcaa1f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.