Rthur2003 commited on
Commit
20fe6c3
·
1 Parent(s): 94ed8e9

Add Gradio dependency to requirements.txt with version constraints

Browse files
app/training/evaluate.py CHANGED
@@ -111,7 +111,7 @@ def evaluate_predictions(
111
 
112
  # Print report
113
  print(f"\n{'=' * 50}")
114
- print(f" {title} Evaluation Report")
115
  print(f"{'=' * 50}")
116
  print(f" Accuracy: {acc:.4f} ({acc:.1%})")
117
  print(f" Precision: {prec:.4f}")
@@ -176,7 +176,7 @@ def evaluate_heuristic_baseline(features_csv: str | Path) -> dict:
176
  y_pred_combined = (combined_scores > 0.5).astype(int)
177
 
178
  print("\n" + "=" * 60)
179
- print(" BASELINE EVALUATION Current Heuristic System")
180
  print("=" * 60)
181
 
182
  print("\n--- Heuristic Only (spectral + temporal + harmonic) ---")
 
111
 
112
  # Print report
113
  print(f"\n{'=' * 50}")
114
+ print(f" {title} - Evaluation Report")
115
  print(f"{'=' * 50}")
116
  print(f" Accuracy: {acc:.4f} ({acc:.1%})")
117
  print(f" Precision: {prec:.4f}")
 
176
  y_pred_combined = (combined_scores > 0.5).astype(int)
177
 
178
  print("\n" + "=" * 60)
179
+ print(" BASELINE EVALUATION - Current Heuristic System")
180
  print("=" * 60)
181
 
182
  print("\n--- Heuristic Only (spectral + temporal + harmonic) ---")
app/training/train_classifier.py CHANGED
@@ -2,25 +2,26 @@
2
  Comprehensive multi-model training pipeline for AURIS.
3
 
4
  Trains and evaluates multiple classifier families on extracted
5
- audio features using stratified k-fold cross-validation, then
6
- selects the best model and exports it for production use.
7
 
8
  Models compared:
 
9
  - Random Forest
10
  - Gradient Boosting
11
- - XGBoost
12
- - LightGBM
13
  - Support Vector Machine (RBF)
14
- - Multi-Layer Perceptron (Neural Network)
 
 
15
 
16
  Usage:
17
  python -m app.training.train_classifier data/training/features.csv
18
 
19
  Outputs:
20
- models/auris_classifier_v1.pkl best trained model
21
- models/feature_scaler_v1.pkl fitted StandardScaler
22
- models/feature_columns_v1.json ordered feature column names
23
- models/training_results.json all model metrics + CV folds
24
  """
25
 
26
  from __future__ import annotations
@@ -30,23 +31,15 @@ import json
30
  import pickle
31
  import sys
32
  import time
 
33
  from pathlib import Path
34
  from typing import Any
35
 
36
  import numpy as np
37
-
38
- from sklearn.ensemble import (
39
- GradientBoostingClassifier,
40
- RandomForestClassifier,
41
- )
42
  from sklearn.linear_model import LogisticRegression
43
- from sklearn.neural_network import MLPClassifier
44
- from sklearn.svm import SVC
45
- from sklearn.model_selection import (
46
- StratifiedKFold,
47
- cross_val_predict,
48
- )
49
- from sklearn.preprocessing import StandardScaler
50
  from sklearn.metrics import (
51
  accuracy_score,
52
  f1_score,
@@ -54,10 +47,16 @@ from sklearn.metrics import (
54
  recall_score,
55
  roc_auc_score,
56
  )
 
 
 
 
 
57
 
58
  # Optional: XGBoost
59
  try:
60
  import xgboost as xgb
 
61
  HAS_XGB = True
62
  except ImportError:
63
  HAS_XGB = False
@@ -65,15 +64,63 @@ except ImportError:
65
  # Optional: LightGBM
66
  try:
67
  import lightgbm as lgb
 
68
  HAS_LGBM = True
69
  except ImportError:
70
  HAS_LGBM = False
71
 
72
  sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
73
- from app.training.evaluate import (
74
- load_features_csv,
75
- evaluate_predictions,
76
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
 
79
  def train(
@@ -87,53 +134,42 @@ def train(
87
  Returns:
88
  Dict with per-model metrics, best model info, and saved paths.
89
  """
 
90
  models_dir = Path(models_dir)
91
  models_dir.mkdir(parents=True, exist_ok=True)
92
 
93
- # ── Load data ──────────────────────────────────
94
  X, y = load_features_csv(features_csv)
95
-
96
- with open(features_csv, "r", encoding="utf-8") as f:
97
- reader = csv.DictReader(f)
98
- # duration_sec and sample_rate are metadata, not audio features —
99
- # including them causes data leakage (duration correlates with source, not content)
100
- _EXCLUDE = {"file_path", "label_int", "duration_sec", "sample_rate"}
101
- feature_cols = [
102
- c for c in reader.fieldnames
103
- if c not in _EXCLUDE
104
- ]
105
-
106
- # ── Handle NaN/Inf ─���───────────────────────────
107
  X = np.nan_to_num(X, nan=0.0, posinf=1.0, neginf=-1.0)
108
 
109
- # ── Scale features ─────────────────────────────
 
 
110
  scaler = StandardScaler()
111
  X_scaled = scaler.fit_transform(X)
112
 
113
- # ── Train multiple models ──────────────────────
114
- candidates = _build_candidates()
115
- cv = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
116
-
117
- best_model = None
118
  best_name = ""
119
- best_auc = 0.0
120
  all_results: dict[str, dict[str, Any]] = {}
121
 
122
- for name, model in candidates:
123
- print(f"\n{'─' * 50}")
124
- print(f" Training: {name}")
125
- print(f"{'─' * 50}")
126
 
127
  t0 = time.time()
128
-
129
- # Cross-validated probability predictions
130
- y_prob = cross_val_predict(
131
- model, X_scaled, y,
132
- cv=cv, method="predict_proba",
133
- )[:, 1]
134
- y_pred = (y_prob > 0.5).astype(int)
135
-
136
- train_time = time.time() - t0
 
 
 
137
 
138
  acc = accuracy_score(y, y_pred)
139
  prec = precision_score(y, y_pred, zero_division=0)
@@ -141,12 +177,14 @@ def train(
141
  f1 = f1_score(y, y_pred, zero_division=0)
142
  auc = roc_auc_score(y, y_prob)
143
 
144
- print(f" Accuracy: {acc:.4f}")
145
- print(f" Precision: {prec:.4f}")
146
- print(f" Recall: {rec:.4f}")
147
- print(f" F1 Score: {f1:.4f}")
148
- print(f" ROC-AUC: {auc:.4f}")
149
- print(f" Train time: {train_time:.1f}s")
 
 
150
 
151
  all_results[name] = {
152
  "accuracy": round(acc, 4),
@@ -154,7 +192,10 @@ def train(
154
  "recall": round(rec, 4),
155
  "f1": round(f1, 4),
156
  "roc_auc": round(auc, 4),
157
- "train_time_sec": round(train_time, 2),
 
 
 
158
  "y_true": y.tolist(),
159
  "y_pred": y_pred.tolist(),
160
  "y_prob": y_prob.tolist(),
@@ -163,48 +204,38 @@ def train(
163
  if auc > best_auc:
164
  best_auc = auc
165
  best_name = name
166
- best_model = model
167
 
168
- # ── Final evaluation of best model ─────────────
169
- print(f"\n{'═' * 60}")
170
- print(f" BEST MODEL: {best_name} (ROC-AUC = {best_auc:.4f})")
171
- print(f"{'═' * 60}")
172
 
173
  y_prob_best = np.array(all_results[best_name]["y_prob"])
174
  y_pred_best = np.array(all_results[best_name]["y_pred"])
 
175
 
176
- evaluate_predictions(
177
- y, y_pred_best, y_prob_best,
178
- title=f"Best: {best_name}",
179
- )
180
-
181
- # ── Train ALL models on full data ────────────────
182
  all_model_paths: dict[str, str] = {}
183
- for name, model in candidates:
184
- print(f"\nTraining final {name} on all {len(y)} samples...")
185
- model.fit(X_scaled, y)
186
- safe_name = name.lower().replace(" ", "_").replace("(", "").replace(")", "")
187
- model_pkl = models_dir / f"model_{safe_name}.pkl"
 
 
 
 
188
  with open(model_pkl, "wb") as f:
189
- pickle.dump(model, f)
190
  all_model_paths[name] = str(model_pkl)
191
- print(f" Saved: {model_pkl}")
192
 
193
- best_model = None
194
- for name, model in candidates:
195
- if name == best_name:
196
- best_model = model
197
- break
198
-
199
- # ── Feature importance ─────────────────────────
200
  importance_data = _extract_importance(best_model, feature_cols)
201
  if importance_data:
202
  print("\nTop 15 features:")
203
  for fname, imp in importance_data[:15]:
204
- bar = "█" * int(imp * 100)
205
- print(f" {fname:<35} {imp:.4f} {bar}")
206
 
207
- # ── Save artifacts ─────────────────────────────
208
  model_path = models_dir / "auris_classifier_v1.pkl"
209
  scaler_path = models_dir / "feature_scaler_v1.pkl"
210
  columns_path = models_dir / "feature_columns_v1.json"
@@ -214,31 +245,38 @@ def train(
214
  pickle.dump(best_model, f)
215
  with open(scaler_path, "wb") as f:
216
  pickle.dump(scaler, f)
217
- with open(columns_path, "w") as f:
218
  json.dump(feature_cols, f, indent=2)
219
 
220
- # Save full results (without numpy arrays for JSON)
221
- json_results = {}
222
  for name, data in all_results.items():
223
  json_results[name] = {
224
- k: v for k, v in data.items()
225
- if k not in ("y_true", "y_pred", "y_prob")
 
226
  }
227
  json_results["_best_model"] = best_name
228
  json_results["_n_samples"] = len(y)
229
  json_results["_n_features"] = X.shape[1]
230
  json_results["_n_folds"] = n_folds
231
- json_results["_data_leakage_fix"] = "duration_sec and sample_rate removed from features (v2)"
 
 
 
 
 
 
 
232
  json_results["_model_paths"] = all_model_paths
233
  if importance_data:
234
  json_results["_feature_importance"] = {
235
- name: round(imp, 6) for name, imp in importance_data
236
  }
237
 
238
- with open(results_path, "w") as f:
239
  json.dump(json_results, f, indent=2)
240
 
241
- print(f"\nSaved:")
242
  print(f" Model: {model_path}")
243
  print(f" Scaler: {scaler_path}")
244
  print(f" Columns: {columns_path}")
@@ -253,116 +291,346 @@ def train(
253
  }
254
 
255
 
256
- def _build_candidates() -> list[tuple[str, Any]]:
257
- """Build list of classifier candidates to evaluate."""
258
- candidates: list[tuple[str, Any]] = [
259
- (
260
- "Logistic Regression",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  LogisticRegression(
262
- C=1.0,
263
- max_iter=1000,
264
  class_weight="balanced",
265
  random_state=42,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  ),
267
- ),
268
- (
269
- "Random Forest",
270
  RandomForestClassifier(
271
- n_estimators=200,
272
- max_depth=10,
273
- min_samples_leaf=10,
274
- min_samples_split=15,
275
- class_weight="balanced",
 
276
  random_state=42,
277
  n_jobs=-1,
278
  ),
279
- ),
280
- (
281
- "Gradient Boosting",
282
  GradientBoostingClassifier(
283
  n_estimators=200,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  max_depth=4,
285
- learning_rate=0.08,
286
  subsample=0.75,
287
- min_samples_leaf=12,
288
- min_samples_split=20,
289
  random_state=42,
290
  ),
291
- ),
292
- (
293
- "SVM (RBF)",
294
  SVC(
295
  kernel="rbf",
296
- C=10.0,
 
 
 
 
 
 
 
 
297
  gamma="scale",
298
  class_weight="balanced",
299
  probability=True,
300
  random_state=42,
301
  ),
302
- ),
303
- (
304
- "MLP Neural Network",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  MLPClassifier(
306
- hidden_layer_sizes=(128, 64, 32),
307
  activation="relu",
308
  solver="adam",
309
- alpha=0.001,
310
  learning_rate="adaptive",
311
  max_iter=500,
312
  early_stopping=True,
313
  validation_fraction=0.15,
314
  random_state=42,
315
  ),
316
- ),
317
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
  if HAS_XGB:
320
- candidates.append((
321
- "XGBoost",
322
  xgb.XGBClassifier(
323
- n_estimators=200,
324
  max_depth=4,
325
- learning_rate=0.08,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  subsample=0.75,
327
  colsample_bytree=0.75,
328
- min_child_weight=8,
329
- reg_alpha=0.3,
330
  reg_lambda=1.5,
331
  gamma=0.2,
332
- scale_pos_weight=1.0,
333
  eval_metric="logloss",
 
334
  random_state=42,
 
335
  verbosity=0,
336
  ),
337
- ))
338
 
339
  if HAS_LGBM:
340
- candidates.append((
341
- "LightGBM",
342
  lgb.LGBMClassifier(
343
- n_estimators=200,
344
- max_depth=4,
345
- learning_rate=0.08,
346
- num_leaves=12,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  subsample=0.75,
348
  colsample_bytree=0.75,
349
- min_child_weight=8,
350
  reg_alpha=0.3,
351
  reg_lambda=1.5,
352
  class_weight="balanced",
353
  random_state=42,
354
  verbose=-1,
355
  ),
356
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
- return candidates
 
 
 
359
 
360
 
361
  def _extract_importance(
362
  model: Any,
363
  feature_cols: list[str],
364
  ) -> list[tuple[str, float]]:
365
- """Extract feature importance from the trained model."""
366
  importances = None
367
 
368
  if hasattr(model, "feature_importances_"):
@@ -373,14 +641,13 @@ def _extract_importance(
373
  if importances is None:
374
  return []
375
 
376
- # Normalize to sum to 1
377
  total = np.sum(importances)
378
  if total > 0:
379
  importances = importances / total
380
 
381
  return sorted(
382
  zip(feature_cols, importances.tolist()),
383
- key=lambda x: x[1],
384
  reverse=True,
385
  )
386
 
 
2
  Comprehensive multi-model training pipeline for AURIS.
3
 
4
  Trains and evaluates multiple classifier families on extracted
5
+ audio features using stratified cross-validation, then selects
6
+ the best model and exports it for production use.
7
 
8
  Models compared:
9
+ - Logistic Regression
10
  - Random Forest
11
  - Gradient Boosting
 
 
12
  - Support Vector Machine (RBF)
13
+ - Multi-Layer Perceptron
14
+ - XGBoost (optional)
15
+ - LightGBM (optional)
16
 
17
  Usage:
18
  python -m app.training.train_classifier data/training/features.csv
19
 
20
  Outputs:
21
+ models/auris_classifier_v1.pkl - best trained model
22
+ models/feature_scaler_v1.pkl - fitted StandardScaler
23
+ models/feature_columns_v1.json - ordered feature column names
24
+ models/training_results.json - model metrics and metadata
25
  """
26
 
27
  from __future__ import annotations
 
31
  import pickle
32
  import sys
33
  import time
34
+ import warnings
35
  from pathlib import Path
36
  from typing import Any
37
 
38
  import numpy as np
39
+ from sklearn.base import clone
40
+ from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier
41
+ from sklearn.exceptions import ConvergenceWarning
 
 
42
  from sklearn.linear_model import LogisticRegression
 
 
 
 
 
 
 
43
  from sklearn.metrics import (
44
  accuracy_score,
45
  f1_score,
 
47
  recall_score,
48
  roc_auc_score,
49
  )
50
+ from sklearn.model_selection import StratifiedKFold, cross_val_predict, train_test_split
51
+ from sklearn.neural_network import MLPClassifier
52
+ from sklearn.pipeline import Pipeline
53
+ from sklearn.preprocessing import StandardScaler
54
+ from sklearn.svm import SVC
55
 
56
  # Optional: XGBoost
57
  try:
58
  import xgboost as xgb
59
+
60
  HAS_XGB = True
61
  except ImportError:
62
  HAS_XGB = False
 
64
  # Optional: LightGBM
65
  try:
66
  import lightgbm as lgb
67
+
68
  HAS_LGBM = True
69
  except ImportError:
70
  HAS_LGBM = False
71
 
72
  sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
73
+ from app.training.evaluate import evaluate_predictions, load_features_csv
74
+
75
+ _EXCLUDED_COLUMNS = {"file_path", "label_int", "duration_sec", "sample_rate"}
76
+ _TUNED_PARAM_KEYS: dict[str, tuple[str, ...]] = {
77
+ "Logistic Regression": ("C", "class_weight", "max_iter"),
78
+ "Random Forest": (
79
+ "n_estimators",
80
+ "max_depth",
81
+ "min_samples_leaf",
82
+ "min_samples_split",
83
+ "class_weight",
84
+ "max_features",
85
+ ),
86
+ "Gradient Boosting": (
87
+ "n_estimators",
88
+ "max_depth",
89
+ "learning_rate",
90
+ "subsample",
91
+ "min_samples_leaf",
92
+ "min_samples_split",
93
+ ),
94
+ "SVM (RBF)": ("C", "gamma", "class_weight"),
95
+ "MLP Neural Network": (
96
+ "hidden_layer_sizes",
97
+ "alpha",
98
+ "max_iter",
99
+ "validation_fraction",
100
+ ),
101
+ "XGBoost": (
102
+ "n_estimators",
103
+ "max_depth",
104
+ "learning_rate",
105
+ "subsample",
106
+ "colsample_bytree",
107
+ "min_child_weight",
108
+ "reg_alpha",
109
+ "reg_lambda",
110
+ "gamma",
111
+ ),
112
+ "LightGBM": (
113
+ "n_estimators",
114
+ "max_depth",
115
+ "learning_rate",
116
+ "num_leaves",
117
+ "subsample",
118
+ "colsample_bytree",
119
+ "min_child_samples",
120
+ "reg_alpha",
121
+ "reg_lambda",
122
+ ),
123
+ }
124
 
125
 
126
  def train(
 
134
  Returns:
135
  Dict with per-model metrics, best model info, and saved paths.
136
  """
137
+ features_csv = Path(features_csv)
138
  models_dir = Path(models_dir)
139
  models_dir.mkdir(parents=True, exist_ok=True)
140
 
 
141
  X, y = load_features_csv(features_csv)
142
+ feature_cols = _load_feature_columns(features_csv)
 
 
 
 
 
 
 
 
 
 
 
143
  X = np.nan_to_num(X, nan=0.0, posinf=1.0, neginf=-1.0)
144
 
145
+ selected_candidates, tuning_results = _select_best_candidates(X, y)
146
+ cv = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
147
+
148
  scaler = StandardScaler()
149
  X_scaled = scaler.fit_transform(X)
150
 
 
 
 
 
 
151
  best_name = ""
152
+ best_auc = -1.0
153
  all_results: dict[str, dict[str, Any]] = {}
154
 
155
+ for name, model in selected_candidates:
156
+ print("\n" + "-" * 56)
157
+ print(f"Training: {name}")
158
+ print("-" * 56)
159
 
160
  t0 = time.time()
161
+ pipeline = _build_eval_pipeline(model)
162
+ with warnings.catch_warnings():
163
+ warnings.simplefilter("ignore", category=ConvergenceWarning)
164
+ y_prob = cross_val_predict(
165
+ pipeline,
166
+ X,
167
+ y,
168
+ cv=cv,
169
+ method="predict_proba",
170
+ )[:, 1]
171
+ y_pred = (y_prob >= 0.5).astype(int)
172
+ cv_time = time.time() - t0
173
 
174
  acc = accuracy_score(y, y_pred)
175
  prec = precision_score(y, y_pred, zero_division=0)
 
177
  f1 = f1_score(y, y_pred, zero_division=0)
178
  auc = roc_auc_score(y, y_prob)
179
 
180
+ tuning_meta = tuning_results.get(name, {})
181
+ print(f" Validation AUC: {tuning_meta.get('validation_auc', 0.0):.4f}")
182
+ print(f" CV Accuracy: {acc:.4f}")
183
+ print(f" CV Precision: {prec:.4f}")
184
+ print(f" CV Recall: {rec:.4f}")
185
+ print(f" CV F1 Score: {f1:.4f}")
186
+ print(f" CV ROC-AUC: {auc:.4f}")
187
+ print(f" CV Time: {cv_time:.1f}s")
188
 
189
  all_results[name] = {
190
  "accuracy": round(acc, 4),
 
192
  "recall": round(rec, 4),
193
  "f1": round(f1, 4),
194
  "roc_auc": round(auc, 4),
195
+ "validation_auc": round(tuning_meta.get("validation_auc", 0.0), 4),
196
+ "selection_time_sec": round(tuning_meta.get("selection_time_sec", 0.0), 2),
197
+ "train_time_sec": round(cv_time, 2),
198
+ "selected_params": tuning_meta.get("selected_params", {}),
199
  "y_true": y.tolist(),
200
  "y_pred": y_pred.tolist(),
201
  "y_prob": y_prob.tolist(),
 
204
  if auc > best_auc:
205
  best_auc = auc
206
  best_name = name
 
207
 
208
+ print("\n" + "=" * 64)
209
+ print(f"BEST MODEL: {best_name} (ROC-AUC = {best_auc:.4f})")
210
+ print("=" * 64)
 
211
 
212
  y_prob_best = np.array(all_results[best_name]["y_prob"])
213
  y_pred_best = np.array(all_results[best_name]["y_pred"])
214
+ evaluate_predictions(y, y_pred_best, y_prob_best, title=f"Best: {best_name}")
215
 
216
+ fitted_models: dict[str, Any] = {}
 
 
 
 
 
217
  all_model_paths: dict[str, str] = {}
218
+ for name, model in selected_candidates:
219
+ print(f"\nFitting final {name} on all {len(y)} samples...")
220
+ final_model = clone(model)
221
+ with warnings.catch_warnings():
222
+ warnings.simplefilter("ignore", category=ConvergenceWarning)
223
+ final_model.fit(X_scaled, y)
224
+ fitted_models[name] = final_model
225
+
226
+ model_pkl = models_dir / f"model_{_safe_model_name(name)}.pkl"
227
  with open(model_pkl, "wb") as f:
228
+ pickle.dump(final_model, f)
229
  all_model_paths[name] = str(model_pkl)
230
+ print(f" Saved: {model_pkl}")
231
 
232
+ best_model = fitted_models[best_name]
 
 
 
 
 
 
233
  importance_data = _extract_importance(best_model, feature_cols)
234
  if importance_data:
235
  print("\nTop 15 features:")
236
  for fname, imp in importance_data[:15]:
237
+ print(f" {fname:<35} {imp:.4f}")
 
238
 
 
239
  model_path = models_dir / "auris_classifier_v1.pkl"
240
  scaler_path = models_dir / "feature_scaler_v1.pkl"
241
  columns_path = models_dir / "feature_columns_v1.json"
 
245
  pickle.dump(best_model, f)
246
  with open(scaler_path, "wb") as f:
247
  pickle.dump(scaler, f)
248
+ with open(columns_path, "w", encoding="utf-8") as f:
249
  json.dump(feature_cols, f, indent=2)
250
 
251
+ json_results: dict[str, Any] = {}
 
252
  for name, data in all_results.items():
253
  json_results[name] = {
254
+ key: value
255
+ for key, value in data.items()
256
+ if key not in ("y_true", "y_pred", "y_prob")
257
  }
258
  json_results["_best_model"] = best_name
259
  json_results["_n_samples"] = len(y)
260
  json_results["_n_features"] = X.shape[1]
261
  json_results["_n_folds"] = n_folds
262
+ json_results["_dataset_path"] = str(features_csv)
263
+ json_results["_class_balance"] = {
264
+ "ai": int(np.sum(y == 1)),
265
+ "human": int(np.sum(y == 0)),
266
+ }
267
+ json_results["_data_leakage_fix"] = (
268
+ "duration_sec and sample_rate removed from features; scaler fitted per fold during CV"
269
+ )
270
  json_results["_model_paths"] = all_model_paths
271
  if importance_data:
272
  json_results["_feature_importance"] = {
273
+ feature_name: round(imp, 6) for feature_name, imp in importance_data
274
  }
275
 
276
+ with open(results_path, "w", encoding="utf-8") as f:
277
  json.dump(json_results, f, indent=2)
278
 
279
+ print("\nSaved artifacts:")
280
  print(f" Model: {model_path}")
281
  print(f" Scaler: {scaler_path}")
282
  print(f" Columns: {columns_path}")
 
291
  }
292
 
293
 
294
+ def _load_feature_columns(features_csv: Path) -> list[str]:
295
+ with open(features_csv, "r", encoding="utf-8") as f:
296
+ reader = csv.DictReader(f)
297
+ return [
298
+ column
299
+ for column in (reader.fieldnames or [])
300
+ if column not in _EXCLUDED_COLUMNS
301
+ ]
302
+
303
+
304
+ def _select_best_candidates(
305
+ X: np.ndarray,
306
+ y: np.ndarray,
307
+ ) -> tuple[list[tuple[str, Any]], dict[str, dict[str, Any]]]:
308
+ """
309
+ Pick one tuned configuration per model family using a stratified holdout.
310
+ """
311
+ X_train, X_val, y_train, y_val = train_test_split(
312
+ X,
313
+ y,
314
+ test_size=0.2,
315
+ stratify=y,
316
+ random_state=42,
317
+ )
318
+
319
+ selected: list[tuple[str, Any]] = []
320
+ tuning_results: dict[str, dict[str, Any]] = {}
321
+
322
+ for name, variants in _build_candidate_families().items():
323
+ print("\n" + "." * 56)
324
+ print(f"Selecting hyperparameters for: {name}")
325
+ print("." * 56)
326
+
327
+ best_model = None
328
+ best_auc = -1.0
329
+ best_params: dict[str, Any] = {}
330
+ selection_start = time.time()
331
+
332
+ for idx, model in enumerate(variants, start=1):
333
+ pipeline = _build_eval_pipeline(model)
334
+ with warnings.catch_warnings():
335
+ warnings.simplefilter("ignore", category=ConvergenceWarning)
336
+ pipeline.fit(X_train, y_train)
337
+ y_prob = pipeline.predict_proba(X_val)[:, 1]
338
+ auc = roc_auc_score(y_val, y_prob)
339
+ params = _summarize_selected_params(name, model)
340
+
341
+ print(f" Candidate {idx}: holdout AUC={auc:.4f} | params={params}")
342
+ if auc > best_auc:
343
+ best_auc = auc
344
+ best_model = model
345
+ best_params = params
346
+
347
+ if best_model is None:
348
+ raise RuntimeError(f"No valid candidate selected for {name}")
349
+
350
+ tuning_results[name] = {
351
+ "validation_auc": float(best_auc),
352
+ "selected_params": best_params,
353
+ "selection_time_sec": time.time() - selection_start,
354
+ }
355
+ selected.append((name, best_model))
356
+ print(f" Selected {name}: AUC={best_auc:.4f}")
357
+
358
+ return selected, tuning_results
359
+
360
+
361
+ def _build_candidate_families() -> dict[str, list[Any]]:
362
+ families: dict[str, list[Any]] = {
363
+ "Logistic Regression": [
364
  LogisticRegression(
365
+ C=value,
366
+ max_iter=2500,
367
  class_weight="balanced",
368
  random_state=42,
369
+ )
370
+ for value in (0.25, 0.5, 1.0, 2.0)
371
+ ],
372
+ "Random Forest": [
373
+ RandomForestClassifier(
374
+ n_estimators=300,
375
+ max_depth=12,
376
+ min_samples_leaf=4,
377
+ min_samples_split=8,
378
+ max_features="sqrt",
379
+ class_weight="balanced_subsample",
380
+ random_state=42,
381
+ n_jobs=-1,
382
+ ),
383
+ RandomForestClassifier(
384
+ n_estimators=450,
385
+ max_depth=18,
386
+ min_samples_leaf=2,
387
+ min_samples_split=4,
388
+ max_features="sqrt",
389
+ class_weight="balanced_subsample",
390
+ random_state=42,
391
+ n_jobs=-1,
392
  ),
 
 
 
393
  RandomForestClassifier(
394
+ n_estimators=500,
395
+ max_depth=None,
396
+ min_samples_leaf=1,
397
+ min_samples_split=2,
398
+ max_features="log2",
399
+ class_weight="balanced_subsample",
400
  random_state=42,
401
  n_jobs=-1,
402
  ),
403
+ ],
404
+ "Gradient Boosting": [
 
405
  GradientBoostingClassifier(
406
  n_estimators=200,
407
+ max_depth=3,
408
+ learning_rate=0.05,
409
+ subsample=0.8,
410
+ min_samples_leaf=10,
411
+ min_samples_split=20,
412
+ random_state=42,
413
+ ),
414
+ GradientBoostingClassifier(
415
+ n_estimators=260,
416
+ max_depth=2,
417
+ learning_rate=0.04,
418
+ subsample=0.85,
419
+ min_samples_leaf=12,
420
+ min_samples_split=24,
421
+ random_state=42,
422
+ ),
423
+ GradientBoostingClassifier(
424
+ n_estimators=180,
425
  max_depth=4,
426
+ learning_rate=0.07,
427
  subsample=0.75,
428
+ min_samples_leaf=8,
429
+ min_samples_split=16,
430
  random_state=42,
431
  ),
432
+ ],
433
+ "SVM (RBF)": [
 
434
  SVC(
435
  kernel="rbf",
436
+ C=1.0,
437
+ gamma="scale",
438
+ class_weight="balanced",
439
+ probability=True,
440
+ random_state=42,
441
+ ),
442
+ SVC(
443
+ kernel="rbf",
444
+ C=3.0,
445
  gamma="scale",
446
  class_weight="balanced",
447
  probability=True,
448
  random_state=42,
449
  ),
450
+ SVC(
451
+ kernel="rbf",
452
+ C=6.0,
453
+ gamma=0.02,
454
+ class_weight="balanced",
455
+ probability=True,
456
+ random_state=42,
457
+ ),
458
+ SVC(
459
+ kernel="rbf",
460
+ C=10.0,
461
+ gamma=0.05,
462
+ class_weight="balanced",
463
+ probability=True,
464
+ random_state=42,
465
+ ),
466
+ ],
467
+ "MLP Neural Network": [
468
  MLPClassifier(
469
+ hidden_layer_sizes=(128, 64),
470
  activation="relu",
471
  solver="adam",
472
+ alpha=0.0005,
473
  learning_rate="adaptive",
474
  max_iter=500,
475
  early_stopping=True,
476
  validation_fraction=0.15,
477
  random_state=42,
478
  ),
479
+ MLPClassifier(
480
+ hidden_layer_sizes=(192, 96, 32),
481
+ activation="relu",
482
+ solver="adam",
483
+ alpha=0.001,
484
+ learning_rate="adaptive",
485
+ max_iter=600,
486
+ early_stopping=True,
487
+ validation_fraction=0.15,
488
+ random_state=42,
489
+ ),
490
+ MLPClassifier(
491
+ hidden_layer_sizes=(256, 128),
492
+ activation="relu",
493
+ solver="adam",
494
+ alpha=0.002,
495
+ learning_rate="adaptive",
496
+ max_iter=700,
497
+ early_stopping=True,
498
+ validation_fraction=0.15,
499
+ random_state=42,
500
+ ),
501
+ ],
502
+ }
503
 
504
  if HAS_XGB:
505
+ families["XGBoost"] = [
 
506
  xgb.XGBClassifier(
507
+ n_estimators=300,
508
  max_depth=4,
509
+ learning_rate=0.05,
510
+ subsample=0.8,
511
+ colsample_bytree=0.8,
512
+ min_child_weight=4,
513
+ reg_alpha=0.2,
514
+ reg_lambda=1.2,
515
+ gamma=0.1,
516
+ eval_metric="logloss",
517
+ tree_method="hist",
518
+ random_state=42,
519
+ n_jobs=-1,
520
+ verbosity=0,
521
+ ),
522
+ xgb.XGBClassifier(
523
+ n_estimators=500,
524
+ max_depth=3,
525
+ learning_rate=0.03,
526
+ subsample=0.9,
527
+ colsample_bytree=0.8,
528
+ min_child_weight=2,
529
+ reg_alpha=0.1,
530
+ reg_lambda=1.0,
531
+ gamma=0.0,
532
+ eval_metric="logloss",
533
+ tree_method="hist",
534
+ random_state=42,
535
+ n_jobs=-1,
536
+ verbosity=0,
537
+ ),
538
+ xgb.XGBClassifier(
539
+ n_estimators=240,
540
+ max_depth=5,
541
+ learning_rate=0.06,
542
  subsample=0.75,
543
  colsample_bytree=0.75,
544
+ min_child_weight=6,
545
+ reg_alpha=0.4,
546
  reg_lambda=1.5,
547
  gamma=0.2,
 
548
  eval_metric="logloss",
549
+ tree_method="hist",
550
  random_state=42,
551
+ n_jobs=-1,
552
  verbosity=0,
553
  ),
554
+ ]
555
 
556
  if HAS_LGBM:
557
+ families["LightGBM"] = [
 
558
  lgb.LGBMClassifier(
559
+ n_estimators=300,
560
+ max_depth=-1,
561
+ learning_rate=0.05,
562
+ num_leaves=31,
563
+ subsample=0.8,
564
+ colsample_bytree=0.8,
565
+ min_child_samples=20,
566
+ reg_alpha=0.1,
567
+ reg_lambda=1.0,
568
+ class_weight="balanced",
569
+ random_state=42,
570
+ verbose=-1,
571
+ ),
572
+ lgb.LGBMClassifier(
573
+ n_estimators=500,
574
+ max_depth=8,
575
+ learning_rate=0.03,
576
+ num_leaves=24,
577
+ subsample=0.9,
578
+ colsample_bytree=0.8,
579
+ min_child_samples=30,
580
+ reg_alpha=0.2,
581
+ reg_lambda=1.2,
582
+ class_weight="balanced",
583
+ random_state=42,
584
+ verbose=-1,
585
+ ),
586
+ lgb.LGBMClassifier(
587
+ n_estimators=220,
588
+ max_depth=6,
589
+ learning_rate=0.07,
590
+ num_leaves=18,
591
  subsample=0.75,
592
  colsample_bytree=0.75,
593
+ min_child_samples=24,
594
  reg_alpha=0.3,
595
  reg_lambda=1.5,
596
  class_weight="balanced",
597
  random_state=42,
598
  verbose=-1,
599
  ),
600
+ ]
601
+
602
+ return families
603
+
604
+
605
+ def _build_eval_pipeline(model: Any) -> Pipeline:
606
+ return Pipeline(
607
+ [
608
+ ("scaler", StandardScaler()),
609
+ ("model", clone(model)),
610
+ ]
611
+ )
612
+
613
+
614
+ def _safe_model_name(name: str) -> str:
615
+ return (
616
+ name.lower()
617
+ .replace(" ", "_")
618
+ .replace("(", "")
619
+ .replace(")", "")
620
+ .replace("/", "_")
621
+ )
622
+
623
 
624
+ def _summarize_selected_params(name: str, model: Any) -> dict[str, Any]:
625
+ tuned_keys = _TUNED_PARAM_KEYS.get(name, ())
626
+ params = model.get_params()
627
+ return {key: params[key] for key in tuned_keys if key in params}
628
 
629
 
630
  def _extract_importance(
631
  model: Any,
632
  feature_cols: list[str],
633
  ) -> list[tuple[str, float]]:
 
634
  importances = None
635
 
636
  if hasattr(model, "feature_importances_"):
 
641
  if importances is None:
642
  return []
643
 
 
644
  total = np.sum(importances)
645
  if total > 0:
646
  importances = importances / total
647
 
648
  return sorted(
649
  zip(feature_cols, importances.tolist()),
650
+ key=lambda item: item[1],
651
  reverse=True,
652
  )
653
 
local_demo.py CHANGED
@@ -1,574 +1,972 @@
1
  """
2
- AURIS Local Demo AI Music Detection
3
- Tüm eğitilmiş modelleri test edebileceğin local Gradio arayüzü.
4
 
5
- Çalıştır:
6
  python local_demo.py
7
  """
8
 
9
- import io
 
 
 
10
  import json
11
  import pickle
 
12
  import time
 
 
 
13
  from pathlib import Path
 
14
 
15
  import gradio as gr
16
  import numpy as np
17
 
18
- # ── Paths ───────────────────────────────────────────────────────
19
-
20
- MODELS_DIR = Path(__file__).parent / "models"
21
- FIGURES_DIR = Path(__file__).parent.parent / "docs" / "academic" / "figures"
22
-
23
- # ── Load artifacts ──────────────────────────────────────────────
24
-
25
- with open(MODELS_DIR / "auris_classifier_v1.pkl", "rb") as f:
26
- main_model = pickle.load(f)
27
-
28
- with open(MODELS_DIR / "feature_scaler_v1.pkl", "rb") as f:
29
- scaler = pickle.load(f)
30
-
31
- with open(MODELS_DIR / "feature_columns_v1.json", "r") as f:
32
- feature_cols = json.load(f)
33
-
34
- with open(MODELS_DIR / "training_results.json", "r") as f:
35
- training_results = json.load(f)
36
-
37
- best_model_name = training_results.get("_best_model", "Gradient Boosting")
38
- n_features = training_results.get("_n_features", 47)
39
- importance = training_results.get("_feature_importance", {})
40
- top_features = sorted(importance.items(), key=lambda x: x[1], reverse=True)[:15]
41
-
42
- # All model results sorted by AUC
43
- all_models = sorted(
44
- ((k, v) for k, v in training_results.items()
45
- if not k.startswith("_") and isinstance(v, dict)),
46
- key=lambda x: -x[1].get("roc_auc", 0),
47
- )
48
-
49
- print(f"AURIS Local Demo")
50
- print(f"Model: {best_model_name} | Features: {n_features}")
51
- print(f"Total models: {len(all_models)}")
52
-
53
-
54
- # ── Feature extraction ──────────────────────────────────────────
55
-
56
- def extract_features_from_audio(audio_path: str) -> tuple[dict, float]:
57
- """Extract 47 features from audio file using librosa."""
58
- import librosa
59
- from scipy import stats as sp_stats
60
-
61
- y, sr = librosa.load(audio_path, sr=22050, mono=True, duration=60.0)
62
- duration_sec = len(y) / sr
63
-
64
- # RMS energy
65
- rms = librosa.feature.rms(y=y, hop_length=512)[0]
66
- rms_mean = float(np.mean(rms))
67
- rms_std = float(np.std(rms))
68
- rms_dynamic_range = float(np.max(rms) - np.min(rms))
69
-
70
- # Spectral features
71
- cent = librosa.feature.spectral_centroid(y=y, sr=sr, hop_length=512)[0]
72
- flat = librosa.feature.spectral_flatness(y=y, hop_length=512)[0]
73
- bw = librosa.feature.spectral_bandwidth(y=y, sr=sr, hop_length=512)[0]
74
- rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr, hop_length=512)[0]
75
- contrast = librosa.feature.spectral_contrast(y=y, sr=sr, hop_length=512)
76
-
77
- # MFCCs
78
- mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13, hop_length=512)
79
- mfcc_delta = librosa.feature.delta(mfcc)
80
- mfcc_delta2 = librosa.feature.delta(mfcc, order=2)
81
-
82
- # Zero crossing
83
- zcr = librosa.feature.zero_crossing_rate(y, hop_length=512)[0]
84
-
85
- # Tempo
86
- tempo, beats = librosa.beat.beat_track(y=y, sr=sr, hop_length=512)
87
- tempo_val = float(np.atleast_1d(tempo)[0])
88
- beat_times = librosa.frames_to_time(beats, sr=sr, hop_length=512)
89
- if len(beat_times) > 1:
90
- ibi = np.diff(beat_times)
91
- tempo_stability = float(np.std(ibi))
92
- tempo_cv = float(np.std(ibi) / np.mean(ibi)) if np.mean(ibi) > 0 else 0.0
93
- else:
94
- tempo_stability = 0.0
95
- tempo_cv = 0.0
96
-
97
- # Chroma
98
- chroma = librosa.feature.chroma_stft(y=y, sr=sr, hop_length=512)
99
- chroma_std = float(np.mean(np.std(chroma, axis=1)))
100
- chroma_entropy = float(-np.sum(
101
- np.mean(chroma, axis=1) * np.log2(np.mean(chroma, axis=1) + 1e-10)
102
- ))
103
- chroma_diff = np.diff(chroma, axis=1)
104
- chroma_transition_rate = float(np.mean(np.abs(chroma_diff)))
105
-
106
- # Tonnetz
107
- tonnetz = librosa.feature.tonnetz(y=y, sr=sr)
108
- tonnetz_std = float(np.mean(np.std(tonnetz, axis=1)))
109
-
110
- # Harmonic ratio
111
- y_harm, y_perc = librosa.effects.hpss(y)
112
- harm_energy = float(np.sum(y_harm ** 2))
113
- perc_energy = float(np.sum(y_perc ** 2))
114
- total_energy = harm_energy + perc_energy + 1e-10
115
- harmonic_ratio = harm_energy / total_energy
116
-
117
- # Mel
118
- mel = librosa.feature.melspectrogram(y=y, sr=sr, hop_length=512)
119
- mel_flatness = float(np.mean(librosa.feature.spectral_flatness(S=mel)))
120
-
121
- # Onset
122
- onset_env = librosa.onset.onset_strength(y=y, sr=sr, hop_length=512)
123
-
124
- # Pitch
125
- pitches, magnitudes = librosa.piptrack(y=y, sr=sr, hop_length=512)
126
- pitch_vals = []
127
- for t in range(pitches.shape[1]):
128
- idx = magnitudes[:, t].argmax()
129
- p = pitches[idx, t]
130
- if p > 50:
131
- pitch_vals.append(p)
132
- pitch_mean_hz = float(np.mean(pitch_vals)) if pitch_vals else 0.0
133
- if len(pitch_vals) > 1 and pitch_mean_hz > 0:
134
- cents = 1200 * np.log2(np.array(pitch_vals) / pitch_mean_hz + 1e-10)
135
- pitch_std_cents = float(np.std(cents))
136
- else:
137
- pitch_std_cents = 0.0
138
-
139
- def _sigmoid(x, center=0.5, steepness=6.0):
140
- return 1.0 / (1.0 + np.exp(-steepness * (x - center)))
141
-
142
- spectral_regularity = float(_sigmoid(1.0 - float(np.std(flat)), 0.5, 4))
143
- temporal_patterns = float(_sigmoid(1.0 - tempo_cv, 0.6, 5) if tempo_cv > 0 else 0.5)
144
- harmonic_structure = float(_sigmoid(harmonic_ratio, 0.5, 4))
145
-
146
- feats = {
147
- "rms_energy": rms_mean,
148
- "rms_std": rms_std,
149
- "spectral_centroid_mean": float(np.mean(cent)),
150
- "spectral_centroid_std": float(np.std(cent)),
151
- "spectral_flatness_mean": float(np.mean(flat)),
152
- "spectral_flatness_std": float(np.std(flat)),
153
- "spectral_bandwidth_mean": float(np.mean(bw)),
154
- "spectral_bandwidth_std": float(np.std(bw)),
155
- "spectral_rolloff_mean": float(np.mean(rolloff)),
156
- "spectral_rolloff_std": float(np.std(rolloff)),
157
- "spectral_contrast_mean": float(np.mean(contrast)),
158
- "spectral_contrast_std": float(np.std(contrast)),
159
- "mfcc_variance": float(np.mean(np.var(mfcc, axis=1))),
160
- "mfcc_delta_var": float(np.mean(np.var(mfcc_delta, axis=1))),
161
- "mfcc_delta2_var": float(np.mean(np.var(mfcc_delta2, axis=1))),
162
- "zero_crossing_rate": float(np.mean(zcr)),
163
- "zero_crossing_std": float(np.std(zcr)),
164
- "tempo_bpm": tempo_val,
165
- "tempo_stability": tempo_stability,
166
- "tempo_cv": tempo_cv,
167
- "beat_count": float(len(beats)),
168
- "rms_dynamic_range": rms_dynamic_range,
169
- "chroma_std": chroma_std,
170
- "chroma_entropy": chroma_entropy,
171
- "chroma_transition_rate": chroma_transition_rate,
172
- "tonnetz_std": tonnetz_std,
173
- "harmonic_ratio": harmonic_ratio,
174
- "mel_flatness": mel_flatness,
175
- "onset_strength_mean": float(np.mean(onset_env)),
176
- "onset_strength_std": float(np.std(onset_env)),
177
- "pitch_mean_hz": pitch_mean_hz,
178
- "pitch_std_cents": pitch_std_cents,
179
- "spectral_regularity": spectral_regularity,
180
- "temporal_patterns": temporal_patterns,
181
- "harmonic_structure": harmonic_structure,
182
- "vocal_confidence": 0.0,
183
- "vocal_ai_score": 0.0,
184
- "vocal_energy_ratio": 0.0,
185
- "vocal_harmonic_ratio": 0.0,
186
- "vocal_texture_score": 0.0,
187
- "has_vocals": 0.0,
188
- "pitch_stability_score": float(_sigmoid(1.0 - min(pitch_std_cents / 200, 1.0), 0.5, 4)),
189
- "vibrato_rate_hz": 0.0,
190
- "vibrato_extent_cents": 0.0,
191
- "vibrato_regularity_score": 0.0,
192
- "formant_consistency_score": 0.0,
193
- "breath_pattern_score": float(_sigmoid(rms_dynamic_range, 0.3, 5)),
194
  }
195
- return feats, duration_sec
196
 
197
 
198
- # ── Prediction ──────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
- def predict(audio_file):
201
- """Run AURIS model on uploaded audio and return rich results."""
202
- if audio_file is None:
203
- return "Dosya yükleyin", None, None, ""
204
 
205
- import matplotlib
206
- matplotlib.use("Agg")
207
- import matplotlib.pyplot as plt
208
 
209
- t0 = time.time()
 
 
 
 
210
 
211
- if isinstance(audio_file, tuple):
212
- audio_path = audio_file[0] if isinstance(audio_file[0], str) else None
213
- if audio_path is None:
214
- return "Gecersiz dosya", None, None, ""
215
- else:
216
- audio_path = audio_file
 
 
 
 
 
 
 
 
217
 
218
- try:
219
- feats, duration = extract_features_from_audio(audio_path)
220
- except Exception as e:
221
- return f"Hata: {e}", None, None, ""
222
-
223
- X = np.array([[feats.get(col, 0.0) for col in feature_cols]], dtype=np.float32)
224
- X = np.nan_to_num(X, nan=0.0, posinf=1.0, neginf=-1.0)
225
- X_scaled = scaler.transform(X)
226
-
227
- elapsed = time.time() - t0
228
- prob = main_model.predict_proba(X_scaled)[0]
229
- ai_prob = float(prob[1])
230
- human_prob = float(prob[0])
231
-
232
- if ai_prob > 0.8:
233
- verdict = f"AI Uretimi Muzik Tespit Edildi | %{ai_prob*100:.1f} guven"
234
- elif ai_prob > 0.5:
235
- verdict = f"Muhtemelen AI Uretimi | %{ai_prob*100:.1f} guven"
236
- elif ai_prob > 0.3:
237
- verdict = f"Muhtemelen Insan Yapimi | %{human_prob*100:.1f} guven"
238
- else:
239
- verdict = f"Insan Yapimi Muzik | %{human_prob*100:.1f} guven"
240
-
241
- sr_pct = feats["spectral_regularity"] * 100
242
- tp_pct = feats["temporal_patterns"] * 100
243
- hs_pct = feats["harmonic_structure"] * 100
244
-
245
- # ── Main dashboard plot (16:9) ──
246
- fig = plt.figure(figsize=(16, 9), facecolor="#1a1207")
247
-
248
- # ── Left: Gauge + verdict ──
249
- ax_gauge = fig.add_axes([0.02, 0.45, 0.28, 0.50], projection="polar")
250
- ax_gauge.set_facecolor("#1a1207")
251
- theta = np.linspace(np.pi, 0, 100)
252
- r = np.ones(100)
253
- ax_gauge.plot(theta, r, color="#3d2817", linewidth=24, alpha=0.4)
254
- score_end = max(1, int(ai_prob * 100))
255
- c = "#7fb069" if ai_prob < 0.4 else "#c99347" if ai_prob < 0.7 else "#a64b3c"
256
- ax_gauge.plot(theta[:score_end], r[:score_end], color=c, linewidth=24)
257
- needle = np.pi - ai_prob * np.pi
258
- ax_gauge.plot([needle, needle], [0, 0.82], color="#faf6ed", linewidth=2.5)
259
- ax_gauge.scatter([needle], [0.82], color="#faf6ed", s=40, zorder=5)
260
- ax_gauge.set_ylim(0, 1.2)
261
- ax_gauge.set_yticklabels([])
262
- ax_gauge.set_xticklabels([])
263
- ax_gauge.spines["polar"].set_visible(False)
264
- ax_gauge.grid(False)
265
-
266
- fig.text(0.16, 0.42, f"%{ai_prob*100:.0f}", ha="center", va="center",
267
- fontsize=42, fontweight="bold", color="#faf6ed")
268
- fig.text(0.16, 0.36, "AI Olasiligi", ha="center", va="center",
269
- fontsize=12, color="#c99347")
270
-
271
- label = "AI URETIMI" if ai_prob > 0.5 else "INSAN YAPIMI"
272
- label_color = "#a64b3c" if ai_prob > 0.5 else "#7fb069"
273
- fig.text(0.16, 0.30, label, ha="center", va="center",
274
- fontsize=16, fontweight="bold", color=label_color,
275
- bbox=dict(boxstyle="round,pad=0.4", facecolor="#2a1f10",
276
- edgecolor=label_color, linewidth=2))
277
-
278
- # ── Middle: Feature bars ──
279
- ax_bars = fig.add_axes([0.35, 0.55, 0.28, 0.35])
280
- ax_bars.set_facecolor("#2a1f10")
281
- bars = [
282
- ("Spektral Duzenlilik", sr_pct),
283
- ("Zamansal Oruntuler", tp_pct),
284
- ("Harmonik Yapi", hs_pct),
285
- ]
286
- y_pos = np.arange(len(bars))
287
- vals = [v for _, v in bars]
288
- colors_b = ["#c99347" if v > 60 else "#7fb069" if v < 40 else "#e7c77a" for v in vals]
289
-
290
- ax_bars.barh(y_pos, vals, color=colors_b, edgecolor="#3d2817", height=0.55)
291
- ax_bars.set_yticks(y_pos)
292
- ax_bars.set_yticklabels([n for n, _ in bars], color="#faf6ed", fontsize=11)
293
- ax_bars.set_xlim(0, 100)
294
- ax_bars.set_xlabel("Skor (%)", color="#c99347", fontsize=10)
295
- ax_bars.tick_params(colors="#c99347", labelsize=9)
296
- for spine in ax_bars.spines.values():
297
- spine.set_color("#3d2817")
298
- for i, v in enumerate(vals):
299
- ax_bars.text(v + 1.5, i, f"%{v:.0f}", va="center", color="#faf6ed",
300
- fontsize=11, fontweight="bold")
301
- ax_bars.set_title("Ses Ozellik Analizi", color="#c99347", fontsize=13,
302
- fontweight="bold", pad=8)
303
-
304
- # ── Right: Top feature importance ──
305
- ax_imp = fig.add_axes([0.70, 0.35, 0.27, 0.58])
306
- ax_imp.set_facecolor("#2a1f10")
307
- top10 = top_features[:10]
308
- imp_names = [n for n, _ in top10]
309
- imp_vals = [v * 100 for _, v in top10]
310
- imp_colors = plt.cm.copper(np.linspace(0.3, 0.85, len(imp_names)))
311
-
312
- ax_imp.barh(np.arange(len(imp_names)), imp_vals, color=imp_colors,
313
- edgecolor="#3d2817", height=0.6)
314
- ax_imp.set_yticks(np.arange(len(imp_names)))
315
- ax_imp.set_yticklabels(imp_names, color="#faf6ed", fontsize=8)
316
- ax_imp.invert_yaxis()
317
- ax_imp.set_xlabel("Onem (%)", color="#c99347", fontsize=9)
318
- ax_imp.tick_params(colors="#c99347", labelsize=8)
319
- for spine in ax_imp.spines.values():
320
- spine.set_color("#3d2817")
321
- ax_imp.set_title("En Onemli Ozellikler", color="#c99347", fontsize=12,
322
- fontweight="bold", pad=8)
323
-
324
- # ── Bottom: Info strip ──
325
- info_text = (
326
- f"Model: {best_model_name} | Sure: {duration:.1f}s | "
327
- f"Islem: {elapsed:.2f}s | Ozellik: {n_features} | "
328
- f"Tempo: {feats['tempo_bpm']:.0f} BPM | "
329
- f"RMS: {feats['rms_energy']:.4f}"
330
  )
331
- fig.text(0.50, 0.06, info_text, ha="center", va="center",
332
- fontsize=10, color="#c99347",
333
- bbox=dict(boxstyle="round,pad=0.6", facecolor="#2a1f10",
334
- edgecolor="#3d2817", linewidth=1))
335
-
336
- # ── Title ──
337
- fig.text(0.50, 0.97, "AURIS — AI Music Detection System",
338
- ha="center", va="top", fontsize=20, fontweight="bold",
339
- color="#c99347")
340
- fig.text(0.50, 0.93,
341
- f"{best_model_name} | AUC={training_results.get(best_model_name, {}).get('roc_auc', 0):.4f}",
342
- ha="center", va="top", fontsize=11, color="#faf6ed", alpha=0.7)
343
-
344
- # ── Bottom left: Mini model comparison ──
345
- ax_models = fig.add_axes([0.04, 0.12, 0.58, 0.22])
346
- ax_models.set_facecolor("#2a1f10")
347
- model_names = [n for n, _ in all_models]
348
- model_aucs = [d.get("roc_auc", 0) for _, d in all_models]
349
- model_types = []
350
- for _, d in all_models:
351
- if d.get("type") == "deep_learning":
352
- model_types.append("#a64b3c")
353
- else:
354
- model_types.append("#c99347")
355
-
356
- x_pos = np.arange(len(model_names))
357
- ax_models.bar(x_pos, model_aucs, color=model_types, edgecolor="#3d2817",
358
- width=0.7)
359
- ax_models.set_xticks(x_pos)
360
- ax_models.set_xticklabels(model_names, rotation=30, ha="right",
361
- color="#faf6ed", fontsize=7)
362
- ax_models.set_ylabel("ROC-AUC", color="#c99347", fontsize=9)
363
- ax_models.set_ylim(0.80, 0.97)
364
- ax_models.tick_params(colors="#c99347", labelsize=8)
365
- for spine in ax_models.spines.values():
366
- spine.set_color("#3d2817")
367
- ax_models.set_title("Tum Modeller (sari=ML, kirmizi=DL)", color="#c99347",
368
- fontsize=10, fontweight="bold", pad=6)
369
- for i, v in enumerate(model_aucs):
370
- ax_models.text(i, v + 0.002, f"{v:.3f}", ha="center", va="bottom",
371
- color="#faf6ed", fontsize=6)
372
-
373
- dashboard_path = str(Path(__file__).parent / "_dashboard_temp.png")
374
- plt.savefig(dashboard_path, dpi=120, bbox_inches="tight",
375
- facecolor="#1a1207", edgecolor="none")
376
- plt.close()
377
-
378
- # ── Details markdown ──
379
- details_md = f"""
380
- ## Detayli Sonuclar
381
-
382
- | Metrik | Deger |
383
- |--------|-------|
384
- | AI Olasiligi | %{ai_prob*100:.2f} |
385
- | Insan Olasiligi | %{human_prob*100:.2f} |
386
- | Model | {best_model_name} |
387
- | Audio Suresi | {duration:.1f}s |
388
- | Islem Suresi | {elapsed:.2f}s |
389
- | Tempo | {feats['tempo_bpm']:.1f} BPM |
390
- | RMS Energy | {feats['rms_energy']:.6f} |
391
- | Spectral Centroid | {feats['spectral_centroid_mean']:.1f} Hz |
392
- | Spectral Flatness | {feats['spectral_flatness_mean']:.6f} |
393
- | Harmonic Ratio | {feats['harmonic_ratio']:.4f} |
394
- | Zero Crossing Rate | {feats['zero_crossing_rate']:.6f} |
395
- | Beat Count | {feats['beat_count']:.0f} |
396
-
397
- ## Tum {n_features} Ozellik Degerleri
398
-
399
- | Ozellik | Deger | Global Onem |
400
- |---------|-------|-------------|
401
- """
402
- for col in feature_cols:
403
- val = feats.get(col, 0.0)
404
- imp_val = importance.get(col, 0.0)
405
- bar = "█" * int(imp_val * 200)
406
- details_md += f"| {col} | {val:.6f} | {imp_val:.4f} {bar} |\n"
407
 
408
- return verdict, dashboard_path, None, details_md
409
 
 
410
 
411
- # ── Figures gallery ─────────────────────────────────────────────
412
 
413
- def get_figure_paths():
414
- if FIGURES_DIR.exists():
415
- return sorted(str(p) for p in FIGURES_DIR.glob("*.png"))
416
- return []
417
 
 
 
 
 
 
 
418
 
419
- # ── Model comparison table ──────────────────────────────────────
420
 
421
- ALL_MODELS_MD = """## Tum Egitilmis Model Sonuclari
 
422
 
423
- > **En iyi model**: {best} | **Veri**: {n_samples} ornek | **Ozellik**: {n_feat} | **CV**: 5-fold stratified
424
 
425
- ### Makine Ogrenmesi (ML) Modelleri
 
 
 
426
 
427
- | Model | Accuracy | Precision | Recall | F1 Score | ROC-AUC | Egitim Suresi |
428
- |-------|----------|-----------|--------|----------|---------|---------------|
429
- """.format(
430
- best=best_model_name,
431
- n_samples=training_results.get("_n_samples", "?"),
432
- n_feat=n_features,
433
- )
434
 
435
- DL_MODELS_MD = """
436
- ### Derin Ogrenme (DL) Modelleri
437
 
438
- | Model | Accuracy | Precision | Recall | F1 Score | ROC-AUC | Egitim Suresi |
439
- |-------|----------|-----------|--------|----------|---------|---------------|
440
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
 
442
- for name, data in all_models:
443
- row = (
444
- f"| {'**' + name + '**' if name == best_model_name else name} | "
445
- f"{data.get('accuracy', 0):.4f} | "
446
- f"{data.get('precision', 0):.4f} | "
447
- f"{data.get('recall', 0):.4f} | "
448
- f"{data.get('f1', 0):.4f} | "
449
- f"{data.get('roc_auc', 0):.4f} | "
450
- f"{data.get('train_time_sec', 0):.1f}s |\n"
 
 
451
  )
452
- if data.get("type") == "deep_learning":
453
- DL_MODELS_MD += row
454
- else:
455
- ALL_MODELS_MD += row
456
 
457
- ALL_MODELS_MD += DL_MODELS_MD
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
 
459
- ALL_MODELS_MD += f"""
460
- ### Ozellik Onemi (En Iyi Model: {best_model_name})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
 
462
- | Sira | Ozellik | Onem | |
463
- |------|---------|------|-|
464
- """
465
- for i, (fname, imp) in enumerate(top_features, 1):
466
- bar = "█" * int(imp * 300)
467
- ALL_MODELS_MD += f"| {i} | {fname} | {imp:.4f} | {bar} |\n"
468
-
469
- ALL_MODELS_MD += f"""
470
- ### Notlar
471
- - **Veri sizintisi duzeltildi**: `duration_sec` ve `sample_rate` ozelliklerden cikarildi (v2)
472
- - **Regularizasyon**: max_depth=4, subsample=0.75, min_samples_leaf=12
473
- - Tree ensemble modellerinde train AUC=1.0 yapisal bir ozelliktir (overfitting degil)
474
- - DL modelleri (Deep MLP, Residual MLP) regularize edilmis ML'yi geciyor
475
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
 
477
- # ── Gradio UI ───────────────────────────────────────────────────
478
 
479
  AURIS_CSS = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  .gradio-container {
481
- background: linear-gradient(135deg, #1a1207 0%, #2a1f10 50%, #1a1207 100%) !important;
482
- max-width: 1400px !important;
483
- }
484
- h1, h2, h3 { color: #c99347 !important; }
485
- p, span, label, td, th { color: #faf6ed !important; }
486
- table { border-color: #3d2817 !important; }
487
- th { background: #2a1f10 !important; color: #c99347 !important; }
488
- footer { display: none !important; }
489
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
 
491
- HEADER_HTML = f"""
492
- <div style="text-align:center; padding: 16px 0;">
493
- <h1 style="color:#c99347; margin:0; font-size:2.2em;">AURIS</h1>
494
- <p style="color:#faf6ed; opacity:0.8; margin:4px 0;">
495
- AI Music Detection System — Yapay Zeka Muzik Tespit Platformu
496
- </p>
497
- <p style="color:#c99347; font-size:0.9em;">
498
- {best_model_name} | {n_features} ozellik |
499
- {training_results.get('_n_samples', '?')} ornek |
500
- AUC: {training_results.get(best_model_name, {}).get('roc_auc', '?')}
501
- </p>
502
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  """
504
 
505
- with gr.Blocks(title="AURIS — AI Music Detection") as demo:
506
 
507
- gr.HTML(HEADER_HTML)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
 
509
  with gr.Tabs():
510
  with gr.Tab("Analiz"):
511
- with gr.Row():
512
- with gr.Column(scale=1):
513
  audio_input = gr.Audio(
514
- label="Audio Dosyasi Yukle (.mp3, .wav, .flac)",
515
  type="filepath",
516
  )
517
- analyze_btn = gr.Button(
518
- "Analiz Et",
519
- variant="primary",
520
- size="lg",
 
521
  )
522
- verdict_text = gr.Textbox(
523
- label="Sonuc",
524
- interactive=False,
525
- lines=2,
526
- )
527
-
528
- with gr.Column(scale=2):
529
- dashboard_img = gr.Image(
530
- label="AURIS Dashboard",
531
- type="filepath",
532
- height=500,
533
- )
534
-
535
- details_output = gr.Markdown()
536
-
537
- analyze_btn.click(
538
- fn=predict,
539
- inputs=[audio_input],
540
- outputs=[verdict_text, dashboard_img, gr.State(), details_output],
541
  )
542
 
543
- with gr.Tab("Model Karsilastirmasi"):
544
- gr.Markdown(ALL_MODELS_MD)
545
 
546
- with gr.Tab("Akademik Gorseller"):
547
- gr.Markdown("## Egitim ve Degerlendirme Gorselleri")
548
- figure_paths = get_figure_paths()
 
 
549
  if figure_paths:
550
  gr.Gallery(
551
  value=figure_paths,
552
- label="Figures",
553
  columns=3,
554
  height="auto",
555
  object_fit="contain",
556
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
 
558
- gr.HTML("""
559
- <div style="text-align:center; padding:12px; opacity:0.6;">
560
- <p style="color:#c99347; font-size:0.85em;">
561
- AURIS v1 — Duzce Universitesi Bilgisayar Muhendisligi Bitirme Projesi<br>
562
- Hasan Arthur Altuntas — 2026
563
- </p>
564
- </div>
565
- """)
566
 
567
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
568
  demo.launch(
569
- server_name="0.0.0.0",
570
- server_port=7862,
571
  share=False,
572
- inbrowser=True,
 
573
  css=AURIS_CSS,
574
  )
 
1
  """
2
+ AURIS Local Demo - AI Music Detection
 
3
 
4
+ Calistir:
5
  python local_demo.py
6
  """
7
 
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import csv
12
  import json
13
  import pickle
14
+ import socket
15
  import time
16
+ import warnings
17
+ from collections import Counter
18
+ from dataclasses import dataclass
19
  from pathlib import Path
20
+ from typing import Any
21
 
22
  import gradio as gr
23
  import numpy as np
24
 
25
+ from app.training.extract_features_batch import extract_sample_features
26
+
27
+ BASE_DIR = Path(__file__).resolve().parent
28
+ PROJECT_ROOT = BASE_DIR.parent
29
+ MODELS_DIR = BASE_DIR / "models"
30
+ FIGURES_DIR = PROJECT_ROOT / "docs" / "academic" / "figures"
31
+ DATASET_DIR = PROJECT_ROOT / "DataSet"
32
+ TEST_AUDIO_DIR = BASE_DIR / "test_audio"
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class DemoArtifacts:
37
+ feature_cols: list[str]
38
+ training_results: dict[str, Any]
39
+ scaler: Any
40
+ loaded_models: dict[str, Any]
41
+ best_model_name: str
42
+ best_model_label: str
43
+ model_labels: list[str]
44
+ label_to_name: dict[str, str]
45
+ feature_importance: dict[str, float]
46
+ feature_stats: dict[str, Any]
47
+ dataset_summary: dict[str, Any]
48
+
49
+
50
+ def _safe_model_name(name: str) -> str:
51
+ return (
52
+ name.lower()
53
+ .replace(" ", "_")
54
+ .replace("(", "")
55
+ .replace(")", "")
56
+ .replace("/", "_")
57
+ )
58
+
59
+
60
+ def _load_pickle(path: Path) -> Any:
61
+ with open(path, "rb") as f:
62
+ return pickle.load(f)
63
+
64
+
65
+ def _load_json(path: Path) -> dict[str, Any]:
66
+ with open(path, "r", encoding="utf-8") as f:
67
+ return json.load(f)
68
+
69
+
70
+ def _require_file(path: Path) -> None:
71
+ if not path.exists():
72
+ raise FileNotFoundError(f"Missing required artifact: {path}")
73
+
74
+
75
+ def _load_feature_stats() -> dict[str, Any]:
76
+ stats_path = MODELS_DIR / "feature_stats_v1.json"
77
+ if not stats_path.exists():
78
+ return {}
79
+ return _load_json(stats_path)
80
+
81
+
82
+ def _load_dataset_summary() -> dict[str, Any]:
83
+ manifest_path = DATASET_DIR / "manifest.csv"
84
+ if not manifest_path.exists():
85
+ return {}
86
+
87
+ label_counts: Counter[str] = Counter()
88
+ generator_counts: Counter[str] = Counter()
89
+ total = 0
90
+
91
+ with open(manifest_path, "r", encoding="utf-8") as f:
92
+ reader = csv.DictReader(f)
93
+ for row in reader:
94
+ total += 1
95
+ label = row.get("label", "").strip() or str(row.get("label_int", ""))
96
+ generator = row.get("generator", "").strip() or "unknown"
97
+ label_counts[label] += 1
98
+ generator_counts[generator] += 1
99
+
100
+ return {
101
+ "manifest_path": str(manifest_path),
102
+ "total": total,
103
+ "ai": label_counts.get("ai", 0) + label_counts.get("1", 0),
104
+ "human": label_counts.get("human", 0) + label_counts.get("0", 0),
105
+ "generators": generator_counts.most_common(8),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
 
107
 
108
 
109
+ def _find_matching_name(raw_name: str, training_results: dict[str, Any]) -> str:
110
+ for name in training_results:
111
+ if name.startswith("_"):
112
+ continue
113
+ if _safe_model_name(name) == raw_name:
114
+ return name
115
+ return raw_name.replace("_", " ").title()
116
+
117
+
118
+ def _is_model_compatible(model: Any, n_features: int) -> bool:
119
+ expected = getattr(model, "n_features_in_", None)
120
+ return expected in (None, n_features)
121
+
122
+
123
+ def _load_artifacts() -> DemoArtifacts:
124
+ scaler_path = MODELS_DIR / "feature_scaler_v1.pkl"
125
+ columns_path = MODELS_DIR / "feature_columns_v1.json"
126
+ results_path = MODELS_DIR / "training_results.json"
127
+ best_model_path = MODELS_DIR / "auris_classifier_v1.pkl"
128
+
129
+ for required in (scaler_path, columns_path, results_path, best_model_path):
130
+ _require_file(required)
131
+
132
+ scaler = _load_pickle(scaler_path)
133
+ feature_cols = _load_json(columns_path)
134
+ training_results = _load_json(results_path)
135
+ feature_importance = training_results.get("_feature_importance", {})
136
+ best_model_name = training_results.get("_best_model", "Gradient Boosting")
137
+
138
+ loaded_models: dict[str, Any] = {}
139
+ for model_path in sorted(MODELS_DIR.glob("model_*.pkl")):
140
+ try:
141
+ model = _load_pickle(model_path)
142
+ except Exception as exc: # noqa: BLE001
143
+ print(f"Skipping model file {model_path.name}: {exc}")
144
+ continue
145
+
146
+ raw_name = model_path.stem.replace("model_", "")
147
+ model_name = _find_matching_name(raw_name, training_results)
148
+ if not _is_model_compatible(model, len(feature_cols)):
149
+ print(
150
+ f"Skipping incompatible model {model_path.name}: "
151
+ f"expected {len(feature_cols)} features"
152
+ )
153
+ continue
154
+ loaded_models[model_name] = model
155
 
156
+ if best_model_name not in loaded_models:
157
+ best_model = _load_pickle(best_model_path)
158
+ if _is_model_compatible(best_model, len(feature_cols)):
159
+ loaded_models[best_model_name] = best_model
160
 
161
+ if not loaded_models:
162
+ raise RuntimeError("No compatible models were found in the models directory.")
 
163
 
164
+ sorted_names = sorted(
165
+ loaded_models,
166
+ key=lambda name: training_results.get(name, {}).get("roc_auc", 0.0),
167
+ reverse=True,
168
+ )
169
 
170
+ label_to_name: dict[str, str] = {}
171
+ model_labels: list[str] = []
172
+ for name in sorted_names:
173
+ result = training_results.get(name, {})
174
+ auc = result.get("roc_auc", 0.0)
175
+ acc = result.get("accuracy", 0.0)
176
+ badge = " [EN IYI]" if name == best_model_name else ""
177
+ label = f"{name}{badge} | AUC {auc:.3f} | Acc {acc:.1%}"
178
+ label_to_name[label] = name
179
+ model_labels.append(label)
180
+
181
+ best_model_label = next(
182
+ label for label, name in label_to_name.items() if name == best_model_name
183
+ )
184
 
185
+ return DemoArtifacts(
186
+ feature_cols=feature_cols,
187
+ training_results=training_results,
188
+ scaler=scaler,
189
+ loaded_models=loaded_models,
190
+ best_model_name=best_model_name,
191
+ best_model_label=best_model_label,
192
+ model_labels=model_labels,
193
+ label_to_name=label_to_name,
194
+ feature_importance=feature_importance,
195
+ feature_stats=_load_feature_stats(),
196
+ dataset_summary=_load_dataset_summary(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
 
199
 
200
+ ARTIFACTS = _load_artifacts()
201
 
 
202
 
203
+ def _example_audio_paths(limit: int = 6) -> list[list[str]]:
204
+ if not TEST_AUDIO_DIR.exists():
205
+ return []
 
206
 
207
+ candidates = sorted(
208
+ path
209
+ for path in TEST_AUDIO_DIR.iterdir()
210
+ if path.is_file() and path.suffix.lower() in {".wav", ".mp3", ".flac"}
211
+ )
212
+ return [[str(path)] for path in candidates[:limit]]
213
 
 
214
 
215
+ def _normalize_score(value: float, cap: float = 1.0) -> float:
216
+ return max(0.0, min(float(value), cap))
217
 
 
218
 
219
+ def _extract_demo_features(audio_path: str) -> tuple[dict[str, float], float]:
220
+ row = extract_sample_features(audio_path)
221
+ if row is None:
222
+ raise RuntimeError("Ozellik cikarimi basarisiz oldu.")
223
 
224
+ features = {
225
+ column: float(row.get(column, 0.0))
226
+ for column in ARTIFACTS.feature_cols
227
+ }
228
+ duration_sec = float(row.get("duration_sec", 0.0))
229
+ return features, duration_sec
 
230
 
 
 
231
 
232
+ def _build_feature_vector(features: dict[str, float]) -> np.ndarray:
233
+ vector = np.array(
234
+ [[features.get(column, 0.0) for column in ARTIFACTS.feature_cols]],
235
+ dtype=np.float32,
236
+ )
237
+ return np.nan_to_num(vector, nan=0.0, posinf=1.0, neginf=-1.0)
238
+
239
+
240
+ def _format_verdict(ai_prob: float) -> tuple[str, str, str]:
241
+ if ai_prob >= 0.75:
242
+ return "ai-high", "Yuksek AI ihtimali", "AI kaynakli izler baskin"
243
+ if ai_prob >= 0.55:
244
+ return "ai-mid", "AI olasiligi yuksek", "Model sentetik duzene yakin buldu"
245
+ if ai_prob >= 0.40:
246
+ return "human-mid", "Sinirda sonuc", "Insan ve AI sinyalleri birbirine yakin"
247
+ return "human-high", "Insan yapimiya yakin", "Dogal varyasyon daha guclu"
248
+
249
+
250
+ def _build_result_html(
251
+ ai_prob: float,
252
+ duration: float,
253
+ elapsed: float,
254
+ selected_model_name: str,
255
+ ) -> str:
256
+ verdict_class, verdict_title, verdict_subtitle = _format_verdict(ai_prob)
257
+ confidence_pct = ai_prob * 100
258
+ human_pct = (1.0 - ai_prob) * 100
259
+
260
+ return f"""
261
+ <section class="hero-card {verdict_class}">
262
+ <div class="hero-card__eyebrow">Canli analiz sonucu</div>
263
+ <div class="hero-card__score">%{confidence_pct:.1f}</div>
264
+ <div class="hero-card__title">{verdict_title}</div>
265
+ <div class="hero-card__subtitle">{verdict_subtitle}</div>
266
+ <div class="hero-card__meta">
267
+ <span>Model: {selected_model_name}</span>
268
+ <span>Sure: {duration:.1f}s</span>
269
+ <span>Islem: {elapsed:.2f}s</span>
270
+ <span>Insan olasiligi: %{human_pct:.1f}</span>
271
+ </div>
272
+ </section>
273
+ """
274
+
275
+
276
+ def _build_signal_html(features: dict[str, float]) -> str:
277
+ rows = [
278
+ ("Spektral duzen", _normalize_score(features.get("spectral_regularity", 0.0))),
279
+ ("Zamansal kalip", _normalize_score(features.get("temporal_patterns", 0.0))),
280
+ ("Harmonik yapi", _normalize_score(features.get("harmonic_structure", 0.0))),
281
+ ("Vokal AI izi", _normalize_score(features.get("vocal_ai_score", 0.0))),
282
+ ("Vokal guveni", _normalize_score(features.get("vocal_confidence", 0.0))),
283
+ ("Pitch stabilitesi", _normalize_score(features.get("pitch_stability_score", 0.0))),
284
+ ]
285
+
286
+ parts = ['<section class="panel-card"><div class="panel-card__title">Sinyal panosu</div>']
287
+ for label, raw_value in rows:
288
+ pct = raw_value * 100
289
+ bar_class = "bar-warm" if pct >= 60 else "bar-cool" if pct <= 35 else "bar-mid"
290
+ parts.append(
291
+ f"""
292
+ <div class="meter-row">
293
+ <div class="meter-row__label">{label}</div>
294
+ <div class="meter-row__track">
295
+ <div class="meter-row__fill {bar_class}" style="width:{pct:.0f}%"></div>
296
+ </div>
297
+ <div class="meter-row__value">%{pct:.0f}</div>
298
+ </div>
299
+ """
300
+ )
301
+ parts.append("</section>")
302
+ return "".join(parts)
303
+
304
+
305
+ def _build_model_table_html(
306
+ selected_model_name: str,
307
+ feature_vector: np.ndarray,
308
+ ) -> str:
309
+ scaled = ARTIFACTS.scaler.transform(feature_vector)
310
+ scored_rows: list[tuple[str, float]] = []
311
+
312
+ for name, model in ARTIFACTS.loaded_models.items():
313
+ try:
314
+ with warnings.catch_warnings():
315
+ warnings.simplefilter("ignore", category=UserWarning)
316
+ probability = float(model.predict_proba(scaled)[0][1])
317
+ except Exception: # noqa: BLE001
318
+ continue
319
+ scored_rows.append((name, probability))
320
+
321
+ scored_rows.sort(key=lambda item: item[1], reverse=True)
322
+ parts = [
323
+ '<section class="panel-card"><div class="panel-card__title">Model karsilastirmasi</div>',
324
+ "<table class='model-table'><thead><tr><th>Model</th><th>Canli AI %</th><th>CV AUC</th><th>Acc</th></tr></thead><tbody>",
325
+ ]
326
+
327
+ for name, probability in scored_rows:
328
+ metrics = ARTIFACTS.training_results.get(name, {})
329
+ row_class = "is-selected" if name == selected_model_name else ""
330
+ best_badge = " <span class='badge'>en iyi</span>" if name == ARTIFACTS.best_model_name else ""
331
+ parts.append(
332
+ f"""
333
+ <tr class="{row_class}">
334
+ <td>{name}{best_badge}</td>
335
+ <td>%{probability * 100:.1f}</td>
336
+ <td>{metrics.get("roc_auc", 0.0):.4f}</td>
337
+ <td>{metrics.get("accuracy", 0.0):.4f}</td>
338
+ </tr>
339
+ """
340
+ )
341
+
342
+ parts.append("</tbody></table></section>")
343
+ return "".join(parts)
344
+
345
+
346
+ def _build_feature_details_md(features: dict[str, float], duration: float) -> str:
347
+ importance = ARTIFACTS.feature_importance
348
+
349
+ lines = [
350
+ "## Ses ozet",
351
+ "",
352
+ "| Metrik | Deger |",
353
+ "|--------|-------|",
354
+ f"| Sure | {duration:.1f}s |",
355
+ f"| Tempo | {features.get('tempo_bpm', 0.0):.1f} BPM |",
356
+ f"| RMS energy | {features.get('rms_energy', 0.0):.6f} |",
357
+ f"| Harmonic ratio | {features.get('harmonic_ratio', 0.0):.4f} |",
358
+ f"| Spectral centroid | {features.get('spectral_centroid_mean', 0.0):.1f} Hz |",
359
+ f"| Vocal confidence | {features.get('vocal_confidence', 0.0):.3f} |",
360
+ "",
361
+ ]
362
 
363
+ insight_block = _build_feature_insights_md(features)
364
+ if insight_block:
365
+ lines.extend([insight_block, ""])
366
+
367
+ lines.extend(
368
+ [
369
+ "## Tum ozellikler",
370
+ "",
371
+ "| Ozellik | Deger | Global onem |",
372
+ "|---------|-------|-------------|",
373
+ ]
374
  )
 
 
 
 
375
 
376
+ for column in ARTIFACTS.feature_cols:
377
+ value = features.get(column, 0.0)
378
+ weight = importance.get(column, 0.0)
379
+ lines.append(f"| {column} | {value:.6f} | {weight:.4f} |")
380
+
381
+ return "\n".join(lines)
382
+
383
+
384
+ def _build_feature_insights_md(features: dict[str, float]) -> str:
385
+ stats = ARTIFACTS.feature_stats
386
+ if not stats:
387
+ return ""
388
+
389
+ by_class = stats.get("_by_class", {})
390
+ rows: list[tuple[float, str, float, float, float, float]] = []
391
+ for column in ARTIFACTS.feature_cols:
392
+ feature_stats = stats.get(column)
393
+ if not feature_stats:
394
+ continue
395
+
396
+ std = float(feature_stats.get("std", 1.0)) or 1.0
397
+ value = float(features.get(column, 0.0))
398
+ z_score = (value - float(feature_stats.get("mean", 0.0))) / std
399
+ ai_mean = float(by_class.get("ai", {}).get(column, {}).get("mean", 0.0))
400
+ human_mean = float(by_class.get("human", {}).get(column, {}).get("mean", 0.0))
401
+ rows.append((abs(z_score), column, value, z_score, ai_mean, human_mean))
402
+
403
+ if not rows:
404
+ return ""
405
+
406
+ rows.sort(reverse=True)
407
+ lines = [
408
+ "## Dikkat ceken sapmalar",
409
+ "",
410
+ "| Ozellik | Deger | Z-score | AI ort. | Human ort. |",
411
+ "|---------|-------|---------|---------|------------|",
412
+ ]
413
+ for _, column, value, z_score, ai_mean, human_mean in rows[:10]:
414
+ lines.append(
415
+ f"| {column} | {value:.6f} | {z_score:+.2f} | {ai_mean:.6f} | {human_mean:.6f} |"
416
+ )
417
+ return "\n".join(lines)
418
+
419
+
420
+ def analyze_audio(audio_file: Any, selected_model_label: str) -> tuple[str, str, str, str]:
421
+ if not audio_file:
422
+ empty = '<section class="hero-card neutral"><div class="hero-card__title">Ses dosyasi bekleniyor</div><div class="hero-card__subtitle">Analiz icin bir .wav, .mp3 veya .flac yukleyin.</div></section>'
423
+ return empty, "", "", ""
424
+
425
+ audio_path = audio_file[0] if isinstance(audio_file, tuple) else audio_file
426
+ selected_model_name = ARTIFACTS.label_to_name.get(
427
+ selected_model_label,
428
+ ARTIFACTS.best_model_name,
429
+ )
430
 
431
+ start_time = time.time()
432
+ try:
433
+ features, duration = _extract_demo_features(str(audio_path))
434
+ except Exception as exc: # noqa: BLE001
435
+ error_html = f'<section class="hero-card neutral"><div class="hero-card__title">Analiz basarisiz</div><div class="hero-card__subtitle">{exc}</div></section>'
436
+ return error_html, "", "", ""
437
+
438
+ feature_vector = _build_feature_vector(features)
439
+ scaled = ARTIFACTS.scaler.transform(feature_vector)
440
+ model = ARTIFACTS.loaded_models[selected_model_name]
441
+ with warnings.catch_warnings():
442
+ warnings.simplefilter("ignore", category=UserWarning)
443
+ ai_prob = float(model.predict_proba(scaled)[0][1])
444
+ elapsed = time.time() - start_time
445
+
446
+ result_html = _build_result_html(ai_prob, duration, elapsed, selected_model_name)
447
+ signal_html = _build_signal_html(features)
448
+ model_table_html = _build_model_table_html(selected_model_name, feature_vector)
449
+ details_md = _build_feature_details_md(features, duration)
450
+ return result_html, signal_html, model_table_html, details_md
451
+
452
+
453
+ def build_models_md() -> str:
454
+ training_results = ARTIFACTS.training_results
455
+ lines = [
456
+ "## Egitilmis modeller",
457
+ "",
458
+ f"- En iyi model: **{ARTIFACTS.best_model_name}**",
459
+ f"- Ornek sayisi: **{training_results.get('_n_samples', '?')}**",
460
+ f"- Ozellik sayisi: **{training_results.get('_n_features', len(ARTIFACTS.feature_cols))}**",
461
+ f"- CV kat sayisi: **{training_results.get('_n_folds', '?')}**",
462
+ "",
463
+ "| Model | CV AUC | Holdout AUC | Acc | F1 |",
464
+ "|------|--------|-------------|-----|----|",
465
+ ]
466
 
467
+ model_names = [
468
+ name
469
+ for name in training_results
470
+ if not name.startswith("_") and isinstance(training_results[name], dict)
471
+ ]
472
+ model_names.sort(key=lambda name: training_results[name].get("roc_auc", 0.0), reverse=True)
473
+
474
+ for name in model_names:
475
+ result = training_results[name]
476
+ display = f"**{name}**" if name == ARTIFACTS.best_model_name else name
477
+ lines.append(
478
+ f"| {display} | {result.get('roc_auc', 0.0):.4f} | "
479
+ f"{result.get('validation_auc', 0.0):.4f} | "
480
+ f"{result.get('accuracy', 0.0):.4f} | {result.get('f1', 0.0):.4f} |"
481
+ )
482
+
483
+ lines.extend(["", "## Secilen parametreler", ""])
484
+ for name in model_names:
485
+ params = training_results[name].get("selected_params", {})
486
+ lines.append(f"- **{name}**: `{json.dumps(params, ensure_ascii=True)}`")
487
+
488
+ importance_items = sorted(
489
+ ARTIFACTS.feature_importance.items(),
490
+ key=lambda item: item[1],
491
+ reverse=True,
492
+ )[:15]
493
+ if importance_items:
494
+ lines.extend(["", "## Ilk 15 ozellik onemi", "", "| Ozellik | Onem |", "|---------|------|"])
495
+ for feature_name, score in importance_items:
496
+ lines.append(f"| {feature_name} | {score:.4f} |")
497
+
498
+ return "\n".join(lines)
499
+
500
+
501
+ def build_dataset_md() -> str:
502
+ summary = ARTIFACTS.dataset_summary
503
+ if not summary:
504
+ return "Veri seti ozeti bulunamadi."
505
+
506
+ lines = [
507
+ "## Egitim veri seti",
508
+ "",
509
+ "| Metrik | Deger |",
510
+ "|--------|-------|",
511
+ f"| Manifest | `{summary.get('manifest_path', '-')}` |",
512
+ f"| Toplam ornek | {summary.get('total', 0)} |",
513
+ f"| AI | {summary.get('ai', 0)} |",
514
+ f"| Human | {summary.get('human', 0)} |",
515
+ f"| Ozellik | {len(ARTIFACTS.feature_cols)} |",
516
+ "",
517
+ "## Kaynak dagilimi",
518
+ "",
519
+ "| Kaynak | Adet |",
520
+ "|--------|------|",
521
+ ]
522
+ for generator, count in summary.get("generators", []):
523
+ lines.append(f"| {generator} | {count} |")
524
+ return "\n".join(lines)
525
 
 
526
 
527
  AURIS_CSS = """
528
+ :root {
529
+ --bg: #120f0b;
530
+ --panel: rgba(31, 24, 17, 0.92);
531
+ --panel-strong: rgba(42, 31, 22, 0.98);
532
+ --line: rgba(215, 182, 122, 0.18);
533
+ --text: #f5ead8;
534
+ --muted: #c8af8a;
535
+ --gold: #dfb56f;
536
+ --gold-soft: #f1d4a2;
537
+ --danger: #d66a55;
538
+ --danger-soft: #5d2218;
539
+ --safe: #7fbb7c;
540
+ --safe-soft: #1f3b2d;
541
+ }
542
+
543
+ body {
544
+ background:
545
+ radial-gradient(circle at top left, rgba(223, 181, 111, 0.12), transparent 28%),
546
+ radial-gradient(circle at bottom right, rgba(88, 43, 23, 0.24), transparent 26%),
547
+ linear-gradient(135deg, #0d0a07 0%, #18120d 45%, #120f0b 100%);
548
+ }
549
+
550
  .gradio-container {
551
+ max-width: 1360px !important;
552
+ margin: 0 auto !important;
553
+ background: transparent !important;
554
+ color: var(--text) !important;
555
+ font-family: "Segoe UI", sans-serif !important;
556
+ }
557
+
558
+ .app-shell {
559
+ padding: 24px 0 10px;
560
+ }
561
+
562
+ .app-hero {
563
+ display: grid;
564
+ grid-template-columns: 1.4fr 1fr;
565
+ gap: 18px;
566
+ align-items: stretch;
567
+ margin-bottom: 18px;
568
+ }
569
+
570
+ .app-brand,
571
+ .app-meta {
572
+ background: linear-gradient(160deg, rgba(35, 26, 18, 0.95), rgba(19, 14, 10, 0.96));
573
+ border: 1px solid var(--line);
574
+ border-radius: 22px;
575
+ padding: 22px 24px;
576
+ box-shadow: 0 24px 70px rgba(0, 0, 0, 0.28);
577
+ }
578
+
579
+ .app-brand__eyebrow {
580
+ text-transform: uppercase;
581
+ letter-spacing: 0.24em;
582
+ font-size: 0.78rem;
583
+ color: var(--gold);
584
+ margin-bottom: 12px;
585
+ }
586
+
587
+ .app-brand__title {
588
+ font-size: 3rem;
589
+ font-weight: 800;
590
+ line-height: 0.98;
591
+ margin: 0;
592
+ color: #fff6e6;
593
+ }
594
+
595
+ .app-brand__subtitle {
596
+ margin: 14px 0 0;
597
+ color: var(--muted);
598
+ line-height: 1.6;
599
+ max-width: 46rem;
600
+ }
601
+
602
+ .app-meta__grid {
603
+ display: grid;
604
+ grid-template-columns: repeat(2, minmax(0, 1fr));
605
+ gap: 12px;
606
+ }
607
+
608
+ .meta-chip {
609
+ background: rgba(255, 255, 255, 0.03);
610
+ border: 1px solid rgba(255, 255, 255, 0.06);
611
+ border-radius: 16px;
612
+ padding: 14px 16px;
613
+ }
614
+
615
+ .meta-chip__label {
616
+ display: block;
617
+ color: var(--muted);
618
+ font-size: 0.78rem;
619
+ margin-bottom: 6px;
620
+ }
621
+
622
+ .meta-chip__value {
623
+ display: block;
624
+ color: #fff2db;
625
+ font-size: 1.1rem;
626
+ font-weight: 700;
627
+ }
628
+
629
+ .hero-card,
630
+ .panel-card {
631
+ background: linear-gradient(180deg, rgba(34, 26, 19, 0.96), rgba(19, 14, 10, 0.96));
632
+ border: 1px solid var(--line);
633
+ border-radius: 20px;
634
+ padding: 20px;
635
+ box-shadow: 0 22px 60px rgba(0, 0, 0, 0.24);
636
+ }
637
+
638
+ .hero-card__eyebrow,
639
+ .panel-card__title {
640
+ font-size: 0.82rem;
641
+ letter-spacing: 0.12em;
642
+ text-transform: uppercase;
643
+ color: var(--gold);
644
+ margin-bottom: 10px;
645
+ }
646
+
647
+ .hero-card__score {
648
+ font-size: clamp(2.8rem, 7vw, 4.8rem);
649
+ line-height: 0.95;
650
+ font-weight: 900;
651
+ color: #fff6e7;
652
+ }
653
+
654
+ .hero-card__title {
655
+ margin-top: 8px;
656
+ font-size: 1.4rem;
657
+ font-weight: 800;
658
+ color: #fff6e7;
659
+ }
660
+
661
+ .hero-card__subtitle {
662
+ margin-top: 8px;
663
+ color: var(--muted);
664
+ line-height: 1.6;
665
+ }
666
+
667
+ .hero-card__meta {
668
+ display: flex;
669
+ flex-wrap: wrap;
670
+ gap: 10px;
671
+ margin-top: 16px;
672
+ }
673
+
674
+ .hero-card__meta span {
675
+ padding: 7px 12px;
676
+ border-radius: 999px;
677
+ background: rgba(255, 255, 255, 0.04);
678
+ border: 1px solid rgba(255, 255, 255, 0.08);
679
+ color: #f8ead4;
680
+ font-size: 0.88rem;
681
+ }
682
+
683
+ .hero-card.ai-high,
684
+ .hero-card.ai-mid {
685
+ background: linear-gradient(150deg, rgba(84, 28, 20, 0.96), rgba(33, 15, 12, 0.97));
686
+ }
687
+
688
+ .hero-card.human-high,
689
+ .hero-card.human-mid {
690
+ background: linear-gradient(150deg, rgba(23, 49, 34, 0.96), rgba(12, 23, 18, 0.97));
691
+ }
692
+
693
+ .hero-card.neutral {
694
+ background: linear-gradient(150deg, rgba(36, 29, 22, 0.96), rgba(17, 14, 11, 0.97));
695
+ }
696
+
697
+ .meter-row {
698
+ display: grid;
699
+ grid-template-columns: 170px minmax(0, 1fr) 54px;
700
+ gap: 12px;
701
+ align-items: center;
702
+ margin-top: 12px;
703
+ }
704
+
705
+ .meter-row__label {
706
+ color: #f7ecd8;
707
+ font-size: 0.92rem;
708
+ }
709
+
710
+ .meter-row__track {
711
+ position: relative;
712
+ height: 14px;
713
+ background: rgba(255, 255, 255, 0.06);
714
+ border-radius: 999px;
715
+ overflow: hidden;
716
+ border: 1px solid rgba(255, 255, 255, 0.08);
717
+ }
718
+
719
+ .meter-row__fill {
720
+ height: 100%;
721
+ border-radius: 999px;
722
+ }
723
+
724
+ .bar-warm {
725
+ background: linear-gradient(90deg, var(--gold), var(--danger));
726
+ }
727
+
728
+ .bar-mid {
729
+ background: linear-gradient(90deg, var(--gold), var(--gold-soft));
730
+ }
731
+
732
+ .bar-cool {
733
+ background: linear-gradient(90deg, #76c490, #5ca39b);
734
+ }
735
 
736
+ .meter-row__value {
737
+ color: var(--gold-soft);
738
+ font-weight: 700;
739
+ text-align: right;
740
+ }
741
+
742
+ .model-table {
743
+ width: 100%;
744
+ border-collapse: collapse;
745
+ }
746
+
747
+ .model-table th,
748
+ .model-table td {
749
+ padding: 10px 12px;
750
+ text-align: left;
751
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
752
+ }
753
+
754
+ .model-table th {
755
+ color: var(--gold);
756
+ font-size: 0.82rem;
757
+ text-transform: uppercase;
758
+ letter-spacing: 0.06em;
759
+ }
760
+
761
+ .model-table td {
762
+ color: #f6ead6;
763
+ }
764
+
765
+ .model-table tr.is-selected td {
766
+ background: rgba(223, 181, 111, 0.08);
767
+ }
768
+
769
+ .badge {
770
+ display: inline-block;
771
+ margin-left: 8px;
772
+ padding: 3px 8px;
773
+ border-radius: 999px;
774
+ font-size: 0.72rem;
775
+ color: #fff2db;
776
+ background: rgba(223, 181, 111, 0.18);
777
+ border: 1px solid rgba(223, 181, 111, 0.25);
778
+ }
779
+
780
+ .block {
781
+ border: 1px solid var(--line) !important;
782
+ border-radius: 18px !important;
783
+ background: var(--panel) !important;
784
+ }
785
+
786
+ .gr-button-primary {
787
+ background: linear-gradient(135deg, #d4a85c, #b46d3f) !important;
788
+ border: 0 !important;
789
+ color: #20150d !important;
790
+ font-weight: 800 !important;
791
+ }
792
+
793
+ .prose,
794
+ .prose * {
795
+ color: var(--text) !important;
796
+ }
797
+
798
+ .prose table {
799
+ border-collapse: collapse;
800
+ width: 100%;
801
+ }
802
+
803
+ .prose th,
804
+ .prose td {
805
+ padding: 8px 10px;
806
+ border: 1px solid rgba(255, 255, 255, 0.08);
807
+ }
808
+
809
+ .prose th {
810
+ background: rgba(255, 255, 255, 0.04);
811
+ color: var(--gold) !important;
812
+ }
813
+
814
+ footer {
815
+ display: none !important;
816
+ }
817
+
818
+ @media (max-width: 920px) {
819
+ .app-hero {
820
+ grid-template-columns: 1fr;
821
+ }
822
+
823
+ .app-meta__grid {
824
+ grid-template-columns: 1fr;
825
+ }
826
+
827
+ .meter-row {
828
+ grid-template-columns: 1fr;
829
+ }
830
+ }
831
  """
832
 
 
833
 
834
+ def _build_header_html() -> str:
835
+ dataset_summary = ARTIFACTS.dataset_summary
836
+ training_results = ARTIFACTS.training_results
837
+ top_generator = dataset_summary.get("generators", [["-", 0]])[0][0] if dataset_summary else "-"
838
+
839
+ return f"""
840
+ <section class="app-shell">
841
+ <div class="app-hero">
842
+ <div class="app-brand">
843
+ <div class="app-brand__eyebrow">AURIS local demo</div>
844
+ <h1 class="app-brand__title">AI Muzik Tespiti<br />Canli Analiz</h1>
845
+ <p class="app-brand__subtitle">
846
+ Demo arayuzu artik egitim artefact'lari ile ayni ozellik semasini kullaniyor.
847
+ Yani yukledigin ses, `DataSet/features.csv` ile egitilen modellerle birebir uyumlu
848
+ sekilde analiz ediliyor.
849
+ </p>
850
+ </div>
851
+ <div class="app-meta">
852
+ <div class="app-meta__grid">
853
+ <div class="meta-chip">
854
+ <span class="meta-chip__label">En iyi model</span>
855
+ <span class="meta-chip__value">{ARTIFACTS.best_model_name}</span>
856
+ </div>
857
+ <div class="meta-chip">
858
+ <span class="meta-chip__label">Model sayisi</span>
859
+ <span class="meta-chip__value">{len(ARTIFACTS.loaded_models)}</span>
860
+ </div>
861
+ <div class="meta-chip">
862
+ <span class="meta-chip__label">Veri seti</span>
863
+ <span class="meta-chip__value">{training_results.get('_n_samples', '?')} ornek</span>
864
+ </div>
865
+ <div class="meta-chip">
866
+ <span class="meta-chip__label">Baskin kaynak</span>
867
+ <span class="meta-chip__value">{top_generator}</span>
868
+ </div>
869
+ </div>
870
+ </div>
871
+ </div>
872
+ </section>
873
+ """
874
+
875
+
876
+ with gr.Blocks(title="AURIS Local Demo") as demo:
877
+ gr.HTML(_build_header_html())
878
 
879
  with gr.Tabs():
880
  with gr.Tab("Analiz"):
881
+ with gr.Row(equal_height=False):
882
+ with gr.Column(scale=1, min_width=320):
883
  audio_input = gr.Audio(
884
+ label="Ses dosyasi yukle",
885
  type="filepath",
886
  )
887
+ model_dropdown = gr.Dropdown(
888
+ choices=ARTIFACTS.model_labels,
889
+ value=ARTIFACTS.best_model_label,
890
+ label="Calistirilacak model",
891
+ interactive=True,
892
  )
893
+ analyze_button = gr.Button("Analizi baslat", variant="primary", size="lg")
894
+ if _example_audio_paths():
895
+ gr.Examples(
896
+ examples=_example_audio_paths(),
897
+ inputs=[audio_input],
898
+ label="Hazir ornekler",
899
+ )
900
+
901
+ with gr.Column(scale=2, min_width=520):
902
+ result_html = gr.HTML()
903
+ with gr.Row(equal_height=False):
904
+ signal_html = gr.HTML()
905
+ model_table_html = gr.HTML()
906
+ details_md = gr.Markdown()
907
+
908
+ analyze_button.click(
909
+ fn=analyze_audio,
910
+ inputs=[audio_input, model_dropdown],
911
+ outputs=[result_html, signal_html, model_table_html, details_md],
912
  )
913
 
914
+ with gr.Tab("Modeller"):
915
+ gr.Markdown(build_models_md())
916
 
917
+ with gr.Tab("Veri Seti"):
918
+ gr.Markdown(build_dataset_md())
919
+
920
+ with gr.Tab("Gorseller"):
921
+ figure_paths = sorted(str(path) for path in FIGURES_DIR.glob("*.png")) if FIGURES_DIR.exists() else []
922
  if figure_paths:
923
  gr.Gallery(
924
  value=figure_paths,
925
+ label="Akademik ciktılar",
926
  columns=3,
927
  height="auto",
928
  object_fit="contain",
929
  )
930
+ else:
931
+ gr.Markdown("Gorsel bulunamadi.")
932
+
933
+
934
+ def _pick_available_port(preferred_port: int) -> int:
935
+ for port in range(preferred_port, preferred_port + 25):
936
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
937
+ sock.settimeout(0.2)
938
+ if sock.connect_ex(("127.0.0.1", port)) != 0:
939
+ return port
940
+ raise RuntimeError("Bos bir port bulunamadi.")
941
+
942
+
943
+ def _parse_args() -> argparse.Namespace:
944
+ parser = argparse.ArgumentParser(description="Run the AURIS local Gradio demo")
945
+ parser.add_argument("--host", default="127.0.0.1", help="Bind address")
946
+ parser.add_argument("--port", type=int, default=7864, help="Preferred port")
947
+ parser.add_argument(
948
+ "--no-browser",
949
+ action="store_true",
950
+ help="Do not open the browser automatically",
951
+ )
952
+ return parser.parse_args()
953
 
 
 
 
 
 
 
 
 
954
 
955
  if __name__ == "__main__":
956
+ args = _parse_args()
957
+ port = _pick_available_port(args.port)
958
+ local_host = "127.0.0.1" if args.host == "0.0.0.0" else args.host
959
+
960
+ print("AURIS local demo")
961
+ print(f"Host: {args.host}")
962
+ print(f"Port: {port}")
963
+ print(f"Open: http://{local_host}:{port}")
964
+
965
  demo.launch(
966
+ server_name=args.host,
967
+ server_port=port,
968
  share=False,
969
+ inbrowser=not args.no_browser,
970
+ show_error=True,
971
  css=AURIS_CSS,
972
  )
requirements.txt CHANGED
@@ -38,6 +38,7 @@ scikit-learn>=1.3.2
38
  aiohttp>=3.9.1
39
  httpx>=0.26.0
40
  requests>=2.31.0
 
41
 
42
  # === Gradio Client (FST Layer 3 - external API) ===
43
  gradio_client>=1.0.0
@@ -62,4 +63,4 @@ google-api-python-client>=2.134.0
62
  google-auth-httplib2>=0.2.0
63
  google-auth-oauthlib>=1.2.0
64
  isodate>=0.6.1
65
- youtube-transcript-api>=0.6.2
 
38
  aiohttp>=3.9.1
39
  httpx>=0.26.0
40
  requests>=2.31.0
41
+ gradio>=6.0.0,<7.0.0
42
 
43
  # === Gradio Client (FST Layer 3 - external API) ===
44
  gradio_client>=1.0.0
 
63
  google-auth-httplib2>=0.2.0
64
  google-auth-oauthlib>=1.2.0
65
  isodate>=0.6.1
66
+ youtube-transcript-api>=0.6.2