tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---- TASK CONFIG ----" && cat task_config.json && echo "---- FEATURE SCHEMA ----" && cat feature_schema.json
total 14
drwxrws--- 3 h2tagent h2tagent 138 Jun 20 18:13 .
drwxr-xr-x 1 root root 4096 Jun 20 20:52 ..
-rw-rw---- 1 h2tagent h2tagent 1639 Jun 20 18:10 candidate_model.py
drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 data
-rw-rw---- 1 h2tagent h2tagent 1183 Jun 20 18:10 dataset.py
-rw-rw---- 1 h2tagent h2tagent 499 Jun 20 18:10 feature_schema.json
-rw-rw---- 1 h2tagent h2tagent 581 Jun 20 18:10 task_config.json
-rw-rw---- 1 h2tagent h2tagent 3117 Jun 20 18:10 train.py
---- TASK CONFIG ----
{
"task_id": "simjeb-bracket-fea-mass-prediction-real",
"dataset_name": "simjeb-bracket-fea-mass-pilot",
"n_points": 2048,
"point_dim": 3,
"label_dim": 1,
"label_per_point": false,
"seed": 20260518,
"model": {
"feature_width": 64,
"global_width": 256,
"head_width": 128
},
"training": {
"epochs": 1,
"batch_size": 16,
"lr": 0.001,
"weight_decay": 0.0001,
"loss": "MSE"
},
"evaluation": {
"primary_metric": "mass_rel_l2_score",
"secondary_metrics": [
"mass_mae_score",
"mass_max_abs_err_score"
]
}
}
---- FEATURE SCHEMA ----
{
"version": "simjeb-bracket-fea-mass-pilot-v1",
"input": {
"type": "point_cloud",
"n_points": 2048,
"dim": 3,
"coord_range": [
-0.5,
0.5
],
"description": "Unordered surface point sample of a jet-engine bracket; each bracket mesh is centered at its bounding-box centroid and rescaled so the axis-aligned bounding box fits inside the unit cube."
},
"label": {
"type": "scalar",
"dim": 1,
"description": "FEA-derived bracket mass in kg."
}
}cd /app && echo "==== candidate_model.py ====" && cat candidate_model.py && echo "==== dataset.py ====" && cat dataset.py && echo "==== train.py ====" && cat train.py
==== candidate_model.py ====
from __future__ import annotations
from typing import Mapping
import torch
import torch.nn as nn
class SharedPointMLP(nn.Module):
def __init__(self, in_dim: int, widths: tuple[int, ...]) -> None:
super().__init__()
layers: list[nn.Module] = []
prev = in_dim
for w in widths:
layers.append(nn.Conv1d(prev, w, kernel_size=1))
layers.append(nn.BatchNorm1d(w))
layers.append(nn.GELU())
prev = w
self.net = nn.Sequential(*layers)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
class PointNetRegressor(nn.Module):
def __init__(self, feature_width: int, global_width: int, head_width: int) -> None:
super().__init__()
self.feature = SharedPointMLP(3, (feature_width, feature_width, feature_width))
self.lift = SharedPointMLP(feature_width, (feature_width * 2, global_width))
self.head = nn.Sequential(
nn.Linear(global_width, head_width),
nn.GELU(),
nn.Linear(head_width, head_width // 2),
nn.GELU(),
nn.Linear(head_width // 2, 1),
)
def forward(self, points: torch.Tensor) -> torch.Tensor:
x = points.transpose(1, 2).contiguous()
x = self.feature(x)
x = self.lift(x)
x = x.max(dim=2).values
return self.head(x)
def build_model(config: Mapping) -> PointNetRegressor:
m = config["model"]
return PointNetRegressor(
feature_width=int(m["feature_width"]),
global_width=int(m["global_width"]),
head_width=int(m["head_width"]),
)
==== dataset.py ====
from __future__ import annotations
from pathlib import Path
from typing import Iterable
import torch
from torch.utils.data import Dataset
class SampleStore(Dataset):
def __init__(self, split_dir: str | Path) -> None:
self.dir = Path(split_dir)
payload = torch.load(self.dir / "samples.pt", map_location="cpu", weights_only=True)
self.features: torch.Tensor = payload["features"].float()
self.labels: torch.Tensor = payload["labels"].float()
if self.features.shape[0] != self.labels.shape[0]:
raise ValueError("features and labels must have the same number of rows")
def __len__(self) -> int:
return self.features.shape[0]
def __getitem__(self, idx: int) -> tuple[torch.Tensor, torch.Tensor]:
return self.features[idx], self.labels[idx]
def iter_batches(
x: torch.Tensor,
y: torch.Tensor,
batch_size: int,
shuffle: bool = True,
) -> Iterable[tuple[torch.Tensor, torch.Tensor]]:
n = x.shape[0]
idx = torch.randperm(n) if shuffle else torch.arange(n)
for start in range(0, n, batch_size):
chunk = idx[start : start + batch_size]
yield x[chunk], y[chunk]
==== train.py ====
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
import torch
import torch.nn.functional as F
APP_DIR = Path(os.environ.get("APP_DIR", "/app"))
sys.path.insert(0, str(APP_DIR))
from candidate_model import build_model # noqa: E402
from dataset import SampleStore, iter_batches # noqa: E402
def _device() -> torch.device:
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
def _load_config() -> dict:
with (APP_DIR / "task_config.json").open() as fh:
return json.load(fh)
def main() -> None:
cfg = _load_config()
device = _device()
torch.manual_seed(int(cfg["seed"]))
train = SampleStore(APP_DIR / "data" / "train")
val = SampleStore(APP_DIR / "data" / "val")
feat_mean = train.features.mean(dim=(0, 1), keepdim=True)
feat_std = train.features.std(dim=(0, 1), keepdim=True).clamp_min(1e-6)
label_mean = train.labels.mean(dim=(0,), keepdim=True)
label_std = train.labels.std(dim=
…[truncated]cd /app && ls -la data && ls -la data/*/ && python -c "
import torch
for split in ['train','val','public_test']:
d = torch.load(f'data/{split}/samples.pt', weights_only=True)
f = d['features']; l = d['labels']
print(split, 'features', f.shape, f.dtype, 'labels', l.shape, l.dtype)
print(' label min/max/mean/std:', float(l.float().min()), float(l.float().max()), float(l.float().mean()), float(l.float().std()))
print(' coord min/max:', float(f.float().min()), float(f.float().max()))
"total 3 drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 . drwxrws--- 3 h2tagent h2tagent 138 Jun 20 18:13 .. drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 public_test drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 train drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 val data/public_test/: total 197 drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 . drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 .. -rw-rw---- 1 h2tagent h2tagent 1134 Jun 20 18:10 manifest.jsonl -rw-rw---- 1 h2tagent h2tagent 198437 Jun 20 18:10 samples.pt data/train/: total 1210 drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 . drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 .. -rw-rw---- 1 h2tagent h2tagent 6483 Jun 20 18:10 manifest.jsonl -rw-rw---- 1 h2tagent h2tagent 1230821 Jun 20 18:10 samples.pt data/val/: total 389 drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 . drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 .. -rw-rw---- 1 h2tagent h2tagent 2011 Jun 20 18:10 manifest.jsonl -rw-rw---- 1 h2tagent h2tagent 395109 Jun 20 18:10 samples.pt train features torch.Size([100, 2048, 3]) torch.float16 labels torch.Size([100, 1]) torch.float16 label min/max/mean/std: 0.207763671875 2.21875 0.7475781440734863 0.3894648551940918 coord min/max: -0.5 0.5 val features torch.Size([32, 2048, 3]) torch.float16 labels torch.Size([32, 1]) torch.float16 label min/max/mean/std: 0.293701171875 2.072265625 0.7621994018554688 0.41264674067497253 coord min/max: -0.5 0.5 public_test features torch.Size([16, 2048, 3]) torch.float16 labels torch.Size([16, 1]) torch.float16 label min/max/mean/std: 0.1983642578125 1.44921875 0.7296066284179688 0.327779084444046 coord min/max: -0.5 0.5
cd /app && echo "---- train manifest (first 3) ----" && head -n 3 data/train/manifest.jsonl && echo "---- val manifest (first 2) ----" && head -n 2 data/val/manifest.jsonl && echo "---- public_test manifest (first 2) ----" && head -n 2 data/public_test/manifest.jsonl
---- train manifest (first 3) ----
{"id": "train_0000", "source_bracket_id": 214, "n_points": 2048}
{"id": "train_0001", "source_bracket_id": 538, "n_points": 2048}
{"id": "train_0002", "source_bracket_id": 546, "n_points": 2048}
---- val manifest (first 2) ----
{"id": "val_0000", "source_bracket_id": 418, "n_points": 2048}
{"id": "val_0001", "source_bracket_id": 441, "n_points": 2048}
---- public_test manifest (first 2) ----
{"id": "public_test_0000", "source_bracket_id": 461, "n_points": 2048}
{"id": "public_test_0001", "source_bracket_id": 440, "n_points": 2048}cd /app && python -c "
import torch
d = torch.load('data/train/samples.pt', weights_only=True)
f = d['features'].float() # [100,2048,3]
l = d['labels'].float().squeeze(1)
# per-bracket min/max per axis
mn = f.min(dim=1).values # [100,3]
mx = f.max(dim=1).values
ext = mx - mn
print('per-axis extent stats (min over brackets):', ext.min(dim=0).values)
print('per-axis extent stats (max over brackets):', ext.max(dim=0).values)
print('per-axis extent stats (mean over brackets):', ext.mean(dim=0))
print()
# max extent per bracket (should be ~1.0 if uniform scaling to unit cube)
maxext = ext.max(dim=1).values
print('max-extent per bracket: min/max/mean', float(maxext.min()), float(maxext.max()), float(maxext.mean()))
print('how many brackets have max-extent within [0.99,1.01]:', int(((maxext>0.99)&(maxext<1.01)).sum()))
print()
# centroid of points
print('per-bracket point centroid mean (abs):', f.mean(dim=1).abs().mean(dim=0))
print('per-bracket bbox center mean:', ((mx+mn)/2).abs().mean(dim=0))
"per-axis extent stats (min over brackets): tensor([0.5022, 0.9912, 0.2758]) per-axis extent stats (max over brackets): tensor([0.6436, 1.0000, 0.4550]) per-axis extent stats (mean over brackets): tensor([0.5887, 0.9991, 0.3499]) max-extent per bracket: min/max/mean 0.9912109375 1.0 0.9991015791893005 how many brackets have max-extent within [0.99,1.01]: 100 per-bracket point centroid mean (abs): tensor([0.0233, 0.0091, 0.0710]) per-bracket bbox center mean: tensor([0.0006, 0.0003, 0.0003])
cd /app && python -c "
import torch, numpy as np
d = torch.load('data/train/samples.pt', weights_only=True)
f = d['features'].float().numpy()
l = d['labels'].float().squeeze(1).numpy()
# Simple geometric features per bracket
feats = {}
mn = f.min(1); mx = f.max(1); ext = mx-mn
feats['ext_x']=ext[:,0]; feats['ext_y']=ext[:,1]; feats['ext_z']=ext[:,2]
feats['bbox_vol']=ext[:,0]*ext[:,1]*ext[:,2]
feats['std_x']=f[:,:,0].std(1); feats['std_y']=f[:,:,1].std(1); feats['std_z']=f[:,:,2].std(1)
# voxel occupancy at various resolutions
for R in [8,16,24,32]:
occ=[]
for i in range(f.shape[0]):
q=np.floor((f[i]+0.5)*R).clip(0,R-1).astype(int)
vox=np.zeros((R,R,R),bool)
vox[q[:,0],q[:,1],q[:,2]]=True
occ.append(vox.sum()/float(R**3))
feats[f'occ{R}']=np.array(occ)
print('Correlation of each feature with mass:')
for k,v in feats.items():
c=np.corrcoef(v,l)[0,1]
print(f' {k:10s} corr={c:+.3f}')
"Correlation of each feature with mass: ext_x corr=+0.234 ext_y corr=+0.143 ext_z corr=+0.177 bbox_vol corr=+0.253 std_x corr=+0.635 std_y corr=+0.149 std_z corr=+0.250 occ8 corr=+0.701 occ16 corr=+0.775 occ24 corr=+0.686 occ32 corr=+0.660
cd /app && python -c "
import torch, numpy as np
def load(s):
d=torch.load(f'data/{s}/samples.pt',weights_only=True)
return d['features'].float().numpy(), d['labels'].float().squeeze(1).numpy()
def featurize(f):
mn=f.min(1);mx=f.max(1);ext=mx-mn
cols=[ext, f.std(1), np.abs(f).mean(1)]
for R in [8,12,16,20,24]:
occ=[]
for i in range(f.shape[0]):
q=np.floor((f[i]+0.5)*R).clip(0,R-1).astype(int)
vox=np.zeros((R,R,R),bool); vox[q[:,0],q[:,1],q[:,2]]=True
occ.append(vox.sum()/float(R**3))
cols.append(np.array(occ)[:,None])
return np.concatenate(cols,1)
Xtr,ytr=load('train'); Xv,yv=load('val'); Xp,yp=load('public_test')
Ftr=featurize(Xtr); Fv=featurize(Xv); Fp=featurize(Xp)
def metrics(pred,true):
e=pred-true
rel=np.sqrt((e**2).sum())/np.sqrt((true**2).sum())
return rel, np.abs(e).mean(), np.abs(e).max()
# baseline: predict train mean
for name,(F,y) in {'val':(Fv,yv),'public':(Fp,yp)}.items():
print(f'mean-predictor {name}: rel/mae/max=', [round(x,4) for x in metrics(np.full_like(y,ytr.mean()),y)])
# standardize feats
mu=Ftr.mean(0);sd=Ftr.std(0)+1e-9
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor
from sklearn.linear_model import Ridge
for mdl,nm in [(Ridge(alpha=1.0),'ridge'),(RandomForestRegressor(n_estimators=400,random_state=0),'rf'),(GradientBoostingRegressor(random_state=0),'gbr')]:
mdl.fit((Ftr-mu)/sd,ytr)
pv=mdl.predict((Fv-mu)/sd); pp=mdl.predict((Fp-mu)/sd)
print(f'{nm:6s} val ',[round(x,4) for x in metrics(pv,yv)], '| public',[round(x,4) for x in metrics(pp,yp)])
" 2>/dev/null || echo "sklearn not available"mean-predictor val: rel/mae/max= [np.float32(0.4706), np.float32(0.3024), np.float32(1.3247)] mean-predictor public: rel/mae/max= [np.float32(0.3995), np.float32(0.2607), np.float32(0.7016)] sklearn not available
cd /app && python -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" && nvidia-smi -L 2>/dev/null || echo "no gpu"; echo "---- CPUs ----"; nproc; echo "---- mem ----"; free -h 2>/dev/null | head -2; echo "---- pip pkgs ----"; pip list 2>/dev/null | grep -iE "numpy|scikit|scipy|torch" torch 2.5.1+cu124 cuda True
GPU 0: NVIDIA H100 80GB HBM3 (UUID: GPU-a5c6efc2-7ed8-4aca-4a53-036d08db2907)
---- CPUs ----
3
---- mem ----
total used free shared buff/cache available
Mem: 30Gi 63Mi 29Gi 0B 29Mi 29Gi
---- pip pkgs ----
numpy 2.1.2
torch 2.5.1+cu124
torchaudio 2.5.1+cu124
torchelastic 0.2.2
torchvision 0.20.1+cu124cd /app && python -c "
import torch, numpy as np
def load(s):
d=torch.load(f'data/{s}/samples.pt',weights_only=True)
return d['features'].float().numpy(), d['labels'].float().squeeze(1).numpy()
def featurize(f):
mn=f.min(1);mx=f.max(1);ext=mx-mn
cols=[ext, f.std(1), np.abs(f).mean(1)]
for R in [8,12,16,20,24]:
occ=[]
for i in range(f.shape[0]):
q=np.floor((f[i]+0.5)*R).clip(0,R-1).astype(int)
vox=np.zeros((R,R,R),bool); vox[q[:,0],q[:,1],q[:,2]]=True
occ.append(vox.sum()/float(R**3))
cols.append(np.array(occ)[:,None])
return np.concatenate(cols,1)
Xtr,ytr=load('train'); Xv,yv=load('val'); Xp,yp=load('public_test')
Ftr=featurize(Xtr); Fv=featurize(Xv); Fp=featurize(Xp)
mu=Ftr.mean(0);sd=Ftr.std(0)+1e-9
A=(Ftr-mu)/sd; Av=(Fv-mu)/sd; Ap=(Fp-mu)/sd
def metrics(pred,true):
e=pred-true; return (np.sqrt((e**2).sum())/np.sqrt((true**2).sum()), np.abs(e).mean(), np.abs(e).max())
# Ridge closed form
for lam in [0.1,1.0,10.0]:
Aa=np.concatenate([A,np.ones((A.shape[0],1))],1)
W=np.linalg.solve(Aa.T@Aa+lam*np.eye(Aa.shape[1]), Aa.T@ytr)
pv=np.concatenate([Av,np.ones((Av.shape[0],1))],1)@W
pp=np.concatenate([Ap,np.ones((Ap.shape[0],1))],1)@W
print(f'ridge lam={lam}: val',[round(x,4) for x in metrics(pv,yv)],'| public',[round(x,4) for x in metrics(pp,yp)])
# kNN
def knn(q,k):
d=((A[None]-q[:,None])**2).sum(-1); idx=np.argsort(d,1)[:,:k]; return ytr[idx].mean(1)
for k in [1,3,5]:
print(f'knn k={k}: val',[round(x,4) for x in metrics(knn(Av,k),yv)],'| public',[round(x,4) for x in metrics(knn(Ap,k),yp)])
"ridge lam=0.1: val [np.float64(0.2061), np.float64(0.1361), np.float64(0.553)] | public [np.float64(0.2906), np.float64(0.1965), np.float64(0.4796)] ridge lam=1.0: val [np.float64(0.2059), np.float64(0.1336), np.float64(0.5565)] | public [np.float64(0.2794), np.float64(0.1912), np.float64(0.4467)] ridge lam=10.0: val [np.float64(0.2161), np.float64(0.1415), np.float64(0.6529)] | public [np.float64(0.2823), np.float64(0.195), np.float64(0.3766)] knn k=1: val [np.float32(0.2238), np.float32(0.1485), np.float32(0.4824)] | public [np.float32(0.3427), np.float32(0.1953), np.float32(0.7788)] knn k=3: val [np.float32(0.2571), np.float32(0.1727), np.float32(0.6559)] | public [np.float32(0.3001), np.float32(0.1632), np.float32(0.6751)] knn k=5: val [np.float32(0.2555), np.float32(0.1712), np.float32(0.6848)] | public [np.float32(0.2238), np.float32(0.1365), np.float32(0.4166)]
cd /app && ls outputs 2>/dev/null || echo "no outputs dir yet"; mkdir -p /app/scratch && python -c "
import torch
d=torch.load('data/train/samples.pt',weights_only=True)
f=d['features'].float()
print('global per-axis mean', f.mean(dim=(0,1)))
print('global per-axis std ', f.std(dim=(0,1)))
print('overall std (all coords)', f.std())
print('rms per axis', (f**2).mean(dim=(0,1)).sqrt())
"no outputs dir yet global per-axis mean tensor([-0.0135, 0.0075, -0.0710]) global per-axis std tensor([0.1421, 0.2702, 0.0871]) overall std (all coords) tensor(0.1863) rms per axis tensor([0.1428, 0.2703, 0.1124])
command: create
path: /app/scratch/trainlib.py
file_text: from __future__ import annotations
import json, os, sys, copy, math
from pathlib import Path
import torch
import torch.nn as nn
import torch.nn.functional as F
APP = Path("/app")
sys.path.insert(0, str(APP))
from candidate_model import build_model # noqa
DEV = torch.device("cuda" if torch.cuda.is_available() else "cpu")
def load_split(name):
d = torch.load(APP / "data" / name / "samples.pt", weights_only=True)
return d["features"].float(), d["labels"].float()
def load_cfg():
with (APP / "task_config.json").open() as fh:
return json.load(fh)
def compute_norm(feats, labels, isotropic=True):
feat_mean = feats.mean(dim=(0, 1)) # [3]
if isotropic:
s = feats.std().repeat(3) # single scalar across axes
else:
s = feats.std(dim=(0, 1))
feat_std = s.clamp_min(1e-6)
label_mean = labels.mean(dim=0) # [1]
label_std = labels.std(dim=0).clamp_min(1e-6)
return feat_mean, feat_std, label_mean, label_std
def augment(x, *, flip=True, jitter=0.0, resample=0.0, scale=0.0, rot=0.0):
"""x: [B,N,3] on device. Returns augmented copy. Mass-preserving ops only."""
B, N, _ = x.shape
out = x
if flip:
signs = (torch.randint(0, 2, (B, 1, 3), device=x.device, dtype=x.dtype) * 2 - 1)
out = out * signs
if resample > 0.0:
# per-sample: drop a random fraction and resample with replacement to N
keep = int(round(N * (1.0 - resample)))
idx = torch.randint(0, keep, (B, N), device=x.device)
base = torch.stack([torch.randperm(N, device=x.device)[:keep] for _ in range(B)])
gather_idx = torch.gather(base, 1, idx)
out = torch.gather(out, 1, gather_idx.unsqueeze(-1).expand(-1, -1, 3))
if scale > 0.0:
s = 1.0 + (torch.rand(B, 1, 3, device=x.device, dtype=x.dtype) * 2 - 1) * scale
out = out * s
if rot > 0.0: # small rotation about Y (vertical/long axis) only, radians
ang = (torch.rand(B, device=x.device) * 2 - 1) * rot
c, s = torch.cos(ang), torch.sin(ang)
xr = out[..., 0] * c[:, None] + out[..., 2] * s[:, None]
zr = -out[..., 0] * s[:, None] + out[..., 2] * c[:, None]
out = torch.stack([xr, out[..., 1], zr], dim=-1)
if jitter > 0.0:
out = out + torch.randn_like(out) * jitter
return out
class EMA:
def __init__(self, model, decay=0.999):
self.decay = decay
self.shadow = copy.deepcopy(model).eval()
for p in self.shadow.parameters():
p.requires_grad_(False)
@torch.no_grad()
def update(self, model):
d = self.decay
for s, m in zip(self.shadow.state_dict().values(), model.state_dict().values()):
if s.dtype.is_floating_point:
s.mul_(d).add_(m.detach(), alpha=1 - d)
else:
s.copy_(m)
@torch.no_grad()
def evaluate(model, x, y, fmean, fstd, lmean, lstd):
model.eval()
xn = (x - fmean) / fstd
preds = []
for i in range(0, xn.shape[0], 64):
preds.append(model(xn[i:i+64]))
pred = torch.cat(preds, 0) * lstd + lmean # physical units
true = y
e = (pred - true).squeeze(1)
t = true.squeeze(1)
rel = torch.sqrt((e**2).sum()) / torch.sqrt((t**2).sum())
mae = e.abs().mean()
mx = e.abs().max()
return float(rel), float(mae), float(mx)
def train_run(cfg_over, train_data, val_data, probe_data=None, verbose=True):
torch.manual_seed(cfg_over.get("seed", 0))
cfg = load_cfg()
xb_all, yb_all = train_data
xb_all, yb_all = xb_all.to(DEV), yb_all.to(DEV)
vx, vy = val_data
vx, vy = vx.to(DEV), vy.to(DEV)
fmean, fstd, lmean, lstd = compute_norm(xb_all, yb_all, cfg_over.get("isotropic", True))
fmean, fstd = fmean.to(DEV), fstd.to(DEV)
lmean, lstd = lmean.to(DEV), lstd.to(DEV)
model = build_model(cfg).to(DEV)
epochs = cfg_over["epochs"]
bs = cfg_over.
…[truncated]New file created successfully at /app/scratch/trainlib.py
cd /app/scratch && python -c "
import torch, trainlib as T
tr = T.load_split('train'); va = T.load_split('val'); pt = T.load_split('public_test')
cfg = dict(seed=0, epochs=400, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, ema_decay=0.999,
loss='mse', isotropic=True, eval_every=40,
aug=dict(flip=True, jitter=0.01, resample=0.15))
best = T.train_run(cfg, tr, va, probe_data=pt)
print('BEST val rel/mae/mx=', round(best['rel'],4), round(best['mae'],4), round(best['mx'],4), 'via', best['which'], 'ep', best['ep'])
"ep 40 raw=0.392/0.297/0.844 ema=0.471/0.300/1.332 | probe(ema) rel/mae/mx=0.399/0.260/0.709 <== best raw ep 80 raw=0.332/0.203/0.971 ema=0.472/0.297/1.345 | probe(ema) rel/mae/mx=0.399/0.259/0.722 <== best raw ep 120 raw=0.288/0.198/0.538 ema=0.475/0.295/1.365 | probe(ema) rel/mae/mx=0.400/0.259/0.742 <== best raw ep 160 raw=0.279/0.190/0.575 ema=0.480/0.296/1.391 | probe(ema) rel/mae/mx=0.404/0.259/0.768 <== best raw ep 200 raw=0.230/0.155/0.470 ema=0.488/0.297/1.421 | probe(ema) rel/mae/mx=0.411/0.259/0.800 <== best raw ep 240 raw=0.264/0.181/0.514 ema=0.497/0.298/1.446 | probe(ema) rel/mae/mx=0.422/0.260/0.831 ep 280 raw=0.225/0.141/0.553 ema=0.507/0.298/1.466 | probe(ema) rel/mae/mx=0.437/0.264/0.861 <== best raw ep 320 raw=0.248/0.161/0.639 ema=0.521/0.306/1.480 | probe(ema) rel/mae/mx=0.458/0.278/0.894 ep 360 raw=0.248/0.167/0.569 ema=0.533/0.312/1.466 | probe(ema) rel/mae/mx=0.480/0.300/0.923 ep 400 raw=0.258/0.172/0.639 ema=0.534/0.313/1.401 | probe(ema) rel/mae/mx=0.496/0.316/0.941 BEST val rel/mae/mx= 0.2247 0.1407 0.553 via raw ep 280
cd /app/scratch && python -c "
import trainlib as T
m = T.build_model(T.load_cfg())
n = sum(p.numel() for p in m.parameters())
print('total params:', n)
for nm,p in m.named_parameters():
print(f' {nm:30s} {tuple(p.shape)}')
" | head -40total params: 92289 feature.net.0.weight (64, 3, 1) feature.net.0.bias (64,) feature.net.1.weight (64,) feature.net.1.bias (64,) feature.net.3.weight (64, 64, 1) feature.net.3.bias (64,) feature.net.4.weight (64,) feature.net.4.bias (64,) feature.net.6.weight (64, 64, 1) feature.net.6.bias (64,) feature.net.7.weight (64,) feature.net.7.bias (64,) lift.net.0.weight (128, 64, 1) lift.net.0.bias (128,) lift.net.1.weight (128,) lift.net.1.bias (128,) lift.net.3.weight (256, 128, 1) lift.net.3.bias (256,) lift.net.4.weight (256,) lift.net.4.bias (256,) head.0.weight (128, 256) head.0.bias (128,) head.2.weight (64, 128) head.2.bias (64,) head.4.weight (1, 64) head.4.bias (1,)
command: str_replace
path: /app/scratch/trainlib.py
old_str: @torch.no_grad()
def evaluate(model, x, y, fmean, fstd, lmean, lstd):
new_str: @torch.no_grad()
def bn_recalibrate(model, x_norm, passes=3, bs=32):
"""Reset BN running stats and re-estimate them from clean normalized data."""
for m in model.modules():
if isinstance(m, nn.BatchNorm1d):
m.reset_running_stats()
m.momentum = None # cumulative average
model.train()
for _ in range(passes):
for i in range(0, x_norm.shape[0], bs):
model(x_norm[i:i+bs])
model.eval()
return model
@torch.no_grad()
def evaluate(model, x, y, fmean, fstd, lmean, lstd):The file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/trainlib.py
old_str: if (ep + 1) % cfg_over.get("eval_every", 25) == 0 or ep == epochs - 1:
rr = evaluate(model, vx, vy, fmean, fstd, lmean, lstd)
re = evaluate(ema.shadow, vx, vy, fmean, fstd, lmean, lstd)
tag = ""
for nm, m in [("raw", rr), ("ema", re)]:
if m[0] < best["rel"]:
best = {"rel": m[0], "mae": m[1], "mx": m[2], "which": nm, "ep": ep+1,
"sd": copy.deepcopy((model if nm == "raw" else ema.shadow).state_dict())}
tag = " <== best " + nm
if verbose:
pr = ""
if probe_data is not None:
px, py = probe_data
pm = evaluate(ema.shadow, px.to(DEV), py.to(DEV), fmean, fstd, lmean, lstd)
pr = f" | probe(ema) rel/mae/mx={pm[0]:.3f}/{pm[1]:.3f}/{pm[2]:.3f}"
print(f"ep{ep+1:4d} raw={rr[0]:.3f}/{rr[1]:.3f}/{rr[2]:.3f} "
f"ema={re[0]:.3f}/{re[1]:.3f}/{re[2]:.3f}{pr}{tag}", flush=True)
new_str: ev = cfg_over.get("eval_every", 1)
if (ep + 1) % ev == 0 or ep == epochs - 1:
rr = evaluate(model, vx, vy, fmean, fstd, lmean, lstd)
# recalibrate EMA BN stats on clean train data, then eval
shadow = copy.deepcopy(ema.shadow)
bn_recalibrate(shadow, xn_all)
re = evaluate(shadow, vx, vy, fmean, fstd, lmean, lstd)
tag = ""
for nm, m, mdl in [("raw", rr, model), ("ema", re, shadow)]:
score = m[0] # rel L2 primary
if score < best["rel"]:
best = {"rel": m[0], "mae": m[1], "mx": m[2], "which": nm, "ep": ep+1,
"sd": copy.deepcopy(mdl.state_dict())}
tag = " <== best " + nm
if verbose and ((ep + 1) % cfg_over.get("log_every", 40) == 0 or ep == epochs - 1):
pr = ""
if probe_data is not None:
px, py = probe_data
pm = evaluate(shadow, px.to(DEV), py.to(DEV), fmean, fstd, lmean, lstd)
pr = f" | probe(ema) {pm[0]:.3f}/{pm[1]:.3f}/{pm[2]:.3f}"
print(f"ep{ep+1:4d} raw={rr[0]:.3f}/{rr[1]:.3f}/{rr[2]:.3f} "
f"ema={re[0]:.3f}/{re[1]:.3f}/{re[2]:.3f}{pr}{tag}", flush=True)The file /app/scratch/trainlib.py has been edited successfully.
cd /app/scratch && python -c "
import torch, trainlib as T
tr = T.load_split('train'); va = T.load_split('val'); pt = T.load_split('public_test')
cfg = dict(seed=0, epochs=400, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, ema_decay=0.997,
loss='mse', isotropic=True, eval_every=1, log_every=50,
aug=dict(flip=True, jitter=0.01, resample=0.15))
best = T.train_run(cfg, tr, va, probe_data=pt)
print('BEST val rel/mae/mx=', round(best['rel'],4), round(best['mae'],4), round(best['mx'],4), 'via', best['which'], 'ep', best['ep'])
"ep 50 raw=0.343/0.215/0.966 ema=0.405/0.288/0.978 | probe(ema) 0.315/0.200/0.449 ep 100 raw=0.302/0.198/0.809 ema=0.365/0.244/0.824 | probe(ema) 0.279/0.170/0.575 ep 150 raw=0.289/0.188/0.824 ema=0.357/0.233/0.785 | probe(ema) 0.348/0.187/0.883 ep 200 raw=0.252/0.170/0.563 ema=0.427/0.263/1.124 | probe(ema) 0.460/0.214/1.206 ep 250 raw=0.243/0.148/0.713 ema=0.473/0.283/1.266 | probe(ema) 0.499/0.228/1.285 <== best raw ep 300 raw=0.251/0.146/0.780 ema=0.476/0.278/1.311 | probe(ema) 0.513/0.241/1.307 ep 350 raw=0.256/0.171/0.651 ema=0.464/0.268/1.283 | probe(ema) 0.484/0.227/1.276 ep 400 raw=0.256/0.166/0.716 ema=0.450/0.257/1.264 | probe(ema) 0.457/0.213/1.230 BEST val rel/mae/mx= 0.2361 0.1488 0.651 via raw ep 298
command: str_replace
path: /app/scratch/trainlib.py
old_str: def save_ckpt(path, sd, norm):
new_str: def metrics_vec(pred, true):
e = pred - true
rel = float(torch.sqrt((e**2).sum()) / torch.sqrt((true**2).sum()))
return rel, float(e.abs().mean()), float(e.abs().max())
def train_core(cfg_over, Xtr, Ytr, Xte, snapshot_epochs, calib_x=None):
"""Train on (Xtr,Ytr); at each snapshot epoch, predict Xte with raw+ema(recal).
Returns dict epoch -> {'raw':pred[Nte,1], 'ema':pred[Nte,1]} in physical units."""
torch.manual_seed(cfg_over.get("seed", 0))
cfg = load_cfg()
Xtr, Ytr, Xte = Xtr.to(DEV), Ytr.to(DEV), Xte.to(DEV)
fmean, fstd, lmean, lstd = compute_norm(Xtr, Ytr, cfg_over.get("isotropic", True))
fmean, fstd = fmean.to(DEV), fstd.to(DEV); lmean, lstd = lmean.to(DEV), lstd.to(DEV)
calib = (Xtr if calib_x is None else calib_x.to(DEV))
calib_n = (calib - fmean) / fstd
model = build_model(cfg).to(DEV)
epochs = cfg_over["epochs"]; bs = cfg_over.get("bs", 16)
lr = cfg_over.get("lr", 1e-3); wd = cfg_over.get("wd", 1e-4)
warm = cfg_over.get("warmup", max(1, epochs // 20)); aug = cfg_over.get("aug", {})
loss_kind = cfg_over.get("loss", "mse"); huber_beta = cfg_over.get("huber_beta", 0.1)
opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=wd)
ema = EMA(model, cfg_over.get("ema_decay", 0.999))
N = Xtr.shape[0]; steps_per = max(1, math.ceil(N / bs)); total = epochs * steps_per
def lr_at(st):
if st < warm * steps_per:
return lr * (st + 1) / (warm * steps_per)
p = (st - warm * steps_per) / max(1, total - warm * steps_per)
return lr * 0.5 * (1 + math.cos(math.pi * p))
yn_all = (Ytr - lmean) / lstd
snaps = {}
step = 0
snapset = set(snapshot_epochs)
for ep in range(epochs):
model.train()
perm = torch.randperm(N, device=DEV)
for s in range(0, N, bs):
idx = perm[s:s+bs]
xb = augment(Xtr[idx], **aug)
xbn = (xb - fmean) / fstd
for g in opt.param_groups:
g["lr"] = lr_at(step)
opt.zero_grad(set_to_none=True)
pred = model(xbn)
yb = yn_all[idx]
if loss_kind == "mse":
loss = F.mse_loss(pred, yb)
elif loss_kind == "huber":
loss = F.smooth_l1_loss(pred, yb, beta=huber_beta)
elif loss_kind == "mse+l1":
loss = F.mse_loss(pred, yb) + 0.3 * F.l1_loss(pred, yb)
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), 5.0)
opt.step(); ema.update(model); step += 1
if (ep + 1) in snapset:
with torch.no_grad():
model.eval()
xten = (Xte - fmean) / fstd
pr = torch.cat([model(xten[i:i+64]) for i in range(0, xten.shape[0], 64)], 0)
pr = pr * lstd + lmean
shadow = copy.deepcopy(ema.shadow); bn_recalibrate(shadow, calib_n)
pe = torch.cat([shadow(xten[i:i+64]) for i in range(0, xten.shape[0], 64)], 0)
pe = pe * lstd + lmean
snaps[ep + 1] = {"raw": pr.cpu(), "ema": pe.cpu()}
return snaps, (fmean.cpu(), fstd.cpu(), lmean.cpu(), lstd.cpu())
def run_cv(cfg_over, pooled, n_folds, snapshot_epochs, seed=0):
X, Y = pooled
N = X.shape[0]
g = torch.Generator().manual_seed(seed)
perm = torch.randperm(N, generator=g)
folds = [perm[i::n_folds] for i in range(n_folds)]
# accumulate OOF predictions per (epoch, which)
oof = {e: {"raw": torch.zeros(N, 1), "ema": torch.zeros(N, 1)} for e in snapshot_epochs}
for k in range(n_folds):
te_idx = folds[k]
tr_mask = torch.ones(N, dtype=torch.bool); tr_mask[te_idx] = False
Xtr, Ytr = X[tr_mask], Y[tr_mask]
Xte = X[te_idx]
snaps, _ = train_core(cfg_over, Xtr, Ytr, Xte, snapshot_epochs)
for e in snapshot_epochs:
oof
…[truncated]The file /app/scratch/trainlib.py has been edited successfully.
cd /app/scratch && python -c "
import torch, trainlib as T, time
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
print('pooled', X.shape, Y.shape)
snaps=[50,100,150,200,250,300]
cfg = dict(seed=0, epochs=300, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, ema_decay=0.997,
loss='mse', isotropic=True, aug=dict(flip=True, jitter=0.01, resample=0.15))
t=time.time()
res=T.run_cv(cfg, (X,Y), n_folds=6, snapshot_epochs=snaps, seed=0)
print(f'elapsed {time.time()-t:.1f}s')
for e in snaps:
for w in ['raw','ema']:
r=res[(e,w)]; print(f' ep{e:4d} {w}: rel={r[0]:.4f} mae={r[1]:.4f} mx={r[2]:.4f}')
"pooled torch.Size([132, 2048, 3]) torch.Size([132, 1]) elapsed 85.4s ep 50 raw: rel=0.3609 mae=0.2269 mx=1.0412 ep 50 ema: rel=0.4064 mae=0.2784 mx=1.1889 ep 100 raw: rel=0.3509 mae=0.2226 mx=0.9302 ep 100 ema: rel=0.3838 mae=0.2666 mx=0.8227 ep 150 raw: rel=0.3429 mae=0.2119 mx=0.8860 ep 150 ema: rel=0.3665 mae=0.2365 mx=1.1594 ep 200 raw: rel=0.3222 mae=0.2065 mx=0.8145 ep 200 ema: rel=0.3884 mae=0.2337 mx=1.3597 ep 250 raw: rel=0.2903 mae=0.1886 mx=0.7459 ep 250 ema: rel=0.4154 mae=0.2378 mx=1.4200 ep 300 raw: rel=0.2952 mae=0.1950 mx=0.8137 ep 300 ema: rel=0.4298 mae=0.2424 mx=1.3895
cd /app/scratch && python -c "
import torch, trainlib as T
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
cfg = dict(seed=0, epochs=250, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, ema_decay=0.997,
loss='mse', isotropic=True, aug=dict(flip=True, jitter=0.01, resample=0.15))
# single CV, collect OOF raw preds at ep250
res=T.run_cv.__wrapped__ if hasattr(T.run_cv,'__wrapped__') else None
# replicate run_cv to get oof vector
import trainlib
N=X.shape[0]; g=torch.Generator().manual_seed(0); perm=torch.randperm(N,generator=g)
folds=[perm[i::6] for i in range(6)]
oof=torch.zeros(N,1)
for k in range(6):
te=folds[k]; m=torch.ones(N,bool); m[te]=False
snaps,_=trainlib.train_core(cfg,X[m],Y[m],X[te],[250])
oof[te]=snaps[250]['raw']
err=(oof-Y).squeeze(1).abs()
order=torch.argsort(err,descending=True)
print('Top-10 abs errors (true_mass, pred, abserr):')
for i in order[:10]:
print(f' true={float(Y[i,0]):.3f} pred={float(oof[i,0]):.3f} err={float(err[i]):.3f}')
print('mass vs err corr:', float(torch.corrcoef(torch.stack([Y.squeeze(1),err]))[0,1]))
import numpy as np
yb=Y.squeeze(1).numpy(); eb=err.numpy()
for lo,hi in [(0,0.5),(0.5,0.9),(0.9,1.3),(1.3,3)]:
msk=(yb>=lo)&(yb<hi); print(f' mass[{lo},{hi}): n={msk.sum():3d} meanerr={eb[msk].mean():.3f}')
"[stderr] Traceback (most recent call last): File "<string>", line 15, in <module> TypeError: ones(): argument 'size' failed to unpack the object at pos 2 with error "type must be tuple of ints,but got type"
cd /app/scratch && python -c "
import torch, trainlib
trX,trY=trainlib.load_split('train'); vaX,vaY=trainlib.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
cfg = dict(seed=0, epochs=250, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse', isotropic=True,
aug=dict(flip=True, jitter=0.01, resample=0.15))
N=X.shape[0]; g=torch.Generator().manual_seed(0); perm=torch.randperm(N,generator=g)
folds=[perm[i::6] for i in range(6)]
oof=torch.zeros(N,1)
for k in range(6):
te=folds[k]; m=torch.ones(N,dtype=torch.bool); m[te]=False
snaps,_=trainlib.train_core(cfg,X[m],Y[m],X[te],[250])
oof[te]=snaps[250]['raw']
err=(oof-Y).squeeze(1).abs()
order=torch.argsort(err,descending=True)
print('Top-8 abs errors (true, pred, err):')
for i in order[:8]:
print(f' true={float(Y[i,0]):.3f} pred={float(oof[i,0]):.3f} err={float(err[i]):.3f}')
import numpy as np
yb=Y.squeeze(1).numpy(); eb=err.numpy()
for lo,hi in [(0,0.5),(0.5,0.9),(0.9,1.3),(1.3,3)]:
msk=(yb>=lo)&(yb<hi); print(f' mass[{lo},{hi}): n={int(msk.sum()):3d} meanerr={eb[msk].mean():.3f} rms={np.sqrt((eb[msk]**2).mean()):.3f}')
print('overall rel', float(torch.sqrt((err**2).sum())/torch.sqrt((Y.squeeze(1)**2).sum())))
"Top-8 abs errors (true, pred, err): true=1.640 pred=0.787 err=0.852 true=1.634 pred=0.875 err=0.759 true=1.366 pred=0.719 err=0.647 true=1.114 pred=0.507 err=0.607 true=2.072 pred=1.497 err=0.575 true=1.584 pred=1.015 err=0.569 true=1.180 pred=0.619 err=0.561 true=1.072 pred=0.572 err=0.500 mass[0,0.5): n= 41 meanerr=0.073 rms=0.092 mass[0.5,0.9): n= 56 meanerr=0.177 rms=0.200 mass[0.9,1.3): n= 20 meanerr=0.297 rms=0.340 mass[1.3,3): n= 15 meanerr=0.458 rms=0.496 overall rel 0.3009372651576996
command: str_replace
path: /app/scratch/trainlib.py
old_str: yn_all = (Ytr - lmean) / lstd
snaps = {}
step = 0
snapset = set(snapshot_epochs)
new_str: yn_all = (Ytr - lmean) / lstd
wpow = cfg_over.get("wpow", 0.0)
wts = (Ytr.squeeze(1) ** wpow)
wts = wts / wts.mean() # normalized sample weights [N]
snaps = {}
step = 0
snapset = set(snapshot_epochs)The file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/trainlib.py
old_str: pred = model(xbn)
yb = yn_all[idx]
if loss_kind == "mse":
loss = F.mse_loss(pred, yb)
elif loss_kind == "huber":
loss = F.smooth_l1_loss(pred, yb, beta=huber_beta)
elif loss_kind == "mse+l1":
loss = F.mse_loss(pred, yb) + 0.3 * F.l1_loss(pred, yb)
new_str: pred = model(xbn)
yb = yn_all[idx]
w = wts[idx].unsqueeze(1)
if loss_kind == "mse":
loss = (w * (pred - yb) ** 2).mean()
elif loss_kind == "huber":
loss = (w * F.smooth_l1_loss(pred, yb, beta=huber_beta, reduction="none")).mean()
elif loss_kind == "mse+l1":
loss = (w * ((pred - yb) ** 2 + 0.3 * (pred - yb).abs())).mean()The file /app/scratch/trainlib.py has been edited successfully.
cd /app/scratch && cat > sweep.py << 'EOF'
import torch, trainlib as T, time, sys
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
snaps=[100,150,200,250,300,350,400]
configs = {
'w0_base': dict(wpow=0.0, wd=1e-4, aug=dict(flip=True,jitter=0.01,resample=0.15)),
'w1_base': dict(wpow=1.0, wd=1e-4, aug=dict(flip=True,jitter=0.01,resample=0.15)),
'w2_base': dict(wpow=2.0, wd=1e-4, aug=dict(flip=True,jitter=0.01,resample=0.15)),
'w1_reg': dict(wpow=1.0, wd=1e-3, aug=dict(flip=True,jitter=0.02,resample=0.30)),
'w2_reg': dict(wpow=2.0, wd=1e-3, aug=dict(flip=True,jitter=0.02,resample=0.30)),
}
base=dict(seed=0, epochs=400, bs=16, lr=1.5e-3, warmup=20, loss='mse', isotropic=True)
for name,over in configs.items():
cfg=dict(base); cfg.update(over)
t=time.time(); res=T.run_cv(cfg,(X,Y),n_folds=6,snapshot_epochs=snaps,seed=0)
best=min(((res[(e,'raw')][0],e,'raw') for e in snaps))
print(f'[{name}] ({time.time()-t:.0f}s) best raw rel={best[0]:.4f} @ep{best[1]}')
for e in snaps:
r=res[(e,'raw')]; print(f' ep{e:4d} raw rel={r[0]:.4f} mae={r[1]:.4f} mx={r[2]:.4f}')
sys.stdout.flush()
EOF
python sweep.py[stderr]
[w0_base] (125s) best raw rel=0.2811 @ep400
ep 100 raw rel=0.3589 mae=0.2271 mx=1.0161
ep 150 raw rel=0.3500 mae=0.2232 mx=0.8778
ep 200 raw rel=0.3470 mae=0.2174 mx=1.0023
ep 250 raw rel=0.3071 mae=0.2043 mx=0.6935
ep 300 raw rel=0.2903 mae=0.1947 mx=0.7389
ep 350 raw rel=0.2846 mae=0.1914 mx=0.6849
ep 400 raw rel=0.2811 mae=0.1885 mx=0.6949
[w1_base] (118s) best raw rel=0.2586 @ep400
ep 100 raw rel=0.3553 mae=0.2431 mx=0.8267
ep 150 raw rel=0.3229 mae=0.2058 mx=0.8235
ep 200 raw rel=0.3012 mae=0.1954 mx=0.7060
ep 250 raw rel=0.2778 mae=0.1786 mx=0.7370
ep 300 raw rel=0.2740 mae=0.1806 mx=0.7131
ep 350 raw rel=0.2646 mae=0.1754 mx=0.7038
ep 400 raw rel=0.2586 mae=0.1718 mx=0.6768Shell command timed out.
Tool interaction exceeded 300 seconds.restart: true
<system>tool has been restarted.</system>
cd /app/scratch && cat > sweep2.py << 'EOF'
import torch, trainlib as T, time, sys
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
snaps=[300,400,500,600,700,800]
configs = {
'w1_long': dict(wpow=1.0, wd=1e-4, aug=dict(flip=True,jitter=0.01,resample=0.15)),
'w2_long': dict(wpow=2.0, wd=1e-4, aug=dict(flip=True,jitter=0.01,resample=0.15)),
'w1_reg': dict(wpow=1.0, wd=1e-3, aug=dict(flip=True,jitter=0.02,resample=0.30)),
'w15_reg': dict(wpow=1.5, wd=5e-4, aug=dict(flip=True,jitter=0.015,resample=0.25)),
}
base=dict(seed=0, epochs=800, bs=16, lr=1.5e-3, warmup=20, loss='mse', isotropic=True)
for name,over in configs.items():
cfg=dict(base); cfg.update(over)
t=time.time(); res=T.run_cv(cfg,(X,Y),n_folds=6,snapshot_epochs=snaps,seed=0)
best=min(((res[(e,'raw')][0],e) for e in snaps))
print(f'[{name}] ({time.time()-t:.0f}s) BEST raw rel={best[0]:.4f} @ep{best[1]}',flush=True)
for e in snaps:
r=res[(e,'raw')]; print(f' ep{e:4d} rel={r[0]:.4f} mae={r[1]:.4f} mx={r[2]:.4f}',flush=True)
EOF
nohup python sweep2.py > sweep2.log 2>&1 &
echo "launched pid $!"launched pid 366
cd /app/scratch && sleep 180 && cat sweep2.log
<system>Tool ran without output or errors</system>
cd /app/scratch && sleep 60 && cat sweep2.log; echo "--- running? ---"; pgrep -f sweep2.py && echo yes || echo no
[w1_long] (194s) BEST raw rel=0.2257 @ep600
ep 300 rel=0.2746 mae=0.1791 mx=0.7363
ep 400 rel=0.2847 mae=0.1872 mx=0.6473
ep 500 rel=0.2621 mae=0.1758 mx=0.5551
ep 600 rel=0.2257 mae=0.1434 mx=0.6546
ep 700 rel=0.2398 mae=0.1595 mx=0.6282
ep 800 rel=0.2341 mae=0.1563 mx=0.5819
--- running? ---
366
yescd /app/scratch && sleep 240 && cat sweep2.log; echo "--- running? ---"; pgrep -f sweep2.py && echo yes || echo no
[w1_long] (194s) BEST raw rel=0.2257 @ep600
ep 300 rel=0.2746 mae=0.1791 mx=0.7363
ep 400 rel=0.2847 mae=0.1872 mx=0.6473
ep 500 rel=0.2621 mae=0.1758 mx=0.5551
ep 600 rel=0.2257 mae=0.1434 mx=0.6546
ep 700 rel=0.2398 mae=0.1595 mx=0.6282
ep 800 rel=0.2341 mae=0.1563 mx=0.5819
[w2_long] (214s) BEST raw rel=0.2154 @ep800
ep 300 rel=0.2659 mae=0.1786 mx=0.6566
ep 400 rel=0.2774 mae=0.1815 mx=0.6753
ep 500 rel=0.2389 mae=0.1624 mx=0.6113
ep 600 rel=0.2228 mae=0.1439 mx=0.6475
ep 700 rel=0.2212 mae=0.1487 mx=0.6311
ep 800 rel=0.2154 mae=0.1421 mx=0.6273
--- running? ---
366
yescd /app/scratch && sleep 240 && tail -n +8 sweep2.log; echo "--- running? ---"; pgrep -f sweep2.py && echo yes || echo no
[w2_long] (214s) BEST raw rel=0.2154 @ep800
ep 300 rel=0.2659 mae=0.1786 mx=0.6566
ep 400 rel=0.2774 mae=0.1815 mx=0.6753
ep 500 rel=0.2389 mae=0.1624 mx=0.6113
ep 600 rel=0.2228 mae=0.1439 mx=0.6475
ep 700 rel=0.2212 mae=0.1487 mx=0.6311
ep 800 rel=0.2154 mae=0.1421 mx=0.6273
[w1_reg] (210s) BEST raw rel=0.3781 @ep300
ep 300 rel=0.3781 mae=0.2482 mx=0.8982
ep 400 rel=0.3978 mae=0.2550 mx=0.9604
ep 500 rel=0.4048 mae=0.2675 mx=0.9276
ep 600 rel=0.3830 mae=0.2575 mx=0.8705
ep 700 rel=0.3900 mae=0.2653 mx=0.9122
ep 800 rel=0.3867 mae=0.2644 mx=0.8924
--- running? ---
366
yescd /app/scratch && sleep 150 && tail -n 8 sweep2.log; echo "--- running? ---"; pgrep -f sweep2.py && echo yes || echo no
ep 800 rel=0.3867 mae=0.2644 mx=0.8924
[w15_reg] (193s) BEST raw rel=0.2886 @ep700
ep 300 rel=0.3035 mae=0.2001 mx=0.7193
ep 400 rel=0.3296 mae=0.2087 mx=0.8700
ep 500 rel=0.3165 mae=0.2126 mx=0.6112
ep 600 rel=0.2930 mae=0.1992 mx=0.6190
ep 700 rel=0.2886 mae=0.1971 mx=0.5953
ep 800 rel=0.2986 mae=0.2041 mx=0.5912
--- running? ---
nocd /app/scratch && cat > sweep3.py << 'EOF'
import torch, trainlib as T, time
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
snaps=[600,800,1000,1200]
configs = {
'fliponly_w2': dict(wpow=2.0, aug=dict(flip=True)),
'flip_j005_w2': dict(wpow=2.0, aug=dict(flip=True,jitter=0.005)),
'flip_j01_w2': dict(wpow=2.0, aug=dict(flip=True,jitter=0.01)),
'light_w3': dict(wpow=3.0, aug=dict(flip=True,jitter=0.01,resample=0.10)),
}
base=dict(seed=0, epochs=1200, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse', isotropic=True)
for name,over in configs.items():
cfg=dict(base); cfg.update(over)
t=time.time(); res=T.run_cv(cfg,(X,Y),n_folds=6,snapshot_epochs=snaps,seed=0)
best=min(((res[(e,'raw')][0],e) for e in snaps))
print(f'[{name}] ({time.time()-t:.0f}s) BEST rel={best[0]:.4f} @ep{best[1]}',flush=True)
for e in snaps:
r=res[(e,'raw')]; print(f' ep{e:4d} rel={r[0]:.4f} mae={r[1]:.4f} mx={r[2]:.4f}',flush=True)
EOF
nohup python sweep3.py > sweep3.log 2>&1 &
echo "launched $!"launched 425
command: str_replace
path: /app/scratch/trainlib.py
old_str: yn_all = (Ytr - lmean) / lstd
wpow = cfg_over.get("wpow", 0.0)
new_str: swa_start = cfg_over.get("swa_start", None) # epoch to begin SWA accumulation
swa_lr = cfg_over.get("swa_lr", None)
swa_state = None
swa_count = 0
yn_all = (Ytr - lmean) / lstd
wpow = cfg_over.get("wpow", 0.0)The file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/trainlib.py
old_str: def lr_at(st):
if st < warm * steps_per:
return lr * (st + 1) / (warm * steps_per)
p = (st - warm * steps_per) / max(1, total - warm * steps_per)
return lr * 0.5 * (1 + math.cos(math.pi * p))
new_str: def lr_at(st):
ep_now = st // steps_per
if swa_start is not None and swa_lr is not None and ep_now >= swa_start:
return swa_lr
if st < warm * steps_per:
return lr * (st + 1) / (warm * steps_per)
denom_end = (swa_start * steps_per) if swa_start is not None else total
p = (st - warm * steps_per) / max(1, denom_end - warm * steps_per)
p = min(p, 1.0)
return lr * 0.5 * (1 + math.cos(math.pi * p))The file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/trainlib.py
old_str: opt.step(); ema.update(model); step += 1
if (ep + 1) in snapset:
new_str: opt.step(); ema.update(model); step += 1
# SWA: accumulate running average of weights once per epoch
if swa_start is not None and (ep + 1) >= swa_start:
sd = model.state_dict()
if swa_state is None:
swa_state = {k: v.detach().clone().float() for k, v in sd.items()}
swa_count = 1
else:
swa_count += 1
for k, v in sd.items():
if v.dtype.is_floating_point:
swa_state[k].mul_(1 - 1.0 / swa_count).add_(v.detach().float(), alpha=1.0 / swa_count)
else:
swa_state[k] = v.detach().clone().float()
if (ep + 1) in snapset:The file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/trainlib.py
old_str: pe = pe * lstd + lmean
snaps[ep + 1] = {"raw": pr.cpu(), "ema": pe.cpu()}
return snaps, (fmean.cpu(), fstd.cpu(), lmean.cpu(), lstd.cpu())
new_str: pe = pe * lstd + lmean
entry = {"raw": pr.cpu(), "ema": pe.cpu()}
if swa_state is not None:
swa_model = build_model(cfg).to(DEV)
swa_model.load_state_dict({k: v.to(DEV) for k, v in swa_state.items()})
bn_recalibrate(swa_model, calib_n)
ps = torch.cat([swa_model(xten[i:i+64]) for i in range(0, xten.shape[0], 64)], 0)
entry["swa"] = (ps * lstd + lmean).cpu()
snaps[ep + 1] = entry
return snaps, (fmean.cpu(), fstd.cpu(), lmean.cpu(), lstd.cpu()), swa_stateThe file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/trainlib.py
old_str: oof = {e: {"raw": torch.zeros(N, 1), "ema": torch.zeros(N, 1)} for e in snapshot_epochs}
for k in range(n_folds):
te_idx = folds[k]
tr_mask = torch.ones(N, dtype=torch.bool); tr_mask[te_idx] = False
Xtr, Ytr = X[tr_mask], Y[tr_mask]
Xte = X[te_idx]
snaps, _ = train_core(cfg_over, Xtr, Ytr, Xte, snapshot_epochs)
for e in snapshot_epochs:
oof[e]["raw"][te_idx] = snaps[e]["raw"]
oof[e]["ema"][te_idx] = snaps[e]["ema"]
results = {}
for e in snapshot_epochs:
for w in ["raw", "ema"]:
results[(e, w)] = metrics_vec(oof[e][w], Y)
return results
new_str: whichs = cfg_over.get("eval_which", ["raw"])
oof = {e: {w: torch.zeros(N, 1) for w in whichs} for e in snapshot_epochs}
for k in range(n_folds):
te_idx = folds[k]
tr_mask = torch.ones(N, dtype=torch.bool); tr_mask[te_idx] = False
Xtr, Ytr = X[tr_mask], Y[tr_mask]
Xte = X[te_idx]
snaps, _, _ = train_core(cfg_over, Xtr, Ytr, Xte, snapshot_epochs)
for e in snapshot_epochs:
for w in whichs:
if w in snaps[e]:
oof[e][w][te_idx] = snaps[e][w]
results = {}
for e in snapshot_epochs:
for w in whichs:
results[(e, w)] = metrics_vec(oof[e][w], Y)
return resultsThe file /app/scratch/trainlib.py has been edited successfully.
cd /app/scratch && python -c "import importlib,trainlib; importlib.reload(trainlib); print('trainlib OK')" && echo "--- sweep3 so far ---" && cat sweep3.log; pgrep -f sweep3.py >/dev/null && echo "[running]" || echo "[done]"trainlib OK --- sweep3 so far --- [running]
cd /app/scratch && sleep 290 && cat sweep3.log; pgrep -f sweep3.py >/dev/null && echo "[running]" || echo "[done]"
[fliponly_w2] (239s) BEST rel=0.1950 @ep1200
ep 600 rel=0.2626 mae=0.1687 mx=0.7676
ep 800 rel=0.2220 mae=0.1366 mx=0.8005
ep1000 rel=0.1962 mae=0.1208 mx=0.6401
ep1200 rel=0.1950 mae=0.1160 mx=0.6655
[running]cd /app/scratch && sleep 280 && cat sweep3.log; pgrep -f sweep3.py >/dev/null && echo "[running]" || echo "[done]"
[fliponly_w2] (239s) BEST rel=0.1950 @ep1200
ep 600 rel=0.2626 mae=0.1687 mx=0.7676
ep 800 rel=0.2220 mae=0.1366 mx=0.8005
ep1000 rel=0.1962 mae=0.1208 mx=0.6401
ep1200 rel=0.1950 mae=0.1160 mx=0.6655
[flip_j005_w2] (242s) BEST rel=0.2020 @ep1200
ep 600 rel=0.2532 mae=0.1555 mx=0.7203
ep 800 rel=0.2150 mae=0.1306 mx=0.7721
ep1000 rel=0.2065 mae=0.1265 mx=0.7030
ep1200 rel=0.2020 mae=0.1224 mx=0.6942
[running]cd /app/scratch && sleep 280 && tail -n 12 sweep3.log; pgrep -f sweep3.py >/dev/null && echo "[running]" || echo "[done]"
ep1000 rel=0.1962 mae=0.1208 mx=0.6401
ep1200 rel=0.1950 mae=0.1160 mx=0.6655
[flip_j005_w2] (242s) BEST rel=0.2020 @ep1200
ep 600 rel=0.2532 mae=0.1555 mx=0.7203
ep 800 rel=0.2150 mae=0.1306 mx=0.7721
ep1000 rel=0.2065 mae=0.1265 mx=0.7030
ep1200 rel=0.2020 mae=0.1224 mx=0.6942
[flip_j01_w2] (231s) BEST rel=0.2113 @ep1200
ep 600 rel=0.2544 mae=0.1627 mx=0.7577
ep 800 rel=0.2300 mae=0.1506 mx=0.6723
ep1000 rel=0.2169 mae=0.1397 mx=0.6405
ep1200 rel=0.2113 mae=0.1331 mx=0.6409
[running]cd /app/scratch && sleep 60 && tail -n 6 sweep3.log; pgrep -f sweep3.py >/dev/null && echo "[running]" || echo "[done]"
ep1200 rel=0.2020 mae=0.1224 mx=0.6942
[flip_j01_w2] (231s) BEST rel=0.2113 @ep1200
ep 600 rel=0.2544 mae=0.1627 mx=0.7577
ep 800 rel=0.2300 mae=0.1506 mx=0.6723
ep1000 rel=0.2169 mae=0.1397 mx=0.6405
ep1200 rel=0.2113 mae=0.1331 mx=0.6409
[running]cd /app/scratch && sleep 120 && tail -n 6 sweep3.log; pgrep -f sweep3.py >/dev/null && echo "[running]" || echo "[done]"
ep1200 rel=0.2113 mae=0.1331 mx=0.6409
[light_w3] (276s) BEST rel=0.2225 @ep1000
ep 600 rel=0.2613 mae=0.1687 mx=0.9144
ep 800 rel=0.2374 mae=0.1545 mx=0.7386
ep1000 rel=0.2225 mae=0.1447 mx=0.6864
ep1200 rel=0.2235 mae=0.1431 mx=0.6484
[done]cd /app/scratch && cat > sweep4.py << 'EOF'
import torch, trainlib as T, time
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
snaps=[1200,1500,1800,2000]
base=dict(seed=0, epochs=2000, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse',
isotropic=True, aug=dict(flip=True), swa_start=1200, swa_lr=2e-4,
eval_which=['raw','swa'])
for wp in [2.0, 2.5, 3.0]:
cfg=dict(base); cfg['wpow']=wp
t=time.time(); res=T.run_cv(cfg,(X,Y),n_folds=6,snapshot_epochs=snaps,seed=0)
print(f'[wpow={wp}] ({time.time()-t:.0f}s)',flush=True)
for e in snaps:
for w in ['raw','swa']:
if (e,w) in res:
r=res[(e,w)]; print(f' ep{e:4d} {w}: rel={r[0]:.4f} mae={r[1]:.4f} mx={r[2]:.4f}',flush=True)
EOF
nohup python sweep4.py > sweep4.log 2>&1 &
echo "launched $!"launched 557
command: str_replace
path: /app/scratch/trainlib.py
old_str: def save_ckpt(path, sd, norm):
new_str: def train_final(cfg_over, X, Y, use="swa", verbose=True):
"""Train on ALL (X,Y); return (state_dict, norm) for chosen weights, BN-recalibrated."""
snaps = [cfg_over["epochs"]]
# Xte is unused for selection here; pass a small slice to keep code path happy.
_, norm, swa_state = train_core(cfg_over, X, Y, X[:2], snaps)
fmean, fstd, lmean, lstd = [t.to(DEV) for t in norm]
cfg = load_cfg()
model = build_model(cfg).to(DEV)
if use == "swa" and swa_state is not None:
model.load_state_dict({k: v.to(DEV) for k, v in swa_state.items()})
calib_n = (X.to(DEV) - fmean) / fstd
bn_recalibrate(model, calib_n, passes=5)
if verbose:
r = evaluate(model, X.to(DEV), Y.to(DEV), fmean, fstd, lmean, lstd)
print(f"[train_final] in-sample rel/mae/mx = {r[0]:.4f}/{r[1]:.4f}/{r[2]:.4f}", flush=True)
return {k: v.detach().cpu() for k, v in model.state_dict().items()}, tuple(t.cpu() for t in norm)
def save_ckpt(path, sd, norm):The file /app/scratch/trainlib.py has been edited successfully.
cd /app/scratch && cat > grade_check.py << 'EOF'
# Mimic the grader: build model from task_config, load checkpoint, apply norm, predict, de-standardize.
import torch, json, sys
from pathlib import Path
sys.path.insert(0,'/app')
from candidate_model import build_model
def load_cfg():
return json.load(open('/app/task_config.json'))
def predict_with_ckpt(ckpt_path, X):
cfg=load_cfg(); m=build_model(cfg)
ck=torch.load(ckpt_path, weights_only=True)
m.load_state_dict(ck['state_dict']); m.eval()
fmean=ck['feat_mean'].view(1,1,3); fstd=ck['feat_std'].view(1,1,3)
lmean=ck['label_mean'].view(1,1); lstd=ck['label_std'].view(1,1)
xn=(X-fmean)/fstd
with torch.no_grad():
out=torch.cat([m(xn[i:i+64]) for i in range(0,xn.shape[0],64)],0)
return out*lstd+lmean
def metrics(pred,true):
e=(pred-true).squeeze(1); t=true.squeeze(1)
return (float(torch.sqrt((e**2).sum())/torch.sqrt((t**2).sum())),
float(e.abs().mean()), float(e.abs().max()))
if __name__=='__main__':
ck=sys.argv[1]
for s in ['train','val','public_test']:
d=torch.load(f'/app/data/{s}/samples.pt',weights_only=True)
X=d['features'].float(); Y=d['labels'].float()
p=predict_with_ckpt(ck,X); m=metrics(p,Y)
print(f' {s:12s} rel/mae/mx = {m[0]:.4f}/{m[1]:.4f}/{m[2]:.4f}')
EOF
echo "grade_check.py written"
# quick smoke test of train_final with tiny epochs
python -c "
import torch, trainlib as T
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val'); ptX,ptY=T.load_split('public_test')
X=torch.cat([trX,vaX,ptX],0); Y=torch.cat([trY,vaY,ptY],0)
cfg=dict(seed=1, epochs=30, bs=16, lr=1.5e-3, wd=1e-4, warmup=3, loss='mse', isotropic=True,
aug=dict(flip=True), wpow=2.0, swa_start=20, swa_lr=2e-4)
sd,norm=T.train_final(cfg, X, Y, use='swa')
T.save_ckpt('/app/scratch/smoke.pt', sd, norm)
print('saved smoke.pt')
"
echo '--- grader-style check on smoke.pt ---'
python grade_check.py /app/scratch/smoke.ptgrade_check.py written [train_final] in-sample rel/mae/mx = 0.4745/0.3309/1.0130 saved smoke.pt --- grader-style check on smoke.pt --- train rel/mae/mx = 0.4631/0.3174/1.0130 val rel/mae/mx = 0.5018/0.3789/0.8167 public_test rel/mae/mx = 0.4869/0.3193/0.8999
cd /app/scratch && cat sweep4.log; pgrep -f sweep4.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 290 && cat sweep4.log; pgrep -f sweep4.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 200 && cat sweep4.log; pgrep -f sweep4.py >/dev/null && echo "[running]" || echo "[done]"
[wpow=2.0] (473s)
ep1200 raw: rel=0.1950 mae=0.1160 mx=0.6655
ep1200 swa: rel=0.4035 mae=0.2541 mx=1.0899
ep1500 raw: rel=0.1883 mae=0.1120 mx=0.6872
ep1500 swa: rel=0.3869 mae=0.2416 mx=1.0717
ep1800 raw: rel=0.1838 mae=0.1102 mx=0.5996
ep1800 swa: rel=0.3751 mae=0.2312 mx=1.0388
ep2000 raw: rel=0.1936 mae=0.1193 mx=0.6272
ep2000 swa: rel=0.3691 mae=0.2262 mx=1.0238
[running]cd /app/scratch && sleep 280 && cat sweep4.log; pgrep -f sweep4.py >/dev/null && echo "[running]" || echo "[done]"
[wpow=2.0] (473s)
ep1200 raw: rel=0.1950 mae=0.1160 mx=0.6655
ep1200 swa: rel=0.4035 mae=0.2541 mx=1.0899
ep1500 raw: rel=0.1883 mae=0.1120 mx=0.6872
ep1500 swa: rel=0.3869 mae=0.2416 mx=1.0717
ep1800 raw: rel=0.1838 mae=0.1102 mx=0.5996
ep1800 swa: rel=0.3751 mae=0.2312 mx=1.0388
ep2000 raw: rel=0.1936 mae=0.1193 mx=0.6272
ep2000 swa: rel=0.3691 mae=0.2262 mx=1.0238
[running]cd /app/scratch && sleep 290 && sed -n '10,40p' sweep4.log; pgrep -f sweep4.py >/dev/null && echo "[running]" || echo "[done]"
[wpow=2.5] (410s)
ep1200 raw: rel=0.2134 mae=0.1313 mx=0.7223
ep1200 swa: rel=0.4464 mae=0.2900 mx=1.1445
ep1500 raw: rel=0.2085 mae=0.1312 mx=0.6869
ep1500 swa: rel=0.4354 mae=0.2809 mx=1.1257
ep1800 raw: rel=0.2023 mae=0.1237 mx=0.6440
ep1800 swa: rel=0.4215 mae=0.2694 mx=1.1058
ep2000 raw: rel=0.2076 mae=0.1277 mx=0.6285
ep2000 swa: rel=0.4139 mae=0.2628 mx=1.0962
[running]cd /app/scratch && sleep 250 && sed -n '19,30p' sweep4.log; pgrep -f sweep4.py >/dev/null && echo "[running]" || echo "[done]"
[wpow=3.0] (397s)
ep1200 raw: rel=0.2325 mae=0.1376 mx=0.8617
ep1200 swa: rel=0.4848 mae=0.3227 mx=1.1336
ep1500 raw: rel=0.2151 mae=0.1290 mx=0.8105
ep1500 swa: rel=0.4699 mae=0.3125 mx=1.1014
ep1800 raw: rel=0.2106 mae=0.1255 mx=0.7854
ep1800 swa: rel=0.4538 mae=0.3006 mx=1.0849
ep2000 raw: rel=0.2196 mae=0.1300 mx=0.7864
ep2000 swa: rel=0.4471 mae=0.2954 mx=1.0861
[done]cd /app/scratch && cat > sweep5.py << 'EOF'
import torch, trainlib as T, time
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
snaps=[1400,1500,1600,1700,1800,1900]
common=dict(epochs=1900, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse',
isotropic=True, aug=dict(flip=True), wpow=2.0, eval_which=['raw'])
trials = [
('A_consttail_s0', dict(seed=0, swa_start=1200, swa_lr=2e-4)),
('C_cosine0_s0', dict(seed=0)),
('A_consttail_s1', dict(seed=1, swa_start=1200, swa_lr=2e-4)),
('A_consttail_s2', dict(seed=2, swa_start=1200, swa_lr=2e-4)),
]
for name,over in trials:
cfg=dict(common); cfg.update(over)
fold_seed = over.get('seed',0)
t=time.time(); res=T.run_cv(cfg,(X,Y),n_folds=6,snapshot_epochs=snaps,seed=fold_seed)
best=min(((res[(e,'raw')][0],e) for e in snaps))
print(f'[{name}] ({time.time()-t:.0f}s) BEST rel={best[0]:.4f} @ep{best[1]}',flush=True)
for e in snaps:
r=res[(e,'raw')]; print(f' ep{e:4d} rel={r[0]:.4f} mae={r[1]:.4f} mx={r[2]:.4f}',flush=True)
EOF
nohup python sweep5.py > sweep5.log 2>&1 &
echo "launched $!"launched 657
command: str_replace
path: /app/scratch/trainlib.py
old_str: snaps[ep + 1] = entry
return snaps, (fmean.cpu(), fstd.cpu(), lmean.cpu(), lstd.cpu()), swa_state
new_str: snaps[ep + 1] = entry
final_raw = {k: v.detach().clone() for k, v in model.state_dict().items()}
return snaps, (fmean.cpu(), fstd.cpu(), lmean.cpu(), lstd.cpu()), swa_state, final_rawThe file /app/scratch/trainlib.py has been edited successfully.
cd /app/scratch && python - << 'EOF'
import re, io
src=open('trainlib.py').read()
# update run_cv unpacking
src=src.replace("snaps, _, _ = train_core(cfg_over, Xtr, Ytr, Xte, snapshot_epochs)",
"snaps, _, _, _ = train_core(cfg_over, Xtr, Ytr, Xte, snapshot_epochs)")
# update train_final to use final_raw for 'raw'
old=""" snaps = [cfg_over["epochs"]]
# Xte is unused for selection here; pass a small slice to keep code path happy.
_, norm, swa_state = train_core(cfg_over, X, Y, X[:2], snaps)
fmean, fstd, lmean, lstd = [t.to(DEV) for t in norm]
cfg = load_cfg()
model = build_model(cfg).to(DEV)
if use == "swa" and swa_state is not None:
model.load_state_dict({k: v.to(DEV) for k, v in swa_state.items()})"""
new=""" snaps = [cfg_over["epochs"]]
# Xte is unused for selection here; pass a small slice to keep code path happy.
_, norm, swa_state, final_raw = train_core(cfg_over, X, Y, X[:2], snaps)
fmean, fstd, lmean, lstd = [t.to(DEV) for t in norm]
cfg = load_cfg()
model = build_model(cfg).to(DEV)
if use == "swa" and swa_state is not None:
model.load_state_dict({k: v.to(DEV) for k, v in swa_state.items()})
else:
model.load_state_dict({k: v.to(DEV) for k, v in final_raw.items()})"""
assert old in src
src=src.replace(old,new)
open('trainlib.py','w').write(src)
print("patched")
EOF
python -c "import importlib,trainlib; importlib.reload(trainlib); print('reimport OK')"patched reimport OK
cd /app/scratch && python -c "
import torch, trainlib as T
trX,trY=T.load_split('train')
cfg=dict(seed=1, epochs=60, bs=16, lr=1.5e-3, wd=1e-4, warmup=3, loss='mse', isotropic=True,
aug=dict(flip=True), wpow=2.0)
sd,norm=T.train_final(cfg, trX, trY, use='raw')
T.save_ckpt('/app/scratch/smoke.pt', sd, norm)
" && python grade_check.py /app/scratch/smoke.pt[train_final] in-sample rel/mae/mx = 0.4866/0.3491/0.8274 train rel/mae/mx = 0.4866/0.3490/0.8273 val rel/mae/mx = 0.5482/0.4253/0.7579 public_test rel/mae/mx = 0.5056/0.3500/0.7564
cd /app/scratch && cat sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 290 && cat sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 120 && cat sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[A_consttail_s0] (398s) BEST rel=0.1838 @ep1800
ep1400 rel=0.1907 mae=0.1164 mx=0.5850
ep1500 rel=0.1883 mae=0.1120 mx=0.6872
ep1600 rel=0.1856 mae=0.1111 mx=0.5494
ep1700 rel=0.1867 mae=0.1112 mx=0.6338
ep1800 rel=0.1838 mae=0.1102 mx=0.5996
ep1900 rel=0.1896 mae=0.1127 mx=0.6276
[running]cd /app/scratch && sleep 290 && sed -n '8,40p' sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 150 && cat sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[A_consttail_s0] (398s) BEST rel=0.1838 @ep1800
ep1400 rel=0.1907 mae=0.1164 mx=0.5850
ep1500 rel=0.1883 mae=0.1120 mx=0.6872
ep1600 rel=0.1856 mae=0.1111 mx=0.5494
ep1700 rel=0.1867 mae=0.1112 mx=0.6338
ep1800 rel=0.1838 mae=0.1102 mx=0.5996
ep1900 rel=0.1896 mae=0.1127 mx=0.6276
[C_cosine0_s0] (389s) BEST rel=0.1922 @ep1600
ep1400 rel=0.1959 mae=0.1186 mx=0.6271
ep1500 rel=0.1974 mae=0.1176 mx=0.7203
ep1600 rel=0.1922 mae=0.1134 mx=0.6548
ep1700 rel=0.1922 mae=0.1157 mx=0.6675
ep1800 rel=0.1952 mae=0.1178 mx=0.6672
ep1900 rel=0.1923 mae=0.1167 mx=0.6331
[running]cd /app/scratch && sleep 290 && sed -n '15,50p' sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[A_consttail_s1] (377s) BEST rel=0.2075 @ep1800
ep1400 rel=0.2119 mae=0.1244 mx=0.7695
ep1500 rel=0.2150 mae=0.1253 mx=0.8226
ep1600 rel=0.2147 mae=0.1248 mx=0.7924
ep1700 rel=0.2152 mae=0.1266 mx=0.8205
ep1800 rel=0.2075 mae=0.1245 mx=0.7297
ep1900 rel=0.2090 mae=0.1249 mx=0.7949
[running]cd /app/scratch && sleep 250 && sed -n '22,50p' sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 160 && sed -n '22,50p' sweep5.log; pgrep -f sweep5.py >/dev/null && echo "[running]" || echo "[done]"
[A_consttail_s2] (386s) BEST rel=0.2018 @ep1500
ep1400 rel=0.2091 mae=0.1253 mx=0.7776
ep1500 rel=0.2018 mae=0.1188 mx=0.7528
ep1600 rel=0.2049 mae=0.1184 mx=0.7867
ep1700 rel=0.2052 mae=0.1226 mx=0.7400
ep1800 rel=0.2040 mae=0.1178 mx=0.8070
ep1900 rel=0.2073 mae=0.1201 mx=0.8017
[done]cd /app/scratch && cat > holdout.py << 'EOF'
import torch, trainlib as T, time
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val'); ptX,ptY=T.load_split('public_test')
Xtr=torch.cat([trX,vaX],0); Ytr=torch.cat([trY,vaY],0) # 132 train
snaps=[1500,1600,1700,1800]
base=dict(epochs=1800, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse', isotropic=True,
aug=dict(flip=True), wpow=2.0, swa_start=1200, swa_lr=2e-4)
def met(pred,true):
e=(pred-true).squeeze(1); t=true.squeeze(1)
return (float(torch.sqrt((e**2).sum())/torch.sqrt((t**2).sum())), float(e.abs().mean()), float(e.abs().max()))
for seed in [0,1,2]:
cfg=dict(base); cfg['seed']=seed
t=time.time()
snapres,_,_,_=T.train_core(cfg, Xtr, Ytr, ptX, snaps)
print(f'[seed={seed}] ({time.time()-t:.0f}s) public_test held-out:',flush=True)
for e in snaps:
m=met(snapres[e]['raw'], ptY)
print(f' ep{e:4d} rel={m[0]:.4f} mae={m[1]:.4f} mx={m[2]:.4f}',flush=True)
EOF
nohup python holdout.py > holdout.log 2>&1 &
echo "launched $!"launched 785
command: create
path: /app/scratch/make_final.py
file_text: import torch, trainlib as T, sys
# Pool ALL labeled data (train + val + public_test) for the final fit.
trX, trY = T.load_split('train')
vaX, vaY = T.load_split('val')
ptX, ptY = T.load_split('public_test')
X = torch.cat([trX, vaX, ptX], 0)
Y = torch.cat([trY, vaY, ptY], 0)
print('final training pool:', X.shape, Y.shape, flush=True)
SEED = int(sys.argv[1]) if len(sys.argv) > 1 else 0
cfg = dict(seed=SEED, epochs=1700, bs=16, lr=1.5e-3, wd=1e-4, warmup=20,
loss='mse', isotropic=True, aug=dict(flip=True), wpow=2.0,
swa_start=1200, swa_lr=2e-4)
sd, norm = T.train_final(cfg, X, Y, use='raw')
T.save_ckpt('/app/outputs/model.pt', sd, norm)
print('WROTE /app/outputs/model.pt', flush=True)
# sanity: inspect saved payload
ck = torch.load('/app/outputs/model.pt', weights_only=True)
print('keys:', list(ck.keys()))
print('feat_mean', ck['feat_mean'].shape, ck['feat_mean'].tolist())
print('feat_std ', ck['feat_std'].shape, ck['feat_std'].tolist())
print('label_mean', ck['label_mean'].shape, ck['label_mean'].tolist())
print('label_std ', ck['label_std'].shape, ck['label_std'].tolist())
New file created successfully at /app/scratch/make_final.py
cd /app/scratch && sleep 170 && cat holdout.log; pgrep -f holdout.py >/dev/null && echo "[running]" || echo "[done]"
[seed=0] (83s) public_test held-out:
ep1500 rel=0.3727 mae=0.2079 mx=0.7690
ep1600 rel=0.3119 mae=0.1693 mx=0.6126
ep1700 rel=0.2887 mae=0.1612 mx=0.5773
ep1800 rel=0.3122 mae=0.1638 mx=0.6567
[seed=1] (78s) public_test held-out:
ep1500 rel=0.3399 mae=0.1959 mx=0.6430
ep1600 rel=0.2887 mae=0.1751 mx=0.5061
ep1700 rel=0.3139 mae=0.1872 mx=0.5692
ep1800 rel=0.3269 mae=0.1808 mx=0.6725
[running]cd /app/scratch && sleep 60 && sed -n '11,20p' holdout.log; pgrep -f holdout.py >/dev/null && echo "[running]" || echo "[done]"
[seed=2] (81s) public_test held-out:
ep1500 rel=0.3498 mae=0.2039 mx=0.7139
ep1600 rel=0.2905 mae=0.1783 mx=0.5338
ep1700 rel=0.3274 mae=0.1969 mx=0.5868
ep1800 rel=0.3379 mae=0.1875 mx=0.6404
[done]cd /app/scratch && python -c "
import torch, trainlib as T, copy
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val'); ptX,ptY=T.load_split('public_test')
Xtr=torch.cat([trX,vaX],0); Ytr=torch.cat([trY,vaY],0)
cfg=dict(seed=0, epochs=1650, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse', isotropic=True,
aug=dict(flip=True), wpow=2.0, swa_start=1200, swa_lr=2e-4)
snaps=[1650]
snapres,norm,_,final_raw=T.train_core(cfg, Xtr, Ytr, ptX, snaps)
fmean,fstd,lmean,lstd=[t.to(T.DEV) for t in norm]
def met(pred,true):
e=(pred-true).squeeze(1); t=true.squeeze(1)
return (float(torch.sqrt((e**2).sum())/torch.sqrt((t**2).sum())), float(e.abs().mean()), float(e.abs().max()))
# (a) raw training-BN stats
print('raw (train BN stats): ', [round(x,4) for x in met(snapres[1650]['raw'], ptY)])
# (b) recalibrated BN on clean 132
m=T.build_model(T.load_cfg()).to(T.DEV); m.load_state_dict({k:v.to(T.DEV) for k,v in final_raw.items()})
T.bn_recalibrate(m,(Xtr.to(T.DEV)-fmean)/fstd,passes=5)
with torch.no_grad():
xten=(ptX.to(T.DEV)-fmean)/fstd
p=torch.cat([m(xten[i:i+64]) for i in range(0,xten.shape[0],64)],0)*lstd+lmean
print('recalibrated BN (clean):', [round(x,4) for x in met(p.cpu(), ptY)])
"raw (train BN stats): [0.3162, 0.1961, 0.6169] recalibrated BN (clean): [0.3284, 0.1897, 0.7125]
command: str_replace path: /app/scratch/trainlib.py old_str: def train_final(cfg_over, X, Y, use="swa", verbose=True): new_str: def train_final(cfg_over, X, Y, use="swa", verbose=True, recalibrate=False):
The file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/trainlib.py
old_str: calib_n = (X.to(DEV) - fmean) / fstd
bn_recalibrate(model, calib_n, passes=5)
if verbose:
new_str: if recalibrate:
calib_n = (X.to(DEV) - fmean) / fstd
bn_recalibrate(model, calib_n, passes=5)
else:
model.eval()
if verbose:The file /app/scratch/trainlib.py has been edited successfully.
command: str_replace
path: /app/scratch/make_final.py
old_str: cfg = dict(seed=SEED, epochs=1700, bs=16, lr=1.5e-3, wd=1e-4, warmup=20,
loss='mse', isotropic=True, aug=dict(flip=True), wpow=2.0,
swa_start=1200, swa_lr=2e-4)
sd, norm = T.train_final(cfg, X, Y, use='raw')
new_str: cfg = dict(seed=SEED, epochs=1650, bs=16, lr=1.5e-3, wd=1e-4, warmup=20,
loss='mse', isotropic=True, aug=dict(flip=True), wpow=2.0,
swa_start=1200, swa_lr=2e-4)
sd, norm = T.train_final(cfg, X, Y, use='raw', recalibrate=False)The file /app/scratch/make_final.py has been edited successfully.
cd /app/scratch && python make_final.py 0 2>&1 | tail -20
final training pool: torch.Size([148, 2048, 3]) torch.Size([148, 1]) [train_final] in-sample rel/mae/mx = 0.1112/0.0723/0.3378 WROTE /app/outputs/model.pt keys: ['state_dict', 'feat_mean', 'feat_std', 'label_mean', 'label_std'] feat_mean torch.Size([3]) [-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std torch.Size([3]) [0.1860007345676422, 0.1860007345676422, 0.1860007345676422] label_mean torch.Size([1]) [0.7487966418266296] label_std torch.Size([1]) [0.38614240288734436]
cd /app/scratch && python grade_check.py /app/outputs/model.pt && echo "--- shape/robustness checks ---" && python -c "
import torch, json, sys
sys.path.insert(0,'/app')
from candidate_model import build_model
ck=torch.load('/app/outputs/model.pt', weights_only=True)
m=build_model(json.load(open('/app/task_config.json'))); m.load_state_dict(ck['state_dict']); m.eval()
fm=ck['feat_mean'].view(1,1,3); fs=ck['feat_std'].view(1,1,3); lm=ck['label_mean']; ls=ck['label_std']
for B in [1,3,16,32]:
x=torch.rand(B,2048,3)-0.5
with torch.no_grad(): out=m((x-fm)/fs)*ls+lm
assert out.shape==(B,1), out.shape
print(f'B={B:2d} -> out {tuple(out.shape)} range[{out.min():.3f},{out.max():.3f}]')
print('dtypes:', {k:(v.dtype if torch.is_tensor(v) else type(v)) for k,v in ck.items() if k!=chr(115)+\"tate_dict\"})
print('state_dict tensors:', len(ck['state_dict']))
"train rel/mae/mx = 0.1070/0.0704/0.3378
val rel/mae/mx = 0.1227/0.0782/0.3297
public_test rel/mae/mx = 0.1112/0.0729/0.1772
--- shape/robustness checks ---
B= 1 -> out (1, 1) range[3.926,3.926]
B= 3 -> out (3, 1) range[3.873,4.205]
B=16 -> out (16, 1) range[3.830,4.061]
B=32 -> out (32, 1) range[3.762,4.181]
dtypes: {'feat_mean': torch.float32, 'feat_std': torch.float32, 'label_mean': torch.float32, 'label_std': torch.float32}
state_dict tensors: 41cd /app/scratch && cat > wpow_scan.py << 'EOF'
import torch, trainlib as T, time
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val'); ptX,ptY=T.load_split('public_test')
Xtr=torch.cat([trX,vaX],0); Ytr=torch.cat([trY,vaY],0)
pool=(Xtr,Ytr)
def met(pred,true):
e=(pred-true).squeeze(1); t=true.squeeze(1)
return (float(torch.sqrt((e**2).sum())/torch.sqrt((t**2).sum())), float(e.abs().mean()), float(e.abs().max()))
snaps=[1500,1650]
base=dict(epochs=1650, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse', isotropic=True,
aug=dict(flip=True), swa_start=1200, swa_lr=2e-4, eval_which=['raw'])
for wp in [0.0,1.0,1.5,2.0]:
cfg=dict(base); cfg['wpow']=wp; cfg['seed']=0
# public holdout (train on 132 -> test 16)
sr,_,_,_=T.train_core(cfg, Xtr, Ytr, ptX, snaps)
ph=met(sr[1650]['raw'], ptY)
# 6-fold CV on 132
res=T.run_cv(cfg, pool, n_folds=6, snapshot_epochs=snaps, seed=0)
cv=res[(1650,'raw')]
print(f'wpow={wp}: PUBLIC rel/mae/mx={ph[0]:.3f}/{ph[1]:.3f}/{ph[2]:.3f} | CV6 rel/mae/mx={cv[0]:.3f}/{cv[1]:.3f}/{cv[2]:.3f}',flush=True)
EOF
nohup python wpow_scan.py > wpow_scan.log 2>&1 &
echo "launched $!"launched 908
cd /app/scratch && sleep 290 && cat wpow_scan.log; pgrep -f wpow_scan.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 200 && cat wpow_scan.log; pgrep -f wpow_scan.py >/dev/null && echo "[running]" || echo "[done]"
wpow=0.0: PUBLIC rel/mae/mx=0.275/0.155/0.592 | CV6 rel/mae/mx=0.196/0.117/0.620 [running]
cd /app/scratch && sleep 290 && cat wpow_scan.log; pgrep -f wpow_scan.py >/dev/null && echo "[running]" || echo "[done]"
wpow=0.0: PUBLIC rel/mae/mx=0.275/0.155/0.592 | CV6 rel/mae/mx=0.196/0.117/0.620 [running]
cd /app/scratch && sleep 220 && cat wpow_scan.log; pgrep -f wpow_scan.py >/dev/null && echo "[running]" || echo "[done]"
wpow=0.0: PUBLIC rel/mae/mx=0.275/0.155/0.592 | CV6 rel/mae/mx=0.196/0.117/0.620 wpow=1.0: PUBLIC rel/mae/mx=0.329/0.182/0.666 | CV6 rel/mae/mx=0.186/0.114/0.564 [running]
cd /app/scratch && sleep 290 && cat wpow_scan.log; pgrep -f wpow_scan.py >/dev/null && echo "[running]" || echo "[done]"
wpow=0.0: PUBLIC rel/mae/mx=0.275/0.155/0.592 | CV6 rel/mae/mx=0.196/0.117/0.620 wpow=1.0: PUBLIC rel/mae/mx=0.329/0.182/0.666 | CV6 rel/mae/mx=0.186/0.114/0.564 wpow=1.5: PUBLIC rel/mae/mx=0.311/0.177/0.623 | CV6 rel/mae/mx=0.192/0.115/0.571 [running]
cd /app/scratch && sleep 250 && cat wpow_scan.log; pgrep -f wpow_scan.py >/dev/null && echo "[running]" || echo "[done]"
wpow=0.0: PUBLIC rel/mae/mx=0.275/0.155/0.592 | CV6 rel/mae/mx=0.196/0.117/0.620 wpow=1.0: PUBLIC rel/mae/mx=0.329/0.182/0.666 | CV6 rel/mae/mx=0.186/0.114/0.564 wpow=1.5: PUBLIC rel/mae/mx=0.311/0.177/0.623 | CV6 rel/mae/mx=0.192/0.115/0.571 [running]
cd /app/scratch && sleep 120 && tail -n 2 wpow_scan.log; pgrep -f wpow_scan.py >/dev/null && echo "[running]" || echo "[done]"
wpow=1.5: PUBLIC rel/mae/mx=0.311/0.177/0.623 | CV6 rel/mae/mx=0.192/0.115/0.571 wpow=2.0: PUBLIC rel/mae/mx=0.282/0.168/0.470 | CV6 rel/mae/mx=0.190/0.114/0.627 [done]
cd /app/scratch && cat > wpow1_cv.py << 'EOF'
import torch, trainlib as T, time
trX,trY=T.load_split('train'); vaX,vaY=T.load_split('val')
X=torch.cat([trX,vaX],0); Y=torch.cat([trY,vaY],0)
snaps=[1600,1650,1700]
base=dict(epochs=1700, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, loss='mse', isotropic=True,
aug=dict(flip=True), wpow=1.0, swa_start=1200, swa_lr=2e-4, eval_which=['raw'])
for seed in [1,2]:
cfg=dict(base); cfg['seed']=seed
t=time.time(); res=T.run_cv(cfg,(X,Y),n_folds=6,snapshot_epochs=snaps,seed=seed)
print(f'[wpow=1 seed={seed}] ({time.time()-t:.0f}s)',flush=True)
for e in snaps:
r=res[(e,'raw')]; print(f' ep{e:4d} rel={r[0]:.4f} mae={r[1]:.4f} mx={r[2]:.4f}',flush=True)
EOF
nohup python wpow1_cv.py > wpow1_cv.log 2>&1 &
echo "launched $!"launched 986
cd /app/scratch && sleep 290 && cat wpow1_cv.log; pgrep -f wpow1_cv.py >/dev/null && echo "[running]" || echo "[done]"
[running]
cd /app/scratch && sleep 160 && cat wpow1_cv.log; pgrep -f wpow1_cv.py >/dev/null && echo "[running]" || echo "[done]"
[wpow=1 seed=1] (313s)
ep1600 rel=0.2062 mae=0.1194 mx=0.7607
ep1650 rel=0.2065 mae=0.1191 mx=0.7713
ep1700 rel=0.1979 mae=0.1179 mx=0.6838
[running]cd /app/scratch && sleep 200 && cat wpow1_cv.log; pgrep -f wpow1_cv.py >/dev/null && echo "[running]" || echo "[done]"
[wpow=1 seed=1] (313s)
ep1600 rel=0.2062 mae=0.1194 mx=0.7607
ep1650 rel=0.2065 mae=0.1191 mx=0.7713
ep1700 rel=0.1979 mae=0.1179 mx=0.6838
[wpow=1 seed=2] (323s)
ep1600 rel=0.1936 mae=0.1165 mx=0.6483
ep1650 rel=0.1974 mae=0.1171 mx=0.6678
ep1700 rel=0.2007 mae=0.1154 mx=0.7018
[done]cd /app/scratch && sed -i 's/wpow=2.0)/wpow=1.0)/' make_final.py && grep -n "wpow" make_final.py && echo "--- running final training (wpow=1) ---" && python make_final.py 0 2>&1 | tail -12
13: loss='mse', isotropic=True, aug=dict(flip=True), wpow=2.0, --- running final training (wpow=1) --- final training pool: torch.Size([148, 2048, 3]) torch.Size([148, 1]) [train_final] in-sample rel/mae/mx = 0.0949/0.0579/0.3414 WROTE /app/outputs/model.pt keys: ['state_dict', 'feat_mean', 'feat_std', 'label_mean', 'label_std'] feat_mean torch.Size([3]) [-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std torch.Size([3]) [0.1860007345676422, 0.1860007345676422, 0.1860007345676422] label_mean torch.Size([1]) [0.7487966418266296] label_std torch.Size([1]) [0.38614240288734436]
command: str_replace path: /app/scratch/make_final.py old_str: loss='mse', isotropic=True, aug=dict(flip=True), wpow=2.0, new_str: loss='mse', isotropic=True, aug=dict(flip=True), wpow=1.0,
The file /app/scratch/make_final.py has been edited successfully.
cd /app/scratch && grep -n "wpow\|epochs=" make_final.py | head && echo "--- running final (wpow=1, ep1650, all 148) ---" && python make_final.py 0 2>&1 | tail -10 && echo "--- grader-style check (all in-sample now) ---" && python grade_check.py /app/outputs/model.pt
12:cfg = dict(seed=SEED, epochs=1650, bs=16, lr=1.5e-3, wd=1e-4, warmup=20, 13: loss='mse', isotropic=True, aug=dict(flip=True), wpow=1.0, --- running final (wpow=1, ep1650, all 148) --- final training pool: torch.Size([148, 2048, 3]) torch.Size([148, 1]) [train_final] in-sample rel/mae/mx = 0.0567/0.0358/0.1627 WROTE /app/outputs/model.pt keys: ['state_dict', 'feat_mean', 'feat_std', 'label_mean', 'label_std'] feat_mean torch.Size([3]) [-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std torch.Size([3]) [0.1860007345676422, 0.1860007345676422, 0.1860007345676422] label_mean torch.Size([1]) [0.7487966418266296] label_std torch.Size([1]) [0.38614240288734436] --- grader-style check (all in-sample now) --- train rel/mae/mx = 0.0582/0.0363/0.1628 val rel/mae/mx = 0.0603/0.0420/0.1217 public_test rel/mae/mx = 0.0323/0.0201/0.0589
cd /app && python - << 'EOF'
import torch, json, sys
sys.path.insert(0,'/app')
from candidate_model import build_model
cfg = json.load(open('/app/task_config.json'))
ck = torch.load('/app/outputs/model.pt', weights_only=True)
# strict load into the fixed architecture
m = build_model(cfg)
missing, unexpected = m.load_state_dict(ck['state_dict'], strict=True)
print('strict load OK; missing=', missing, 'unexpected=', unexpected)
m.eval()
# required keys & shapes
for k in ['state_dict','feat_mean','feat_std','label_mean','label_std']:
assert k in ck, f'missing key {k}'
assert ck['feat_mean'].numel()==3 and ck['feat_std'].numel()==3
assert ck['label_mean'].numel()==1 and ck['label_std'].numel()==1
print('keys/shapes OK')
fm=ck['feat_mean'].float().view(1,1,3); fs=ck['feat_std'].float().view(1,1,3)
lm=ck['label_mean'].float().view(1,1); ls=ck['label_std'].float().view(1,1)
def metrics(pred,true):
e=(pred-true).squeeze(1); t=true.squeeze(1)
return (float(torch.sqrt((e**2).sum())/torch.sqrt((t**2).sum())), float(e.abs().mean()), float(e.abs().max()))
# test on each split, with BOTH float32 and float16-origin inputs
for s in ['train','val','public_test']:
d=torch.load(f'/app/data/{s}/samples.pt', weights_only=True)
Xf=d['features'].float(); Y=d['labels'].float()
with torch.no_grad():
out=torch.cat([m((Xf[i:i+64]-fm)/fs) for i in range(0,Xf.shape[0],64)],0)*ls+lm
# also pass the raw float16 tensor (as the grader might)
X16=d['features'] # float16
with torch.no_grad():
out16=m(((X16[:8].float()-fm)/fs))*ls+lm
print(f'{s:12s} out.shape={tuple(out.shape)} dtype={out.dtype} metrics(rel/mae/mx)={[round(x,4) for x in metrics(out,Y)]}')
print('All validations passed.')
EOF
ls -l /app/outputs/model.ptstrict load OK; missing= [] unexpected= [] keys/shapes OK train out.shape=(100, 1) dtype=torch.float32 metrics(rel/mae/mx)=[0.0582, 0.0363, 0.1628] val out.shape=(32, 1) dtype=torch.float32 metrics(rel/mae/mx)=[0.0603, 0.042, 0.1217] public_test out.shape=(16, 1) dtype=torch.float32 metrics(rel/mae/mx)=[0.0323, 0.0201, 0.0589] All validations passed. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:23 /app/outputs/model.pt
command: create
path: /app/train.py
file_text: from __future__ import annotations
"""Train a PointNet mass regressor for jet-engine brackets.
Recipe (selected via 6-fold cross-validation on the pooled train+val set):
* Input standardized with a per-axis mean and a single ISOTROPIC std so that
the bracket aspect ratios (which carry mass signal) are preserved.
* Target (mass) standardized to zero mean / unit std; de-standardized linearly
at inference as required by the checkpoint contract.
* Augmentation: axis sign-flips (reflections) ONLY. Reflections preserve both
mass and the bbox-normalization convention. Jitter / resampling / scaling /
rotation were all found to DESTROY the subtle scale signal and were dropped.
* Mass-proportional sample weighting (w ~ mass) to counter the systematic
under-prediction of rare heavy brackets (helps rel-L2, MAE and worst case).
* Loss: weighted MSE (directly aligned with the relative-L2 metric).
* Schedule: warmup -> cosine decay -> short constant low-LR tail.
* EMA / SWA were tested and REJECTED: weight averaging regresses the heavy
brackets toward the mean and hurts every metric here. We ship the raw model.
Writes /app/outputs/model.pt with keys: state_dict, feat_mean, feat_std,
label_mean, label_std.
"""
import json
import math
import os
import sys
from pathlib import Path
import torch
import torch.nn as nn
import torch.nn.functional as F
APP_DIR = Path(os.environ.get("APP_DIR", "/app"))
sys.path.insert(0, str(APP_DIR))
from candidate_model import build_model # noqa: E402
from dataset import SampleStore # noqa: E402
def _device() -> torch.device:
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
def _load_config() -> dict:
with (APP_DIR / "task_config.json").open() as fh:
return json.load(fh)
def _load_pool():
"""Pool every available labeled split (train + val + public_test)."""
feats, labels = [], []
for split in ["train", "val", "public_test"]:
p = APP_DIR / "data" / split
if (p / "samples.pt").exists():
store = SampleStore(p)
feats.append(store.features)
labels.append(store.labels)
X = torch.cat(feats, 0)
Y = torch.cat(labels, 0)
return X, Y
def augment_flip(x: torch.Tensor) -> torch.Tensor:
"""Random per-axis sign flips: mass- and bbox-normalization-preserving."""
signs = torch.randint(0, 2, (x.shape[0], 1, 3), device=x.device, dtype=x.dtype) * 2 - 1
return x * signs
# ---- Hyperparameters (fixed from cross-validation) ----
EPOCHS = int(os.environ.get("TASK_EPOCHS_OVERRIDE", 1650))
BATCH = 16
LR = 1.5e-3
WD = 1e-4
WARMUP = 20
WPOW = 1.0 # sample weight exponent: w ~ mass ** WPOW
TAIL_START = 1200 # epoch where cosine ends and the constant tail begins
TAIL_LR = 2e-4
SEED = 0
def main() -> None:
cfg = _load_config()
device = _device()
torch.manual_seed(SEED)
X, Y = _load_pool()
X, Y = X.to(device), Y.to(device)
n = X.shape[0]
print(f"[train] device={device} pooled_samples={n} epochs={EPOCHS}", flush=True)
# Normalization: per-axis mean, single isotropic std (preserves aspect ratio).
feat_mean = X.mean(dim=(0, 1))
feat_std = X.std().repeat(3).clamp_min(1e-6)
label_mean = Y.mean(dim=0)
label_std = Y.std(dim=0).clamp_min(1e-6)
Xn_mean = feat_mean.view(1, 1, 3)
Xn_std = feat_std.view(1, 1, 3)
Yn = (Y - label_mean) / label_std
# Mass-proportional sample weights (normalized to mean 1).
w = (Y.squeeze(1) ** WPOW)
w = w / w.mean()
model = build_model(cfg).to(device)
opt = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WD)
steps_per = max(1, math.ceil(n / BATCH))
cos_end = TAIL_START * steps_per
def lr_at(step: int) -> float:
ep = step // steps_per
if ep >= TAIL_START:
…[truncated]Existing file was successfully overwritten at /app/train.py
cd /app && time python train.py 2>&1 | tail -15
[train] device=cuda pooled_samples=148 epochs=1650 [train] epoch=200/1650 insample_rel_l2=0.2311 [train] epoch=400/1650 insample_rel_l2=0.1536 [train] epoch=600/1650 insample_rel_l2=0.1437 [train] epoch=800/1650 insample_rel_l2=0.0936 [train] epoch=1000/1650 insample_rel_l2=0.0828 [train] epoch=1200/1650 insample_rel_l2=0.0767 [train] epoch=1400/1650 insample_rel_l2=0.0696 [train] epoch=1600/1650 insample_rel_l2=0.0849 [train] epoch=1650/1650 insample_rel_l2=0.0606 [train] wrote /app/outputs/model.pt [stderr] real 1m0.943s user 0m57.540s sys 0m1.680s
cd /app && python - << 'EOF'
import torch, json, sys
sys.path.insert(0,'/app')
from candidate_model import build_model
cfg=json.load(open('/app/task_config.json'))
ck=torch.load('/app/outputs/model.pt', weights_only=True)
m=build_model(cfg); m.load_state_dict(ck['state_dict'], strict=True); m.eval()
fm=ck['feat_mean'].float().view(1,1,3); fs=ck['feat_std'].float().view(1,1,3)
lm=ck['label_mean'].float().view(1,1); ls=ck['label_std'].float().view(1,1)
def metrics(p,t):
e=(p-t).squeeze(1); y=t.squeeze(1)
return [round(float(torch.sqrt((e**2).sum())/torch.sqrt((y**2).sum())),4), round(float(e.abs().mean()),4), round(float(e.abs().max()),4)]
print('required keys present:', all(k in ck for k in ['state_dict','feat_mean','feat_std','label_mean','label_std']))
print('shapes:', tuple(ck['feat_mean'].shape), tuple(ck['feat_std'].shape), tuple(ck['label_mean'].shape), tuple(ck['label_std'].shape))
for s in ['train','val','public_test']:
d=torch.load(f'/app/data/{s}/samples.pt', weights_only=True)
X=d['features'].float(); Y=d['labels'].float()
with torch.no_grad():
out=torch.cat([m((X[i:i+64]-fm)/fs) for i in range(0,X.shape[0],64)],0)*ls+lm
print(f'{s:12s} (in-sample) rel/mae/mx = {metrics(out,Y)} out={tuple(out.shape)}')
# B=1 robustness
with torch.no_grad():
o1=m((torch.zeros(1,2048,3)-fm)/fs)*ls+lm
print('B=1 output shape', tuple(o1.shape))
print('FINAL CHECKPOINT VALID.')
EOFrequired keys present: True shapes: (3,) (3,) (1,) (1,) train (in-sample) rel/mae/mx = [0.0554, 0.0371, 0.1459] out=(100, 1) val (in-sample) rel/mae/mx = [0.0727, 0.0406, 0.245] out=(32, 1) public_test (in-sample) rel/mae/mx = [0.064, 0.0369, 0.1311] out=(16, 1) B=1 output shape (1, 1) FINAL CHECKPOINT VALID.
cd /app && pkill -f 'sweep|holdout|wpow' 2>/dev/null; sleep 1; echo "running python procs:"; pgrep -af python | grep -v grep || echo " none"; echo; echo "=== Deliverable ==="; ls -l /app/outputs/model.pt; python -c "
import torch
ck=torch.load('/app/outputs/model.pt', weights_only=True)
print('keys:', list(ck.keys()))
print('feat_std (isotropic):', [round(x,4) for x in ck['feat_std'].tolist()])
print('label_mean/std:', round(float(ck['label_mean']),4), round(float(ck['label_std']),4))
print('state_dict entries:', len(ck['state_dict']))
"running python procs:
15 /bin/sh -c /opt/h2t-venv/bin/python3 /mcp_server/server.py
16 /opt/h2t-venv/bin/python3 /mcp_server/server.py
348 python -c import sys; sys.path.insert(0, "/tmp/line_io_root_kvzos"); import runpy; runpy.run_module("line_io_tools._shell_line_io_repl", run_name="__main__", alter_sys=True) --use-redirected-stdin --env null
=== Deliverable ===
-rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:25 /app/outputs/model.pt
keys: ['state_dict', 'feat_mean', 'feat_std', 'label_mean', 'label_std']
feat_std (isotropic): [0.186, 0.186, 0.186]
label_mean/std: 0.7488 0.3861
state_dict entries: 41No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary
# task: simjeb-bracket-fea-mass-prediction-real
# attempt: 10 status: completed raw_score: 0.57
# reward rule: continuous: score 0.57 >= 0.5
# => reward = 1.0 (PASS)
#
# Note: these tasks score via an external hidden grader; no pytest/verifier
# block is embedded in the agent transcript. The block below is the last
# sizable tool-output captured in the run (supporting evidence).
----------------------------------------------------------------------
running python procs:
15 /bin/sh -c /opt/h2t-venv/bin/python3 /mcp_server/server.py
16 /opt/h2t-venv/bin/python3 /mcp_server/server.py
348 python -c import sys; sys.path.insert(0, "/tmp/line_io_root_kvzos"); import runpy; runpy.run_module("line_io_tools._shell_line_io_repl", run_name="__main__", alter_sys=True) --use-redirected-stdin --env null
=== Deliverable ===
-rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:25 /app/outputs/model.pt
keys: ['state_dict', 'feat_mean', 'feat_std', 'label_mean', 'label_std']
feat_std (isotropic): [0.186, 0.186, 0.186]
label_mean/std: 0.7488 0.3861
state_dict entries: 41Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_e0b691b638a1478f. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_e0b691b638a1478f · verifier authoritative; classifier explanatory.