tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.Explore app structure
ls -la && echo "---SUBMISSION---" && ls -la submission/ && echo "---DATA---" && ls -la data/ && echo "---run_checks---" && head -50 run_checks.py
total 101 drwxr-xr-x 4 user user 4096 Jun 30 22:28 . drwxr-xr-x 21 root root 4096 Jun 30 22:28 .. -rw-r--r-- 1 user user 14238 Jun 30 22:28 PROMPT.txt -rw-r--r-- 1 user user 5 Jun 30 22:28 agent.pid -rw-r--r-- 1 user user 0 Jun 30 22:28 agent.stderr -rw-r--r-- 1 user user 4605 Jun 30 22:28 agent_trajectory.jsonl drwxr-xr-x 2 user user 60 Jun 30 22:28 data -rw-r--r-- 1 user user 0 Jun 30 22:28 run.log -rwxr-xr-x 1 user user 57740 Jun 30 11:01 run_checks.py drwxr-xr-x 2 user user 128 Jun 30 22:28 submission -rwxr-xr-x 1 user user 998 Jun 30 11:01 test.sh -rwxr-xr-x 1 user user 629 Jun 30 11:01 timer.sh ---SUBMISSION--- total 80 drwxr-xr-x 2 user user 128 Jun 30 22:28 . drwxr-xr-x 4 user user 4096 Jun 30 22:28 .. -rw-r--r-- 1 user user 5490 Jun 30 11:01 advtrain.py -rw-r--r-- 1 user user 8776 Jun 30 11:01 attack.py -rw-r--r-- 1 user user 18341 Jun 30 11:01 autograd.py -rw-r--r-- 1 user user 2167 Jun 30 11:01 data.py -rw-r--r-- 1 user user 15988 Jun 30 11:01 nn.py -rw-r--r-- 1 user user 8853 Jun 30 11:01 optim.py -rw-r--r-- 1 user user 3742 Jun 30 11:01 train.py ---DATA--- total 2904 drwxr-xr-x 2 user user 60 Jun 30 22:28 . drwxr-xr-x 4 user user 4096 Jun 30 22:28 .. -rw-r--r-- 1 user user 2967336 Jun 30 11:01 train.npz ---run_checks--- #!/usr/bin/env python3 """PUBLIC component check runner for the from-scratch numpy framework + the adversarial machinery. Run this AS OFTEN AS YOU LIKE while you implement to get a per-check prog …[truncated 2602 chars]
/app/submission/autograd.py
1 """Reverse-mode automatic differentiation over numpy arrays (IMPLEMENT THIS). 2 3 A ``Tensor`` wraps a single ``numpy.ndarray`` (``float64``) and records the operation that 4 produced it so that ``.backward()`` can walk the graph in reverse-topological order and 5 accumulate ``.grad`` (the gradient of the scalar on which ``.backward()`` was called w.r.t. 6 this tensor) into every leaf. 7 8 This task is ADVERSARIAL ROBUSTNESS: the model is a small convolutional classifier and the 9 attacks (PGD/FGSM/MI-FGSM/PGD-L2/CW) ascend on the gradient of an attack loss w.r.t. the INPUT 10 image, so the autograd engine must (a) flow gradients back to PARAMETERS (for training) AND 11 (b) flow gradients back to the INPUT tensor (for the attack). BOTH directions are exercised by the 12 verifier's finite-difference gradient checks, so the convolution's input-gradient (the ``_col2im`` 13 adjoint) is load-bearing. 14 15 Contract (the verifier's conformance suite differentiates your analytic backward against finite 16 differences and checks your forward against its own reference, so match this exactly): 17 18 * ``Tensor(data, requires_grad=False)`` -- ``data`` is array-like, stored as ``float64`` in 19 ``self.data``; ``self.grad`` starts as ``None`` and becomes a same-shape ``float64`` array after 20 backward. ``requires_grad`` leaves accumulate grad; non-requiring leaves do not. 21 * Every op returns a NEW ``Tensor`` whose ``requires_grad`` is the OR of its differentiable inputs. 22 …[truncated 18629 chars]
/app/submission/nn.py
1 """Neural-network layers + a small convolutional classifier, built on the autograd engine 2 (IMPLEMENT THE FORWARDS). 3 4 Every module exposes ``params()`` -> list[Tensor] (the leaf tensors with ``requires_grad=True``) 5 and ``named_params()`` -> dict[str, Tensor] with STABLE names that match the checkpoint format the 6 verifier loads. ``SmallCNN.state_dict()`` / ``load_state_dict()`` round-trip a plain 7 ``dict[str, np.ndarray]`` (saved as an ``.npz``); the verifier loads YOUR checkpoint into its OWN 8 reference SmallCNN via these names, so the names + shapes below are part of the contract. 9 10 Naming contract for SmallCNN (do not change -- the verifier indexes by these exact keys): 11 conv1.weight (C1, C_in, 3, 3) conv1.bias (C1,) 12 conv2.weight (C2, C1, 3, 3) conv2.bias (C2,) 13 fc1.weight (C2*(H/4)*(W/4), F) fc1.bias (F,) 14 fc2.weight (F, n_classes) fc2.bias (n_classes,) 15 16 Architecture (pinned): for an (N, C_in, H, W) input image batch, 17 conv1 (C_in->C1, 3x3, pad 1) -> relu 18 maxpool 2x2 (H,W -> H/2,W/2) 19 conv2 (C1->C2, 3x3, pad 1) -> relu 20 maxpool 2x2 (H/2,W/2 -> H/4,W/4) 21 flatten -> fc1 (-> F) -> relu -> fc2 (-> n_classes) logits. 22 ``Linear`` computes ``x @ weight + bias`` (weight is (in, out)). No BatchNorm inside SmallCNN (so 23 the attack runs a single deterministic eval-mode forward with no running-stat dependence). 24 25 The other …[truncated 15896 chars]
/app/submission/optim.py
1 """Optimizers + LR schedulers over a flat list of autograd ``Tensor`` parameters (IMPLEMENT THESE). 2 3 Every optimizer operates IN PLACE on ``param.data`` using ``param.grad`` (the gradient accumulated 4 by ``Tensor.backward``). ``zero_grad()`` resets every grad to None. The EXACT update formula each 5 optimizer must match (the verifier checks one and several steps against its own reference) is in 6 its docstring. 7 8 The adversarial-training loop uses AdamW (decoupled decay, with ``no_decay_ndim`` excluding 1-D 9 params/biases) + global grad-norm clipping; the rest of the optimizer zoo + the four LR schedulers 10 are part of the from-scratch framework the conformance suite audits. 11 """ 12 from __future__ import annotations 13 14 import math 15 from typing import List 16 17 import numpy as np 18 19 from autograd import Tensor 20 21 22 class Optimizer: 23 def __init__(self, params: List[Tensor]): 24 self.params = list(params) 25 26 def zero_grad(self): 27 for p in self.params: 28 p.grad = None 29 30 31 class SGD(Optimizer): 32 """SGD + momentum + optional Nesterov + (coupled/L2) weight decay: 33 g = grad + wd*param; v = mu*v + g; step = (g + mu*v) if nesterov else v; param -= lr*step.""" 34 35 def __init__(self, params, lr=0.1, momentum=0.0, weight_decay=0.0, nesterov=False): 36 super().__init__(params) 37 self.lr = lr 38 self.momentum = momentum 39 self.weight_decay = weight …[truncated 8125 chars]
/app/submission/data.py
1 """Data loading + minibatching for the shape-classification set (IMPLEMENT THESE).
2
3 The visible train split ships as ``/app/data/train.npz`` with arrays ``X`` (N, C, H, W) float
4 pixels in [0,1] and ``y`` (N,) int labels. The SEALED held-out split is NOT in the image -- the
5 verifier regenerates it from a secret feature seed and runs its own attack on it.
6
7 Contract:
8 load_npz(path) -> (X float64 (N,C,H,W), y int64 (N,))
9 train_val_split(X, y, val_frac, rng) -> ((Xtr,ytr),(Xval,yval)) random disjoint split (val slice
10 is a held-BACK slice of the VISIBLE train data you may use for best-checkpoint selection --
11 it is NOT the verifier's sealed held-out set)
12 iter_batches(X, y, batch_size, rng) -> yields (xb, yb) random-permutation minibatches
13 get_batch(X, y, batch_size, rng) -> a single random minibatch
14 to_onehot(y, n_classes) -> (N, n_classes) one-hot float matrix
15 clip01(X) -> X clamped into the [0,1] pixel domain
16 """
17 from __future__ import annotations
18
19 from typing import Iterator, Tuple
20
21 import numpy as np
22
23
24 def load_npz(path: str) -> Tuple[np.ndarray, np.ndarray]:
25 raise NotImplementedError("implement load_npz")
26
27
28 def train_val_split(X: np.ndarray, y: np.ndarray, val_frac: float,
29 rng: np.random.Generator):
30 raise NotImplementedError("implement train_val_split")
31
32
33 def iter_batches(X: np.ndarray, y: np.ndarray, batch_size: int,
34
…[truncated 814 chars]/app/submission/attack.py
1 """White-box evasion attacks built on the from-scratch autograd engine (IMPLEMENT THESE). 2 3 An adversary perturbs each input within a norm budget (L-inf eps in the [0,1] pixel domain by 4 default) and tries to make the classifier WRONG. Every attack ascends on the gradient of an attack 5 loss with respect to the INPUT image, which is exactly the gradient the autograd engine produces 6 when the input is wrapped in a ``requires_grad=True`` Tensor and the loss is backpropagated -- so a 7 correct ``conv2d`` input-gradient (the ``_col2im`` adjoint) is load-bearing. 8 9 THE PRIMITIVE 10 input_grad(model, x, y) d/dx of mean cross-entropy of model(x) vs y (ndarray). 11 loss_input_grad(model, x, y, loss_fn) d/dx of an ARBITRARY scalar attack loss loss_fn(logits,y). 12 13 L-INF ATTACKS 14 fgsm(model, x, y, eps) clip(x + eps*sign(input_grad)). 15 pgd_attack(model, x, y, eps, steps, alpha) iterated FGSM with random start + project to the 16 L-inf eps-ball around x AND the [0,1] box each step. 17 mi_fgsm(model, x, y, eps, steps, alpha, mu) momentum-iterative FGSM: accumulate a decaying 18 momentum of the L1-NORMALIZED gradient, step on its sign, 19 project each step. 20 targeted_pgd(model, x, y_target, eps, steps, alpha) DESCEND CE toward y_target (step on the 21 …[truncated 7820 chars]
/app/submission/advtrain.py
1 """Adversarial-training objectives built on the from-scratch autograd engine (IMPLEMENT THESE).
2
3 Several standard recipes turn a fragile classifier into a robust one. Each crafts adversarial
4 inputs per minibatch with the model's own attack, then takes a gradient step on a robustness-aware
5 loss; the recipes differ in the loss. All run a single forward+backward; the caller does grad-clip
6 + the optimizer step. ``x_clean`` / ``x_adv`` are ndarrays (N, C, H, W); ``y`` is an int (N,).
7
8 pgd_at_loss(model, x_adv, y) Madry PGD-AT: CE on the ADVERSARIAL batch.
9 trades_loss(model, x_clean, x_adv, y, beta) TRADES: CE(clean) + beta*KL(stopgrad(clean)||adv).
10 The clean branch INSIDE the KL is a STOP-GRADIENT
11 target (detached): the KL backprops only through
12 the adversarial branch.
13 mart_loss(model, x_clean, x_adv, y, beta) MART: boosted-CE on the adversarial batch
14 ( CE(adv,y) - mean log(1 - max_{j!=y} p_adv_j) ) +
15 beta * mean_i[ (1 - p_clean_i[y_i]) *
16 KL_i(softmax(adv)||stopgrad softmax(clean)) ].
17 alp_loss(model, x_clean, x_adv, y, lam) Adversarial Logit Pairing:
18 0.5*(CE(clean)+CE(ad
…[truncated 4278 chars]/app/submission/train.py
1 """Adversarial-training loop for the small CNN, using the from-scratch numpy framework 2 (IMPLEMENT THIS). 3 4 Standard (clean) training reaches ~99% CLEAN accuracy but ~chance ROBUST accuracy -- it is broken 5 by the verifier's STRONG multi-restart L-inf PGD attack. WEAK robust training is not enough either: 6 single-step (FGSM) adversarial training suffers "catastrophic overfitting" and is shattered by the 7 multi-step / multi-restart eval attack, and adversarial training at a SMALLER perturbation budget 8 than the eval budget leaves the model fragile exactly where it is attacked. The effective recipe is 9 full multi-step PGD ADVERSARIAL TRAINING (Madry et al. 2018) with the perturbation budget MATCHED 10 to the (larger) eval budget: 11 12 * on every minibatch, craft an inner k-step L-inf PGD adversarial batch against the CURRENT model 13 (attack.pgd_attack), at the eps the eval uses, then take the AdamW step on advtrain.pgd_at_loss 14 (cross-entropy on the adversarial batch); 15 * train long enough for robustness to converge (a few epochs is not enough at this eps); 16 * stability details that materially raise the achieved robust accuracy and make it reproducible: 17 (a) exclude biases (1-D params) from weight decay (AdamW no_decay_ndim=1); (b) clip the global 18 gradient norm (the adversarial loss surface is sharp); (c) keep the BEST-by-robust-accuracy 19 checkpoint, evaluated each epoch with the model's own multi-step PGD on a held-BACK slic …[truncated 2473 chars]
/app/run_checks.py
50 TOL_GRAD = 2e-3
51 TOL_PROP = 1e-6
52 TOL_OPT = 1e-7
53 EPS = 1e-5
54 PUBLIC_SEED_BASE = 1000
55
56
57 def _load_module(name, path, inject=None):
58 saved = {}
59 if inject:
60 for k, v in inject.items():
61 saved[k] = sys.modules.get(k)
62 sys.modules[k] = v
63 try:
64 spec = importlib.util.spec_from_file_location(name, str(path))
65 mod = importlib.util.module_from_spec(spec)
66 spec.loader.exec_module(mod)
67 return mod
68 finally:
69 if inject:
70 for k, old in saved.items():
71 if old is None:
72 sys.modules.pop(k, None)
73 else:
74 sys.modules[k] = old
75
76
77 def load_framework(root):
78 ag = _load_module("agent_autograd", root / "autograd.py")
79 nn = _load_module("agent_nn", root / "nn.py", inject={"autograd": ag})
80 opt = _load_module("agent_optim", root / "optim.py", inject={"autograd": ag})
81 data = _load_module("agent_data", root / "data.py")
82 atk = _load_module("agent_attack", root / "attack.py", inject={"autograd": ag})
83 at = _load_module("agent_advtrain", root / "advtrain.py", inject={"autograd": ag})
84 return {"autograd": ag, "nn": nn, "optim": opt, "data": data, "attack": atk, "advtrain": at}
85
86
87 def _num_grad_input(forward, x, eps=EPS):
88 g = np.zeros_like(x)
89 flat = x.reshape(-1)
90 gf = g.reshape(-1)
91 for i in range(
…[truncated 8334 chars]/app/run_checks.py
249
250 def emb_grad():
251 rng = np.random.default_rng(sb + 40)
252 w0 = rng.standard_normal((6, 4)); idx = np.array([[0, 2, 2], [1, 5, 0]])
253 wt = T(w0.copy(), requires_grad=True)
254 ag.sum(ag.embedding(wt, idx)).backward()
255 gnum = _num_grad_input(lambda wv: ag.embedding(T(wv), idx).data, w0.copy())
256 results.append(("grad/embedding", _max_abs(wt.grad, gnum) < TOL_GRAD, ""))
257 guard("embedding", emb_grad)
258
259 def loss_grad(name, build_loss, shape, seed, is_int=False, nc=4):
260 rng = np.random.default_rng(sb + seed)
261 x0 = rng.standard_normal(shape)
262 tgt = rng.integers(0, nc, size=shape[0]) if is_int else rng.standard_normal(shape)
263 lt = T(x0.copy(), requires_grad=True)
264 build_loss(lt, tgt).backward()
265 gnum = _num_grad_input(lambda xv: np.array(float(build_loss(T(xv), tgt).data)), x0.copy())
266 results.append((f"grad/{name}", _max_abs(lt.grad, gnum) < TOL_GRAD, ""))
267 guard("mse_loss", lambda: loss_grad("mse_loss", lambda t, tg: ag.mse_loss(t, tg), (6, 4), 41))
268 guard("cross_entropy", lambda: loss_grad("cross_entropy", lambda t, tg: ag.cross_entropy(t, tg), (6, 7), 42, True, 7))
269
270 def kl_grad(name, wrt_p, seed):
271 rng = np.random.default_rng(sb + seed)
272 p0 = rng.standard_normal((5, 4)); q0 = rng.standard_normal((5, 4))
273 if wrt_p:
274 pt = T(p0.copy(), requires_gr
…[truncated 10980 chars]Search for detach and check sections
grep -n "detach\|stop_grad\|stopgrad\|\.data\b" run_checks.py | head -40; echo "---advtrain section---"; grep -n "check_advtrain\|check_attack\|check_optim\|check_integration\|check_data\|def check" run_checks.py
107: gnum = np.zeros_like(p.data)
108: flat = p.data.reshape(-1)
113: lp = float(loss_now().data)
115: lm = float(loss_now().data)
142: ffun = fdfun or (lambda xv: build(T(xv)).data)
163: case("two_path_accum", lambda t: ag.mul(t, t), (4, 3), 9, fdfun=lambda xv: (T(xv) * T(xv)).data)
208: fdfun=lambda xv: ag.prod(ag.add(ag.mul(T(xv), 0.1), T(np.full((4, 5), 2.0))), axis=1).data)
219: gnum = _num_grad_input(lambda xv: ag.groupnorm(T(xv), gamma, beta, 3).data, x0.copy())
228: gnum = _num_grad_input(lambda xv: ag.rmsnorm(T(xv), gamma).data, x0.copy())
237: gnum = _num_grad_input(lambda xv: ag.layernorm(T(xv), gamma, beta).data, x0.copy())
246: gnum = _num_grad_input(lambda xv: ag.batchnorm(T(xv), gamma, beta).data, x0.copy())
255: gnum = _num_grad_input(lambda wv: ag.embedding(T(wv), idx).data, w0.copy())
265: gnum = _num_grad_input(lambda xv: np.array(float(build_loss(T(xv), tgt).data)), x0.copy())
276: gnum = _num_grad_input(lambda pv: np.array(ag.kl_div(T(pv), T(q0)).data), p0.copy())
281: gnum = _num_grad_input(lambda qv: np.array(ag.kl_div(T(p0), T(qv)).data), q0.copy())
292: gnum = _num_grad_input(lambda xv: ag.conv2d(T(xv), T(W), T(b), pad=pad).data, x.copy())
297: gnum = _num_grad_input(lambda Wv: ag.conv2d(T(x), T(Wv), T(b), pad=pad).data, W.copy())
302: gnum = _num_grad_input(lambda bv: ag.conv2d(T(x), T(W), T(bv), pad=pad).data, b.
…[truncated 2296 chars]/app/run_checks.py
588 def check_optim(fw, sb):
589 ag = fw["autograd"]
590 opt = fw["optim"]
591 T = ag.Tensor
592 results = []
593
594 def mk(seed, shapes):
595 rng = np.random.default_rng(seed)
596 return [T(rng.standard_normal(s), requires_grad=True) for s in shapes]
597
598 def run(name, opt_fn, ref_step, nsteps, gseed, shapes):
599 try:
600 ps = mk(sb + 500, shapes)
601 ref = [p.data.copy() for p in ps]
602 o = opt_fn(ps)
603 st = {}
604 for s in range(nsteps):
605 grng = np.random.default_rng(gseed + s)
606 grads = [grng.standard_normal(p.data.shape) for p in ps]
607 for p, g in zip(ps, grads):
608 p.grad = g.copy()
609 o.step()
610 ref = [ref_step(i, ref[i], grads[i].copy(), s + 1, st) for i in range(len(ps))]
611 err = max(_max_abs(p.data, ref[i]) for i, p in enumerate(ps))
612 results.append((f"optim/{name}", err < TOL_OPT, f"max_abs={err:.1e}"))
613 except Exception as e: # noqa: BLE001
614 results.append((f"optim/{name}", False, f"raised {type(e).__name__}: {e}"))
615
616 def sgd_step(lr, mu, wd, nesterov=False):
617 def f(i, p, g, t, st):
618 key = ("v", i)
619 v = st.get(key, np.zeros_like(p))
620 g = g + wd * p
621 v = mu * v + g
622 st[key] = v
623
…[truncated 16869 chars]/app/run_checks.py
924 def check_advtrain(fw, sb):
925 ag = fw["autograd"]
926 nn = fw["nn"]
927 at = fw["advtrain"]
928 results = []
929
930 def prop(name, fn):
931 try:
932 ok, info = fn()
933 results.append((f"advtrain/{name}", ok, info))
934 except Exception as e: # noqa: BLE001
935 results.append((f"advtrain/{name}", False, f"raised {type(e).__name__}: {e}"))
936
937 rng = np.random.default_rng(sb + 50)
938 Xc = np.clip(rng.random((4, 1, 8, 8)), 0, 1)
939 Xa = np.clip(Xc + rng.uniform(-0.1, 0.1, Xc.shape), 0, 1)
940 y = np.array([0, 1, 2, 3])
941
942 def _m(seed):
943 return nn.SmallCNN(n_classes=4, c_in=1, img=8, c1=6, c2=8, ff=16, seed=sb + seed)
944
945 def unwrap(o):
946 return o[0] if isinstance(o, tuple) else o
947
948 def param_grad(name, make_loss, key, seed):
949 m = _m(seed)
950 for p in m.params():
951 p.grad = None
952 unwrap(make_loss(m)).backward()
953 p = m.named_params()[key]
954 gnum = _param_grad_fd(lambda: unwrap(make_loss(m)), p)
955 return _max_abs(p.grad, gnum) < TOL_GRAD, f"max_abs={_max_abs(p.grad, gnum):.2e}"
956
957 prop("at_param_grad", lambda: param_grad("at", lambda m: at.pgd_at_loss(m, Xa, y), "fc2.weight", 61))
958 prop("trades_param_grad", lambda: param_grad("trades", lambda m: at.trades_loss(m, Xc, Xa, y, beta=6.0), "fc1.weight", 62))
959 prop("mart_param_grad", lam
…[truncated 10158 chars]/app/run_checks.py
466 def softmax_rows(): 467 x = np.random.default_rng(sb + 6).standard_normal((4, 5)) 468 s = ag.softmax(T(x)).data 469 return abs(s.sum(axis=-1) - 1.0).max() < 1e-9 and (s >= 0).all(), "" 470 471 def ce_value(): 472 x = np.random.default_rng(sb + 7).standard_normal((5, 4)); tgt = np.array([0, 1, 2, 3, 0]) 473 logp = x - np.log(np.exp(x - x.max(-1, keepdims=True)).sum(-1, keepdims=True)) - x.max(-1, keepdims=True) 474 ref = float(-logp[np.arange(5), tgt].mean()) 475 return abs(float(ag.cross_entropy(T(x), tgt).data) - ref) < 1e-6, "" 476 477 def kl_nonneg(): 478 x = np.random.default_rng(sb + 8).standard_normal((4, 5)) 479 return float(ag.kl_div(T(x), T(x)).data) < 1e-9, "KL(p||p)==0" 480 481 def layernorm_stats(): 482 x = np.random.default_rng(sb + 9).standard_normal((4, 6)) 483 y = ag.layernorm(T(x), T(np.ones(6)), T(np.zeros(6))).data 484 return abs(y.mean(-1)).max() < 1e-9 and abs(y.std(-1) - 1.0).max() < 1e-4, "" 485 486 def conv_arith(): 487 # a known 1-filter average-ish conv must match a direct numpy correlation on a tiny case. 488 x = np.random.default_rng(sb + 10).standard_normal((2, 1, 5, 5)) 489 W = np.random.default_rng(sb + 11).standard_normal((1, 1, 3, 3)); b = np.zeros(1) 490 out = ag.conv2d(T(x), T(W), T(b), pad=1).data 491 xp = np.pad(x, ((0, 0), (0, 0), (1, 1), (1, 1))) 492 ref = np.zeros((2, …[truncated 4556 chars]
Inspect data and test.sh
python3 -c "
import numpy as np
d=np.load('data/train.npz')
for k in d.files: print(k, d[k].shape, d[k].dtype)
print('y range', d['y'].min(), d['y'].max(), 'n_classes', len(np.unique(d['y'])))
print('X range', d['X'].min(), d['X'].max())
" && echo "---test.sh---" && cat test.shX (3600, 1, 16, 16) float32
y (3600,) int64
y range 0 5 n_classes 6
X range 0.0 1.0
---test.sh---
#!/usr/bin/env bash
# Smoke test (NOT the grader): confirms the environment is wired -- numpy imports, the training
# split loads, and the submission stubs are present. It does NOT check correctness (the stubs raise
# NotImplementedError until you implement them).
set -uo pipefail
python3 - <<'PY'
import numpy as np, os
p = "/app/data/train.npz"
assert os.path.exists(p), "missing train.npz"
d = np.load(p)
print(f"train.npz: X{d['X'].shape} {d['X'].dtype} y{d['y'].shape} classes={len(set(d['y'].tolist()))}")
for f in ("autograd.py", "nn.py", "optim.py", "data.py", "attack.py", "advtrain.py", "train.py"):
assert os.path.exists(f"/app/submission/{f}"), f"missing /app/submission/{f}"
assert os.path.exists("/app/run_checks.py"), "missing /app/run_checks.py (public component runner)"
print("submission stubs present:", sorted(os.listdir("/app/submission")))
print("numpy", np.__version__)
PY
echo "app_test smoke OK (run 'python3 /app/run_checks.py' for the per-check progress report)""""Reverse-mode automatic differentiation over numpy arrays."""
from __future__ import annotations
import math
from typing import Callable, Iterable, List, Optional
import numpy as np
_erf = np.vectorize(math.erf)
_INV_SQRT2 = 1.0 / math.sqrt(2.0)
_INV_SQRT2PI = 1.0 / math.sqrt(2.0 * math.pi)
def _as_f64(x) -> np.ndarray:
return np.asarray(x, dtype=np.float64)
def _unbroadcast(grad: np.ndarray, shape: tuple) -> np.ndarray:
grad = np.asarray(grad, dtype=np.float64)
while grad.ndim > len(shape):
grad = grad.sum(axis=0)
for i, s in enumerate(shape):
if s == 1 and grad.shape[i] != 1:
grad = grad.sum(axis=i, keepdims=True)
return grad
def _reduce_grad(g, a_shape, axis, keepdims):
g = np.asarray(g, dtype=np.float64)
if axis is None:
return np.broadcast_to(g, a_shape).copy()
ax = axis if isinstance(axis, tuple) else (axis,)
ax = tuple(x % len(a_shape) for x in ax)
if not keepdims:
shape = list(a_shape)
for x in ax:
shape[x] = 1
g = g.reshape(shape)
return np.broadcast_to(g, a_shape).copy()
def _count(a_shape, axis):
if axis is None:
n = 1
for s in a_shape:
n *= s
return n
ax = axis if isinstance(axis, tuple) else (axis,)
n = 1
for x in ax:
n *= a_shape[x]
return n
class Tensor:
__slots__ = ("data", "grad", "requires_grad", "_backward", "_parents")
def __init__(self, data, requires_gra
…[truncated 7692 chars]The file /app/submission/autograd.py has been updated successfully. (file state is current in your context , no need to Read it back)
Append reductions/structural ops
cat >> /app/submission/autograd.py <<'PYEOF'
# ===================================================================== reductions / reshape
def sum(a, axis=None, keepdims=False) -> Tensor:
a = _ensure(a)
out = _mk(np.sum(a.data, axis=axis, keepdims=keepdims), (a,), None, a.requires_grad)
def bw():
if a.requires_grad:
a._accum(_reduce_grad(out.grad, a.data.shape, axis, keepdims))
out._backward = bw
return out
def mean(a, axis=None, keepdims=False) -> Tensor:
a = _ensure(a)
out = _mk(np.mean(a.data, axis=axis, keepdims=keepdims), (a,), None, a.requires_grad)
n = _count(a.data.shape, axis)
def bw():
if a.requires_grad:
a._accum(_reduce_grad(out.grad, a.data.shape, axis, keepdims) / n)
out._backward = bw
return out
def max(a, axis=None, keepdims=False) -> Tensor:
a = _ensure(a)
out_keep = np.max(a.data, axis=axis, keepdims=True)
out_data = out_keep if keepdims else np.max(a.data, axis=axis, keepdims=False)
out = _mk(out_data, (a,), None, a.requires_grad)
def bw():
if not a.requires_grad:
return
mask = (a.data == out_keep)
cnt = mask.sum(axis=axis, keepdims=True)
gg = _reduce_grad(out.grad, a.data.shape, axis, keepdims)
a._accum(mask * gg / cnt)
out._backward = bw
return out
def min(a, axis=None, keepdims=False) -> Tensor:
a = _ensure(a)
out_keep = np.min(a.data, axis=axis, keepdims=True)
out_data =
…[truncated 3016 chars]appended reductions parse ok
Append nonlinearities and losses
cat >> /app/submission/autograd.py <<'PYEOF'
# ===================================================================== nonlinearities
def relu(a) -> Tensor:
a = _ensure(a)
out = _mk(np.maximum(a.data, 0.0), (a,), None, a.requires_grad)
def bw():
if a.requires_grad:
a._accum(out.grad * (a.data > 0))
out._backward = bw
return out
def leaky_relu(a, slope: float = 0.01) -> Tensor:
a = _ensure(a)
out = _mk(np.where(a.data > 0, a.data, slope * a.data), (a,), None, a.requires_grad)
def bw():
if a.requires_grad:
a._accum(out.grad * np.where(a.data > 0, 1.0, slope))
out._backward = bw
return out
def sigmoid(a) -> Tensor:
a = _ensure(a)
s = 1.0 / (1.0 + np.exp(-a.data))
out = _mk(s, (a,), None, a.requires_grad)
def bw():
if a.requires_grad:
a._accum(out.grad * s * (1.0 - s))
out._backward = bw
return out
def tanh(a) -> Tensor:
a = _ensure(a)
t = np.tanh(a.data)
out = _mk(t, (a,), None, a.requires_grad)
def bw():
if a.requires_grad:
a._accum(out.grad * (1.0 - t * t))
out._backward = bw
return out
def gelu(a) -> Tensor:
a = _ensure(a)
x = a.data
cdf = 0.5 * (1.0 + _erf(x * _INV_SQRT2))
out = _mk(x * cdf, (a,), None, a.requires_grad)
def bw():
if a.requires_grad:
pdf = _INV_SQRT2PI * np.exp(-0.5 * x * x)
a._accum(out.grad * (cdf + x * pdf))
out._backward =
…[truncated 5376 chars]appended nonlin/losses parse ok
Append norm, conv, pool, extra reductions
cat >> /app/submission/autograd.py <<'PYEOF'
# ===================================================================== normalization
def layernorm(a, gamma, beta, eps: float = 1e-5) -> Tensor:
a = _ensure(a); gamma = _ensure(gamma); beta = _ensure(beta)
x = a.data
mu = x.mean(-1, keepdims=True)
xc = x - mu
var = (xc * xc).mean(-1, keepdims=True)
istd = 1.0 / np.sqrt(var + eps)
xhat = xc * istd
out = _mk(gamma.data * xhat + beta.data, (a, gamma, beta), None,
a.requires_grad or gamma.requires_grad or beta.requires_grad)
def bw():
g = out.grad
axes = tuple(range(g.ndim - 1))
if a.requires_grad:
dxhat = g * gamma.data
dx = istd * (dxhat - dxhat.mean(-1, keepdims=True) - xhat * (dxhat * xhat).mean(-1, keepdims=True))
a._accum(dx)
if gamma.requires_grad:
gamma._accum((g * xhat).sum(axis=axes))
if beta.requires_grad:
beta._accum(g.sum(axis=axes))
out._backward = bw
return out
def batchnorm(a, gamma, beta, eps: float = 1e-5) -> Tensor:
a = _ensure(a); gamma = _ensure(gamma); beta = _ensure(beta)
x = a.data
mu = x.mean(0, keepdims=True)
xc = x - mu
var = (xc * xc).mean(0, keepdims=True)
istd = 1.0 / np.sqrt(var + eps)
xhat = xc * istd
out = _mk(gamma.data * xhat + beta.data, (a, gamma, beta), None,
a.requires_grad or gamma.requires_grad or beta.requires_grad)
def bw():
…[truncated 9920 chars]done parse ok
Implement all nn.py forwards
cd /app/submission && python3 - <<'PY'
import re
src = open('nn.py').read()
repls = [
(''' def forward(self, x: Tensor) -> Tensor:
"""ag.conv2d(x, weight, bias, pad)."""
raise NotImplementedError("implement Conv2d.forward")''',
''' def forward(self, x: Tensor) -> Tensor:
return ag.conv2d(x, self.weight, self.bias, self.pad)'''),
(''' def forward(self, x: Tensor) -> Tensor:
"""ag.maxpool2d(x, k)."""
raise NotImplementedError("implement MaxPool2d.forward")''',
''' def forward(self, x: Tensor) -> Tensor:
return ag.maxpool2d(x, self.k)'''),
(''' def forward(self, x: Tensor) -> Tensor:
"""ag.avgpool2d(x, k)."""
raise NotImplementedError("implement AvgPool2d.forward")''',
''' def forward(self, x: Tensor) -> Tensor:
return ag.avgpool2d(x, self.k)'''),
(''' def forward(self, x: Tensor) -> Tensor:
"""ag.matmul(x, weight) + bias."""
raise NotImplementedError("implement Linear.forward")''',
''' def forward(self, x: Tensor) -> Tensor:
return ag.add(ag.matmul(x, self.weight), self.bias)'''),
(''' def forward(self, x: Tensor) -> Tensor:
"""ag.layernorm(x, weight, bias, eps)."""
raise NotImplementedError("implement LayerNorm.forward")''',
''' def forward(self, x: Tensor) -> Tensor:
return ag.layernorm(x, self.weight, self.bias, self.eps)'''),
(''' def forward(self, x: Tensor) -> Tensor:
"""ag.batchnorm(x, weight, bias, eps).""
…[truncated 4759 chars]nn.py ok
"""Data loading + minibatching for the shape-classification set."""
from __future__ import annotations
from typing import Iterator, Tuple
import numpy as np
def load_npz(path: str) -> Tuple[np.ndarray, np.ndarray]:
d = np.load(path)
X = np.asarray(d["X"], dtype=np.float64)
y = np.asarray(d["y"], dtype=np.int64)
return X, y
def train_val_split(X: np.ndarray, y: np.ndarray, val_frac: float,
rng: np.random.Generator):
n = X.shape[0]
perm = rng.permutation(n)
n_val = int(round(n * val_frac))
val_idx = perm[:n_val]
tr_idx = perm[n_val:]
return (X[tr_idx], y[tr_idx]), (X[val_idx], y[val_idx])
def iter_batches(X: np.ndarray, y: np.ndarray, batch_size: int,
rng: np.random.Generator) -> Iterator[Tuple[np.ndarray, np.ndarray]]:
n = X.shape[0]
perm = rng.permutation(n)
for s in range(0, n, batch_size):
idx = perm[s:s + batch_size]
yield X[idx], y[idx]
def get_batch(X: np.ndarray, y: np.ndarray, batch_size: int,
rng: np.random.Generator) -> Tuple[np.ndarray, np.ndarray]:
n = X.shape[0]
if batch_size <= n:
idx = rng.choice(n, size=batch_size, replace=False)
else:
idx = rng.choice(n, size=batch_size, replace=True)
return X[idx], y[idx]
def to_onehot(y: np.ndarray, n_classes: int) -> np.ndarray:
y = np.asarray(y).astype(int)
oh = np.zeros((y.shape[0], n_classes), dtype=np.float64)
oh[np.arange(y.shape[0]), y] = 1.0
…[truncated 86 chars]The file /app/submission/data.py has been updated successfully. (file state is current in your context , no need to Read it back)
Implement all optimizers and schedulers
python3 - <<'PY'
src = open('optim.py').read()
R = [
(''' def step(self):
raise NotImplementedError("implement SGD.step")''',
''' def step(self):
for i, p in enumerate(self.params):
if p.grad is None:
continue
g = p.grad + self.weight_decay * p.data
self._v[i] = self.momentum * self._v[i] + g
if self.nesterov:
step = g + self.momentum * self._v[i]
else:
step = self._v[i]
p.data -= self.lr * step'''),
(''' def step(self):
raise NotImplementedError("implement Adagrad.step")''',
''' def step(self):
for i, p in enumerate(self.params):
if p.grad is None:
continue
g = p.grad + self.weight_decay * p.data
self._s[i] = self._s[i] + g * g
p.data -= self.lr * g / (np.sqrt(self._s[i]) + self.eps)'''),
(''' def step(self):
raise NotImplementedError("implement RMSprop.step")''',
''' def step(self):
for i, p in enumerate(self.params):
if p.grad is None:
continue
g = p.grad + self.weight_decay * p.data
self._s[i] = self.alpha * self._s[i] + (1 - self.alpha) * (g * g)
p.data -= self.lr * g / (np.sqrt(self._s[i]) + self.eps)'''),
(''' def step(self):
raise NotImplementedError("implement Adam.step")''',
''' def step(self):
self.t += 1
t = self.t
…[truncated 5401 chars]optim ok
Run autograd_grads checks
cd /app && python3 run_checks.py --only autograd_grads 2>&1 | tail -25
grad/conv2d_bias OK grad/conv2d_pad0_input OK (max_abs=7.79e-10) grad/maxpool2d OK grad/avgpool2d OK grad/nn.Conv2d_x OK grad/nn.Linear_x OK grad/nn.LayerNorm_x OK grad/nn.BatchNorm1d_x OK grad/cnn_grad_fc2.bias OK (max_abs=1.21e-11) grad/cnn_grad_conv1.weight OK (max_abs=2.55e-11) grad/cnn_grad_conv2.weight OK (max_abs=2.61e-11) grad/cnn_grad_fc1.weight OK (max_abs=3.00e-11) grad/cnn_input_grad OK (max_abs=2.27e-11) grad/nn.GroupNorm_x OK grad/nn.RMSNorm_x OK grad/rnncell OK (max_abs=1.03e-10) grad/grucell OK (max_abs=1.84e-10) grad/lstmcell OK (max_abs=4.13e-11) grad/rescnn_param OK (max_abs=1.81e-11) grad/rescnn_input OK (max_abs=1.58e-11) ------------------------------------------------------------------------ autograd_grads 82/82 TOTAL 82/82 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks with DIFFERENT seeds/shapes + its OWN strong PGD.
Run nn_forward, optim, data checks
for m in nn_forward optim data; do echo "=== $m ==="; python3 run_checks.py --only $m 2>&1 | tail -3; done
=== nn_forward === nn_forward 22/22 TOTAL 22/22 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks with DIFFERENT seeds/shapes + its OWN strong PGD. === optim === optim 20/20 TOTAL 20/20 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks with DIFFERENT seeds/shapes + its OWN strong PGD. === data === data 3/3 TOTAL 3/3 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks with DIFFERENT seeds/shapes + its OWN strong PGD.
Implement attack.py
python3 - <<'PY'
src = open('attack.py').read()
R = [
(''' """d/dx of a scalar attack loss ``loss_fn(logits, y)`` w.r.t. the input image (ndarray). Wrap x
in a requires_grad=True Tensor, forward, compute loss_fn, backprop, return xt.grad."""
raise NotImplementedError("implement loss_input_grad")''',
''' xt = Tensor(np.asarray(x, dtype=np.float64), requires_grad=True)
loss = loss_fn(model(xt), y)
loss.backward()
return np.asarray(xt.grad, dtype=np.float64)'''),
(''' """d/dx of mean CROSS-ENTROPY of model(x) vs y (the standard attack gradient)."""
raise NotImplementedError("implement input_grad")''',
''' return loss_input_grad(model, x, y, lambda lg, t: ag.cross_entropy(lg, t))'''),
(''' Build it with autograd ops so the input-gradient flows; returns a scalar Tensor."""
raise NotImplementedError("implement cw_margin_loss")''',
''' Build it with autograd ops so the input-gradient flows; returns a scalar Tensor."""
logits = logits if isinstance(logits, Tensor) else Tensor(logits)
N, C = logits.shape
yy = np.asarray(y).astype(int)
onehot = np.zeros((N, C))
onehot[np.arange(N), yy] = 1.0
z_y = ag.sum(ag.mul(logits, Tensor(onehot)), axis=1)
other = ag.max(ag.add(logits, Tensor(onehot * -1e30)), axis=1)
neg_margin = ag.sub(other, z_y)
return ag.mean(ag.maximum(neg_margin, Tensor(np.full(N, -kappa))))'''),
(''' it differentiates exactly and the input-gradient flows; returns a scalar Tensor."""
…[truncated 7984 chars]Exit code 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> FileNotFoundError: [Errno 2] No such file or directory: 'attack.py'
Implement attack.py
cd /app/submission && python3 - <<'PY'
src = open('attack.py').read()
R = [
(''' """d/dx of a scalar attack loss ``loss_fn(logits, y)`` w.r.t. the input image (ndarray). Wrap x
in a requires_grad=True Tensor, forward, compute loss_fn, backprop, return xt.grad."""
raise NotImplementedError("implement loss_input_grad")''',
''' xt = Tensor(np.asarray(x, dtype=np.float64), requires_grad=True)
loss = loss_fn(model(xt), y)
loss.backward()
return np.asarray(xt.grad, dtype=np.float64)'''),
(''' """d/dx of mean CROSS-ENTROPY of model(x) vs y (the standard attack gradient)."""
raise NotImplementedError("implement input_grad")''',
''' """d/dx of mean CROSS-ENTROPY of model(x) vs y (the standard attack gradient)."""
return loss_input_grad(model, x, y, lambda lg, t: ag.cross_entropy(lg, t))'''),
(''' Build it with autograd ops so the input-gradient flows; returns a scalar Tensor."""
raise NotImplementedError("implement cw_margin_loss")''',
''' Build it with autograd ops so the input-gradient flows; returns a scalar Tensor."""
logits = logits if isinstance(logits, Tensor) else Tensor(logits)
N, C = logits.shape
yy = np.asarray(y).astype(int)
onehot = np.zeros((N, C))
onehot[np.arange(N), yy] = 1.0
z_y = ag.sum(ag.mul(logits, Tensor(onehot)), axis=1)
other = ag.max(ag.add(logits, Tensor(onehot * -1e30)), axis=1)
neg_margin = ag.sub(other, z_y)
return ag.mean(ag.maximum(neg_margin, Tensor(np.full(N, -kapp
…[truncated 8092 chars]Exit code 1
Traceback (most recent call last):
File "<stdin>", line 215, in <module>
File "/usr/local/lib/python3.11/ast.py", line 50, in parse
return compile(source, filename, mode, flags,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<unknown>", line 137
project to the L-inf ball + box each step."""
^^
SyntaxError: invalid syntaxFind broken docstring lines
cd /app/submission && grep -n 'project to the L-inf ball + box each step."""\|into the L-2 eps-ball + pixel box each step."""\|step on sign(g_mom); project each step."""' attack.py
137: project to the L-inf ball + box each step.""" 185: into the L-2 eps-ball + pixel box each step.""" 231: g_mom = mu*g_mom + grad/||grad||_1, step on sign(g_mom); project each step.""" 268: """PGD ascending the DLR loss; project to the L-inf ball + box each step."""
Fix broken docstrings in attack.py
python3 - <<'PY'
src=open('attack.py').read()
fixes=[
(' project to the L-inf ball + box each step."""\n lo, hi = clip\n xa = x.copy()\n g_mom = np.zeros_like(x)',
' """Momentum-Iterative FGSM; project to the L-inf ball + box each step."""\n lo, hi = clip\n xa = x.copy()\n g_mom = np.zeros_like(x)'),
(' into the L-2 eps-ball + pixel box each step."""\n lo, hi = clip\n xa = x.copy()',
' """L-2-ball PGD; project the perturbation into the L-2 eps-ball + box each step."""\n lo, hi = clip\n xa = x.copy()'),
(' g_mom = mu*g_mom + grad/||grad||_1, step on sign(g_mom); project each step."""\n lo, hi = clip\n xa = x.copy()\n g_mom = np.zeros_like(x)',
' """Nesterov-Iterative FGSM; project each step."""\n lo, hi = clip\n xa = x.copy()\n g_mom = np.zeros_like(x)'),
]
for o,n in fixes:
assert o in src, "NF "+repr(o[:40])
src=src.replace(o,n)
open('attack.py','w').write(src)
import ast; ast.parse(src); print("attack ok")
PYExit code 1
Traceback (most recent call last):
File "<stdin>", line 14, in <module>
File "/usr/local/lib/python3.11/ast.py", line 50, in parse
return compile(source, filename, mode, flags,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<unknown>", line 268
"""PGD ascending the DLR loss; project to the L-inf ball + box each step."""
^
SyntaxError: unterminated triple-quoted string literal (detected at line 279)View dlr_pgd region
sed -n '255,280p' attack.py
best = xa.copy()
best_loss = losses
else:
better = losses > best_loss
bm = better.reshape((-1,) + (1,) * (xa.ndim - 1))
best = np.where(bm, xa, best)
best_loss = np.where(better, losses, best_loss)
return best
def dlr_pgd(model, x: np.ndarray, y: np.ndarray, eps: float, steps: int, alpha: float,
clip: Tuple[float, float] = (0.0, 1.0),
rng: Optional[np.random.Generator] = None) -> np.ndarray:
"""PGD ascending the DLR loss; project to the L-inf ball + box each step."""
lo, hi = clip
lf = lambda lg, t: dlr_loss(lg, t)
if rng is not None:
xa = x + rng.uniform(-eps, eps, size=x.shape)
else:
xa = x.copy()
xa = project_linf(xa, x, eps, lo, hi)
for _ in range(steps):
g = loss_input_grad(model, xa, y, lf)
xa = project_linf(xa + alpha * np.sign(g), x, eps, lo, hi)
return xaReparse attack.py
python3 -c "import ast; ast.parse(open('attack.py').read()); print('attack ok')"Exit code 1
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/local/lib/python3.11/ast.py", line 50, in parse
return compile(source, filename, mode, flags,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<unknown>", line 268
"""PGD ascending the DLR loss; project to the L-inf ball + box each step."""
^
SyntaxError: unterminated triple-quoted string literal (detected at line 279)View suspicious regions
sed -n '128,140p;180,192p;225,240p' attack.py
for _ in range(steps):
g = input_grad(model, xa, y)
xa = project_linf(xa + alpha * np.sign(g), center, eps, lo, hi)
return xa
def mi_fgsm(model, x: np.ndarray, y: np.ndarray, eps: float, steps: int, alpha: float,
mu: float = 1.0, clip: Tuple[float, float] = (0.0, 1.0),
rng: Optional[np.random.Generator] = None) -> np.ndarray:
"""Momentum-Iterative FGSM; project to the L-inf ball + box each step."""
lo, hi = clip
xa = x.copy()
g_mom = np.zeros_like(x)
def pgd_l2(model, x: np.ndarray, y: np.ndarray, eps: float, steps: int, alpha: float,
clip: Tuple[float, float] = (0.0, 1.0),
rng: Optional[np.random.Generator] = None) -> np.ndarray:
"""L-2-ball PGD; project the perturbation into the L-2 eps-ball + box each step."""
lo, hi = clip
xa = x.copy()
for _ in range(steps):
g = input_grad(model, xa, y)
gflat = g.reshape(g.shape[0], -1)
gnorm = np.linalg.norm(gflat, axis=1, keepdims=True)
gn = (gflat / (gnorm + 1e-12)).reshape(g.shape)
def ni_fgsm(model, x: np.ndarray, y: np.ndarray, eps: float, steps: int, alpha: float,
mu: float = 1.0, clip: Tuple[float, float] = (0.0, 1.0),
rng: Optional[np.random.Generator] = None) -> np.ndarray:
"""Nesterov-Iterative FGSM: look ahead (x_nes = x_adv + alpha*mu*g_mom) before the gradient,
"""Nesterov-Iterative FGSM; project each step."""
lo, hi = clip
xa = x.copy()
g_mom
…[truncated 305 chars]"""White-box evasion attacks built on the from-scratch autograd engine."""
from __future__ import annotations
from typing import Callable, Optional, Tuple
import numpy as np
import autograd as ag
from autograd import Tensor
def loss_input_grad(model, x: np.ndarray, y: np.ndarray,
loss_fn: Callable[[Tensor, np.ndarray], Tensor]) -> np.ndarray:
xt = Tensor(np.asarray(x, dtype=np.float64), requires_grad=True)
loss = loss_fn(model(xt), y)
loss.backward()
return np.asarray(xt.grad, dtype=np.float64)
def input_grad(model, x: np.ndarray, y: np.ndarray) -> np.ndarray:
return loss_input_grad(model, x, y, lambda lg, t: ag.cross_entropy(lg, t))
def cw_margin_loss(logits, y, kappa: float = 0.0) -> Tensor:
logits = logits if isinstance(logits, Tensor) else Tensor(logits)
N, C = logits.shape
yy = np.asarray(y).astype(int)
onehot = np.zeros((N, C))
onehot[np.arange(N), yy] = 1.0
z_y = ag.sum(ag.mul(logits, Tensor(onehot)), axis=1)
other = ag.max(ag.add(logits, Tensor(onehot * -1e30)), axis=1)
neg_margin = ag.sub(other, z_y)
return ag.mean(ag.maximum(neg_margin, Tensor(np.full(N, -kappa))))
def dlr_loss(logits, y, eps: float = 1e-12) -> Tensor:
logits = logits if isinstance(logits, Tensor) else Tensor(logits)
N, C = logits.shape
yy = np.asarray(y).astype(int)
onehot = np.zeros((N, C))
onehot[np.arange(N), yy] = 1.0
z_y = ag.sum(ag.mul(logits, Tensor(onehot)), axis=1)
other = ag.
…[truncated 6757 chars]<tool_use_error>File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.</tool_use_error>
/app/submission/attack.py
1 """White-box evasion attacks built on the from-scratch autograd engine (IMPLEMENT THESE). 2 3 An adversary perturbs each input within a norm budget (L-inf eps in the [0,1] pixel domain by 4 default) and tries to make the classifier WRONG. Every attack ascends on the gradient of an attack 5 loss with respect to the INPUT image, which is exactly the gradient the autograd engine produces
"""White-box evasion attacks built on the from-scratch autograd engine."""
from __future__ import annotations
from typing import Callable, Optional, Tuple
import numpy as np
import autograd as ag
from autograd import Tensor
def loss_input_grad(model, x: np.ndarray, y: np.ndarray,
loss_fn: Callable[[Tensor, np.ndarray], Tensor]) -> np.ndarray:
xt = Tensor(np.asarray(x, dtype=np.float64), requires_grad=True)
loss = loss_fn(model(xt), y)
loss.backward()
return np.asarray(xt.grad, dtype=np.float64)
def input_grad(model, x: np.ndarray, y: np.ndarray) -> np.ndarray:
return loss_input_grad(model, x, y, lambda lg, t: ag.cross_entropy(lg, t))
def cw_margin_loss(logits, y, kappa: float = 0.0) -> Tensor:
logits = logits if isinstance(logits, Tensor) else Tensor(logits)
N, C = logits.shape
yy = np.asarray(y).astype(int)
onehot = np.zeros((N, C))
onehot[np.arange(N), yy] = 1.0
z_y = ag.sum(ag.mul(logits, Tensor(onehot)), axis=1)
other = ag.max(ag.add(logits, Tensor(onehot * -1e30)), axis=1)
neg_margin = ag.sub(other, z_y)
return ag.mean(ag.maximum(neg_margin, Tensor(np.full(N, -kappa))))
def dlr_loss(logits, y, eps: float = 1e-12) -> Tensor:
logits = logits if isinstance(logits, Tensor) else Tensor(logits)
N, C = logits.shape
yy = np.asarray(y).astype(int)
onehot = np.zeros((N, C))
onehot[np.arange(N), yy] = 1.0
z_y = ag.sum(ag.mul(logits, Tensor(onehot)), axis=1)
other = ag.
…[truncated 6757 chars]The file /app/submission/attack.py has been updated successfully. (file state is current in your context , no need to Read it back)
Run attack checks
cd /app && python3 run_checks.py --only attack 2>&1 | tail -25
[attack] 18/18 attack/input_grad_fd OK (max_abs=2.73e-11) attack/cw_loss_input_grad_fd OK (max_abs=4.13e-12) attack/dlr_loss_grad OK (max_abs=1.04e-09) attack/dlr_loss_input_grad_fd OK (max_abs=3.51e-11) attack/fgsm_step OK attack/pgd_projection OK attack/mi_fgsm_projection OK attack/cw_pgd_projection OK attack/pgd_l2_projection OK attack/bim_projection OK attack/rfgsm_projection OK attack/ni_fgsm_projection OK attack/pgd_restarts_projection OK attack/dlr_pgd_projection OK attack/pgd_stronger_than_fgsm OK (fgsm=1.450 pgd=1.469) attack/bim_stronger_than_fgsm OK (fgsm=1.411 bim=1.423) attack/pgd_restarts_worst_case OK (single=1.521 restarts=1.525) attack/targeted_pgd_lowers_target_ce OK (1.388->1.333) ------------------------------------------------------------------------ attack 18/18 TOTAL 18/18 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks with DIFFERENT seeds/shapes + its OWN strong PGD.
"""Adversarial-training objectives built on the from-scratch autograd engine."""
from __future__ import annotations
from typing import Dict, Tuple
import numpy as np
import autograd as ag
from autograd import Tensor
def _onehot(y, C):
y = np.asarray(y).astype(int)
oh = np.zeros((y.shape[0], C))
oh[np.arange(y.shape[0]), y] = 1.0
return oh
def pgd_at_loss(model, x_adv: np.ndarray, y: np.ndarray) -> Tensor:
return ag.cross_entropy(model(Tensor(np.asarray(x_adv, dtype=np.float64))), y)
def trades_loss(model, x_clean: np.ndarray, x_adv: np.ndarray, y: np.ndarray,
beta: float = 6.0) -> Tuple[Tensor, Dict[str, float]]:
cl = model(Tensor(np.asarray(x_clean, dtype=np.float64)))
ad = model(Tensor(np.asarray(x_adv, dtype=np.float64)))
ce = ag.cross_entropy(cl, y)
tgt = Tensor(cl.data.copy())
kl = ag.kl_div(tgt, ad)
loss = ag.add(ce, ag.mul(kl, float(beta)))
return loss, {"ce": float(ce.data), "kl": float(kl.data), "loss": float(loss.data)}
def mart_loss(model, x_clean: np.ndarray, x_adv: np.ndarray, y: np.ndarray,
beta: float = 5.0) -> Tuple[Tensor, Dict[str, float]]:
cl = model(Tensor(np.asarray(x_clean, dtype=np.float64)))
ad = model(Tensor(np.asarray(x_adv, dtype=np.float64)))
N, C = ad.shape
oh = _onehot(y, C)
ce = ag.cross_entropy(ad, y)
p_adv = ag.softmax(ad, axis=1)
other_p = ag.max(ag.add(p_adv, Tensor(oh * -1e30)), axis=1)
boost = ag.mul(ag.mean(ag.log(ag.su
…[truncated 3848 chars]The file /app/submission/advtrain.py has been updated successfully. (file state is current in your context , no need to Read it back)
Run advtrain and integration checks
python3 run_checks.py --only advtrain 2>&1 | tail -25; echo "=== integration ==="; python3 run_checks.py --only integration 2>&1 | tail -5
[advtrain] 14/14 advtrain/at_param_grad OK (max_abs=2.56e-11) advtrain/trades_param_grad OK (max_abs=5.66e-04) advtrain/mart_param_grad OK (max_abs=1.14e-04) advtrain/alp_param_grad OK (max_abs=2.60e-11) advtrain/robust_ce_param_grad OK (max_abs=5.12e-11) advtrain/clp_param_grad OK (max_abs=2.84e-05) advtrain/kl_at_param_grad OK (max_abs=1.62e-03) advtrain/logit_squeeze_param_grad OK (max_abs=4.80e-11) advtrain/ls_at_param_grad OK (max_abs=2.76e-11) advtrain/label_smoothed_ce_grad OK advtrain/trades_stopgrad OK (max_abs=0.00e+00) advtrain/alp_pairing OK (max_abs=0.00e+00) advtrain/clp_stopgrad OK (max_abs=0.00e+00) advtrain/kl_at_stopgrad OK (max_abs=0.00e+00) ------------------------------------------------------------------------ advtrain 14/14 TOTAL 14/14 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks with DIFFERENT seeds/shapes + its OWN strong PGD. === integration === ------------------------------------------------------------------------ integration 4/4 TOTAL 4/4 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks w …[truncated 48 chars]
Run full check suite
python3 run_checks.py 2>&1 | tail -15
[integration] 4/4 integration/pgd_at_finite OK (1.425->1.389) integration/pgd_at_decreases OK (1.4247->1.3885) integration/trades_finite OK (1.393->1.337) integration/trades_decreases OK (1.3933->1.3367) [data] 3/3 data/get_batch_shape OK ((16, 1, 8, 8),(16,)) data/train_val_split OK (30/10) data/to_onehot OK ------------------------------------------------------------------------ autograd_grads 82/82 | nn_forward 22/22 | optim 20/20 | attack 18/18 | advtrain 14/14 | integration 4/4 | data 3/3 TOTAL 163/163 public checks passing All public checks pass. Now make sure model.npz clears the held-out ROBUST-accuracy threshold; the grader re-checks with DIFFERENT seeds/shapes + its OWN strong PGD.
/app/submission/train.py
def train(train_path, out_path, epochs=12, batch_size=128, lr=1.2e-3, eps=0.12,
at_steps=7, alpha=0.03, grad_clip=1.0, weight_decay=5e-4, val_frac=0.15,
sel_steps=7, seed=0, log_every=1):
"""Run multi-step PGD adversarial training and save the best-by-robust-val checkpoint to
``out_path``. See the module docstring for the recipe."""
raise NotImplementedError("implement the adversarial-training loop")def _robust_acc(model, X, y, eps, steps, alpha, restarts, rng, batch=256):
n = X.shape[0]
robust = np.zeros(n, dtype=bool)
clean_ok = np.zeros(n, dtype=bool)
for s in range(0, n, batch):
xb = X[s:s + batch]; yb = y[s:s + batch]
# clean prediction
pred = np.argmax(model(xb).data, axis=1)
ok = (pred == yb)
surv = ok.copy()
for r in range(restarts):
xa = A.pgd_attack(model, xb, yb, eps, steps, alpha, rng=rng)
pa = np.argmax(model(xa).data, axis=1)
surv = surv & (pa == yb)
clean_ok[s:s + batch] = ok
robust[s:s + batch] = surv
return float(robust.mean()), float(clean_ok.mean())
def train(train_path, out_path, epochs=24, batch_size=128, lr=1e-3, eps=0.12,
at_steps=7, alpha=0.03, grad_clip=1.0, weight_decay=5e-4, val_frac=0.1,
sel_steps=10, seed=0, log_every=1):
"""Run multi-step PGD adversarial training and save the best-by-robust-val checkpoint."""
rng = np.random.default_rng(seed)
X, y = D.load_npz(train_path)
n_classes = int(y.max()) + 1
c_in = X.shape[1]
img = X.shape[2]
(Xtr, ytr), (Xval, yval) = D.train_val_split(X, y, val_frac, rng)
model = SmallCNN(n_classes=n_classes, c_in=c_in, img=img, c1=C1, c2=C2, ff=FF, seed=seed)
opt = O.AdamW(model.params(), lr=lr, betas=(0.9, 0.999),
weight_decay=weight_decay, no_decay_ndim=1)
total_steps = epochs * int(np.ceil(len(Xtr) / batch_size)
…[truncated 1397 chars]The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
Benchmark training step speed
cd /app/submission && python3 - <<'PY'
import time, numpy as np
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
rng=np.random.default_rng(0)
xb,yb=X[:128],y[:128]
t=time.time()
for s in range(5):
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(s))
loss=AT.pgd_at_loss(m,xa,yb)
opt.zero_grad(); loss.backward(); O.clip_grad_norm(m.params(),1.0); opt.step()
dt=(time.time()-t)/5
print(f"per-step {dt*1000:.1f} ms ; est per-epoch(28 batches) {dt*28:.1f}s ; 24 epochs {dt*28*24/60:.1f} min")
PYExit code 137
/bin/bash: line 33: 1561 Killed python3 - <<'PY'
import time, numpy as np
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
rng=np.random.default_rng(0)
xb,yb=X[:128],y[:128]
t=time.time()
for s in range(5):
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(s))
loss=AT.pgd_at_loss(m,xa,yb)
opt.zero_grad(); loss.backward(); O.clip_grad_norm(m.params(),1.0); opt.step()
dt=(time.time()-t)/5
print(f"per-step {dt*1000:.1f} ms ; est per-epoch(28 batches) {dt*28:.1f}s ; 24 epochs {dt*28*24/60:.1f} min")
PYTest single input_grad memory and time
cd /app/submission && free -m; ulimit -v; python3 - <<'PY'
import time, numpy as np, resource
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
xb,yb=X[:64].copy(),y[:64].copy()
t=time.time()
g=A.input_grad(m,xb,yb)
print("one input_grad ms", (time.time()-t)*1000)
print("maxrss MB", resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024)
PYtotal used free shared buff/cache available Mem: 481 238 190 1 66 243 Swap: 0 0 0 unlimited one input_grad ms 72.94535636901855 maxrss MB 64.49609375
Benchmark full step at batch 64
python3 - <<'PY'
import time, numpy as np, resource, gc
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
t=time.time()
for s in range(6):
xb,yb=X[s*64:(s+1)*64].copy(),y[s*64:(s+1)*64].copy()
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(s))
loss=AT.pgd_at_loss(m,xa,yb)
opt.zero_grad(); loss.backward(); O.clip_grad_norm(m.params(),1.0); opt.step()
gc.collect()
dt=(time.time()-t)/6
print(f"per-step {dt*1000:.0f}ms epoch(56) {dt*56:.0f}s 20ep {dt*56*20/60:.1f}min maxrss {resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024:.0f}MB")
PYper-step 457ms epoch(56) 26s 20ep 8.5min maxrss 160MB
/app/submission/train.py
import argparse import json import os import sys
import argparse import gc import json import os import sys
The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
/app/submission/train.py
def train(train_path, out_path, epochs=24, batch_size=128, lr=1e-3, eps=0.12,
at_steps=7, alpha=0.03, grad_clip=1.0, weight_decay=5e-4, val_frac=0.1,
sel_steps=10, seed=0, log_every=1):def train(train_path, out_path, epochs=22, batch_size=64, lr=1e-3, eps=0.12,
at_steps=7, alpha=0.03, grad_clip=1.0, weight_decay=5e-4, val_frac=0.1,
sel_steps=10, seed=0, log_every=1):The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
/app/submission/train.py
O.clip_grad_norm(model.params(), grad_clip)
opt.step()
step += 1 O.clip_grad_norm(model.params(), grad_clip)
opt.step()
step += 1
if step % 8 == 0:
gc.collect()The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
/app/submission/train.py
def _robust_acc(model, X, y, eps, steps, alpha, restarts, rng, batch=256):
def _robust_acc(model, X, y, eps, steps, alpha, restarts, rng, batch=64):
The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
Launch training in background
nohup python3 train.py --train /app/data/train.npz --out /app/submission/model.npz > /app/submission/train.log 2>&1 & echo "launched PID $!"; sleep 60; echo "--- log so far ---"; cat /app/submission/train.log
launched PID 1581 /bin/bash: line 3: 1581 Killed nohup python3 train.py --train /app/data/train.npz --out /app/submission/model.npz > /app/submission/train.log 2>&1 --- log so far ---
Track memory over one epoch foreground
python3 - <<'PY'
import numpy as np, resource, gc, time
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
rng=np.random.default_rng(0)
(Xtr,ytr),(Xv,yv)=D.train_val_split(X,y,0.1,rng)
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
t=time.time()
for i,(xb,yb) in enumerate(D.iter_batches(Xtr,ytr,64,rng)):
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(i))
loss=AT.pgd_at_loss(m,xa,yb); opt.zero_grad(); loss.backward()
O.clip_grad_norm(m.params(),1.0); opt.step()
if i%8==0: gc.collect()
if i%10==0:
print(i, "rss MB", resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024, "t", round(time.time()-t,1), flush=True)
print("done one epoch", round(time.time()-t,1))
PYExit code 137
0 rss MB 145.828125 t 0.5
/bin/bash: line 37: 1592 Killed python3 - <<'PY'
import numpy as np, resource, gc, time
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
rng=np.random.default_rng(0)
(Xtr,ytr),(Xv,yv)=D.train_val_split(X,y,0.1,rng)
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
t=time.time()
for i,(xb,yb) in enumerate(D.iter_batches(Xtr,ytr,64,rng)):
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(i))
loss=AT.pgd_at_loss(m,xa,yb); opt.zero_grad(); loss.backward()
O.clip_grad_norm(m.params(),1.0); opt.step()
if i%8==0: gc.collect()
if i%10==0:
print(i, "rss MB", resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024, "t", round(time.time()-t,1), flush=True)
print("done one epoch", round(time.time()-t,1))
PY/app/submission/autograd.py
build(self)
if grad is None:
grad = np.ones_like(self.data)
self._accum(_as_f64(grad))
for node in reversed(topo):
if node.grad is not None:
node._backward() build(self)
if grad is None:
grad = np.ones_like(self.data)
self._accum(_as_f64(grad))
for node in reversed(topo):
if node.grad is not None:
node._backward()
# break the closure<->tensor reference cycles so the graph frees by refcount
# immediately (memory is tight); grads on leaves are preserved.
for node in topo:
node._backward = _NOOP
node._parents = ()<tool_use_error>File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.</tool_use_error>
/app/submission/autograd.py
84 def backward(self, grad: Optional[np.ndarray] = None): 85 topo: List[Tensor] = [] 86 visited = set() 87 88 def build(t): 89 if id(t) in visited: 90 return 91 visited.add(id(t)) 92 for p in t._parents: 93 build(p) 94 topo.append(t) 95 96 build(self) 97 if grad is None: 98 grad = np.ones_like(self.data) 99 self._accum(_as_f64(grad)) 100 for node in reversed(topo): 101 if node.grad is not None: 102 node._backward() 103
/app/submission/autograd.py
self._accum(_as_f64(grad))
for node in reversed(topo):
if node.grad is not None:
node._backward()
self._accum(_as_f64(grad))
for node in reversed(topo):
if node.grad is not None:
node._backward()
# break closure<->tensor reference cycles so the graph frees by refcount
# immediately (memory is tight); leaf grads are preserved.
for node in topo:
node._backward = _NOOP
node._parents = ()
The file /app/submission/autograd.py has been updated successfully. (file state is current in your context , no need to Read it back)
/app/submission/autograd.py
def _as_f64(x) -> np.ndarray:
return np.asarray(x, dtype=np.float64)def _NOOP():
return None
def _as_f64(x) -> np.ndarray:
return np.asarray(x, dtype=np.float64)The file /app/submission/autograd.py has been updated successfully. (file state is current in your context , no need to Read it back)
Re-test memory after cycle fix
python3 - <<'PY'
import numpy as np, resource, time
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
rng=np.random.default_rng(0)
(Xtr,ytr),(Xv,yv)=D.train_val_split(X,y,0.1,rng)
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
t=time.time()
for i,(xb,yb) in enumerate(D.iter_batches(Xtr,ytr,64,rng)):
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(i))
loss=AT.pgd_at_loss(m,xa,yb); opt.zero_grad(); loss.backward()
O.clip_grad_norm(m.params(),1.0); opt.step()
if i%15==0: print(i,"rss MB",round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024),"t",round(time.time()-t,1),flush=True)
print("epoch done",round(time.time()-t,1),"maxrss",round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024))
PYExit code 137
0 rss MB 162 t 0.5
/bin/bash: line 33: 1600 Killed python3 - <<'PY'
import numpy as np, resource, time
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
X,y=D.load_npz('/app/data/train.npz')
rng=np.random.default_rng(0)
(Xtr,ytr),(Xv,yv)=D.train_val_split(X,y,0.1,rng)
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
t=time.time()
for i,(xb,yb) in enumerate(D.iter_batches(Xtr,ytr,64,rng)):
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(i))
loss=AT.pgd_at_loss(m,xa,yb); opt.zero_grad(); loss.backward()
O.clip_grad_norm(m.params(),1.0); opt.step()
if i%15==0: print(i,"rss MB",round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024),"t",round(time.time()-t,1),flush=True)
print("epoch done",round(time.time()-t,1),"maxrss",round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024))
PYMeasure per-step RSS with gc, batch 32
python3 - <<'PY'
import numpy as np, gc, time, os
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
def rss():
return int(open('/proc/self/statm').read().split()[1])*4096//1024//1024
X,y=D.load_npz('/app/data/train.npz')
rng=np.random.default_rng(0)
(Xtr,ytr),(Xv,yv)=D.train_val_split(X,y,0.1,rng)
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
print("start rss",rss())
for i in range(20):
xb,yb=Xtr[i*32:(i+1)*32],ytr[i*32:(i+1)*32]
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(i))
loss=AT.pgd_at_loss(m,xa,yb); opt.zero_grad(); loss.backward()
O.clip_grad_norm(m.params(),1.0); opt.step()
gc.collect()
print(i,"rss",rss(),flush=True)
PYstart rss 47 0 rss 95 1 rss 104 2 rss 105 3 rss 105 4 rss 105 5 rss 105 6 rss 105 7 rss 105 8 rss 105 9 rss 105 10 rss 105 11 rss 105 12 rss 105 13 rss 105 14 rss 105 15 rss 105 16 rss 105 17 rss 105 18 rss 105 19 rss 105
Verify batch 64 + gc every step
python3 - <<'PY'
import numpy as np, gc, time
import data as D, optim as O, attack as A, advtrain as AT
from nn import SmallCNN
def rss(): return int(open('/proc/self/statm').read().split()[1])*4096//1024//1024
X,y=D.load_npz('/app/data/train.npz')
rng=np.random.default_rng(0)
(Xtr,ytr),(Xv,yv)=D.train_val_split(X,y,0.1,rng)
m=SmallCNN(n_classes=6,c_in=1,img=16,c1=14,c2=28,ff=96,seed=0)
opt=O.AdamW(m.params(),lr=1e-3,weight_decay=5e-4,no_decay_ndim=1)
t=time.time(); mx=0
for i in range(30):
xb,yb=Xtr[i*64:(i+1)*64],ytr[i*64:(i+1)*64]
xa=A.pgd_attack(m,xb,yb,0.12,7,0.03,rng=np.random.default_rng(i))
loss=AT.pgd_at_loss(m,xa,yb); opt.zero_grad(); loss.backward()
O.clip_grad_norm(m.params(),1.0); opt.step(); gc.collect(); mx=max(mx,rss())
print("30 steps t",round(time.time()-t,1),"max rss",mx,"per-step ms",round((time.time()-t)/30*1000))
PY30 steps t 13.8 max rss 164 per-step ms 460
/app/submission/train.py
opt.step()
step += 1
if step % 8 == 0:
gc.collect() opt.step()
step += 1
gc.collect()The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
/app/submission/train.py
for r in range(restarts):
xa = A.pgd_attack(model, xb, yb, eps, steps, alpha, rng=rng)
pa = np.argmax(model(xa).data, axis=1)
surv = surv & (pa == yb) for r in range(restarts):
xa = A.pgd_attack(model, xb, yb, eps, steps, alpha, rng=rng)
pa = np.argmax(model(xa).data, axis=1)
surv = surv & (pa == yb)
gc.collect()The file /app/submission/train.py has been updated successfully. (file state is current in your context , no need to Read it back)
Set alpha=0.04 and verify
sed -i 's/at_steps=7, alpha=0.03, grad_clip/at_steps=7, alpha=0.04, grad_clip/' train.py && sed -i 's/ap.add_argument("--alpha", type=float, default=0.03)/ap.add_argument("--alpha", type=float, default=0.04)/' train.py && grep -n "alpha=0.04\|default=0.04" train.py && python3 -c "import ast;ast.parse(open('train.py').read());print('ok')"71: at_steps=7, alpha=0.04, grad_clip=1.0, weight_decay=5e-4, val_frac=0.1,
132: ap.add_argument("--alpha", type=float, default=0.04)
okLaunch 22-epoch training in background
nohup python3 train.py --epochs 22 --out /app/submission/model.npz > /app/submission/train.log 2>&1 & echo "PID $!"; sleep 90; echo "--- log ---"; cat /app/submission/train.log
PID 1681 --- log --- epoch 0 lr=0.0009 loss=1.8317 val_clean=0.522 val_robust=0.203 best=0.203 epoch 1 lr=0.0010 loss=1.0487 val_clean=0.678 val_robust=0.525 best=0.525
Wait and check training progress
sleep 240; tail -12 /app/submission/train.log
<tool_use_error>Blocked: sleep 240 followed by: tail -12 /app/submission/train.log. To wait for a condition, use Monitor with an until-loop (e.g. `until <check>; do sleep 2; done`). To wait for a command you started, use run_in_background: true. Do not chain shorter sleeps to work around this block.</tool_use_error>
Wait for training to finish
until ! kill -0 1681 2>/dev/null; do sleep 10; done; echo "=== training finished ==="; tail -30 /app/submission/train.log
No file changes, the agent left the workspace untouched.
=== SyncVals verifier (local re-grade of the agent's own checkpoint) === COMPONENT CONFORMANCE SUITE: 201/201 cases passed (all milestones pass: True) FINAL METRIC: PGD robust accuracy = 0.6625 >= threshold 0.6 REWARD = 1 (1 iff every component milestone passes AND the sealed held-out metric clears its threshold) NOTE: the in-sandbox training in the 481MB e2b box was OOM-killed (0-byte checkpoint), so the raw in-sandbox reward was an infrastructure artifact. This reward is from a faithful LOCAL re-train of the agent's OWN train.py (24GB, no OOM) with its own default hyperparameters, then re-graded by the real verifier.
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_247628a24e2749a5. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_247628a24e2749a5 · verifier authoritative; classifier explanatory.