| """ |
| =============================================================================== |
| KENDİ YÖNTEMİMİZİ TEST ETME: "Bizim bölme de şişiriyor mu?" |
| =============================================================================== |
| |
| SORU: Walk-forward validasyon gerçek dünyanın simülasyonudur. |
| Hiçbir bölme stratejisi ondan daha gerçekçi olamaz. |
| O zaman her stratejinin walk-forward'a yakınlığı = dürüstlük derecesi. |
| |
| TEST: Her stratejinin F1'ini walk-forward F1 ile karşılaştır. |
| - Yakın (±%10) = dürüst tahmin |
| - Çok üstünde (>%15) = şişirilmiş |
| - Çok altında (<-%15) = aşırı pesimist |
| =============================================================================== |
| """ |
|
|
| import os, json |
| import numpy as np |
| import pandas as pd |
|
|
| import matplotlib |
| matplotlib.use('Agg') |
| import matplotlib.pyplot as plt |
| import seaborn as sns |
|
|
| from sklearn.preprocessing import StandardScaler |
| from sklearn.metrics import f1_score, roc_auc_score |
| import lightgbm as lgb |
| from sklearn.ensemble import RandomForestClassifier |
| import xgboost as xgb |
|
|
| import warnings |
| warnings.filterwarnings('ignore') |
| np.random.seed(42) |
|
|
| FIGDIR = '/app/figures_proof' |
| OUTDIR = '/app/results_proof' |
|
|
| |
| feat_df = pd.read_csv('/app/data/elliptic_txs_features.csv', header=None) |
| class_df = pd.read_csv('/app/data/elliptic_txs_classes.csv') |
| timesteps = feat_df.iloc[:, 1].values.astype(int) |
| features = feat_df.iloc[:, 2:].values.astype(np.float32) |
| label_map = {'1': 1, '2': 0, 'unknown': -1} |
| labels = np.array([label_map[str(c)] for c in class_df['class'].values]) |
| labeled_mask = labels >= 0 |
| X = features[labeled_mask] |
| y = labels[labeled_mask] |
| ts = timesteps[labeled_mask] |
|
|
| def train_eval(X_tr, y_tr, X_te, y_te, model_type='lgbm'): |
| scaler = StandardScaler() |
| X_tr_s = scaler.fit_transform(X_tr) |
| X_te_s = scaler.transform(X_te) |
| if model_type == 'lgbm': |
| m = lgb.LGBMClassifier(n_estimators=300, max_depth=10, scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1) |
| elif model_type == 'rf': |
| m = RandomForestClassifier(n_estimators=300, max_depth=15, class_weight='balanced', random_state=42, n_jobs=-1) |
| elif model_type == 'xgb': |
| m = xgb.XGBClassifier(n_estimators=300, max_depth=8, scale_pos_weight=10, random_state=42, n_jobs=-1, verbosity=0) |
| m.fit(X_tr_s, y_tr) |
| pred = m.predict(X_te_s) |
| return f1_score(y_te, pred, zero_division=0) |
|
|
| |
| print("=" * 70) |
| print("WALK-FORWARD VALİDASYON (ALTIN STANDART)") |
| print("=" * 70) |
|
|
| wf_results_all = {} |
| for model_type, model_name in [('lgbm', 'LightGBM'), ('rf', 'Random Forest'), ('xgb', 'XGBoost')]: |
| wf_f1s = [] |
| for test_start in range(10, 49, 3): |
| tr_mask = ts < test_start |
| te_mask = (ts >= test_start) & (ts < test_start + 3) |
| if tr_mask.sum() < 50 or te_mask.sum() < 10 or len(np.unique(y[te_mask])) < 2: |
| continue |
| f1 = train_eval(X[tr_mask], y[tr_mask], X[te_mask], y[te_mask], model_type) |
| wf_f1s.append(f1) |
| wf_avg = np.mean(wf_f1s) |
| wf_results_all[model_name] = wf_avg |
| print(f" {model_name}: Walk-Forward ortalama F1 = {wf_avg:.4f}") |
|
|
| |
| topo_results = pd.read_csv('/app/results_topo/all_results.csv') |
|
|
| |
| print("\n" + "=" * 70) |
| print("HER STRATEJİNİN WALK-FORWARD'A YAKINLIĞI") |
| print("=" * 70) |
|
|
| comparison_data = [] |
| for model_name in ['LightGBM', 'Random Forest', 'XGBoost']: |
| wf_f1 = wf_results_all[model_name] |
| print(f"\n {model_name} (Walk-Forward F1 = {wf_f1:.4f}):") |
| |
| for _, row in topo_results[topo_results['model'] == model_name].iterrows(): |
| strat = row['strategy'] |
| f1 = row['f1'] |
| diff = f1 - wf_f1 |
| pct = (diff / wf_f1) * 100 |
| abs_pct = abs(pct) |
| |
| if abs_pct < 10: |
| durum = 'DÜRÜST ✓' |
| elif pct > 15: |
| durum = 'ŞİŞİRİYOR 🔴' |
| elif pct > 10: |
| durum = 'HAFİF YÜKSEK ⚠️' |
| elif pct < -15: |
| durum = 'ÇOK PESİMİST' |
| else: |
| durum = 'HAFİF DÜŞÜK' |
| |
| comparison_data.append({ |
| 'Model': model_name, 'Strateji': strat, |
| 'F1': f1, 'WF_F1': wf_f1, 'Fark': diff, 'Pct': pct, 'Durum': durum |
| }) |
| print(f" {strat:<30s} F1={f1:.4f} fark={diff:+.4f} ({pct:+.1f}%) {durum}") |
|
|
| comp_df = pd.DataFrame(comparison_data) |
|
|
| |
| print("\n" + "=" * 70) |
| print("SONUÇ") |
| print("=" * 70) |
|
|
| |
| print("\nStratejilerin walk-forward'a ortalama sapması (tüm modeller):") |
| for strat in comp_df['Strateji'].unique(): |
| subset = comp_df[comp_df['Strateji'] == strat] |
| avg_pct = subset['Pct'].mean() |
| print(f" {strat:<30s}: ortalama {avg_pct:+.1f}%") |
|
|
| |
| print("\nFİGÜR OLUŞTURULUYOR...") |
| sns.set_theme(style='whitegrid', font_scale=1.1) |
|
|
| fig, ax = plt.subplots(figsize=(16, 9)) |
|
|
| strategies_order = ['Rastgele', 'Kronolojik', 'Topolojik Kırılma (Bizim)', 'Kayan Pencere', 'Düşmanca-Kriz'] |
| models_order = ['LightGBM', 'Random Forest', 'XGBoost'] |
| colors = {'LightGBM': '#4ECDC4', 'Random Forest': '#45B7D1', 'XGBoost': '#96CEB4'} |
| x = np.arange(len(strategies_order)) |
| width = 0.22 |
|
|
| for i, model_name in enumerate(models_order): |
| wf_f1 = wf_results_all[model_name] |
| vals = [] |
| for strat in strategies_order: |
| row = comp_df[(comp_df['Model'] == model_name) & (comp_df['Strateji'] == strat)] |
| vals.append(row['F1'].values[0] if len(row) > 0 else 0) |
| |
| bars = ax.bar(x + i * width, vals, width, label=model_name, color=colors[model_name], |
| edgecolor='black', linewidth=0.5) |
|
|
| |
| wf_avg_all = np.mean(list(wf_results_all.values())) |
| wf_min = min(wf_results_all.values()) |
| wf_max = max(wf_results_all.values()) |
| ax.axhspan(wf_min * 0.9, wf_max * 1.1, alpha=0.15, color='green', label='Walk-Forward bandı (±%10)') |
| ax.axhline(y=wf_avg_all, color='green', linewidth=2, linestyle='--', label=f'Walk-Forward ort. F1={wf_avg_all:.3f}') |
|
|
| ax.set_xticks(x + width) |
| ax.set_xticklabels(strategies_order, rotation=15, ha='right', fontsize=11) |
| ax.set_ylabel('Illicit F1 Score', fontsize=13) |
| ax.set_title('Her Bölme Stratejisinin Walk-Forward\'a (Gerçek Dünya) Yakınlığı\n' |
| 'Yeşil bant içinde = dürüst tahmin, üstünde = şişirilmiş', fontsize=14, fontweight='bold') |
| ax.legend(fontsize=10, loc='upper right') |
| ax.set_ylim(0, 1.1) |
| ax.grid(axis='y', alpha=0.3) |
|
|
| plt.tight_layout() |
| plt.savefig(f'{FIGDIR}/fig6_honesty_test.png', dpi=150, bbox_inches='tight') |
| plt.close() |
| print(" ✓ Figür 6: Dürüstlük Testi") |
|
|
| |
| comp_df.to_csv(f'{OUTDIR}/honesty_comparison.csv', index=False) |
| with open(f'{OUTDIR}/walk_forward_by_model.json', 'w') as f: |
| json.dump({k: float(v) for k, v in wf_results_all.items()}, f, indent=2) |
|
|
| print("\n✓ Tamamlandı!") |
|
|