|
| 1 | +#!/usr/bin/env python3 |
| 2 | +from pathlib import Path |
| 3 | +import argparse,json |
| 4 | +import numpy as np |
| 5 | +from scipy.sparse import csr_matrix,hstack |
| 6 | +from sklearn.feature_extraction.text import HashingVectorizer |
| 7 | +from sklearn.linear_model import LogisticRegression |
| 8 | +from sklearn.model_selection import GroupKFold |
| 9 | +from v75_canonical_trajectory import load_training,trajectory_views,SEED |
| 10 | +from v71_mastery_events import load_transcript |
| 11 | +from v94_related_control import segmented_control |
| 12 | +from v135_nested_supported_stack import prior_apply,feats,fit_stack,ALPHA,EPS |
| 13 | + |
| 14 | +BASE_C=.25 |
| 15 | + |
| 16 | +def fit_base_model(X,y): return LogisticRegression(C=BASE_C,max_iter=300,solver='liblinear',random_state=SEED).fit(X,y) |
| 17 | +def pred(m,X): return np.clip(m.predict_proba(X)[:,1],EPS,1-EPS) |
| 18 | + |
| 19 | +def main(a): |
| 20 | + f=load_training(a.features,a.labels).reset_index(drop=True); y=f.target.to_numpy(int) |
| 21 | + sessions=f.session_id.astype(str).to_numpy(); support=f.learning_objective.astype(str).to_numpy() |
| 22 | + cache={sid:load_transcript(a.transcripts/f'{sid}.csv') for sid in np.unique(sessions)} |
| 23 | + views=[]; nums=[]; rt=[]; rz=[] |
| 24 | + for i,r in f.iterrows(): |
| 25 | + v,n,_=trajectory_views(cache[str(r.session_id)],str(r.learning_objective)); views.append(v); nums.append(n) |
| 26 | + t,z=segmented_control(cache[str(r.session_id)],str(r.learning_objective),'related');rt.append(t);rz.append(z) |
| 27 | + if (i+1)%5000==0: print('ROWS',i+1,flush=True) |
| 28 | + hv75=HashingVectorizer(n_features=2**18,alternate_sign=False,norm='l2',ngram_range=(1,2),lowercase=True) |
| 29 | + parts=[hv75.transform([f'[OBJECTIVE] {x}' for x in f.learning_objective])] |
| 30 | + for k in views[0].keys(): parts.append(hv75.transform([f'[{k.upper()}] '+v[k] for v in views])) |
| 31 | + N=np.vstack(nums).astype(float); nmean=N.mean(0); nstd=N.std(0)+1e-6; parts.append(csr_matrix((N-nmean)/nstd)); X75=hstack(parts,format='csr') |
| 32 | + hvr=HashingVectorizer(n_features=2**17,alternate_sign=False,norm='l2',ngram_range=(1,2),lowercase=True) |
| 33 | + R=np.vstack(rz).astype(float); rmean=R.mean(0); rstd=R.std(0)+1e-6; Xr=hstack([hvr.transform(rt),csr_matrix((R-rmean)/rstd)],format='csr') |
| 34 | + print('MATRICES',X75.shape,Xr.shape,flush=True) |
| 35 | + # Session-OOF component field for leakage-safe final stack training. |
| 36 | + p75=np.zeros(len(y)); pr=np.zeros(len(y)); pp=np.zeros(len(y)); cc=np.zeros(len(y)); seen=np.zeros(len(y),bool) |
| 37 | + for k,(tr,va) in enumerate(GroupKFold(4).split(np.zeros(len(y)),y,sessions),1): |
| 38 | + m75=fit_base_model(X75[tr],y[tr]); mr=fit_base_model(Xr[tr],y[tr]); p75[va]=pred(m75,X75[va]); pr[va]=pred(mr,Xr[va]) |
| 39 | + q,c,s=prior_apply(y,tr,va,support);pp[va]=q;cc[va]=c;seen[va]=s;print('OOF',k,flush=True) |
| 40 | + stack=fit_stack(feats(p75[seen],pr[seen],pp[seen],cc[seen],True),y[seen]) |
| 41 | + final75=fit_base_model(X75,y); finalr=fit_base_model(Xr,y) |
| 42 | + global_mean=float(y.mean()); sums={};counts={} |
| 43 | + for k,v in zip(support,y): sums[k]=sums.get(k,0.0)+float(v);counts[k]=counts.get(k,0)+1 |
| 44 | + out=Path(a.out);out.mkdir(parents=True,exist_ok=True) |
| 45 | + np.savez_compressed(out/'v135_assets.npz',v75_coef=final75.coef_.ravel(),v75_intercept=final75.intercept_,v75_num_mean=nmean,v75_num_std=nstd, |
| 46 | + related_coef=finalr.coef_.ravel(),related_intercept=finalr.intercept_,related_num_mean=rmean,related_num_std=rstd, |
| 47 | + stack_coef=stack.coef_.ravel(),stack_intercept=stack.intercept_) |
| 48 | + man={'protocol':'V141_V135_RUNTIME_ASSETS','rows':len(y),'base_C':BASE_C,'stack_C':0.10,'prior_alpha':ALPHA,'global_mean':global_mean, |
| 49 | + 'objective_counts':counts,'objective_sums':sums,'stack_training':'4-fold session-grouped OOF supported rows only','seed':SEED} |
| 50 | + (out/'manifest.json').write_text(json.dumps(man,indent=2)) |
| 51 | + # Save a small exact component fixture for parity checks. |
| 52 | + idx=np.arange(min(512,len(y))) |
| 53 | + np.savez_compressed(out/'parity_fixture.npz',idx=idx,p75=p75[idx],related=pr[idx],prior=pp[idx],count=cc[idx],seen=seen[idx],y=y[idx]) |
| 54 | + print(json.dumps({'rows':len(y),'supported_oof':int(seen.sum()),'assets':str(out)},indent=2),flush=True) |
| 55 | +if __name__=='__main__': |
| 56 | + p=argparse.ArgumentParser();p.add_argument('--features',type=Path,required=True);p.add_argument('--labels',type=Path,required=True);p.add_argument('--transcripts',type=Path,required=True);p.add_argument('--out',type=Path,required=True);main(p.parse_args()) |
0 commit comments