Spaces:
Sleeping
Sleeping
File size: 5,111 Bytes
906c392 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | """
Spectral flatness ablation study (reviewer priority #3).
The paper reports spectral_flatness_std as the single most important
LightGBM feature and attributes AI/human separability to it. The reviewer
asks for a direct ablation: does performance meaningfully depend on this one
feature, or would the model do just as well without it (in which case the
"AI music has flatter spectra" narrative should be stated more cautiously)?
Three configurations, each evaluated with 5-fold stratified CV + LightGBM:
(a) all 47 features
(b) 46 features, spectral_flatness_std removed
(c) spectral_flatness_std alone (1 feature)
Usage:
python -m app.training.ablation_flatness
"""
from __future__ import annotations
import csv
import sys
import warnings
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
import lightgbm as lgb
from sklearn.exceptions import ConvergenceWarning
from sklearn.metrics import balanced_accuracy_score, f1_score, roc_auc_score, roc_curve
from sklearn.model_selection import StratifiedKFold, cross_val_predict
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from app.training.evaluate import load_features_csv
FEATURES_CSV = Path("D:/CrownCode/DataSet/features.csv")
OUTPUT_CSV = Path(__file__).resolve().parents[3] / "docs/academic/paper/real_tables/flatness_ablation.csv"
_EXCLUDED_COLUMNS = {"file_path", "label_int", "duration_sec", "sample_rate"}
TARGET_FEATURE = "spectral_flatness_std"
_LGBM_PARAMS = dict(
n_estimators=300, max_depth=-1, learning_rate=0.05, num_leaves=31,
subsample=0.8, colsample_bytree=0.8, min_child_samples=20,
reg_alpha=0.1, reg_lambda=1.0, class_weight="balanced",
random_state=42, verbose=-1,
)
def _feature_columns() -> list[str]:
with open(FEATURES_CSV, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
return [c for c in (reader.fieldnames or []) if c not in _EXCLUDED_COLUMNS]
def _optimal_threshold(y_true: np.ndarray, y_prob: np.ndarray) -> float:
fpr, tpr, thresholds = roc_curve(y_true, y_prob)
return float(thresholds[np.argmax(tpr - fpr)])
def _evaluate_feature_set(X: np.ndarray, y: np.ndarray, n_folds: int = 5) -> dict:
cv = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
pipeline = Pipeline([
("scaler", StandardScaler()),
("model", lgb.LGBMClassifier(**_LGBM_PARAMS)),
])
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=ConvergenceWarning)
y_prob = cross_val_predict(pipeline, X, y, cv=cv, method="predict_proba")[:, 1]
threshold = _optimal_threshold(y, y_prob)
y_pred = (y_prob >= threshold).astype(int)
return {
"roc_auc": round(float(roc_auc_score(y, y_prob)), 4),
"f1": round(float(f1_score(y, y_pred, zero_division=0)), 4),
"balanced_accuracy": round(float(balanced_accuracy_score(y, y_pred)), 4),
"threshold": round(threshold, 4),
}
def run() -> None:
X_full, y = load_features_csv(FEATURES_CSV)
X_full = np.nan_to_num(X_full, nan=0.0, posinf=1.0, neginf=-1.0)
feature_cols = _feature_columns()
if TARGET_FEATURE not in feature_cols:
raise RuntimeError(f"{TARGET_FEATURE} not found in {FEATURES_CSV} columns: {feature_cols}")
target_idx = feature_cols.index(TARGET_FEATURE)
keep_idx = [i for i in range(len(feature_cols)) if i != target_idx]
configs = {
"47_features_all": X_full,
"46_features_without_flatness_std": X_full[:, keep_idx],
"1_feature_flatness_std_only": X_full[:, [target_idx]],
}
results = []
for name, X in configs.items():
print(f"\nEvaluating: {name} ({X.shape[1]} features)")
metrics = _evaluate_feature_set(X, y)
metrics["config"] = name
metrics["n_features"] = X.shape[1]
results.append(metrics)
print(f" AUC={metrics['roc_auc']:.4f} F1={metrics['f1']:.4f} "
f"BalAcc={metrics['balanced_accuracy']:.4f}")
OUTPUT_CSV.parent.mkdir(parents=True, exist_ok=True)
fieldnames = ["config", "n_features", "roc_auc", "f1", "balanced_accuracy", "threshold"]
with open(OUTPUT_CSV, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for r in results:
writer.writerow({k: r[k] for k in fieldnames})
print(f"\nOutput: {OUTPUT_CSV}")
full_auc = next(r["roc_auc"] for r in results if r["config"] == "47_features_all")
no_flatness_auc = next(r["roc_auc"] for r in results if r["config"] == "46_features_without_flatness_std")
flatness_only_auc = next(r["roc_auc"] for r in results if r["config"] == "1_feature_flatness_std_only")
print("\n" + "=" * 60)
print(f" 47 features: AUC={full_auc:.4f}")
print(f" 46 features (no flatness): AUC={no_flatness_auc:.4f} (diff={full_auc - no_flatness_auc:+.4f})")
print(f" flatness_std alone: AUC={flatness_only_auc:.4f}")
print("=" * 60)
if __name__ == "__main__":
run()
|