import os, sys, json, random import numpy as np import torch import torch.nn as nn sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, make_model, train_model, sweep_baseline, evaluate, make_report SEEDS=tuple(range(8)); SWEEP=tuple(range(4)) # Three shared learning-rate/strength settings: baseline and idea see identical settings. GRID=[{'lr':1e-3,'coef':0.03},{'lr':3e-3,'coef':0.10},{'lr':1e-2,'coef':0.30}] EPOCHS=5; BATCH=128; NTR=400; NTE=200 def seed_all(s): random.seed(s); np.random.seed(s); torch.manual_seed(s) if torch.cuda.is_available(): torch.cuda.manual_seed_all(s) class FeatureCNN(nn.Module): def __init__(self): super().__init__() self.c1=nn.Conv2d(3,32,3,padding=1); self.c2=nn.Conv2d(32,64,3,padding=1); self.c3=nn.Conv2d(64,96,3,padding=1) self.pool=nn.MaxPool2d(2); self.fc=nn.Linear(96*4*4,128); self.out=nn.Linear(128,10) def forward(self,x,features=False): h1=self.pool(torch.relu(self.c1(x))); h2=self.pool(torch.relu(self.c2(h1))); h3=self.pool(torch.relu(self.c3(h2))) z=torch.relu(self.fc(h3.flatten(1))); y=self.out(z) return (y,h2) if features else y def persistence_events(h): # Differentiable cubical-filtration surrogate: each channel's activation # distribution gives (birth=min, death=max), persistence is its range. # This is a cheap neural prototype of 0D persistence events. flat=h.flatten(2); birth=flat.amin(2); death=flat.amax(2) pts=torch.stack((birth,death),-1) w=(death-birth).clamp_min(1e-6) return pts,w def pce_loss(teacher_h, student_h, sigma=.18, eps=1e-7): # Teacher detached; unequal event counts are supported by max over student events. t,wt=persistence_events(teacher_h.detach()); s,_=persistence_events(student_h) p=wt/(wt.sum(1,keepdim=True)+eps) d2=((t[:,:,None,:]-s[:,None,:,:])**2).sum(-1) a=torch.exp(-d2/(2*sigma*sigma)).amax(2) loss=-(p*torch.log(p*a+eps)).sum(1).mean() entropy=-(p*torch.log(p+eps)).sum(1).mean() unexpl=1-(p*a).sum(1).mean() return loss,entropy,unexpl def train_pair(seed,cfg,method, return_sig=False): seed_all(seed); d=get_dataset('vision',seed,n_train=NTR,n_test=NTE) # Teacher is trained once per seed using standard bench path, then frozen. teacher=FeatureCNN() teacher,_,_=train_model(teacher,d,epochs=EPOCHS,lr=3e-3,batch=BATCH) teacher=teacher.eval() for p in teacher.parameters(): p.requires_grad=False seed_all(seed+10000); student=FeatureCNN() try: device='cpu'; student=student.to(device); teacher=teacher.to(device) except Exception: device='cpu'; student=student.cpu(); teacher=teacher.cpu() x,y=d['xtr'].to(device),d['ytr'].to(device); xt,yt=d['xte'].to(device),d['yte'].to(device) opt=torch.optim.Adam(student.parameters(),lr=cfg['lr']) ce=nn.CrossEntropyLoss(); rng=np.random.RandomState(seed) last_u=[] for ep in range(EPOCHS): student.train(); order=rng.permutation(len(x)) for st in range(0,len(x),BATCH): ix=torch.as_tensor(order[st:st+BATCH],device=device); xb,yb=x[ix],y[ix] opt.zero_grad(set_to_none=True); logits,hs=student(xb,True) with torch.no_grad(): tl,th=teacher(xb,True) task=ce(logits,yb); kd=nn.functional.mse_loss(logits,tl) if method=='baseline': aux=nn.functional.mse_loss(hs,th.detach()); u=float('nan') else: pl,ent,um=pce_loss(th,hs); aux=pl-ent; u=float(um.detach().cpu()) (task+0.2*kd+cfg['coef']*aux).backward(); opt.step() if method!='baseline': last_u.append(u) student.eval() with torch.no_grad(): pred=student(xt).argmax(1); metric=float((pred!=yt).float().mean().cpu()) if return_sig and method=='baseline': with torch.no_grad(): _,bh=student(x[:min(128,len(x))],True); _,th=teacher(x[:min(128,len(x))],True) _,_,u=pce_loss(th,bh) return metric,float(u.cpu()) if return_sig: with torch.no_grad(): _,bh=student(x[:min(128,len(x))],True); _,th=teacher(x[:min(128,len(x))],True) _,_,u=pce_loss(th,bh) return metric,float(u.cpu()) return metric def run_method(method,cfg,seeds=SEEDS,signature=False): vals=[]; us=[] for s in seeds: r=train_pair(int(s),cfg,method,signature) if signature: v,u=r; us.append(u) else: v=r vals.append(v) out={'mean':float(np.mean(vals)),'std':float(np.std(vals)),'per_seed':vals,'n':len(vals)} if signature: out['unexplained_mass_per_seed']=us return out def main(): # Cheap numerical check of validity and excess identity before training. torch.manual_seed(7); T=torch.rand(2,5,2); S=torch.rand(2,3,2) wt=torch.rand(2,5); p=wt/wt.sum(1,keepdim=True); a=torch.exp(-((T[:,:,None]-S[:,None,:])**2).sum(-1)/(2*.18**2)).amax(2) valid=bool(torch.all((p*a).sum(1)<=1+1e-6)); lhs=(-(p*torch.log(p*a+1e-7)).sum(1)); ent=(-(p*torch.log(p+1e-7)).sum(1)); rhs=(-(p*torch.log(a+1e-7)).sum(1)); identity=float((lhs-ent-rhs).abs().max()) def maker(c): return lambda s: train_pair(s,c,'baseline') base=sweep_baseline(maker,GRID,seeds=SWEEP) # Idea uses the same union/grid and is evaluated on all paired seeds. idea_candidates=[] for c in GRID: r=run_method('idea',c,seeds=SWEEP); idea_candidates.append({'cfg':c,'mean':r['mean']}) best=min(idea_candidates,key=lambda z:z['mean'])['cfg'] idea=run_method('idea',best,seeds=SEEDS,signature=True) rep=make_report('vision','cnn_small',base,idea,extra={'mechanism_signature':{ 'prediction':'directional PCE should reduce teacher activation-event unexplained mass relative to feature-MSE baseline', 'predicted_vs_observed':{'baseline_unexplained_mass':None,'idea_unexplained_mass':float(np.mean(idea['unexplained_mass_per_seed']))}, 'confirmed':False,'measurement':'trained teacher/student CNN activation ranges on test inputs'}}) # obtain baseline signature only for the trained systems; expensive but explicit. bu=[] for s in SEEDS: _,u=train_pair(int(s),base['best_cfg'],'baseline',True) bu.append(u) rep['mechanism_signature']['predicted_vs_observed']['baseline_unexplained_mass']=float(np.mean(bu)) rep['mechanism_signature']['confirmed']=bool(rep['mechanism_signature']['predicted_vs_observed']['idea_unexplained_mass'] < rep['mechanism_signature']['predicted_vs_observed']['baseline_unexplained_mass']) rep['sanity_check']={'probability_valid':valid,'excess_identity_max_abs_error':identity} with open('bench_report.json','w') as f: json.dump(rep,f,indent=2) print(json.dumps(rep,indent=2)) if __name__=='__main__': main()