DERBACH's picture
download
raw
5.37 kB
"""Paired bootstrap with prompts as sampling units and independent model seeds."""
import numpy as np
from eval.metrics import axis_metrics,corr,matching
def paired_cluster_mean(a,b,prompt_ids,n=10000,seed=191):
"""Bootstrap an example mean while resampling whole prompt clusters.
Every prompt has equal sampling probability; every observation retains
the weight it has in the reported metric. Seeds are paired across methods.
Unlike an equal-prompt mean, this matches the overall leakage mean when
different prompts have different numbers of accepted interventions.
"""
a=np.asarray(a,float);b=np.asarray(b,float);pids=np.asarray(prompt_ids)
if a.shape!=b.shape or a.ndim!=2 or a.shape[1]!=len(pids):
raise ValueError('Expected aligned [seed, example] arrays and prompt IDs')
if not np.isfinite(a).all() or not np.isfinite(b).all():
raise ValueError('Missing predictions must not silently change the estimand')
if not a.size:raise ValueError('Empty evaluation')
unique,inverse=np.unique(pids,return_inverse=True);counts=np.bincount(inverse)
diff=np.zeros((len(unique),len(a)))
for s in range(len(a)):np.add.at(diff[:,s],inverse,b[s]-a[s])
g=np.random.default_rng(seed);boot=[]
for _ in range(n):
si=g.integers(len(a),size=len(a));pi=g.integers(len(unique),size=len(unique))
boot.append(float(diff[pi][:,si].sum()/(counts[pi].sum()*len(a))))
return {'difference':float((b-a).mean()),'ci95':np.quantile(boot,[.025,.975]).tolist() if len(unique)>1 else None,'bootstrap_samples':n,'prompts':len(unique),'examples':a.shape[1],'units':'paired prompt clusters and paired training seeds','prompt_weighting':'uniform cluster sampling; example-weighted metric','random_seed':seed}
def paired_prompt_mean(a,b,prompt_ids,n=10000,seed=190):
"""a,b [seed,example], nan allowed; each prompt gets equal weight."""
valid=np.isfinite(a).any(0)&np.isfinite(b).any(0);a=a[:,valid];b=b[:,valid];prompt_ids=np.asarray(prompt_ids)[valid]
unique,inverse=np.unique(prompt_ids,return_inverse=True);diff=np.empty((len(a),len(unique)))
for j in range(len(unique)):diff[:,j]=np.nanmean(b[:,inverse==j]-a[:,inverse==j],axis=1)
g=np.random.default_rng(seed);boots=[]
for _ in range(n):
si=g.integers(len(a),size=len(a));pi=g.integers(len(unique),size=len(unique));boots.append(np.nanmean(diff[si][:,pi]))
return {'difference':float(np.nanmean(diff)),'ci95':np.quantile(boots,[.025,.975]).tolist(),'bootstrap_samples':n,'prompts':len(unique),'units':'paired prompt resampling and training-seed resampling','prompt_weighting':'equal'}
def semantic_bootstrap(a,b,attributes,prompt_ids,n=1000,seed=193):
"""Exact rank/pair statistic with shared prompt draws, conditional on fitted seeds.
Caller must verify response identities and paired run seeds. Axis purity
rematches the baseline on each draw; named ILR axes keep their identity.
"""
a=np.asarray(a);b=np.asarray(b);attributes=np.asarray(attributes);prompt_ids=np.asarray(prompt_ids)
if a.ndim!=3 or b.ndim!=3 or a.shape[:2]!=b.shape[:2] or a.shape[0]<2:
raise ValueError('Need at least two aligned [seed,response,axis] model arrays')
if attributes.shape!=(a.shape[1],4) or prompt_ids.shape!=(a.shape[1],) or min(a.shape[2],b.shape[2])<4:
raise ValueError('Expected four human attributes and matching response/prompt axes')
if not all(np.isfinite(v).all() for v in [a,b,attributes]):raise ValueError('Nonfinite semantic input')
pairs=[(i,j) for i in range(len(a)) for j in range(i+1,len(a))]
def statistic(ix):
if any(np.any(np.std(v,axis=-2)<1e-12) for v in [a[:,ix],b[:,ix],attributes[ix]]):return np.array([np.nan,np.nan])
pd=[];asd=[]
for s in range(len(a)):
ma,_=axis_metrics(a[s][ix],attributes[ix],rank=True,named=False);mb,_=axis_metrics(b[s][ix],attributes[ix],rank=True,named=True);pd.append(mb['axis_purity']-ma['axis_purity'])
for i,j in pairs:
ca=corr(a[i][ix],a[j][ix]);order,_=matching(a[i][ix],a[j][ix]);sa=abs(ca[order,np.arange(a[j].shape[1])]).mean()
sb=np.diag(corr(b[i][ix,:4],b[j][ix,:4])).mean();asd.append(sb-sa)
return np.array([np.mean(pd),np.mean(asd)])
unique=np.unique(prompt_ids);groups=[np.flatnonzero(prompt_ids==p) for p in unique];g=np.random.default_rng(seed)
point=statistic(np.arange(a.shape[1]));boots=[]
for _ in range(n):
ix=np.concatenate([groups[i] for i in g.integers(len(groups),size=len(groups))]);boots.append(statistic(ix))
boots=np.array(boots);out={}
for j,name in enumerate(['axis_purity','axis_stability']):
v=boots[:,j];undefined=int((~np.isfinite(v)).sum());ok=not undefined and np.isfinite(point[j]) and len(unique)>1
out[name]={'difference':float(point[j]) if np.isfinite(point[j]) else None,'ci95':np.quantile(v,[.025,.975]).tolist() if ok else None,'bootstrap_mean':float(v.mean()) if not undefined else None,'bootstrap_samples':n,'bootstrap_undefined_samples':undefined,'interval_status':'ok' if ok else 'degenerate_or_insufficient_clusters','prompts':len(unique),'training_seeds':len(a),'distinct_seed_pairs':len(pairs),'random_seed':seed,'units':'prompt clusters conditional on supplied trained seeds; exact rank recomputation; seed SD reported separately'}
return out

Xet Storage Details

Size:
5.37 kB
·
Xet hash:
e609077f05d7f75924a3472a4a0a303be75cb4fa60df78aa1021f346f92c91c3

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.