salescode-recapture-detector / scripts /feature_ablation.py
Kartikeya Mishra
Deploy SalesCode recapture detector to Space
7c34b66
Raw
History Blame Contribute Delete
4.71 kB
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()