diag: H 段(老师 oracle 走 rollout / 前缀消融 / 子目标越过顶点规则 / 分轴与分类别指标);训练:EMA、选模不落在 p_neq 预热、闭环分数选模、24000 步余弦、p_neq 0.5 + p_edge 0.1、子目标头切断抄输入、前缀/dq 增广;仿真:last 记真正提交的候选、碰撞与到达按每题时限、回放门禁读 pass+指纹并走 rollout;数据:动作标签按 1.25·STEP 归一化(act_scale 随 meta/ckpt/ONNX 传递,旧模型 1.0)、收回亏空结转、DAgger 带 qPrev 与起步速度、打包签名变更自动重打;ik-policy.js 同步(actScale、子目标规则、DLS 时清 lastPlan);本机执行清单与 README 更新
This commit is contained in:
+66
-7
@@ -1,12 +1,17 @@
|
||||
"""样本加载:ml/data/samples/*.{X,Y,V,SG,TID}.npy + *.tasks.jsonl(ml/prep/build_dataset.cjs 产出)
|
||||
"""样本加载:ml/data/samples/*.{X,Y,V,SG,TID}.npy + *.tasks.jsonl + *.meta.json(ml/prep/build_dataset.cjs 产出)
|
||||
python -m ml.data --index ml/data/samples # 汇总样本/任务数,写 index.json
|
||||
全部载入内存(200 万样本约 1.5 GB),按任务 split 字段划分 train / val。"""
|
||||
全部载入内存(200 万样本约 1.5 GB),按任务 split 字段划分 train / val。
|
||||
动作尺度(2026-10-19):Y = 每步增量 / (act_scale·STEP)。act_scale 取自各 shard 的 meta.json(DATA_VER 2 起写;
|
||||
旧数据没有 meta → act_scale 1.0、data_ver 1,即原口径)。同一次训练的所有目录(主数据 + DAgger)必须同一个 act_scale。"""
|
||||
import argparse, glob, json, os
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
PREFIX_SL, MASK_SL, DQ_SL, SUB_SL = slice(47, 65), slice(65, 68), slice(15, 21), slice(68, 75) # 特征分组(features.py 拼接顺序)
|
||||
|
||||
|
||||
def load_tasks(dirs):
|
||||
if isinstance(dirs, str): dirs = [dirs]
|
||||
tasks = []
|
||||
for d in dirs:
|
||||
for f in sorted(glob.glob(os.path.join(d, '*.tasks.jsonl'))):
|
||||
@@ -16,10 +21,28 @@ def load_tasks(dirs):
|
||||
return tasks
|
||||
|
||||
|
||||
def read_meta(d):
|
||||
"""目录内各 shard 的打包参数(data_ver、act_scale、增广参数、sig)。没有 meta 的旧打包 → {'data_ver': 1, 'act_scale': 1.0}。
|
||||
一部分有、一部分没有,或 act_scale 不一致 → 拒绝(新旧标签尺度混在一起会让动作幅度错 25%)。"""
|
||||
xs = sorted(glob.glob(os.path.join(d, '*.X.npy')))
|
||||
ms = [json.load(open(f[:-6] + '.meta.json', encoding='utf-8')) for f in xs if os.path.exists(f[:-6] + '.meta.json')]
|
||||
if not ms: return {'data_ver': 1, 'act_scale': 1.0}
|
||||
if len(ms) != len(xs): raise SystemExit(f'{d}:{len(xs) - len(ms)} 个 shard 缺 meta.json(新旧打包混在一起),重跑 prep')
|
||||
sc = {float(m.get('act_scale', 1.0)) for m in ms}
|
||||
if len(sc) != 1: raise SystemExit(f'{d}:各 shard 的 act_scale 不一致 {sorted(sc)},重跑 prep')
|
||||
return ms[0]
|
||||
|
||||
|
||||
class Samples:
|
||||
def __init__(self, dirs, device='cpu', split=None):
|
||||
def __init__(self, dirs, device='cpu', split=None, val_dirs=None):
|
||||
"""val_dirs:只用这些目录的 val 任务划 val(默认 = 全部 dirs)。2026-10-19(审查 review_closedloop F11):
|
||||
DAgger 目录的任务起点带速度、来自策略状态,不该混进预训练的 val;train.py / apg 传主数据目录,DAgger 样本全部算 train。"""
|
||||
if isinstance(dirs, str): dirs = [dirs]
|
||||
xs, ys, vs, ss, ts = [], [], [], [], []
|
||||
metas = [read_meta(d) for d in dirs]
|
||||
sc = {float(m['act_scale']) for m in metas}
|
||||
if len(sc) > 1: raise SystemExit(f'样本目录的 act_scale 不一致 {sorted(sc)}({dirs}):DAgger 样本须与主数据同一版打包')
|
||||
self.act_scale = sc.pop(); self.data_ver = int(metas[0].get('data_ver', 1)) if metas else 1
|
||||
for d in dirs:
|
||||
for fx in sorted(glob.glob(os.path.join(d, '*.X.npy'))):
|
||||
b = fx[:-6]
|
||||
@@ -27,7 +50,7 @@ class Samples:
|
||||
ss.append(np.load(b + '.SG.npy')); ts.append(np.load(b + '.TID.npy'))
|
||||
if not xs: raise SystemExit(f'没有样本:{dirs}(先跑 ml/prep/run_prep.sh)')
|
||||
self.tasks = load_tasks(dirs)
|
||||
val_tid = {t['tid'] for t in self.tasks if t['split'] == 'val'}
|
||||
val_tid = {t['tid'] for t in load_tasks(val_dirs if val_dirs is not None else dirs) if t['split'] == 'val'}
|
||||
X, Y, V, SG, TID = map(np.concatenate, (xs, ys, vs, ss, ts))
|
||||
is_val = np.isin(TID, np.fromiter(val_tid, dtype=np.int64)) if val_tid else np.zeros(len(TID), bool)
|
||||
sel = {'train': ~is_val, 'val': is_val, None: np.ones(len(TID), bool)}[split]
|
||||
@@ -35,9 +58,43 @@ class Samples:
|
||||
self.X, self.Y, self.V, self.SG, self.TID = t(X), t(Y), t(V), t(SG), t(TID)
|
||||
self.n = self.X.shape[0]
|
||||
|
||||
def batch(self, bs, gen=None):
|
||||
def batch(self, bs, gen=None, aug=None):
|
||||
"""aug:训练配置(train 段),按 aug_pre_drop / aug_pre_sigma / aug_dq_sigma 做条件输入增广(见 augment);None = 原样。"""
|
||||
i = torch.randint(0, self.n, (bs,), device=self.X.device, generator=gen)
|
||||
return self.X[i], self.Y[i], self.V[i], self.SG[i]
|
||||
X, Y = self.X[i], self.Y[i]
|
||||
if aug: X, Y = self.augment(X.clone(), Y.clone(), aug, gen)
|
||||
return X, Y, self.V[i], self.SG[i]
|
||||
|
||||
def augment(self, X, Y, cfg, gen=None):
|
||||
"""条件输入增广(2026-10-19,审查 review_data_actions C3 / review_model_train P1-4:前缀与 dq 的 copycat 捷径)。
|
||||
训练集里 r=3 时要执行的 Y[3:6] 几乎就是前缀第 3 步的延续,dq 又永远是老师的干净速度,模型容易学成「照抄上一块」,
|
||||
闭环里自己的误差经前缀 / dq 原样带回。三种扰动,标签都保持自洽:
|
||||
- 前缀丢弃(aug_pre_drop):r>0 的行以该概率清空前缀与掩码 → 变成 r=0 样本。Y 本来就从当前 q 起算,r=0 时标签不变;
|
||||
- 前缀噪声(aug_pre_sigma,STEP 为单位):r>0 的行一半加噪声 ε。前缀 = 已提交、一定会执行的步,所以标签前 r 步同样 +ε,
|
||||
第 r 步减去 Σε 把位置拉回老师轨迹(亏空在下一步补齐,与增广收回同理);
|
||||
- dq 噪声(aug_dq_sigma,STEP 为单位):一半的行给速度特征加噪声,状态与标签不变。"""
|
||||
B, dev = X.shape[0], X.device
|
||||
rnd = lambda *s: torch.rand(*s, device=dev, generator=gen)
|
||||
rn = lambda *s: torch.randn(*s, device=dev, generator=gen)
|
||||
pl = X[:, MASK_SL].sum(-1).round().long()
|
||||
pd = float(cfg.get('aug_pre_drop', 0.0))
|
||||
if pd > 0:
|
||||
m = (rnd(B) < pd) & (pl > 0)
|
||||
X[:, 47:68] = torch.where(m[:, None], torch.zeros_like(X[:, 47:68]), X[:, 47:68]); pl = torch.where(m, torch.zeros_like(pl), pl)
|
||||
ps = float(cfg.get('aug_pre_sigma', 0.0))
|
||||
if ps > 0:
|
||||
m = (rnd(B) < 0.5) & (pl > 0)
|
||||
km = (torch.arange(3, device=dev)[None] < pl[:, None]) & m[:, None] # [B,3] 前缀有效步
|
||||
eps = rn(B, 3, 6) * ps * km[..., None].to(X.dtype) # STEP 为单位
|
||||
X[:, PREFIX_SL] = X[:, PREFIX_SL] + eps.reshape(B, 18)
|
||||
Y[:, :3] = Y[:, :3] + eps / self.act_scale # Y 以 act_scale·STEP 为单位
|
||||
Y[torch.arange(B, device=dev), pl.clamp(max=Y.shape[1] - 1)] -= eps.sum(1) / self.act_scale
|
||||
Y.clamp_(-1, 1)
|
||||
ds = float(cfg.get('aug_dq_sigma', 0.0))
|
||||
if ds > 0:
|
||||
m = (rnd(B) < 0.5).to(X.dtype)
|
||||
X[:, DQ_SL] = X[:, DQ_SL] + rn(B, 6) * ds * m[:, None]
|
||||
return X, Y
|
||||
|
||||
|
||||
def main():
|
||||
@@ -46,7 +103,9 @@ def main():
|
||||
tasks = load_tasks([a.index])
|
||||
cats = {}
|
||||
for t in tasks: cats[t['cat'] + '/' + t['mode']] = cats.get(t['cat'] + '/' + t['mode'], 0) + 1
|
||||
info = {'train_samples': tr.n, 'val_samples': va.n, 'tasks': len(tasks), 'val_tasks': sum(t['split'] == 'val' for t in tasks), 'by_cat_mode': cats}
|
||||
meta = read_meta(a.index)
|
||||
info = {'train_samples': tr.n, 'val_samples': va.n, 'tasks': len(tasks), 'val_tasks': sum(t['split'] == 'val' for t in tasks), 'by_cat_mode': cats,
|
||||
'data_ver': int(meta.get('data_ver', 1)), 'act_scale': float(meta.get('act_scale', 1.0)), 'sig': meta.get('sig')}
|
||||
json.dump(info, open(os.path.join(a.index, 'index.json'), 'w'), indent=1)
|
||||
print(json.dumps(info, indent=1))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user