| import os |
| import sys |
| import numpy as np |
| import pandas as pd |
| from sklearn.model_selection import StratifiedKFold |
| from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score |
| from sklearn.calibration import CalibratedClassifierCV |
| import xgboost as xgb |
|
|
| sys.path.append(os.path.join(os.path.dirname(__file__), '..')) |
| from train import gather_data |
| from features import get_feature_names |
|
|
| def run_ablation(): |
| feature_groups = { |
| 'brightness_color': ['brightness', 'contrast', 'saturation'], |
| 'blur_sharpness': ['laplacian_var', 'sobel_mean', 'edge_density'], |
| 'fft_global_freq': ['fft_hf_ratio', 'h_freq_peak', 'v_freq_peak', 'diag_freq_peak'], |
| 'local_patch_fft': ['local_fft_hf'], |
| 'moire_banding': ['moire_score', 'banding_score'], |
| 'jpeg_compression': ['compression_diff', 'blockiness'], |
| 'bezel_border': ['bezel_score', 'rect_contour_score'], |
| 'perspective_rect': ['perspective_score'], |
| 'glare_overexposure': ['glare_ratio', 'glare_patch_size'], |
| 'printout_paper': ['paper_texture'] |
| } |
| |
| data_dir = os.path.join(os.path.dirname(__file__), '..', 'dataset') |
| phone_dir = os.path.join(os.path.dirname(__file__), '..', 'dataset', 'my_photos') |
| |
| print("Loading datasets...") |
| X_tr_g, y_tr_g, _ = gather_data(data_dir) |
| X_phone, y_phone, _ = gather_data(phone_dir) |
| |
| f_names = get_feature_names() |
| |
| results = [] |
| |
| def eval_model(X_train, y_train, X_test, y_test, disabled_indices=[]): |
| X_train_sub = np.delete(X_train, disabled_indices, axis=1) |
| X_test_sub = np.delete(X_test, disabled_indices, axis=1) |
| |
| ratio = float(np.sum(y_train == 0)) / np.sum(y_train == 1) if np.sum(y_train == 1) > 0 else 1.0 |
| base = xgb.XGBClassifier(n_estimators=100, max_depth=4, reg_lambda=10, random_state=42, scale_pos_weight=ratio, tree_method='hist', device='cpu') |
| model = CalibratedClassifierCV(base, method='sigmoid', cv=3) |
| model.fit(X_train_sub, y_train) |
| |
| probs = model.predict_proba(X_test_sub)[:, 1] |
| |
| best_th, best_f1 = 0.5, 0 |
| for th in np.arange(0.1, 0.9, 0.05): |
| f1 = f1_score(y_test, (probs >= th).astype(int), zero_division=0) |
| if f1 > best_f1: |
| best_f1, best_th = f1, th |
| |
| preds = (probs >= best_th).astype(int) |
| acc = accuracy_score(y_test, preds) |
| prec = precision_score(y_test, preds, zero_division=0) |
| rec = recall_score(y_test, preds, zero_division=0) |
| |
| fp = np.sum((preds == 1) & (y_test == 0)) |
| fn = np.sum((preds == 0) & (y_test == 1)) |
| |
| return acc, prec, rec, best_f1, fp, fn |
| |
| def cv_eval(disabled_indices): |
| skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) |
| metrics = [] |
| for tr_idx, te_idx in skf.split(X_phone, y_phone): |
| X_comb = np.vstack([X_tr_g, X_phone[tr_idx]]) |
| y_comb = np.concatenate([y_tr_g, y_phone[tr_idx]]) |
| |
| acc, prec, rec, f1, fp, fn = eval_model(X_comb, y_comb, X_phone[te_idx], y_phone[te_idx], disabled_indices) |
| metrics.append([acc, prec, rec, f1, fp, fn]) |
| |
| return np.mean(metrics, axis=0) |
| |
| print("Evaluating Baseline...") |
| base_metrics = cv_eval([]) |
| results.append({ |
| 'Ablated Group': 'None (Baseline)', |
| 'Accuracy': base_metrics[0], |
| 'Precision': base_metrics[1], |
| 'Recall': base_metrics[2], |
| 'F1': base_metrics[3], |
| 'False Positives': base_metrics[4] * 5, |
| 'False Negatives': base_metrics[5] * 5 |
| }) |
| |
| for group, features in feature_groups.items(): |
| print(f"Evaluating without {group}...") |
| indices = [f_names.index(f) for f in features if f in f_names] |
| metrics = cv_eval(indices) |
| results.append({ |
| 'Ablated Group': group, |
| 'Accuracy': metrics[0], |
| 'Precision': metrics[1], |
| 'Recall': metrics[2], |
| 'F1': metrics[3], |
| 'False Positives': metrics[4] * 5, |
| 'False Negatives': metrics[5] * 5 |
| }) |
| |
| df = pd.DataFrame(results) |
| |
| os.makedirs(os.path.join(os.path.dirname(__file__), '..', 'reports'), exist_ok=True) |
| df.to_csv(os.path.join(os.path.dirname(__file__), '..', 'reports', 'feature_ablation.csv'), index=False) |
| |
| with open(os.path.join(os.path.dirname(__file__), '..', 'reports', 'feature_ablation_summary.md'), 'w') as f: |
| f.write("# Feature Ablation Summary\n\n") |
| f.write(df.to_markdown(index=False)) |
| f.write("\n") |
|
|
| if __name__ == "__main__": |
| run_ablation() |
|
|