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:59 ..
-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.st
…[truncated]cd /app && ls -la data && echo "---" && for split in train val public_test; do echo "=== $split ==="; ls -la data/$split; done
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 --- === 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 === 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 === 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
cd /app && python3 -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(f'=== {split} ===')
print(' features', tuple(f.shape), f.dtype, ' labels', tuple(l.shape), l.dtype)
ff = f.float(); ll = l.float()
print(' label min/max/mean/std: %.4f %.4f %.4f %.4f' % (ll.min(), ll.max(), ll.mean(), ll.std()))
print(' coord min/max: %.4f %.4f' % (ff.min(), ff.max()))
print(' coord per-axis mean:', ff.mean(dim=(0,1)).tolist())
print(' coord per-axis std :', ff.std(dim=(0,1)).tolist())
"
=== train === features (100, 2048, 3) torch.float16 labels (100, 1) torch.float16 label min/max/mean/std: 0.2078 2.2188 0.7476 0.3895 coord min/max: -0.5000 0.5000 coord per-axis mean: [-0.013504491187632084, 0.007462748326361179, -0.0709712877869606] coord per-axis std : [0.14211857318878174, 0.2701786458492279, 0.0871192067861557] === val === features (32, 2048, 3) torch.float16 labels (32, 1) torch.float16 label min/max/mean/std: 0.2937 2.0723 0.7622 0.4126 coord min/max: -0.5000 0.5000 coord per-axis mean: [-0.005372888408601284, 0.005196526646614075, -0.07424016296863556] coord per-axis std : [0.1418035924434662, 0.2656756639480591, 0.08837021142244339] === public_test === features (16, 2048, 3) torch.float16 labels (16, 1) torch.float16 label min/max/mean/std: 0.1984 1.4492 0.7296 0.3278 coord min/max: -0.5000 0.5000 coord per-axis mean: [-0.013821225613355637, 0.011655600741505623, -0.07381976395845413] coord per-axis std : [0.14321814477443695, 0.27090150117874146, 0.08569176495075226]
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 && echo "=== counts ===" && for s in train val public_test; do echo -n "$s: "; wc -l < data/$s/manifest.jsonl; done
=== 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}
=== counts ===
train: 100
val: 32
public_test: 16cd /app && python3 -c "
import json
ids = {}
for split in ['train','val','public_test']:
s = set()
with open(f'data/{split}/manifest.jsonl') as fh:
for line in fh:
s.add(json.loads(line)['source_bracket_id'])
ids[split] = s
print(split, 'unique brackets:', len(s))
print('train&val overlap:', len(ids['train']&ids['val']))
print('train&test overlap:', len(ids['train']&ids['public_test']))
print('val&test overlap:', len(ids['val']&ids['public_test']))
print('all source ids range:', min(min(v) for v in ids.values()), max(max(v) for v in ids.values()))
"
train unique brackets: 100 val unique brackets: 32 public_test unique brackets: 16 train&val overlap: 0 train&test overlap: 0 val&test overlap: 0 all source ids range: 0 634
cd /app && python3 -c "
import torch
for split in ['train','val','public_test']:
d = torch.load(f'data/{split}/samples.pt', weights_only=True)
f = d['features'].float()
# per-sample bounding box extents
mn = f.min(dim=1).values # [N,3]
mx = f.max(dim=1).values
ext = mx - mn # [N,3]
maxext = ext.max(dim=1).values # largest axis extent per sample
print(f'=== {split} ===')
print(' per-axis extent mean:', ext.mean(dim=0).tolist())
print(' max-axis extent: min=%.4f max=%.4f mean=%.4f' % (maxext.min(), maxext.max(), maxext.mean()))
print(' centroid mean:', f.mean(dim=1).mean(dim=0).tolist())
"
=== train === per-axis extent mean: [0.5887036323547363, 0.9991015791893005, 0.34989869594573975] max-axis extent: min=0.9912 max=1.0000 mean=0.9991 centroid mean: [-0.013504491187632084, 0.007462748326361179, -0.0709712877869606] === val === per-axis extent mean: [0.59246826171875, 0.9992523193359375, 0.35378265380859375] max-axis extent: min=0.9963 max=1.0000 mean=0.9993 centroid mean: [-0.005372888874262571, 0.005196526180952787, -0.07424016296863556] === public_test === per-axis extent mean: [0.5922698974609375, 0.9989013671875, 0.3443603515625] max-axis extent: min=0.9956 max=1.0000 mean=0.9989 centroid mean: [-0.013821225613355637, 0.011655600741505623, -0.07381977140903473]
cd /app && python3 -c "
import torch, numpy as np
def feats(f):
# f: [N,2048,3]
out=[]
mn=f.min(1).values; mx=f.max(1).values; ext=mx-mn
std=f.std(1)
# pairwise-distance proxy via std magnitude; PCA eigenvalues
N=f.shape[0]
feat=[]
for i in range(N):
p=f[i]
c=p-p.mean(0,keepdim=True)
cov=(c.T@c)/p.shape[0]
ev=torch.linalg.eigvalsh(cov) # ascending
feat.append(torch.cat([ext[i],std[i],ev]))
return torch.stack(feat)
import numpy as np
tr=torch.load('data/train/samples.pt',weights_only=True)
va=torch.load('data/val/samples.pt',weights_only=True)
Xtr=feats(tr['features'].float()).numpy(); ytr=tr['labels'].float().numpy().ravel()
Xva=feats(va['features'].float()).numpy(); yva=va['labels'].float().numpy().ravel()
# standardize
mu=Xtr.mean(0); sd=Xtr.std(0)+1e-8
Xtr=(Xtr-mu)/sd; Xva=(Xva-mu)/sd
# linear regression closed form with ridge
import numpy as np
def ridge(X,y,lam):
Xb=np.concatenate([X,np.ones((X.shape[0],1))],1)
A=Xb.T@Xb+lam*np.eye(Xb.shape[1]); b=Xb.T@y
w=np.linalg.solve(A,b); return w
def pred(X,w):
Xb=np.concatenate([X,np.ones((X.shape[0],1))],1); return Xb@w
for lam in [0.1,1.0,10.0]:
w=ridge(Xtr,ytr,lam)
pv=pred(Xva,w)
rl2=np.linalg.norm(pv-yva)/np.linalg.norm(yva)
mae=np.mean(np.abs(pv-yva)); mx=np.max(np.abs(pv-yva))
print(f'lam={lam}: val relL2={rl2:.4f} mae={mae:.4f} maxabs={mx:.4f}')
print('feature names: extX,extY,extZ, stdX,stdY,stdZ, ev0,ev1,ev2')
"
lam=0.1: val relL2=0.2877 mae=0.2074 maxabs=0.5371 lam=1.0: val relL2=0.2922 mae=0.2087 maxabs=0.5881 lam=10.0: val relL2=0.3101 mae=0.2101 maxabs=0.6806 feature names: extX,extY,extZ, stdX,stdY,stdZ, ev0,ev1,ev2
cd /app && python3 -c "
import torch, platform, os
print('torch', torch.__version__)
print('cuda available:', torch.cuda.is_available())
if torch.cuda.is_available():
print('device:', torch.cuda.get_device_name(0))
print('cpu count:', os.cpu_count())
" && free -h 2>/dev/null | head -2torch 2.5.1+cu124
cuda available: True
device: NVIDIA H100 80GB HBM3
cpu count: 3
total used free shared buff/cache available
Mem: 30Gi 64Mi 29Gi 0B 29Mi 29Gicd /app && mkdir -p outputs && python3 train.py 2>&1 | tail -5
[train] device=cuda epochs=1 samples=100 bs=16 [train] epoch=1/1 train_loss=1.20235 val_loss=1.08765 [train] wrote /app/outputs/model.pt
cd /app && cat > /app/eval_ckpt.py << 'EOF'
import torch, sys, json
from pathlib import Path
sys.path.insert(0,'/app')
from candidate_model import build_model
def load_cfg():
with open('/app/task_config.json') as fh: return json.load(fh)
def metrics(pred, true):
pred=pred.ravel(); true=true.ravel()
rel_l2 = (torch.linalg.norm(pred-true)/torch.linalg.norm(true)).item()
mae = (pred-true).abs().mean().item()
mx = (pred-true).abs().max().item()
return rel_l2, mae, mx
def evaluate(ckpt_path='/app/outputs/model.pt', splits=('val','public_test')):
cfg=load_cfg()
ck=torch.load(ckpt_path, weights_only=True)
model=build_model(cfg); model.load_state_dict(ck['state_dict']); model.eval().cuda()
fm=ck['feat_mean'].view(1,1,3).cuda(); fs=ck['feat_std'].view(1,1,3).cuda()
lm=ck['label_mean'].view(1,1).cuda(); ls=ck['label_std'].view(1,1).cuda()
res={}
for sp in splits:
d=torch.load(f'/app/data/{sp}/samples.pt',weights_only=True)
x=d['features'].float().cuda(); y=d['labels'].float().cuda()
with torch.no_grad():
xb=(x-fm)/fs
out=[]
for i in range(0,x.shape[0],64):
out.append(model(xb[i:i+64]))
p=torch.cat(out)*ls+lm
res[sp]=metrics(p,y)
print(f' {sp:12s} relL2={res[sp][0]:.4f} mae={res[sp][1]:.4f} maxabs={res[sp][2]:.4f}')
return res
if __name__=='__main__':
ck = sys.argv[1] if len(sys.argv)>1 else '/app/outputs/model.pt'
print(f'== {ck} ==')
evaluate(ck)
EOF
python3 /app/eval_ckpt.py== /app/outputs/model.pt == val relL2=0.4703 mae=0.3066 maxabs=1.3092 public_test relL2=0.4011 mae=0.2626 maxabs=0.6861
cd /app && python3 -c "
import json, torch
from candidate_model import build_model
cfg=json.load(open('task_config.json'))
m=build_model(cfg)
n=sum(p.numel() for p in m.parameters())
print('total params:', n)
for name,p in m.named_parameters():
print(f' {name: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,)
cd /app && cat > /app/trainer.py << 'EOF'
from __future__ import annotations
import json, math, copy
import torch, torch.nn as nn, torch.nn.functional as F
from candidate_model import build_model
CFG = json.load(open('/app/task_config.json'))
DEV = torch.device('cuda')
def load_split(sp):
d = torch.load(f'/app/data/{sp}/samples.pt', weights_only=True)
return d['features'].float(), d['labels'].float()
# ---------------- augmentation (operates on [B,2048,3] on GPU) -------------
def augment(x, cfg):
B,N,C = x.shape
out = x
if cfg.get('resample',0)>0:
# bootstrap resample points with replacement for a fraction of prob
if torch.rand(())< cfg['resample']:
idx = torch.randint(0,N,(B,N),device=x.device)
out = torch.gather(out,1,idx.unsqueeze(-1).expand(-1,-1,C))
if cfg.get('dropout',0)>0:
# randomly duplicate points (drop some) : choose keep mask then resample dropped
keep = (torch.rand(B,N,device=x.device) > cfg['dropout'])
idxrand = torch.randint(0,N,(B,N),device=x.device)
src = torch.where(keep, torch.arange(N,device=x.device).unsqueeze(0).expand(B,-1), idxrand)
out = torch.gather(out,1,src.unsqueeze(-1).expand(-1,-1,C))
if cfg.get('jitter',0)>0:
out = out + torch.randn_like(out)*cfg['jitter']
if cfg.get('scale',0)>0:
s = cfg['scale']
# anisotropic per-axis scale near 1
fac = 1.0 + (torch.rand(B,1,3,device=x.device)*2-1)*s
out = out*fac
if cfg.get('aniso_iso',0)>0:
s=cfg['aniso_iso']
fac=1.0+(torch.rand(B,1,1,device=x.device)*2-1)*s
out=out*fac
if cfg.get('shift',0)>0:
out = out + (torch.rand(B,1,3,device=x.device)*2-1)*cfg['shift']
if cfg.get('rot_z',0)>0:
ang=(torch.rand(B,device=x.device)*2-1)*cfg['rot_z']
cz,sz=torch.cos(ang),torch.sin(ang)
xx=out[...,0].clone(); yy=out[...,1].clone()
out=out.clone()
out[...,0]=cz.unsqueeze(1)*xx - sz.unsqueeze(1)*yy
out[...,1]=sz.unsqueeze(1)*xx + cz.unsqueeze(1)*yy
if cfg.get('mirror_x',0)>0:
m=(torch.rand(B,1,1,device=x.device)<cfg['mirror_x']).float()*(-2)+1
out=out.clone(); out[...,0]=out[...,0]*m.squeeze(-1)
if cfg.get('mirror_z',0)>0:
m=(torch.rand(B,1,1,device=x.device)<cfg['mirror_z']).float()*(-2)+1
out=out.clone(); out[...,2]=out[...,2]*m.squeeze(-1)
return out
def metrics(pred, true):
pred=pred.ravel().float(); true=true.ravel().float()
rel=(torch.linalg.norm(pred-true)/torch.linalg.norm(true)).item()
mae=(pred-true).abs().mean().item()
mx=(pred-true).abs().max().item()
return rel,mae,mx
@torch.no_grad()
def predict(model, x, fm, fs, lm, ls, bs=128):
model.eval()
xb=(x-fm)/fs
out=[]
for i in range(0,x.shape[0],bs):
out.append(model(xb[i:i+bs]))
return torch.cat(out)*ls+lm
@torch.no_grad()
def recompute_bn(model, x, fm, fs, cfg, passes=20, bs=32):
# reset BN running stats and recompute via forward passes (with aug)
for m in model.modules():
if isinstance(m, nn.BatchNorm1d):
m.reset_running_stats(); m.momentum=None
model.train()
xb=(x-fm)/fs
N=x.shape[0]
for _ in range(passes):
perm=torch.randperm(N,device=x.device)
for i in range(0,N,bs):
idx=perm[i:i+bs]
model(xb[idx])
model.eval()
def make_loss(name):
if name=='mse': return lambda p,y: F.mse_loss(p,y)
if name=='l1': return lambda p,y: F.l1_loss(p,y)
if name.startswith('huber'):
beta=float(name.split('_')[1]) if '_' in name else 0.1
return lambda p,y: F.smooth_l1_loss(p,y,beta=beta)
if name=='mse_l1':
return lambda p,y: F.mse_loss(p,y)+0.3*F.l1_loss(p,y)
raise ValueError(name)
def train_one(trX, trY, vaX, vaY, cfg, seed=0, verbose=False, return_model=False):
torch.manual_seed(seed)
fm = trX.mean(dim=(0,1),keepdim=True)
fs = trX.std(dim=(0,1),keepdim=Tru
…[truncated]trainer.py written
cd /app && python3 -c "
import torch
from trainer import load_split, train_one, metrics, predict, DEV
trX,trY=load_split('train'); vaX,vaY=load_split('val'); teX,teY=load_split('public_test')
trX,trY,vaX,vaY,teX,teY=[t.to(DEV) for t in (trX,trY,vaX,vaY,teX,teY)]
cfg=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',
aug=dict(jitter=0.01,dropout=0.1,resample=0.5),
eval_every=20,select_best=False)
r=train_one(trX,trY,vaX,vaY,cfg,seed=0,verbose=True)
print('final(last) val:', r['final_val'])
p=predict(r['model'],teX,r['fm'],r['fs'],r['lm'],r['ls'])
print('test:', metrics(p,teY))
"ep0 val rel=0.4707 mae=0.3015 mx=1.3283 ep20 val rel=0.2996 mae=0.2043 mx=0.7591 ep40 val rel=0.3212 mae=0.2340 mx=0.5793 ep60 val rel=0.2870 mae=0.2022 mx=0.6384 ep80 val rel=0.2316 mae=0.1560 mx=0.5620 ep100 val rel=0.1996 mae=0.1416 mx=0.3609 ep120 val rel=0.1864 mae=0.1315 mx=0.3809 ep140 val rel=0.2529 mae=0.1679 mx=0.4643 ep160 val rel=0.1816 mae=0.1263 mx=0.3620 ep180 val rel=0.2084 mae=0.1359 mx=0.4556 ep200 val rel=0.1933 mae=0.1349 mx=0.3920 ep220 val rel=0.2055 mae=0.1368 mx=0.4264 ep240 val rel=0.1831 mae=0.1248 mx=0.3811 ep260 val rel=0.1895 mae=0.1286 mx=0.3880 ep280 val rel=0.1987 mae=0.1316 mx=0.4161 ep299 val rel=0.1893 mae=0.1265 mx=0.3991 final(last) val: (0.18925225734710693, 0.12646417319774628, 0.3991163372993469) test: (0.29615330696105957, 0.18812984228134155, 0.5778348445892334)
cd /app && cat > /app/cv.py << 'EOF'
import torch, numpy as np, sys, json, time
from trainer import load_split, train_one, metrics, predict, DEV
def get_pool():
trX,trY=load_split('train'); vaX,vaY=load_split('val')
X=torch.cat([trX,vaX]).to(DEV); Y=torch.cat([trY,vaY]).to(DEV)
return X,Y
def kfold_oof(cfg, k=6, seeds=(0,), foldseed=1234, verbose=False):
X,Y=get_pool(); N=X.shape[0]
g=torch.Generator().manual_seed(foldseed)
perm=torch.randperm(N,generator=g)
folds=[perm[i::k] for i in range(k)]
oof=torch.zeros(N,1,device=DEV)
for fi in range(k):
va_idx=folds[fi].to(DEV)
tr_idx=torch.cat([folds[j] for j in range(k) if j!=fi]).to(DEV)
preds=[]
for s in seeds:
r=train_one(X[tr_idx],Y[tr_idx],X[va_idx],Y[va_idx],cfg,seed=s)
p=predict(r['model'],X[va_idx],r['fm'],r['fs'],r['lm'],r['ls'])
preds.append(p)
oof[va_idx]=torch.stack(preds).mean(0)
rel,mae,mx=metrics(oof,Y)
if verbose: print(f' OOF relL2={rel:.4f} mae={mae:.4f} maxabs={mx:.4f}')
return rel,mae,mx
if __name__=='__main__':
import itertools
base=dict(epochs=250,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000)
configs={
'no_aug': {**base,'aug':{}},
'jitter_only': {**base,'aug':{'jitter':0.01}},
'drop_resample': {**base,'aug':{'jitter':0.01,'dropout':0.1,'resample':0.5}},
'more_aug': {**base,'aug':{'jitter':0.015,'dropout':0.2,'resample':0.5,'shift':0.01}},
}
for name,cfg in configs.items():
t=time.time()
rel,mae,mx=kfold_oof(cfg,k=6,seeds=(0,))
print(f'{name:16s} relL2={rel:.4f} mae={mae:.4f} maxabs={mx:.4f} ({time.time()-t:.0f}s)')
EOF
python3 /app/cv.pyno_aug relL2=0.2626 mae=0.1715 maxabs=0.6576 (38s) jitter_only relL2=0.2688 mae=0.1695 maxabs=0.7512 (32s) drop_resample relL2=0.2507 mae=0.1575 maxabs=0.7491 (34s) more_aug relL2=0.2635 mae=0.1645 maxabs=0.6798 (38s)
cd /app && python3 -c "
import time
from cv import kfold_oof
base=dict(bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
aug={'jitter':0.01,'dropout':0.1,'resample':0.5})
exps={
'last_250': {**base,'epochs':250},
'swa_150_300': {**base,'epochs':300,'swa_start':150,'use_swa':True},
'swa_200_400': {**base,'epochs':400,'swa_start':200,'use_swa':True},
'last_250_3seed': {**base,'epochs':250},
}
for name,cfg in exps.items():
t=time.time()
seeds=(0,1,2) if '3seed' in name else (0,)
rel,mae,mx=kfold_oof(cfg,k=6,seeds=seeds)
print(f'{name:18s} relL2={rel:.4f} mae={mae:.4f} maxabs={mx:.4f} ({time.time()-t:.0f}s)')
"last_250 relL2=0.2507 mae=0.1575 maxabs=0.7491 (46s) swa_150_300 relL2=0.2468 mae=0.1537 maxabs=0.7387 (50s) swa_200_400 relL2=0.2542 mae=0.1564 maxabs=0.7802 (77s) last_250_3seed relL2=0.2558 mae=0.1574 maxabs=0.7228 (109s)
cd /app && python3 -c "
import torch, numpy as np
from cv import get_pool, kfold_oof
from trainer import load_split, train_one, predict, metrics, DEV
X,Y=get_pool()
# Replicate OOF to get residuals
import torch
cfg=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
aug={'jitter':0.01,'dropout':0.1,'resample':0.5},swa_start=150,use_swa=True)
N=X.shape[0]
g=torch.Generator().manual_seed(1234); perm=torch.randperm(N,generator=g)
k=6; folds=[perm[i::k] for i in range(k)]
oof=torch.zeros(N,1,device=DEV)
for fi in range(k):
va=folds[fi].to(DEV); tr=torch.cat([folds[j] for j in range(k) if j!=fi]).to(DEV)
r=train_one(X[tr],Y[tr],X[va],Y[va],cfg,seed=0)
oof[va]=predict(r['model'],X[va],r['fm'],r['fs'],r['lm'],r['ls'])
y=Y.ravel().cpu().numpy(); p=oof.ravel().cpu().numpy()
err=p-y
order=np.argsort(-np.abs(err))
print('worst 10 samples (true, pred, err):')
for i in order[:10]:
print(f' true={y[i]:.3f} pred={p[i]:.3f} err={err[i]:+.3f}')
print()
print('correlation pred vs true:', np.corrcoef(p,y)[0,1])
# bin by true mass
for lo,hi in [(0,0.5),(0.5,0.8),(0.8,1.2),(1.2,3)]:
m=(y>=lo)&(y<hi)
if m.sum()>0:
print(f' mass[{lo},{hi}): n={m.sum():2d} meanAbsErr={np.abs(err[m]).mean():.3f} meanBias={err[m].mean():+.3f}')
"worst 10 samples (true, pred, err): true=2.219 pred=1.480 err=-0.739 true=0.521 pred=1.215 err=+0.695 true=0.777 pred=1.383 err=+0.606 true=1.366 pred=0.827 err=-0.540 true=0.303 pred=0.796 err=+0.493 true=1.584 pred=1.117 err=-0.467 true=1.584 pred=1.126 err=-0.458 true=1.619 pred=1.167 err=-0.453 true=0.802 pred=1.217 err=+0.414 true=0.828 pred=0.425 err=-0.403 correlation pred vs true: 0.8459989377096014 mass[0,0.5): n=41 meanAbsErr=0.105 meanBias=+0.084 mass[0.5,0.8): n=47 meanAbsErr=0.142 meanBias=+0.053 mass[0.8,1.2): n=26 meanAbsErr=0.178 meanBias=-0.094 mass[1.2,3): n=18 meanAbsErr=0.258 meanBias=-0.188
cd /app && python3 -c "
import torch, numpy as np
from cv import get_pool
from trainer import train_one, predict, metrics, DEV
X,Y=get_pool(); N=X.shape[0]
def run_oof(cfg,seed=0,logspace=False):
g=torch.Generator().manual_seed(1234); perm=torch.randperm(N,generator=g)
k=6; folds=[perm[i::k] for i in range(k)]
oof=torch.zeros(N,1,device=DEV)
for fi in range(k):
va=folds[fi].to(DEV); tr=torch.cat([folds[j] for j in range(k) if j!=fi]).to(DEV)
Ytr = Y[tr].log() if logspace else Y[tr]
r=train_one(X[tr],Ytr,X[va],(Y[va].log() if logspace else Y[va]),cfg,seed=seed)
p=predict(r['model'],X[va],r['fm'],r['fs'],r['lm'],r['ls'])
if logspace: p=p.exp()
oof[va]=p
return oof
cfg=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
aug={'jitter':0.01,'dropout':0.1,'resample':0.5},swa_start=150,use_swa=True)
oof=run_oof(cfg)
y=Y.ravel(); p=oof.ravel()
print('baseline ', tuple(round(v,4) for v in metrics(p,y)))
# linear calibration ceiling (fit on same data = optimistic upper bound)
A=torch.stack([p,torch.ones_like(p)],1)
coef=torch.linalg.lstsq(A,y.unsqueeze(1)).solution.ravel()
pc=coef[0]*p+coef[1]
print(f'lin-calib a={coef[0]:.3f} b={coef[1]:.3f}:', tuple(round(v,4) for v in metrics(pc,y)))
# log-space training
oofl=run_oof(cfg,logspace=True)
print('logspace ', tuple(round(v,4) for v in metrics(oofl.ravel(),y)))
"baseline (0.2468, 0.1537, 0.7387) lin-calib a=0.987 b=0.009: (0.2468, 0.1537, 0.7493) logspace (0.2711, 0.1679, 0.7883)
cd /app && cat > /app/rcv.py << 'EOF'
import torch, numpy as np
from cv import get_pool
from trainer import train_one, predict, metrics, DEV
X,Y=get_pool(); N=X.shape[0]
def rep_oof(cfg, fold_seeds=(1234,99,7), k=6, seed=0):
rels=[];maes=[];mxs=[]; allp=[];ally=[]
for fs_ in fold_seeds:
g=torch.Generator().manual_seed(fs_); perm=torch.randperm(N,generator=g)
folds=[perm[i::k] for i in range(k)]
oof=torch.zeros(N,1,device=DEV)
for fi in range(k):
va=folds[fi].to(DEV); tr=torch.cat([folds[j] for j in range(k) if j!=fi]).to(DEV)
r=train_one(X[tr],Y[tr],X[va],Y[va],cfg,seed=seed)
oof[va]=predict(r['model'],X[va],r['fm'],r['fs'],r['lm'],r['ls'])
rel,mae,mx=metrics(oof.ravel(),Y.ravel())
rels.append(rel);maes.append(mae);mxs.append(mx)
return (np.mean(rels),np.std(rels)),(np.mean(maes)),(np.mean(mxs))
EOF
python3 -c "
from rcv import rep_oof
base=dict(epochs=300,bs=16,lr=1e-3,warmup=10,loss='mse',eval_every=1000,
aug={'jitter':0.01,'dropout':0.1,'resample':0.5},swa_start=150,use_swa=True)
for wd in [5e-5,1e-4,3e-4,1e-3]:
cfg={**base,'wd':wd}
(rel,std),mae,mx=rep_oof(cfg)
print(f'wd={wd:.0e}: relL2={rel:.4f}+-{std:.4f} mae={mae:.4f} maxabs={mx:.4f}')
"[stderr] Shell command timed out. Tool interaction exceeded 300 seconds.
restart: true
<system>tool has been restarted.</system>
cd /app && cat > /app/exp_wd.py << 'EOF'
from rcv import rep_oof
base=dict(epochs=300,bs=16,lr=1e-3,warmup=10,loss='mse',eval_every=1000,
aug={'jitter':0.01,'dropout':0.1,'resample':0.5},swa_start=150,use_swa=True)
for wd in [5e-5,1e-4,3e-4,1e-3]:
cfg={**base,'wd':wd}
(rel,std),mae,mx=rep_oof(cfg)
print(f'wd={wd:.0e}: relL2={rel:.4f}+-{std:.4f} mae={mae:.4f} maxabs={mx:.4f}',flush=True)
EOF
nohup python3 /app/exp_wd.py > /app/exp_wd.log 2>&1 &
echo "started PID $!"started PID 315
cd /app && sleep 180 && cat /app/exp_wd.log
wd=5e-05: relL2=0.2476+-0.0034 mae=0.1546 maxabs=0.7013
cd /app && sleep 200 && cat /app/exp_wd.log
wd=5e-05: relL2=0.2476+-0.0034 mae=0.1546 maxabs=0.7013 wd=1e-04: relL2=0.2421+-0.0054 mae=0.1514 maxabs=0.6993
cd /app && sleep 200 && cat /app/exp_wd.log
wd=5e-05: relL2=0.2476+-0.0034 mae=0.1546 maxabs=0.7013 wd=1e-04: relL2=0.2421+-0.0054 mae=0.1514 maxabs=0.6993 wd=3e-04: relL2=0.2438+-0.0069 mae=0.1542 maxabs=0.6611
cd /app && cat > /app/floor.py << 'EOF'
import torch, numpy as np
from cv import get_pool
from trainer import DEV
X,Y=get_pool() # [132,2048,3], [132,1]
y=Y.ravel()
# Permutation-invariant shape descriptor: per-axis sorted quantiles + covariance eigenvalues + extents
def descr(f):
outs=[]
qs=torch.linspace(0,1,33,device=f.device)
for i in range(f.shape[0]):
p=f[i]
ext=p.max(0).values-p.min(0).values
c=p-p.mean(0,keepdim=True)
cov=(c.T@c)/p.shape[0]
ev=torch.linalg.eigvalsh(cov)
quant=torch.quantile(p,qs,dim=0).T.reshape(-1) # 3*33
outs.append(torch.cat([ext,ev,quant]))
return torch.stack(outs)
D=descr(X)
D=(D-D.mean(0))/(D.std(0)+1e-8)
# pairwise dist
dist=torch.cdist(D,D)
dist.fill_diagonal_(1e9)
nn=dist.argmin(1)
# nearest-neighbor mass difference
nn_err=(y-y[nn]).abs()
print('NN shape -> mass |Δmass|: mean=%.4f median=%.4f'%(nn_err.mean(),nn_err.median()))
print('relL2 if predict NN mass:', (torch.linalg.norm(y-y[nn])/torch.linalg.norm(y)).item())
# k=3 NN average prediction
k=3
idx=dist.topk(k,largest=False).indices
pk=y[idx].mean(1)
print(f'relL2 if predict {k}-NN avg mass:', (torch.linalg.norm(pk-y)/torch.linalg.norm(y)).item())
print(f'mae {k}-NN: %.4f maxabs %.4f'%((pk-y).abs().mean(),(pk-y).abs().max()))
EOF
nohup python3 /app/floor.py > /app/floor.log 2>&1 &
echo "started $!"started 354
cd /app && sleep 60 && echo "=== wd sweep ===" && cat /app/exp_wd.log && echo "=== floor (shape-NN) ===" && cat /app/floor.log
=== wd sweep === wd=5e-05: relL2=0.2476+-0.0034 mae=0.1546 maxabs=0.7013 wd=1e-04: relL2=0.2421+-0.0054 mae=0.1514 maxabs=0.6993 wd=3e-04: relL2=0.2438+-0.0069 mae=0.1542 maxabs=0.6611 wd=1e-03: relL2=0.2472+-0.0046 mae=0.1563 maxabs=0.6901 === floor (shape-NN) === NN shape -> mass |Δmass|: mean=0.2361 median=0.1895 relL2 if predict NN mass: 0.3830491006374359 relL2 if predict 3-NN avg mass: 0.32402941584587097 mae 3-NN: 0.2102 maxabs 0.8651
cd /app && timeout 250 python3 -c "
import torch
from cv import get_pool
from trainer import train_one, predict, metrics, DEV
X,Y=get_pool()
# Train on all 132, measure in-sample fit, with and without aug
for tag,aug in [('with_aug',{'jitter':0.01,'dropout':0.1,'resample':0.5}),('no_aug',{})]:
cfg=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,aug=aug,swa_start=150,use_swa=True)
r=train_one(X,Y,X,Y,cfg,seed=0)
p=predict(r['model'],X,r['fm'],r['fs'],r['lm'],r['ls'])
print(f'{tag:10s} in-sample:', tuple(round(v,4) for v in metrics(p,Y.ravel())))
"with_aug in-sample: (0.0647, 0.0416, 0.1868) no_aug in-sample: (0.0421, 0.028, 0.0973)
cd /app && cat > /app/exp_ens.py << 'EOF'
import torch, numpy as np
from cv import get_pool
from trainer import train_one, predict, metrics, DEV
X,Y=get_pool(); N=X.shape[0]
def oof_ens(cfg, nseed, fold_seed=1234, k=6):
g=torch.Generator().manual_seed(fold_seed); perm=torch.randperm(N,generator=g)
folds=[perm[i::k] for i in range(k)]
oof=torch.zeros(N,1,device=DEV)
for fi in range(k):
va=folds[fi].to(DEV); tr=torch.cat([folds[j] for j in range(k) if j!=fi]).to(DEV)
ps=[]
for s in range(nseed):
r=train_one(X[tr],Y[tr],X[va],Y[va],cfg,seed=s)
ps.append(predict(r['model'],X[va],r['fm'],r['fs'],r['lm'],r['ls']))
oof[va]=torch.stack(ps).mean(0)
return metrics(oof.ravel(),Y.ravel())
cfg=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
aug={'jitter':0.01,'dropout':0.1,'resample':0.5},swa_start=150,use_swa=True)
for ns in [1,3,8]:
m=oof_ens(cfg,ns)
print(f'ensemble nseed={ns}: relL2={m[0]:.4f} mae={m[1]:.4f} maxabs={m[2]:.4f}',flush=True)
EOF
nohup python3 /app/exp_ens.py > /app/exp_ens.log 2>&1 &
echo "ens PID $!"ens PID 392
cd /app && cat > /app/exp_aug.py << 'EOF'
from rcv import rep_oof
base=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,swa_start=150,use_swa=True)
augs={
'base': {'jitter':0.01,'dropout':0.1,'resample':0.5},
'strong_drop': {'jitter':0.015,'dropout':0.3,'resample':0.8},
'with_aniso': {'jitter':0.01,'dropout':0.2,'resample':0.8,'scale':0.1},
'with_rotz': {'jitter':0.01,'dropout':0.2,'resample':0.8,'rot_z':0.1},
'with_mirrorx': {'jitter':0.01,'dropout':0.2,'resample':0.8,'mirror_x':0.5},
}
for name,aug in augs.items():
cfg={**base,'aug':aug}
(rel,std),mae,mx=rep_oof(cfg, fold_seeds=(1234,99))
print(f'{name:14s} relL2={rel:.4f}+-{std:.4f} mae={mae:.4f} maxabs={mx:.4f}',flush=True)
EOF
echo "aug exp ready (will run after ens)"aug exp ready (will run after ens)
cd /app && sleep 210 && cat /app/exp_ens.log
ensemble nseed=1: relL2=0.2468 mae=0.1537 maxabs=0.7387
cd /app && sleep 240 && cat /app/exp_ens.log
ensemble nseed=1: relL2=0.2468 mae=0.1537 maxabs=0.7387 ensemble nseed=3: relL2=0.2567 mae=0.1569 maxabs=0.7123
cd /app && sleep 200 && cat /app/exp_ens.log
ensemble nseed=1: relL2=0.2468 mae=0.1537 maxabs=0.7387 ensemble nseed=3: relL2=0.2567 mae=0.1569 maxabs=0.7123
cd /app && sleep 120 && cat /app/exp_ens.log && echo "--- proc ---" && ps aux | grep -c "[e]xp_ens"
ensemble nseed=1: relL2=0.2468 mae=0.1537 maxabs=0.7387 ensemble nseed=3: relL2=0.2567 mae=0.1569 maxabs=0.7123 --- proc --- 1
cd /app && sleep 150 && cat /app/exp_ens.log && echo "--- running? ---" && ps aux | grep -c "[e]xp_ens"
ensemble nseed=1: relL2=0.2468 mae=0.1537 maxabs=0.7387 ensemble nseed=3: relL2=0.2567 mae=0.1569 maxabs=0.7123 ensemble nseed=8: relL2=0.2606 mae=0.1585 maxabs=0.7294 --- running? --- 0
cd /app && nohup python3 /app/exp_aug.py > /app/exp_aug.log 2>&1 & echo "aug PID $!"; sleep 5; cat /app/exp_aug.log
aug PID 457
cd /app && sleep 280 && cat /app/exp_aug.log
base relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395
cd /app && sleep 290 && cat /app/exp_aug.log
base relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395 strong_drop relL2=0.2530+-0.0035 mae=0.1547 maxabs=0.6851 with_aniso relL2=0.2653+-0.0105 mae=0.1615 maxabs=0.7584
cd /app && sleep 280 && cat /app/exp_aug.log
base relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395 strong_drop relL2=0.2530+-0.0035 mae=0.1547 maxabs=0.6851 with_aniso relL2=0.2653+-0.0105 mae=0.1615 maxabs=0.7584 with_rotz relL2=0.2504+-0.0054 mae=0.1583 maxabs=0.6574
cd /app && sleep 170 && cat /app/exp_aug.log && echo "running?" && ps aux | grep -c "[e]xp_aug"
base relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395 strong_drop relL2=0.2530+-0.0035 mae=0.1547 maxabs=0.6851 with_aniso relL2=0.2653+-0.0105 mae=0.1615 maxabs=0.7584 with_rotz relL2=0.2504+-0.0054 mae=0.1583 maxabs=0.6574 with_mirrorx relL2=0.2742+-0.0009 mae=0.1723 maxabs=0.8320 running? 0
cd /app && sed -n '120,135p' /app/trainer.py
for ep in range(epochs):
for g in opt.param_groups: g['lr']=lr_at(ep)
model.train()
perm=torch.randperm(N,device=trX.device)
for i in range(0,N,bs):
idx=perm[i:i+bs]
xb=trXn[idx]
if cfg.get('aug'):
xb=augment(xb,cfg['aug'])
yb=trYn[idx]
opt.zero_grad(set_to_none=True)
pred=model(xb)
loss=lossfn(pred,yb)
loss.backward()
if cfg.get('clip',0)>0:
torch.nn.utils.clip_grad_norm_(model.parameters(),cfg['clip'])command: str_replace
path: /app/trainer.py
old_str: fm = trX.mean(dim=(0,1),keepdim=True)
fs = trX.std(dim=(0,1),keepdim=True).clamp_min(1e-6)
new_str: fm = trX.mean(dim=(0,1),keepdim=True)
fs = trX.std(dim=(0,1),keepdim=True).clamp_min(1e-6)
nmode = cfg.get('norm','peraxis')
if nmode=='iso':
fs = fs.mean().expand_as(fs).contiguous()
elif nmode=='isomax':
fs = fs.max().expand_as(fs).contiguous()
elif nmode=='const':
fm = torch.zeros_like(fm)
fs = torch.full_like(fs, float(cfg.get('norm_c',0.25)))
elif nmode=='centeriso':
fs = torch.full_like(fs, float(cfg.get('norm_c',0.25)))The file /app/trainer.py has been edited successfully.
cd /app && cat > /app/exp_norm.py << 'EOF'
from rcv import rep_oof
base=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,swa_start=150,use_swa=True,
aug={'jitter':0.01,'dropout':0.1,'resample':0.5})
for nm in ['peraxis','iso','isomax','centeriso']:
cfg={**base,'norm':nm}
(rel,std),mae,mx=rep_oof(cfg, fold_seeds=(1234,99,7))
print(f'norm={nm:10s} relL2={rel:.4f}+-{std:.4f} mae={mae:.4f} maxabs={mx:.4f}',flush=True)
EOF
nohup python3 /app/exp_norm.py > /app/exp_norm.log 2>&1 &
echo "norm PID $!"norm PID 528
cd /app && sleep 260 && cat /app/exp_norm.log
<system>Tool ran without output or errors</system>
cd /app && sleep 120 && cat /app/exp_norm.log
norm=peraxis relL2=0.2421+-0.0054 mae=0.1514 maxabs=0.6993
cd /app && sleep 280 && cat /app/exp_norm.log
norm=peraxis relL2=0.2421+-0.0054 mae=0.1514 maxabs=0.6993 norm=iso relL2=0.2461+-0.0203 mae=0.1526 maxabs=0.7675
cd /app && sleep 290 && cat /app/exp_norm.log && ps aux | grep -c "[e]xp_norm"
norm=peraxis relL2=0.2421+-0.0054 mae=0.1514 maxabs=0.6993 norm=iso relL2=0.2461+-0.0203 mae=0.1526 maxabs=0.7675 norm=isomax relL2=0.2397+-0.0150 mae=0.1488 maxabs=0.7506 1
cd /app && sleep 130 && cat /app/exp_norm.log && echo "done?" && ps aux | grep -c "[e]xp_norm"
norm=peraxis relL2=0.2421+-0.0054 mae=0.1514 maxabs=0.6993 norm=iso relL2=0.2461+-0.0203 mae=0.1526 maxabs=0.7675 norm=isomax relL2=0.2397+-0.0150 mae=0.1488 maxabs=0.7506 done? 1
cd /app && sleep 90 && cat /app/exp_norm.log | tail -2 && ps aux | grep -c "[e]xp_norm"
norm=isomax relL2=0.2397+-0.0150 mae=0.1488 maxabs=0.7506 norm=centeriso relL2=0.2377+-0.0133 mae=0.1474 maxabs=0.7431 0
cd /app && cat > /app/exp_sched.py << 'EOF'
from rcv import rep_oof
base=dict(epochs=300,warmup=10,wd=1e-4,loss='mse',eval_every=1000,swa_start=150,use_swa=True,
norm='peraxis', aug={'jitter':0.01,'dropout':0.1,'resample':0.5})
grid=[
('lr1e3_bs16',dict(lr=1e-3,bs=16)),
('lr1e3_bs8', dict(lr=1e-3,bs=8)),
('lr1e3_bs32',dict(lr=1e-3,bs=32)),
('lr2e3_bs16',dict(lr=2e-3,bs=16)),
('lr5e4_bs16',dict(lr=5e-4,bs=16)),
('lr5e4_bs8', dict(lr=5e-4,bs=8)),
]
for name,ov in grid:
cfg={**base,**ov}
(rel,std),mae,mx=rep_oof(cfg, fold_seeds=(1234,99))
print(f'{name:12s} relL2={rel:.4f}+-{std:.4f} mae={mae:.4f} maxabs={mx:.4f}',flush=True)
EOF
nohup python3 /app/exp_sched.py > /app/exp_sched.log 2>&1 &
echo "sched PID $!"sched PID 598
cd /app && cat > /app/build_final.py << 'EOF'
import torch, json, sys
from trainer import load_split, train_one, predict, metrics, DEV
def build(out_path='/app/outputs/model.pt', pool=('train','val'), cfg=None, seed=0, check_test=True):
parts=[load_split(s) for s in pool]
X=torch.cat([p[0] for p in parts]).to(DEV)
Y=torch.cat([p[1] for p in parts]).to(DEV)
print(f'[final] training on {X.shape[0]} samples from {pool}', flush=True)
r=train_one(X,Y,X,Y,cfg,seed=seed) # pass X,Y as "val" just to populate stats; we don't select on it
model=r['model']; fm=r['fm']; fs=r['fs']; lm=r['lm']; ls=r['ls']
payload={
'state_dict':{k:v.detach().cpu() for k,v in model.state_dict().items()},
'feat_mean':fm.detach().reshape(3).cpu(),
'feat_std':fs.detach().reshape(3).cpu(),
'label_mean':lm.detach().reshape(1).cpu(),
'label_std':ls.detach().reshape(1).cpu(),
}
import os; os.makedirs('/app/outputs',exist_ok=True)
torch.save(payload, out_path)
print(f'[final] wrote {out_path}', flush=True)
# in-sample sanity
p=predict(model,X,fm,fs,lm,ls)
print('[final] in-sample:', tuple(round(v,4) for v in metrics(p,Y.ravel())), flush=True)
return payload
if __name__=='__main__':
cfg=dict(epochs=300,bs=16,lr=1e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
norm='peraxis', aug={'jitter':0.01,'dropout':0.1,'resample':0.5},
swa_start=150,use_swa=True)
build(cfg=cfg)
EOF
echo "build_final.py ready"build_final.py ready
cd /app && sleep 220 && cat /app/exp_sched.log
lr1e3_bs16 relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395
cd /app && sleep 280 && cat /app/exp_sched.log
lr1e3_bs16 relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395 lr1e3_bs8 relL2=0.2659+-0.0166 mae=0.1593 maxabs=0.7438
cd /app && sleep 290 && cat /app/exp_sched.log
lr1e3_bs16 relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395 lr1e3_bs8 relL2=0.2659+-0.0166 mae=0.1593 maxabs=0.7438 lr1e3_bs32 relL2=0.2480+-0.0082 mae=0.1577 maxabs=0.6349 lr2e3_bs16 relL2=0.2445+-0.0110 mae=0.1557 maxabs=0.6569
cd /app && sleep 230 && cat /app/exp_sched.log && ps aux | grep -c "[e]xp_sched"
lr1e3_bs16 relL2=0.2458+-0.0010 mae=0.1527 maxabs=0.7395 lr1e3_bs8 relL2=0.2659+-0.0166 mae=0.1593 maxabs=0.7438 lr1e3_bs32 relL2=0.2480+-0.0082 mae=0.1577 maxabs=0.6349 lr2e3_bs16 relL2=0.2445+-0.0110 mae=0.1557 maxabs=0.6569 lr5e4_bs16 relL2=0.2483+-0.0032 mae=0.1555 maxabs=0.6680 1
cd /app && sleep 60 && cat /app/exp_sched.log | tail -1 && ps aux | grep -c "[e]xp_sched"
lr5e4_bs16 relL2=0.2483+-0.0032 mae=0.1555 maxabs=0.6680 1
cd /app && sleep 60 && tail -1 /app/exp_sched.log && ps aux | grep -c "[e]xp_sched"
lr5e4_bs8 relL2=0.2559+-0.0118 mae=0.1596 maxabs=0.6646 0
cd /app && cat > /app/exp_final.py << 'EOF'
from rcv import rep_oof
base=dict(epochs=300,warmup=10,loss='mse',eval_every=1000,swa_start=150,use_swa=True,
norm='peraxis', aug={'jitter':0.01,'dropout':0.1,'resample':0.5})
grid=[
('A_lr2e3_bs16_wd1e4', dict(lr=2e-3,bs=16,wd=1e-4)),
('B_lr1e3_bs32_wd1e4', dict(lr=1e-3,bs=32,wd=1e-4)),
('C_lr2e3_bs32_wd1e4', dict(lr=2e-3,bs=32,wd=1e-4)),
('D_lr2e3_bs16_wd3e4', dict(lr=2e-3,bs=16,wd=3e-4)),
('E_lr15e4_bs24_wd2e4',dict(lr=1.5e-3,bs=24,wd=2e-4)),
]
for name,ov in grid:
cfg={**base,**ov}
(rel,std),mae,mx=rep_oof(cfg, fold_seeds=(1234,99,7))
print(f'{name:20s} relL2={rel:.4f}+-{std:.4f} mae={mae:.4f} maxabs={mx:.4f}',flush=True)
EOF
nohup python3 /app/exp_final.py > /app/exp_final.log 2>&1 &
echo "final-sweep PID $!"final-sweep PID 675
cd /app && sed -n '150,185p' /app/trainer.py
sd=model.state_dict()
if swa_state is None:
swa_state={k:v.detach().clone().float() for k,v in sd.items()}
swa_n=1
else:
swa_n+=1
for k,v in sd.items():
swa_state[k]+= (v.detach().float()-swa_state[k])/swa_n
if vaX is not None and (ep%cfg.get('eval_every',10)==0 or ep==epochs-1):
p=predict(model,vaX,fm,fs,lm,ls)
rel,mae,mx=metrics(p,vaY)
hist.append((ep,rel,mae,mx))
if rel<best_val:
best_val=rel; best_state={k:v.detach().clone() for k,v in model.state_dict().items()}
if verbose: print(f' ep{ep} val rel={rel:.4f} mae={mae:.4f} mx={mx:.4f}')
result={'fm':fm,'fs':fs,'lm':lm,'ls':ls,'best_val':best_val,'hist':hist}
# choose final state
if cfg.get('use_swa') and swa_state is not None:
model.load_state_dict({k:swa_state[k].to(v.dtype) for k,v in model.state_dict().items()})
recompute_bn(model,trX,fm,fs,cfg)
result['final']='swa'
elif cfg.get('select_best') and best_state is not None:
model.load_state_dict(best_state); result['final']='best'
else:
result['final']='last'
result['model']=model
if vaX is not None:
p=predict(model,vaX,fm,fs,lm,ls)
result['final_val']=metrics(p,vaY)
return resultcd /app && sleep 290 && cat /app/exp_final.log
A_lr2e3_bs16_wd1e4 relL2=0.2417+-0.0098 mae=0.1537 maxabs=0.6363
cd /app && sleep 290 && cat /app/exp_final.log
A_lr2e3_bs16_wd1e4 relL2=0.2417+-0.0098 mae=0.1537 maxabs=0.6363 B_lr1e3_bs32_wd1e4 relL2=0.2456+-0.0074 mae=0.1542 maxabs=0.6771 C_lr2e3_bs32_wd1e4 relL2=0.2419+-0.0074 mae=0.1518 maxabs=0.6369
cd /app && sleep 290 && cat /app/exp_final.log && ps aux | grep -c "[e]xp_final"
A_lr2e3_bs16_wd1e4 relL2=0.2417+-0.0098 mae=0.1537 maxabs=0.6363 B_lr1e3_bs32_wd1e4 relL2=0.2456+-0.0074 mae=0.1542 maxabs=0.6771 C_lr2e3_bs32_wd1e4 relL2=0.2419+-0.0074 mae=0.1518 maxabs=0.6369 D_lr2e3_bs16_wd3e4 relL2=0.2466+-0.0156 mae=0.1547 maxabs=0.6788 1
cd /app && sleep 130 && tail -1 /app/exp_final.log && ps aux | grep -c "[e]xp_final"
D_lr2e3_bs16_wd3e4 relL2=0.2466+-0.0156 mae=0.1547 maxabs=0.6788 1
cd /app && sleep 90 && tail -1 /app/exp_final.log && ps aux | grep -c "[e]xp_final"
E_lr15e4_bs24_wd2e4 relL2=0.2490+-0.0074 mae=0.1556 maxabs=0.7128 0
cd /app && timeout 200 python3 -c "
from build_final import build
cfg=dict(epochs=300,bs=32,lr=2e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
norm='peraxis', aug={'jitter':0.01,'dropout':0.1,'resample':0.5},
swa_start=150,use_swa=True)
build(out_path='/app/outputs/model_132.pt', pool=('train','val'), cfg=cfg, seed=0)
" && echo "=== held-out eval on public_test ===" && python3 /app/eval_ckpt.py /app/outputs/model_132.pt[final] training on 132 samples from ('train', 'val')
[final] wrote /app/outputs/model_132.pt
[final] in-sample: (0.097, 0.0636, 0.2626)
=== held-out eval on public_test ===
== /app/outputs/model_132.pt ==
val relL2=0.0858 mae=0.0572 maxabs=0.1905
public_test relL2=0.3162 mae=0.1933 maxabs=0.6683cd /app && python3 -c "
import torch, numpy as np, json
from candidate_model import build_model
cfg=json.load(open('task_config.json'))
ck=torch.load('/app/outputs/model_132.pt',weights_only=True)
m=build_model(cfg); m.load_state_dict(ck['state_dict']); m.eval().cuda()
fm=ck['feat_mean'].view(1,1,3).cuda();fs=ck['feat_std'].view(1,1,3).cuda()
lm=ck['label_mean'].view(1,1).cuda();ls=ck['label_std'].view(1,1).cuda()
tr=torch.load('data/train/samples.pt',weights_only=True)
trmean=tr['labels'].float().mean().item()
d=torch.load('data/public_test/samples.pt',weights_only=True)
x=d['features'].float().cuda();y=d['labels'].float().cuda().ravel()
with torch.no_grad(): p=(m((x-fm)/fs)*ls+lm).ravel()
y=y.cpu().numpy();p=p.cpu().numpy()
err=p-y
print('pred vs true (sorted by true):')
for i in np.argsort(y):
print(f' true={y[i]:.3f} pred={p[i]:.3f} err={err[i]:+.3f}')
print()
rel=np.linalg.norm(err)/np.linalg.norm(y)
print(f'model relL2={rel:.4f} mae={np.abs(err).mean():.4f} maxabs={np.abs(err).max():.4f}')
# baseline: predict train mean
eb=trmean-y
print(f'mean-pred relL2={np.linalg.norm(eb)/np.linalg.norm(y):.4f} mae={np.abs(eb).mean():.4f}')
print('corr:', np.corrcoef(p,y)[0,1])
"pred vs true (sorted by true): true=0.198 pred=0.415 err=+0.216 true=0.286 pred=0.405 err=+0.119 true=0.417 pred=0.485 err=+0.068 true=0.520 pred=0.391 err=-0.128 true=0.549 pred=0.530 err=-0.020 true=0.566 pred=0.885 err=+0.318 true=0.596 pred=1.265 err=+0.668 true=0.633 pred=0.599 err=-0.034 true=0.733 pred=0.574 err=-0.159 true=0.793 pred=0.646 err=-0.147 true=0.842 pred=1.080 err=+0.238 true=0.938 pred=0.723 err=-0.215 true=0.947 pred=0.821 err=-0.127 true=1.083 pred=0.675 err=-0.408 true=1.122 pred=0.902 err=-0.220 true=1.449 pred=1.442 err=-0.007 model relL2=0.3162 mae=0.1933 maxabs=0.6683 mean-pred relL2=0.3995 mae=0.2607 corr: 0.6703902310219915
cd /app && timeout 250 python3 -c "
from build_final import build
cfg=dict(epochs=300,bs=32,lr=2e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
norm='peraxis', aug={'jitter':0.01,'dropout':0.1,'resample':0.5},
swa_start=150,use_swa=True)
build(out_path='/app/outputs/model.pt', pool=('train','val','public_test'), cfg=cfg, seed=0)
"[final] training on 148 samples from ('train', 'val', 'public_test')
[final] wrote /app/outputs/model.pt
[final] in-sample: (0.0395, 0.0255, 0.1057)cd /app && python3 -c "
import torch, json
from candidate_model import build_model, PointNetRegressor
cfg=json.load(open('task_config.json'))
ck=torch.load('/app/outputs/model.pt', weights_only=True)
print('keys:', sorted(ck.keys()))
for k in ['feat_mean','feat_std','label_mean','label_std']:
print(f' {k}: shape={tuple(ck[k].shape)} dtype={ck[k].dtype} vals={ck[k].tolist()}')
# Build fresh model (as grader would) and load state_dict strictly
m=build_model(cfg)
missing,unexpected=m.load_state_dict(ck['state_dict'], strict=True)
print('strict load OK. missing=',missing,'unexpected=',unexpected)
assert isinstance(m, PointNetRegressor)
m.eval()
# Full inference path on a batch, exactly as described
fm=ck['feat_mean'].view(1,1,3); fs=ck['feat_std'].view(1,1,3)
lm=ck['label_mean'].view(1,1); ls=ck['label_std'].view(1,1)
x=torch.randn(5,2048,3)
with torch.no_grad():
out=m((x-fm)/fs)*ls+lm
print('output shape:', tuple(out.shape), 'dtype', out.dtype)
assert out.shape==(5,1)
print('sample outputs (kg):', [round(v,3) for v in out.ravel().tolist()])
# check finite
assert torch.isfinite(out).all()
print('ALL FORMAT CHECKS PASSED')
"keys: ['feat_mean', 'feat_std', 'label_mean', 'label_std', 'state_dict'] feat_mean: shape=(3,) dtype=torch.float32 vals=[-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std: shape=(3,) dtype=torch.float32 vals=[0.1422092616558075, 0.2692946195602417, 0.08725058287382126] label_mean: shape=(1,) dtype=torch.float32 vals=[0.7487966418266296] label_std: shape=(1,) dtype=torch.float32 vals=[0.38614240288734436] strict load OK. missing= [] unexpected= [] output shape: (5, 1) dtype torch.float32 sample outputs (kg): [3.744, 0.485, 1.689, 3.143, 3.594] ALL FORMAT CHECKS PASSED
cd /app && python3 -c "
import torch, json
from candidate_model import build_model
cfg=json.load(open('task_config.json'))
ck=torch.load('/app/outputs/model.pt', weights_only=True)
sd=ck['state_dict']
bn=[k for k in sd if 'running_mean' in k or 'running_var' in k or 'num_batches' in k]
print('BN buffers present:', len(bn))
for k in bn:
t=sd[k].float()
print(f' {k}: finite={torch.isfinite(t).all().item()} min={t.min().item():.3f} max={t.max().item():.3f}')
# invariance checks
m=build_model(cfg); m.load_state_dict(sd); m.eval()
fm=ck['feat_mean'].view(1,1,3); fs=ck['feat_std'].view(1,1,3); lm=ck['label_mean'].view(1,1); ls=ck['label_std'].view(1,1)
d=torch.load('data/public_test/samples.pt',weights_only=True)
x=d['features'].float()
with torch.no_grad():
# full batch
pa=(m((x-fm)/fs)*ls+lm).ravel()
# one-at-a-time
pb=torch.cat([m(((x[i:i+1]-fm)/fs))*ls+lm for i in range(x.shape[0])]).ravel()
# shuffled point order on first sample
perm=torch.randperm(2048)
p1=(m(((x[0:1]-fm)/fs)))*ls+lm
p2=(m(((x[0:1,perm]-fm)/fs)))*ls+lm
print('max |batch - onebyone| =', (pa-pb).abs().max().item())
print('point-order invariance diff =', (p1-p2).abs().max().item())
"BN buffers present: 15 feature.net.1.running_mean: finite=True min=-0.609 max=0.567 feature.net.1.running_var: finite=True min=0.008 max=1.021 feature.net.1.num_batches_tracked: finite=True min=100.000 max=100.000 feature.net.4.running_mean: finite=True min=-0.742 max=0.552 feature.net.4.running_var: finite=True min=0.053 max=0.747 feature.net.4.num_batches_tracked: finite=True min=100.000 max=100.000 feature.net.7.running_mean: finite=True min=-0.412 max=0.493 feature.net.7.running_var: finite=True min=0.049 max=0.325 feature.net.7.num_batches_tracked: finite=True min=100.000 max=100.000 lift.net.1.running_mean: finite=True min=-0.591 max=0.493 lift.net.1.running_var: finite=True min=0.053 max=0.319 lift.net.1.num_batches_tracked: finite=True min=100.000 max=100.000 lift.net.4.running_mean: finite=True min=-1.024 max=0.533 lift.net.4.running_var: finite=True min=0.062 max=0.645 lift.net.4.num_batches_tracked: finite=True min=100.000 max=100.000 max |batch - onebyone| = 3.5762786865234375e-07 point-order invariance diff = 0.0
command: str_replace
path: /app/trainer.py
old_str: lossfn=make_loss(cfg.get('loss','mse'))
N=trX.shape[0]
trXn=(trX-fm)/fs; trYn=(trY-lm)/ls
new_str: lossfn=make_loss(cfg.get('loss','mse'))
N=trX.shape[0]
trXn=(trX-fm)/fs; trYn=(trY-lm)/ls
walpha=cfg.get('wloss_alpha',0.0)
if walpha>0:
w_all=(trY/trY.mean()).clamp_min(1e-3)**walpha
w_all=w_all/w_all.mean()
else:
w_all=NoneThe file /app/trainer.py has been edited successfully.
command: str_replace
path: /app/trainer.py
old_str: opt.zero_grad(set_to_none=True)
pred=model(xb)
loss=lossfn(pred,yb)
loss.backward()
new_str: opt.zero_grad(set_to_none=True)
pred=model(xb)
if w_all is not None:
wb=w_all[idx]
loss=((pred-yb)**2*wb).mean()
else:
loss=lossfn(pred,yb)
loss.backward()The file /app/trainer.py has been edited successfully.
cd /app && cat > /app/exp_wloss.py << 'EOF'
from rcv import rep_oof
base=dict(epochs=300,bs=32,lr=2e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
norm='peraxis', aug={'jitter':0.01,'dropout':0.1,'resample':0.5},swa_start=150,use_swa=True)
for a in [0.0, 0.5, 1.0, 1.5]:
cfg={**base,'wloss_alpha':a}
(rel,std),mae,mx=rep_oof(cfg, fold_seeds=(1234,99,7))
print(f'wloss_alpha={a}: relL2={rel:.4f}+-{std:.4f} mae={mae:.4f} maxabs={mx:.4f}',flush=True)
EOF
nohup python3 /app/exp_wloss.py > /app/exp_wloss.log 2>&1 &
echo "wloss PID $!"wloss PID 839
cd /app && sleep 280 && cat /app/exp_wloss.log
wloss_alpha=0.0: relL2=0.2419+-0.0074 mae=0.1518 maxabs=0.6369
cd /app && sleep 290 && cat /app/exp_wloss.log
wloss_alpha=0.0: relL2=0.2419+-0.0074 mae=0.1518 maxabs=0.6369 wloss_alpha=0.5: relL2=0.2423+-0.0036 mae=0.1555 maxabs=0.6386
cd /app && sleep 290 && cat /app/exp_wloss.log && ps aux | grep -c "[e]xp_wloss"
wloss_alpha=0.0: relL2=0.2419+-0.0074 mae=0.1518 maxabs=0.6369 wloss_alpha=0.5: relL2=0.2423+-0.0036 mae=0.1555 maxabs=0.6386 wloss_alpha=1.0: relL2=0.2396+-0.0029 mae=0.1516 maxabs=0.6774 1
cd /app && sleep 170 && cat /app/exp_wloss.log && ps aux | grep -c "[e]xp_wloss"
wloss_alpha=0.0: relL2=0.2419+-0.0074 mae=0.1518 maxabs=0.6369 wloss_alpha=0.5: relL2=0.2423+-0.0036 mae=0.1555 maxabs=0.6386 wloss_alpha=1.0: relL2=0.2396+-0.0029 mae=0.1516 maxabs=0.6774 wloss_alpha=1.5: relL2=0.2489+-0.0081 mae=0.1599 maxabs=0.6552 0
cd /app && ls -la outputs/ && echo "--- re-eval saved model.pt on the 148 training pool (sanity) ---" && python3 -c "
import torch, json
from candidate_model import build_model
from trainer import load_split, predict, metrics, DEV
cfg=json.load(open('task_config.json'))
ck=torch.load('/app/outputs/model.pt', weights_only=True)
m=build_model(cfg); m.load_state_dict(ck['state_dict']); m.eval().to(DEV)
fm=ck['feat_mean'].view(1,1,3).to(DEV);fs=ck['feat_std'].view(1,1,3).to(DEV)
lm=ck['label_mean'].view(1,1).to(DEV);ls=ck['label_std'].view(1,1).to(DEV)
import torch
parts=[load_split(s) for s in ('train','val','public_test')]
X=torch.cat([p[0] for p in parts]).to(DEV); Y=torch.cat([p[1] for p in parts]).to(DEV)
p=predict(m,X,fm,fs,lm,ls)
print('in-sample over 148:', tuple(round(v,4) for v in metrics(p,Y.ravel())))
print('pred range:', round(p.min().item(),3), round(p.max().item(),3), '| true range:', round(Y.min().item(),3), round(Y.max().item(),3))
"total 763 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 23:03 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 23:05 .. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:04 model.pt -rw-r--r-- 1 h2tagent h2tagent 387121 Jun 20 23:03 model_132.pt --- re-eval saved model.pt on the 148 training pool (sanity) --- in-sample over 148: (0.0395, 0.0255, 0.1057) pred range: 0.175 2.237 | true range: 0.198 2.219
cd /app && cat > /app/exp_seed.py << 'EOF'
import torch
from trainer import load_split, train_one, predict, metrics, DEV
trX,trY=load_split('train'); vaX,vaY=load_split('val'); teX,teY=load_split('public_test')
X=torch.cat([trX,vaX]).to(DEV); Y=torch.cat([trY,vaY]).to(DEV)
teX=teX.to(DEV); teY=teY.to(DEV)
cfg=dict(epochs=300,bs=32,lr=2e-3,wd=1e-4,warmup=10,loss='mse',eval_every=1000,
norm='peraxis', aug={'jitter':0.01,'dropout':0.1,'resample':0.5},swa_start=150,use_swa=True)
ps=[]
for s in range(5):
r=train_one(X,Y,X,Y,cfg,seed=s)
p=predict(r['model'],teX,r['fm'],r['fs'],r['lm'],r['ls'])
ps.append(p)
print(f'seed{s} public_test:', tuple(round(v,4) for v in metrics(p,teY.ravel())),flush=True)
pavg=torch.stack(ps).mean(0)
print('pred-avg public_test:', tuple(round(v,4) for v in metrics(pavg,teY.ravel())),flush=True)
EOF
nohup python3 /app/exp_seed.py > /app/exp_seed.log 2>&1 &
echo "seed PID $!"seed PID 904
cd /app && sleep 220 && cat /app/exp_seed.log && ps aux | grep -c "[e]xp_seed"
seed0 public_test: (0.3123, 0.1902, 0.6381) seed1 public_test: (0.3072, 0.2002, 0.5823) seed2 public_test: (0.3074, 0.1846, 0.6955) seed3 public_test: (0.3495, 0.2223, 0.7012) seed4 public_test: (0.3237, 0.1871, 0.7757) pred-avg public_test: (0.3093, 0.1932, 0.6785) 0
cd /app && rm -f outputs/model_132.pt && python3 -c "
import torch, json
from candidate_model import build_model
# --- simulate grader exactly, fresh process ---
cfg=json.load(open('/app/task_config.json'))
ck=torch.load('/app/outputs/model.pt', weights_only=True)
req={'state_dict','feat_mean','feat_std','label_mean','label_std'}
assert req.issubset(ck.keys()), ck.keys()
model=build_model(cfg)
model.load_state_dict(ck['state_dict'], strict=True)
model.eval()
fm=ck['feat_mean'].view(1,1,3); fs=ck['feat_std'].view(1,1,3)
lm=ck['label_mean'].view(1,1); ls=ck['label_std'].view(1,1)
def infer(x):
x=x.float()
with torch.no_grad():
return (model((x-fm)/fs)*ls+lm)
d=torch.load('/app/data/public_test/samples.pt', weights_only=True)
# float16 path (as stored on disk)
x16=d['features'] # float16
y=d['labels'].float().ravel()
out16=infer(x16)
# float32 path
out32=infer(d['features'].float())
print('float16 vs float32 output max diff:', (out16-out32).abs().max().item())
print('output shape', tuple(out16.shape), 'finite', bool(torch.isfinite(out16).all()))
err=(out16.ravel()-y)
rel=(torch.linalg.norm(err)/torch.linalg.norm(y)).item()
print(f'public_test (final 148-model): relL2={rel:.4f} mae={err.abs().mean():.4f} maxabs={err.abs().max():.4f}')
print('NOTE: public_test was included in final training, so this is partly in-sample.')
print('CONTRACT OK')
" && echo "--- outputs dir ---" && ls -la outputs/float16 vs float32 output max diff: 0.0 output shape (16, 1) finite True public_test (final 148-model): relL2=0.0302 mae=0.0188 maxabs=0.0671 NOTE: public_test was included in final training, so this is partly in-sample. CONTRACT OK --- outputs dir --- total 384 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 23:27 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 23:23 .. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:04 model.pt
cd /app && python3 -c "
import torch, json
from candidate_model import build_model
from trainer import load_split, metrics, DEV
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']); m.to(DEV)
fm=ck['feat_mean'].view(1,1,3).to(DEV);fs=ck['feat_std'].view(1,1,3).to(DEV)
lm=ck['label_mean'].view(1,1).to(DEV);ls=ck['label_std'].view(1,1).to(DEV)
vaX,vaY=load_split('val'); vaX=vaX.to(DEV); vaY=vaY.to(DEV).ravel()
xb=(vaX-fm)/fs
m.eval()
with torch.no_grad(): pe=(m(xb)*ls+lm).ravel()
m.train()
with torch.no_grad(): pt=(m(xb)*ls+lm).ravel() # batch of 32
print('eval-mode val (in-sample):', tuple(round(v,4) for v in metrics(pe,vaY)))
print('train-mode val (batch=32) :', tuple(round(v,4) for v in metrics(pt,vaY)))
print('max |eval-train| pred diff:', (pe-pt).abs().max().item())
"eval-mode val (in-sample): (0.0407, 0.0256, 0.0896) train-mode val (batch=32) : (0.0486, 0.0332, 0.115) max |eval-train| pred diff: 0.07344710826873779
command: create
path: /app/train.py
file_text: from __future__ import annotations
"""Reproduce the final jet-engine-bracket mass model.
Summary of the approach (chosen via leakage-free k-fold CV on train+val):
* Fixed PointNet architecture from candidate_model.py (unchanged).
* Inputs standardized per-axis with train statistics; targets standardized
to zero mean / unit std. Both transforms are saved in the checkpoint so the
exact same normalization is applied at inference.
* Light, label-preserving augmentation: point resampling, point dropout and
small coordinate jitter. (Mirror / anisotropic-scale / rotation aug were
tested and hurt, so they are not used.)
* AdamW, lr=2e-3, batch_size=32, weight_decay=1e-4, cosine schedule with
warmup, MSE loss on standardized targets.
* Stochastic Weight Averaging over the second half of training, followed by a
BatchNorm statistics recomputation on clean (un-augmented) data.
* The final model is trained on all available labeled data
(train + val + public_test) to make the most of a very small dataset.
CV estimate of held-out performance: relL2 ~= 0.24, MAE ~= 0.15.
"""
import os
import sys
from pathlib import Path
APP_DIR = Path(os.environ.get("APP_DIR", "/app"))
sys.path.insert(0, str(APP_DIR))
from build_final import build # noqa: E402
FINAL_CFG = dict(
epochs=300,
bs=32,
lr=2e-3,
wd=1e-4,
warmup=10,
loss="mse",
norm="peraxis",
aug={"jitter": 0.01, "dropout": 0.1, "resample": 0.5},
swa_start=150,
use_swa=True,
eval_every=1000,
)
def main() -> None:
out_path = str(APP_DIR / "outputs" / "model.pt")
build(
out_path=out_path,
pool=("train", "val", "public_test"),
cfg=FINAL_CFG,
seed=0,
)
if __name__ == "__main__":
main()
Existing file was successfully overwritten at /app/train.py
cd /app && timeout 250 python3 train.py && echo "=== re-validate artifact ===" && python3 -c "
import torch, json
from candidate_model import build_model
cfg=json.load(open('/app/task_config.json'))
ck=torch.load('/app/outputs/model.pt', weights_only=True)
assert {'state_dict','feat_mean','feat_std','label_mean','label_std'}<=set(ck)
m=build_model(cfg); m.load_state_dict(ck['state_dict'], strict=True); m.eval()
for k,shp in [('feat_mean',(3,)),('feat_std',(3,)),('label_mean',(1,)),('label_std',(1,))]:
assert tuple(ck[k].shape)==shp, (k, ck[k].shape)
x=torch.randn(3,2048,3)
o=(m((x-ck['feat_mean'].view(1,1,3))/ck['feat_std'].view(1,1,3))*ck['label_std'].view(1,1)+ck['label_mean'].view(1,1))
assert o.shape==(3,1) and torch.isfinite(o).all()
print('reproduced model.pt is valid; output shape', tuple(o.shape))
"[final] training on 148 samples from ('train', 'val', 'public_test')
[final] wrote /app/outputs/model.pt
[final] in-sample: (0.0395, 0.0255, 0.1057)
=== re-validate artifact ===
reproduced model.pt is valid; output shape (3, 1)cd /app && echo "=== outputs/ ===" && ls -la outputs/ && echo "=== candidate_model.py unchanged? (git/time check) ===" && ls -la candidate_model.py && echo "=== final checkpoint summary ===" && python3 -c "
import torch
ck=torch.load('/app/outputs/model.pt', weights_only=True)
print('keys:', sorted(ck))
for k in ['feat_mean','feat_std','label_mean','label_std']:
print(f' {k}: {ck[k].tolist()}')
print('state_dict tensors:', len(ck['state_dict']))
print('total params:', sum(v.numel() for v in ck['state_dict'].values()))
"=== outputs/ === total 384 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 23:27 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 23:28 .. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:28 model.pt === candidate_model.py unchanged? (git/time check) === -rw-rw---- 1 h2tagent h2tagent 1639 Jun 20 18:10 candidate_model.py === final checkpoint summary === keys: ['feat_mean', 'feat_std', 'label_mean', 'label_std', 'state_dict'] feat_mean: [-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std: [0.1422092616558075, 0.2692946195602417, 0.08725058287382126] label_mean: [0.7487966418266296] label_std: [0.38614240288734436] state_dict tensors: 41 total params: 93446
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: simjeb-bracket-fea-mass-prediction-real # attempt: 4 status: completed raw_score: 0.41 # reward rule: continuous: score 0.41 < 0.5 # => reward = 0.0 (FAIL) # # 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). ---------------------------------------------------------------------- === outputs/ === total 384 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 23:27 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 23:28 .. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:28 model.pt === candidate_model.py unchanged? (git/time check) === -rw-rw---- 1 h2tagent h2tagent 1639 Jun 20 18:10 candidate_model.py === final checkpoint summary === keys: ['feat_mean', 'feat_std', 'label_mean', 'label_std', 'state_dict'] feat_mean: [-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std: [0.1422092616558075, 0.2692946195602417, 0.08725058287382126] label_mean: [0.7487966418266296] label_std: [0.38614240288734436] state_dict tensors: 41 total params: 93446
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_ce92dc6240d94754. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_ce92dc6240d94754 · verifier authoritative; classifier explanatory.