Spaces:
Sleeping
Sleeping
Download scripts/evaluate_test.py from nhdang117/ToxicScope: direct link, hf CLI and curl.
- Browser
- Download file 6.92 kB
-
https://huggingface.co/spaces/nhdang117/ToxicScope/resolve/main/scripts/evaluate_test.py
- Command line
-
hf download hf://spaces/nhdang117/ToxicScope/scripts/evaluate_test.py
-
curl -L -o evaluate_test.py https://huggingface.co/spaces/nhdang117/ToxicScope/resolve/main/scripts/evaluate_test.py
6.92 kB
| """Build a comparable report on the same rows, with traceable prediction sources. | |
| Use --full-phobert to rerun all PhoBERT rows instead of validated supplied predictions. | |
| """ | |
| from pathlib import Path | |
| import sys,json,hashlib,argparse,time | |
| ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT)) | |
| import numpy as np | |
| import pandas as pd | |
| from model_registry import registry,slug | |
| from model_service import service | |
| from metrics import evaluate,bootstrap_f1_ci | |
| def main(): | |
| parser=argparse.ArgumentParser();parser.add_argument('--full-phobert',action='store_true');args=parser.parse_args() | |
| frames={s:pd.read_csv(ROOT/f'data/processed/{s}.csv',keep_default_na=False) for s in ['train','dev','test']} | |
| train,dev,test=[frames[s] for s in ['train','dev','test']] | |
| dev_tune=dev.loc[~dev.clean.isin(set(train.clean))] | |
| unseen=~test.clean.isin(set(train.clean));strict=unseen & ~test.clean.isin(set(dev_tune.clean)) | |
| masks=dict(test_official=np.ones(len(test),bool),test_unseen_train=unseen.to_numpy(),test_strict=strict.to_numpy()) | |
| ids=[m['id'] for m in registry() if m['available'] and (args.full_phobert or m['id']!='phobert')] | |
| last=[0.] | |
| def progress(p,s): | |
| if time.monotonic()-last[0]>8: | |
| print(f'{p:.0%} {s}',flush=True);last[0]=time.monotonic() | |
| data=service.predict_many(test.free_text.tolist(),ids,progress) | |
| if data['errors']: raise RuntimeError(data['errors']) | |
| per_model={} | |
| for m in data['rows'][0]['models']: | |
| key=m['id'];entries=[next(a for a in r['models'] if a['id']==key) for r in data['rows']] | |
| per_model[key]=dict(name=m['name'],origin=m['origin'],source='inference_on_supplied_test', | |
| pred=np.array([a['label']=='TOXIC' for a in entries],int), | |
| p=np.array([a['prob_toxic'] for a in entries],float) if m['prob_toxic'] is not None else None, | |
| margin=np.array([a['decision_score'] for a in entries],float) if m['decision_score'] is not None else None) | |
| verification={} | |
| if not args.full_phobert: | |
| original=pd.read_csv(ROOT/'reports/original/phobert_test_predictions_selected_seed.csv',keep_default_na=False) | |
| assert original.free_text.tolist()==test.free_text.tolist() | |
| assert original.label_bin.tolist()==test.label_bin.tolist() | |
| indices=np.random.default_rng(42).choice(len(test),16,replace=False) | |
| check=service.predict_many(test.free_text.iloc[indices].tolist(),['phobert']) | |
| if check['errors']: raise RuntimeError(check['errors']) | |
| actual=np.array([r['models'][0]['prob_toxic'] for r in check['rows']]) | |
| expected=original.prob_toxic.to_numpy()[indices] | |
| difference=float(np.abs(actual-expected).max()) | |
| if difference>1e-4: raise RuntimeError(f'PhoBERT checkpoint mismatch: max probability difference {difference}. Run --full-phobert.') | |
| verification=dict(sample_n=16,seed=42,max_abs_probability_difference=difference,tolerance=1e-4, | |
| policy='Full report uses supplied predictions; 16 sampled rows verified by live checkpoint inference.') | |
| per_model['phobert']=dict(name='PhoBERT · seed 87',origin='original_checkpoint', | |
| source='supplied_predictions_verified_16_rows',pred=original.pred.to_numpy(),p=original.prob_toxic.to_numpy(),margin=None) | |
| y=test.label_bin.to_numpy();records=[] | |
| predictions=test[['free_text','label_bin']].copy();predictions['in_test_strict']=strict | |
| historic=pd.read_csv(ROOT/'reports/original/baseline_test_predictions.csv') | |
| comparison=[] | |
| for key,values in per_model.items(): | |
| predictions[key+'__pred']=values['pred'] | |
| if values['p'] is not None: predictions[key+'__prob_toxic']=values['p'] | |
| if values['margin'] is not None: predictions[key+'__decision_score']=values['margin'] | |
| for subset,mask in masks.items(): | |
| metrics=evaluate(y[mask],values['pred'][mask],values['p'][mask] if values['p'] is not None else None, | |
| values['margin'][mask] if values['margin'] is not None else None) | |
| if subset=='test_strict':metrics['f1_macro_ci95']=bootstrap_f1_ci(y[mask],values['pred'][mask]) | |
| records.append(dict(id=key,name=values['name'],origin=values['origin'],source=values['source'],subset=subset,**metrics)) | |
| for c in historic.columns: | |
| if c.startswith('pred__') and slug(c[6:])==key: | |
| comparison.append(dict(id=key,agreement=float(np.mean(historic[c].to_numpy()==values['pred'])))) | |
| report=dict(title='ToxicScope · đánh giá các mô hình đang triển khai', | |
| dataset=dict(name='UIT-ViHSD',train_n=len(train),dev_n=len(dev),dev_tune_n=len(dev_tune), | |
| **{s:int(m.sum()) for s,m in masks.items()}, | |
| sha256={s:hashlib.sha256((ROOT/f'data/processed/{s}.csv').read_bytes()).hexdigest() for s in frames}), | |
| records=records,phobert_verification=verification,baseline_historical_agreement=comparison, | |
| notes=[ | |
| 'Test official là 6.671 dòng còn lại sau tiền xử lý, không phải toàn bộ 6.680 dòng thô.', | |
| 'Test strict loại clean xuất hiện trong Train hoặc Dev tune; vẫn có va chạm ở biểu diễn token. Không khẳng định độc lập tuyệt đối ở mọi mức biểu diễn.', | |
| 'Checkpoint BiLSTM (Word2Vec + BiLSTM128, ngưỡng 0,67), baseline và PhoBERT đều được chọn trên Dev tune; ngưỡng BiLSTM được khóa bằng Dev tune trước khi đánh giá Test.', | |
| '6 classifier baseline được fit lại bằng tham số Dev đã chốt. FastText baseline được train lại vì file gốc thiếu vectors_ngrams.npy; chỉ số hiện tại không thay thế báo cáo lịch sử.', | |
| '27 cấu hình DL chưa có trọng số; không được đưa vào vote hay bịa chỉ số Test. 33 dòng Dev lịch sử chứa 30 cấu hình phân biệt.', | |
| 'Điểm Dev và Test trình bày riêng. Mô hình Majority luôn SAFE được dùng làm đối chứng và bị loại khỏi vote.', | |
| 'Khoảng tin cậy F1: bootstrap phân tầng 300 lần trên cặp nhãn/dự đoán; chỉ phản ánh bất định lấy mẫu, không phản ánh phương sai huấn luyện.', | |
| 'Phân loại độc hại là thành phần của đề tài. Không có cạnh lan truyền/timestamp trong dữ liệu hiện có để đo mạng lan truyền thực tế.' | |
| ]) | |
| (ROOT/'reports/evaluation.json').write_text(json.dumps(report,ensure_ascii=False,indent=2,allow_nan=False),encoding='utf-8') | |
| predictions.to_csv(ROOT/'reports/deployment_test_predictions.csv',index=False,encoding='utf-8-sig') | |
| print('Wrote evaluation.json',len(per_model),'models',masks['test_strict'].sum(),'strict rows',flush=True) | |
| print('PhoBERT verification',verification,flush=True) | |
| if __name__=='__main__':main() | |