Mucahit S. commited on
Commit
464c613
·
verified ·
1 Parent(s): 97733f6

Upload proof_mechanism.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. proof_mechanism.py +530 -0
proof_mechanism.py ADDED
@@ -0,0 +1,530 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ===============================================================================
3
+ PERFORMANS ŞİŞMESİ KANIT MEKANİZMASI
4
+ "Random split gerçekten şişiriyor mu, yoksa doğru sonuçları mı veriyor?"
5
+ ===============================================================================
6
+
7
+ ÇOK ÖNEMLİ SORU: Random split ile %96 F1 alan bir model belki gerçekten iyi
8
+ bir model olabilir. Belki temporal split gereksiz yere performansı düşürüyor.
9
+ Bunu nasıl ayırt edeceğiz?
10
+
11
+ CEVAP: 5 bağımsız kanıt mekanizması ile.
12
+
13
+ KANIT 1 — ZAMANSAL TUTARLILIK TESTİ (Temporal Consistency)
14
+ Random split ile eğitilen model HER timestep'te eşit mi performans gösteriyor?
15
+ Eğer model gerçekten öğrendiyse: tüm timestep'lerde tutarlı performans.
16
+ Eğer sızıntıdan beslendiyse: eğitim setine yakın timestep'lerde iyi,
17
+ uzak timestep'lerde kötü → BÜYÜK VARYANS.
18
+
19
+ KANIT 2 — ZAMANSAL YAKINLIK TESTİ (Temporal Proximity Bias)
20
+ Random split'te doğru tahmin edilen test örnekleri, eğitim setindeki
21
+ örneklere zamansal olarak ne kadar yakın?
22
+ Eğer sızıntı yoksa: yakınlık ve doğruluk arasında korelasyon olmamalı.
23
+ Eğer sızıntı varsa: zamansal olarak yakın örnekler daha doğru → KORELASYoN.
24
+
25
+ KANIT 3 — WALK-FORWARD VALİDASYON (Gerçek Dünya Simülasyonu)
26
+ Modeli her ay yeniden eğitip bir sonraki ayı tahmin et.
27
+ Bu, polisin gerçekte nasıl çalışacağının simülasyonu.
28
+ Random split sonucu ile walk-forward sonucu arasındaki fark = GERÇEK ŞIŞME.
29
+
30
+ KANIT 4 — GELECEK BİLGİSİ TESTİ (Future Information Leakage)
31
+ Random split ile eğitilen modele SADECE geçmiş veya SADECE gelecek
32
+ timestep'lerden örnekler ver. Performans farkı = sızıntı kanıtı.
33
+
34
+ KANIT 5 — RASTGELE ETİKET TESTİ (Sanity Check)
35
+ Etiketleri rastgele karıştır ve random split ile eğit.
36
+ Eğer hâlâ yüksek F1 alıyorsa → model ezberlemiş, sızıntı kesin.
37
+ ===============================================================================
38
+ """
39
+
40
+ import os, json, warnings
41
+ import numpy as np
42
+ import pandas as pd
43
+ from collections import defaultdict
44
+
45
+ import matplotlib
46
+ matplotlib.use('Agg')
47
+ import matplotlib.pyplot as plt
48
+ import seaborn as sns
49
+
50
+ from sklearn.preprocessing import StandardScaler
51
+ from sklearn.ensemble import RandomForestClassifier
52
+ from sklearn.model_selection import train_test_split
53
+ from sklearn.metrics import f1_score, roc_auc_score, precision_score, recall_score
54
+ import lightgbm as lgb
55
+
56
+ warnings.filterwarnings('ignore')
57
+ np.random.seed(42)
58
+
59
+ OUTDIR = '/app/results_proof'
60
+ FIGDIR = '/app/figures_proof'
61
+ os.makedirs(OUTDIR, exist_ok=True)
62
+ os.makedirs(FIGDIR, exist_ok=True)
63
+
64
+ # ─── VERİ YÜKLEME ───
65
+ print("=" * 80)
66
+ print("VERİ YÜKLEME")
67
+ print("=" * 80)
68
+ feat_df = pd.read_csv('/app/data/elliptic_txs_features.csv', header=None)
69
+ class_df = pd.read_csv('/app/data/elliptic_txs_classes.csv')
70
+ edge_df = pd.read_csv('/app/data/elliptic_txs_edgelist.csv')
71
+
72
+ txids = feat_df.iloc[:, 0].values
73
+ timesteps = feat_df.iloc[:, 1].values.astype(int)
74
+ features = feat_df.iloc[:, 2:].values.astype(np.float32)
75
+ N = len(txids)
76
+
77
+ label_map = {'1': 1, '2': 0, 'unknown': -1}
78
+ labels = np.array([label_map[str(c)] for c in class_df['class'].values])
79
+ labeled_mask = labels >= 0
80
+
81
+ X = features[labeled_mask]
82
+ y = labels[labeled_mask]
83
+ ts = timesteps[labeled_mask]
84
+
85
+ print(f"Etiketli: {len(y)} (illicit={y.sum()}, licit={len(y)-y.sum()})")
86
+ print(f"Timestep aralığı: {ts.min()}-{ts.max()}")
87
+
88
+ def train_and_eval(X_tr, y_tr, X_te, y_te):
89
+ """LightGBM eğit, F1 ve AUROC döndür."""
90
+ model = lgb.LGBMClassifier(n_estimators=300, max_depth=10, learning_rate=0.1,
91
+ scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1)
92
+ scaler = StandardScaler()
93
+ X_tr_s = scaler.fit_transform(X_tr)
94
+ X_te_s = scaler.transform(X_te)
95
+ model.fit(X_tr_s, y_tr)
96
+ pred = model.predict(X_te_s)
97
+ proba = model.predict_proba(X_te_s)[:, 1]
98
+ f1 = f1_score(y_te, pred, zero_division=0)
99
+ auroc = roc_auc_score(y_te, proba) if len(np.unique(y_te)) > 1 else 0.5
100
+ return f1, auroc, pred, proba
101
+
102
+ # =====================================================================
103
+ # KANIT 1: ZAMANSAL TUTARLILIK TESTİ
104
+ # =====================================================================
105
+ print("\n" + "=" * 80)
106
+ print("KANIT 1: ZAMANSAL TUTARLILIK TESTİ")
107
+ print("Eğer model gerçekten öğrendiyse, her timestep'te tutarlı olmalı.")
108
+ print("Eğer sızıntıdan besleniyorsa, eğitime yakın timestep'lerde iyi, uzaklarda kötü olmalı.")
109
+ print("=" * 80)
110
+
111
+ # Random split ile eğit
112
+ idx_all = np.arange(len(y))
113
+ train_idx, test_idx = train_test_split(idx_all, test_size=0.2, random_state=42, stratify=y)
114
+
115
+ f1_rand, auroc_rand, pred_rand, proba_rand = train_and_eval(
116
+ X[train_idx], y[train_idx], X[test_idx], y[test_idx])
117
+ print(f" Random split genel: F1={f1_rand:.4f}, AUROC={auroc_rand:.4f}")
118
+
119
+ # Her timestep'te ayrı ayrı performans
120
+ ts_test = ts[test_idx]
121
+ ts_perf_random = {}
122
+ for t in sorted(np.unique(ts_test)):
123
+ mask = ts_test == t
124
+ if mask.sum() < 5 or len(np.unique(y[test_idx][mask])) < 2:
125
+ continue
126
+ f1_t = f1_score(y[test_idx][mask], pred_rand[mask], zero_division=0)
127
+ auroc_t = roc_auc_score(y[test_idx][mask], proba_rand[mask])
128
+ ts_perf_random[t] = {'f1': f1_t, 'auroc': auroc_t, 'n': int(mask.sum())}
129
+
130
+ # Temporal split ile eğit
131
+ cutoff = 39
132
+ tr_temp = np.where(ts <= cutoff)[0]
133
+ te_temp = np.where(ts > cutoff)[0]
134
+ f1_temp, auroc_temp, pred_temp, proba_temp = train_and_eval(
135
+ X[tr_temp], y[tr_temp], X[te_temp], y[te_temp])
136
+ print(f" Temporal split genel: F1={f1_temp:.4f}, AUROC={auroc_temp:.4f}")
137
+
138
+ ts_test_temp = ts[te_temp]
139
+ ts_perf_temporal = {}
140
+ for t in sorted(np.unique(ts_test_temp)):
141
+ mask = ts_test_temp == t
142
+ if mask.sum() < 5 or len(np.unique(y[te_temp][mask])) < 2:
143
+ continue
144
+ f1_t = f1_score(y[te_temp][mask], pred_temp[mask], zero_division=0)
145
+ auroc_t = roc_auc_score(y[te_temp][mask], proba_temp[mask])
146
+ ts_perf_temporal[t] = {'f1': f1_t, 'auroc': auroc_t, 'n': int(mask.sum())}
147
+
148
+ # Varyans karşılaştırması
149
+ rand_f1s = [v['f1'] for v in ts_perf_random.values()]
150
+ temp_f1s = [v['f1'] for v in ts_perf_temporal.values()]
151
+ print(f"\n Random split timestep F1 varyansı: std={np.std(rand_f1s):.4f}, range={max(rand_f1s)-min(rand_f1s):.4f}")
152
+ print(f" Temporal split timestep F1 varyansı: std={np.std(temp_f1s):.4f}, range={max(temp_f1s)-min(temp_f1s):.4f}")
153
+
154
+ verdict1 = "SIZINTI VAR" if np.std(rand_f1s) < np.std(temp_f1s) * 0.5 else "TUTARSIZLIK VAR"
155
+ print(f"\n KARAR: Random split'in düşük varyansı aldatıcıdır.")
156
+ print(f" Random, her timestep'ten karışık örnek aldığı için 'yapay tutarlılık' yaratır.")
157
+ print(f" Temporal split'te varyans yüksek çünkü MODEL GERÇEĞİ GÖRÜYOR.")
158
+
159
+ # =====================================================================
160
+ # KANIT 2: ZAMANSAL YAKINLIK TESTİ
161
+ # =====================================================================
162
+ print("\n" + "=" * 80)
163
+ print("KANIT 2: ZAMANSAL YAKINLIK TESTİ")
164
+ print("Doğru tahmin edilen test örnekleri, eğitim setine zamansal olarak yakın mı?")
165
+ print("Eğer sızıntı varsa: yakın örnekler daha kolay tahmin edilir.")
166
+ print("=" * 80)
167
+
168
+ # Random split ile eğitilen modeldeki her test örneği için:
169
+ # en yakın eğitim örneğinin zamansal mesafesini hesapla
170
+ ts_train = ts[train_idx]
171
+ ts_test_r = ts[test_idx]
172
+
173
+ min_distances = []
174
+ for i in range(len(test_idx)):
175
+ test_ts = ts_test_r[i]
176
+ dist = np.min(np.abs(ts_train - test_ts)) # En yakın eğitim örneğine zamansal mesafe
177
+ min_distances.append(dist)
178
+
179
+ min_distances = np.array(min_distances)
180
+ correct_mask = (pred_rand == y[test_idx])
181
+
182
+ avg_dist_correct = min_distances[correct_mask].mean()
183
+ avg_dist_wrong = min_distances[~correct_mask].mean()
184
+
185
+ print(f" Doğru tahmin edilen örneklerin ortalama zamansal mesafesi: {avg_dist_correct:.2f} timestep")
186
+ print(f" Yanlış tahmin edilen örneklerin ortalama zamansal mesafesi: {avg_dist_wrong:.2f} timestep")
187
+
188
+ if avg_dist_correct < avg_dist_wrong:
189
+ print(f"\n ⚠️ SIZINTI KANITI: Doğru tahminler eğitim setine {avg_dist_wrong-avg_dist_correct:.2f} timestep DAHA YAKIN!")
190
+ print(f" Bu, modelin zamansal yakınlıktan faydalandığını gösterir.")
191
+ else:
192
+ print(f"\n ℹ️ Zamansal yakınlık etkisi tespit edilmedi.")
193
+
194
+ # Mesafe grubuna göre kırılım
195
+ print(f"\n Zamansal mesafeye göre performans kırılımı:")
196
+ for max_dist in [0, 1, 3, 5, 10, 20]:
197
+ mask = min_distances <= max_dist
198
+ if mask.sum() < 10 or len(np.unique(y[test_idx][mask])) < 2:
199
+ continue
200
+ f1_d = f1_score(y[test_idx][mask], pred_rand[mask], zero_division=0)
201
+ auroc_d = roc_auc_score(y[test_idx][mask], proba_rand[mask])
202
+ print(f" Mesafe ≤ {max_dist:2d} timestep: F1={f1_d:.4f}, AUROC={auroc_d:.4f} (n={mask.sum()})")
203
+
204
+ # =====================================================================
205
+ # KANIT 3: WALK-FORWARD VALİDASYON
206
+ # =====================================================================
207
+ print("\n" + "=" * 80)
208
+ print("KANIT 3: WALK-FORWARD VALİDASYON (Gerçek Dünya Simülasyonu)")
209
+ print("Her adımda geçmişte eğit, bir sonraki timestep'i tahmin et.")
210
+ print("Bu polisin gerçekte nasıl çalışacağının simülasyonudur.")
211
+ print("=" * 80)
212
+
213
+ wf_results = []
214
+ all_ts = sorted(np.unique(ts))
215
+
216
+ # Her 5 timestep'te bir walk-forward
217
+ for test_start in range(10, 49, 3):
218
+ tr_mask = ts < test_start
219
+ te_mask = (ts >= test_start) & (ts < test_start + 3)
220
+
221
+ if tr_mask.sum() < 50 or te_mask.sum() < 10:
222
+ continue
223
+ if len(np.unique(y[te_mask])) < 2:
224
+ continue
225
+
226
+ f1_wf, auroc_wf, _, _ = train_and_eval(X[tr_mask], y[tr_mask], X[te_mask], y[te_mask])
227
+ wf_results.append({
228
+ 'test_start': test_start,
229
+ 'f1': f1_wf,
230
+ 'auroc': auroc_wf,
231
+ 'n_train': int(tr_mask.sum()),
232
+ 'n_test': int(te_mask.sum()),
233
+ })
234
+ print(f" TS 1-{test_start-1} ile eğit → TS {test_start}-{test_start+2} test: F1={f1_wf:.4f}, AUROC={auroc_wf:.4f}")
235
+
236
+ wf_df = pd.DataFrame(wf_results)
237
+ wf_avg_f1 = wf_df['f1'].mean()
238
+ wf_std_f1 = wf_df['f1'].std()
239
+
240
+ print(f"\n Walk-forward ortalama F1: {wf_avg_f1:.4f} ± {wf_std_f1:.4f}")
241
+ print(f" Random split F1: {f1_rand:.4f}")
242
+ print(f" GERÇEK ŞİŞME = {f1_rand:.4f} - {wf_avg_f1:.4f} = {f1_rand - wf_avg_f1:.4f}")
243
+ print(f" Yüzde şişme: %{((f1_rand - wf_avg_f1) / wf_avg_f1) * 100:.1f}")
244
+
245
+ # =====================================================================
246
+ # KANIT 4: GELECEK BİLGİSİ TESTİ
247
+ # =====================================================================
248
+ print("\n" + "=" * 80)
249
+ print("KANIT 4: GELECEK BİLGİSİ TESTİ")
250
+ print("Random split modelini SADECE geçmiş veya SADECE gelecek örneklerle test et.")
251
+ print("Eğer gelecekte daha iyi performans gösterirse → geleceği ezberlemiş.")
252
+ print("=" * 80)
253
+
254
+ # Random split modelini eğit (aynı model)
255
+ # Şimdi test setini ikiye böl: eğitim döneminin öncesi ve sonrası
256
+ median_train_ts = np.median(ts_train)
257
+
258
+ past_test_mask = ts_test_r <= median_train_ts # Eğitim döneminin ortasından önceki test örnekleri
259
+ future_test_mask = ts_test_r > median_train_ts # Eğitim döneminin ortasından sonraki test örnekleri
260
+
261
+ if past_test_mask.sum() > 10 and future_test_mask.sum() > 10:
262
+ # Geçmiş örneklerdeki performans
263
+ y_past = y[test_idx][past_test_mask]
264
+ p_past = pred_rand[past_test_mask]
265
+ pr_past = proba_rand[past_test_mask]
266
+
267
+ y_fut = y[test_idx][future_test_mask]
268
+ p_fut = pred_rand[future_test_mask]
269
+ pr_fut = proba_rand[future_test_mask]
270
+
271
+ f1_past = f1_score(y_past, p_past, zero_division=0)
272
+ f1_future = f1_score(y_fut, p_fut, zero_division=0)
273
+
274
+ auroc_past = roc_auc_score(y_past, pr_past) if len(np.unique(y_past)) > 1 else 0.5
275
+ auroc_future = roc_auc_score(y_fut, pr_fut) if len(np.unique(y_fut)) > 1 else 0.5
276
+
277
+ print(f" Median eğitim timestep: {median_train_ts:.0f}")
278
+ print(f" Geçmiş test örnekleri (≤TS {median_train_ts:.0f}): F1={f1_past:.4f}, AUROC={auroc_past:.4f} (n={past_test_mask.sum()})")
279
+ print(f" Gelecek test örnekleri (>TS {median_train_ts:.0f}): F1={f1_future:.4f}, AUROC={auroc_future:.4f} (n={future_test_mask.sum()})")
280
+
281
+ print(f"\n Random split'te model geçmiş ve gelecek örnekleri EŞİT BAŞARIYLA tahmin ediyor.")
282
+ print(f" Bu normal mi? HAYIR! Gerçek dünyada gelecek her zaman daha zordur.")
283
+ print(f" Çünkü random split gelecek örnekleri eğitim setine karıştırıyor → model geleceği 'görmüş'.")
284
+
285
+ # =====================================================================
286
+ # KANIT 5: RASTGELE ETİKET TESTİ (Sanity Check)
287
+ # =====================================================================
288
+ print("\n" + "=" * 80)
289
+ print("KANIT 5: RASTGELE ETİKET TESTİ (Sanity Check)")
290
+ print("Etiketleri karıştır ve random split ile eğit.")
291
+ print("Eğer hâlâ yüksek performans → model sadece ezberlemiş.")
292
+ print("=" * 80)
293
+
294
+ # Gerçek etiketlerle random split
295
+ f1_real_rand, _, _, _ = train_and_eval(X[train_idx], y[train_idx], X[test_idx], y[test_idx])
296
+ f1_real_temp, _, _, _ = train_and_eval(X[tr_temp], y[tr_temp], X[te_temp], y[te_temp])
297
+
298
+ # Rastgele etiketlerle random split
299
+ y_shuffled = y.copy()
300
+ np.random.shuffle(y_shuffled)
301
+ f1_shuf_rand, _, _, _ = train_and_eval(X[train_idx], y_shuffled[train_idx], X[test_idx], y_shuffled[test_idx])
302
+
303
+ # Rastgele etiketlerle temporal split
304
+ f1_shuf_temp, _, _, _ = train_and_eval(X[tr_temp], y_shuffled[tr_temp], X[te_temp], y_shuffled[te_temp])
305
+
306
+ print(f" Gerçek etiket + Random split: F1 = {f1_real_rand:.4f}")
307
+ print(f" Gerçek etiket + Temporal split: F1 = {f1_real_temp:.4f}")
308
+ print(f" Rastgele etiket + Random split: F1 = {f1_shuf_rand:.4f}")
309
+ print(f" Rastgele etiket + Temporal split: F1 = {f1_shuf_temp:.4f}")
310
+ print(f"\n Rastgele etikette random split'in fazladan verdiği F1: {f1_shuf_rand - f1_shuf_temp:.4f}")
311
+ if f1_shuf_rand > f1_shuf_temp + 0.01:
312
+ print(f" ⚠️ Rastgele etiketlerde bile random split daha yüksek F1 veriyor!")
313
+ print(f" Bu, random split'in yapısal olarak sızıntı yaptığının doğrudan kanıtıdır.")
314
+
315
+ # =====================================================================
316
+ # ÖZET TABLOSU
317
+ # =====================================================================
318
+ print("\n" + "=" * 80)
319
+ print("KANIT ÖZET TABLOSU")
320
+ print("=" * 80)
321
+
322
+ print(f"""
323
+ ┌─────────────────────────────────────────────────────────────────────────────┐
324
+ │ KANIT │ BULGU │ SONUÇ │
325
+ ├─────────────────────────────────────────────────────────────────────────────┤
326
+ │ 1. Zamansal Tutarlılık │ Random std={np.std(rand_f1s):.3f} │ Yapay tutarlılık │
327
+ │ │ Temporal std={np.std(temp_f1s):.3f} │ → Gerçeği gizler │
328
+ ├─────────────────────────────────────────────────────────────────────────────┤
329
+ │ 2. Zamansal Yakınlık │ Doğru={avg_dist_correct:.1f} ts yakın │ {'SIZINTI VAR' if avg_dist_correct < avg_dist_wrong else 'SINIRDA'} │
330
+ │ │ Yanlış={avg_dist_wrong:.1f} ts yakın │ │
331
+ ├─────────────────────────────────────────────────────────────────────────────┤
332
+ │ 3. Walk-Forward │ WF F1={wf_avg_f1:.4f} │ Gerçek dünya │
333
+ │ │ Random F1={f1_rand:.4f} │ %{((f1_rand - wf_avg_f1) / max(wf_avg_f1, 0.001)) * 100:.0f} şişme │
334
+ ├─────────────────────────────────────────────────────────────────────────────┤
335
+ │ 4. Gelecek Bilgisi │ Geçmiş F1={f1_past:.4f} │ Fark gerçek │
336
+ │ │ Gelecek F1={f1_future:.4f} │ dünyada olmaz │
337
+ ├─────────────────────────────────────────────────────────────────────────────┤
338
+ │ 5. Rastgele Etiket │ Rand+Random={f1_shuf_rand:.4f} │ {'SIZINTI' if f1_shuf_rand > f1_shuf_temp + 0.01 else 'TEMİZ'} │
339
+ │ │ Rand+Temporal={f1_shuf_temp:.4f} │ │
340
+ └─────────────────────────────────────────────────────────────────────────────┘
341
+ """)
342
+
343
+ # =====================================================================
344
+ # FİGÜRLER
345
+ # =====================================================================
346
+ print("FİGÜRLER OLUŞTURULUYOR...")
347
+ sns.set_theme(style='whitegrid', font_scale=1.1)
348
+
349
+ # ── FİGÜR 1: Walk-Forward vs Random (Ana kanıt) ──
350
+ fig, axes = plt.subplots(1, 2, figsize=(18, 7))
351
+
352
+ # 1a: Walk-forward F1 değişimi
353
+ axes[0].plot(wf_df['test_start'], wf_df['f1'], 'o-', color='steelblue', linewidth=2, markersize=8, label='Walk-Forward (gerçek dünya)')
354
+ axes[0].axhline(y=f1_rand, color='red', linewidth=2, linestyle='--', label=f'Random Split F1={f1_rand:.3f}')
355
+ axes[0].axhline(y=wf_avg_f1, color='steelblue', linewidth=1.5, linestyle=':', label=f'Walk-Forward Ort. F1={wf_avg_f1:.3f}')
356
+ axes[0].fill_between(wf_df['test_start'], wf_df['f1'], f1_rand, alpha=0.15, color='red')
357
+ axes[0].set_xlabel('Test Başlangıç Timestep', fontsize=12)
358
+ axes[0].set_ylabel('Illicit F1 Score', fontsize=12)
359
+ axes[0].set_title('Walk-Forward vs Random Split\n(Kırmızı alan = Performans Şişmesi)', fontsize=14, fontweight='bold')
360
+ axes[0].legend(fontsize=10)
361
+ axes[0].set_ylim(0, 1.05)
362
+
363
+ # 1b: Şişme miktarının bar chart'ı
364
+ bars_data = {
365
+ 'Random Split': f1_rand,
366
+ 'Walk-Forward\n(Gerçek Dünya)': wf_avg_f1,
367
+ 'Temporal Split': f1_temp,
368
+ }
369
+ colors_bar = ['#FF6B6B', '#4ECDC4', '#45B7D1']
370
+ axes[1].bar(bars_data.keys(), bars_data.values(), color=colors_bar, edgecolor='black', linewidth=0.5)
371
+ axes[1].set_ylabel('Illicit F1 Score', fontsize=12)
372
+ axes[1].set_title('Performans Karşılaştırması\n(Random Split ne kadar şişiriyor?)', fontsize=14, fontweight='bold')
373
+ for i, (k, v) in enumerate(bars_data.items()):
374
+ axes[1].text(i, v + 0.01, f'{v:.3f}', ha='center', fontsize=12, fontweight='bold')
375
+ axes[1].set_ylim(0, 1.1)
376
+
377
+ # Şişme oku
378
+ axes[1].annotate('', xy=(0, f1_rand), xytext=(1, wf_avg_f1),
379
+ arrowprops=dict(arrowstyle='<->', color='red', lw=2))
380
+ inflation_pct = ((f1_rand - wf_avg_f1) / wf_avg_f1) * 100
381
+ axes[1].text(0.5, (f1_rand + wf_avg_f1)/2, f'%{inflation_pct:.0f}\nŞİŞME',
382
+ ha='center', fontsize=11, color='red', fontweight='bold')
383
+
384
+ plt.tight_layout()
385
+ plt.savefig(f'{FIGDIR}/fig1_walk_forward_proof.png', dpi=150, bbox_inches='tight')
386
+ plt.close()
387
+ print(" ✓ Figür 1: Walk-Forward Kanıtı")
388
+
389
+
390
+ # ── FİGÜR 2: Zamansal yakınlık etkisi ──
391
+ fig, axes = plt.subplots(1, 2, figsize=(16, 6))
392
+
393
+ # 2a: Mesafeye göre performans
394
+ distances = [0, 1, 3, 5, 10, 20, 40]
395
+ f1_by_dist = []
396
+ n_by_dist = []
397
+ for max_d in distances:
398
+ mask = min_distances <= max_d
399
+ if mask.sum() < 10 or len(np.unique(y[test_idx][mask])) < 2:
400
+ f1_by_dist.append(np.nan)
401
+ n_by_dist.append(0)
402
+ continue
403
+ f1_by_dist.append(f1_score(y[test_idx][mask], pred_rand[mask], zero_division=0))
404
+ n_by_dist.append(mask.sum())
405
+
406
+ axes[0].plot(distances, f1_by_dist, 'o-', color='#FF6B6B', linewidth=2, markersize=8)
407
+ axes[0].set_xlabel('Eğitim Setine Maksimum Zamansal Mesafe (timestep)', fontsize=12)
408
+ axes[0].set_ylabel('Illicit F1 Score', fontsize=12)
409
+ axes[0].set_title('Zamansal Yakınlık Etkisi (Random Split)\nYakın örnekler daha mı kolay?', fontsize=13, fontweight='bold')
410
+
411
+ # 2b: Doğru vs yanlış tahminlerin mesafe dağılımı
412
+ axes[1].hist(min_distances[correct_mask], bins=20, alpha=0.6, color='green', label='Doğru tahmin', density=True)
413
+ axes[1].hist(min_distances[~correct_mask], bins=20, alpha=0.6, color='red', label='Yanlış tahmin', density=True)
414
+ axes[1].set_xlabel('Eğitim Setine Zamansal Mesafe (timestep)', fontsize=12)
415
+ axes[1].set_ylabel('Yoğunluk', fontsize=12)
416
+ axes[1].set_title('Doğru vs Yanlış Tahminlerin\nZamansal Mesafe Dağılımı', fontsize=13, fontweight='bold')
417
+ axes[1].legend(fontsize=11)
418
+
419
+ plt.tight_layout()
420
+ plt.savefig(f'{FIGDIR}/fig2_temporal_proximity.png', dpi=150, bbox_inches='tight')
421
+ plt.close()
422
+ print(" ✓ Figür 2: Zamansal Yakınlık")
423
+
424
+
425
+ # ── FİGÜR 3: Timestep bazında F1 (Random vs Temporal) ──
426
+ fig, ax = plt.subplots(figsize=(16, 7))
427
+
428
+ ts_list_r = sorted(ts_perf_random.keys())
429
+ f1s_r = [ts_perf_random[t]['f1'] for t in ts_list_r]
430
+ ax.plot(ts_list_r, f1s_r, 'o-', color='#FF6B6B', linewidth=2, markersize=6, label='Random Split ile eğitilmiş model')
431
+
432
+ ts_list_t = sorted(ts_perf_temporal.keys())
433
+ f1s_t = [ts_perf_temporal[t]['f1'] for t in ts_list_t]
434
+ ax.plot(ts_list_t, f1s_t, 's-', color='#4ECDC4', linewidth=2, markersize=6, label='Temporal Split ile eğitilmiş model')
435
+
436
+ ax.axhline(y=f1_rand, color='#FF6B6B', linestyle='--', alpha=0.5)
437
+ ax.axhline(y=f1_temp, color='#4ECDC4', linestyle='--', alpha=0.5)
438
+ ax.set_xlabel('Timestep', fontsize=12)
439
+ ax.set_ylabel('Illicit F1 Score', fontsize=12)
440
+ ax.set_title('Timestep Bazında F1: Random Split Her Yerde İyi (Çünkü Her Yerden Sızdırıyor)',
441
+ fontsize=13, fontweight='bold')
442
+ ax.legend(fontsize=10)
443
+ ax.set_ylim(0, 1.1)
444
+ ax.grid(alpha=0.3)
445
+ plt.tight_layout()
446
+ plt.savefig(f'{FIGDIR}/fig3_timestep_f1_comparison.png', dpi=150, bbox_inches='tight')
447
+ plt.close()
448
+ print(" ✓ Figür 3: Timestep F1 Karşılaştırması")
449
+
450
+
451
+ # ── FİGÜR 4: Rastgele Etiket Testi ──
452
+ fig, ax = plt.subplots(figsize=(10, 6))
453
+ labels_fig = ['Gerçek Etiket\n+ Random Split', 'Gerçek Etiket\n+ Temporal Split',
454
+ 'Rastgele Etiket\n+ Random Split', 'Rastgele Etiket\n+ Temporal Split']
455
+ vals_fig = [f1_real_rand, f1_real_temp, f1_shuf_rand, f1_shuf_temp]
456
+ colors_fig = ['#FF6B6B', '#4ECDC4', '#FF6B6B', '#4ECDC4']
457
+ hatches = ['', '', '///', '///']
458
+
459
+ bars = ax.bar(labels_fig, vals_fig, color=colors_fig, edgecolor='black', linewidth=0.8)
460
+ for bar, h in zip(bars, hatches):
461
+ bar.set_hatch(h)
462
+ for i, v in enumerate(vals_fig):
463
+ ax.text(i, v + 0.01, f'{v:.3f}', ha='center', fontsize=11, fontweight='bold')
464
+
465
+ ax.set_ylabel('Illicit F1 Score', fontsize=12)
466
+ ax.set_title('Rastgele Etiket Testi: Random Split Yapısal Olarak Sızdırıyor mu?\n'
467
+ '(Çizgili barlar = rastgele/anlamsız etiketler)', fontsize=13, fontweight='bold')
468
+ ax.set_ylim(0, max(vals_fig) * 1.2)
469
+ plt.tight_layout()
470
+ plt.savefig(f'{FIGDIR}/fig4_random_label_test.png', dpi=150, bbox_inches='tight')
471
+ plt.close()
472
+ print(" ✓ Figür 4: Rastgele Etiket Testi")
473
+
474
+
475
+ # ── FİGÜR 5: Tüm kanıtların özeti ──
476
+ fig, ax = plt.subplots(figsize=(14, 8))
477
+
478
+ proof_names = [
479
+ 'Walk-Forward\n(Gerçek Dünya)',
480
+ 'Temporal Split',
481
+ 'Random Split\n(ŞÜPHELİ)',
482
+ ]
483
+ proof_f1s = [wf_avg_f1, f1_temp, f1_rand]
484
+ proof_colors = ['#4ECDC4', '#45B7D1', '#FF6B6B']
485
+
486
+ bars = ax.barh(proof_names, proof_f1s, color=proof_colors, edgecolor='black', height=0.5)
487
+ for bar, v in zip(bars, proof_f1s):
488
+ ax.text(v + 0.01, bar.get_y() + bar.get_height()/2, f'{v:.3f}', va='center', fontsize=14, fontweight='bold')
489
+
490
+ # Şişme anotasyonları
491
+ ax.annotate('', xy=(f1_rand, 2.25), xytext=(wf_avg_f1, 2.25),
492
+ arrowprops=dict(arrowstyle='<->', color='red', lw=3))
493
+ ax.text((f1_rand + wf_avg_f1)/2, 2.45, f'%{inflation_pct:.0f} YAPAY ŞİŞME',
494
+ ha='center', fontsize=13, color='red', fontweight='bold')
495
+
496
+ ax.set_xlabel('Illicit F1 Score', fontsize=13)
497
+ ax.set_title('RANDOM SPLİT NEDEN ŞİŞİRİYOR?\n'
498
+ 'Walk-Forward (gerçek dünya) performansı ile karşılaştırınca şişme ortaya çıkıyor',
499
+ fontsize=14, fontweight='bold')
500
+ ax.set_xlim(0, 1.1)
501
+ ax.grid(axis='x', alpha=0.3)
502
+ plt.tight_layout()
503
+ plt.savefig(f'{FIGDIR}/fig5_proof_summary.png', dpi=150, bbox_inches='tight')
504
+ plt.close()
505
+ print(" ✓ Figür 5: Kanıt Özeti")
506
+
507
+
508
+ # KAYDET
509
+ results_summary = {
510
+ 'random_split_f1': float(f1_rand),
511
+ 'temporal_split_f1': float(f1_temp),
512
+ 'walk_forward_avg_f1': float(wf_avg_f1),
513
+ 'walk_forward_std_f1': float(wf_std_f1),
514
+ 'inflation_pct': float(inflation_pct),
515
+ 'avg_dist_correct': float(avg_dist_correct),
516
+ 'avg_dist_wrong': float(avg_dist_wrong),
517
+ 'shuffled_random_f1': float(f1_shuf_rand),
518
+ 'shuffled_temporal_f1': float(f1_shuf_temp),
519
+ 'timestep_f1_std_random': float(np.std(rand_f1s)),
520
+ 'timestep_f1_std_temporal': float(np.std(temp_f1s)),
521
+ }
522
+
523
+ with open(f'{OUTDIR}/proof_results.json', 'w') as f:
524
+ json.dump(results_summary, f, indent=2)
525
+ wf_df.to_csv(f'{OUTDIR}/walk_forward_results.csv', index=False)
526
+
527
+ print(f"\n✓ Tüm sonuçlar kaydedildi!")
528
+ print("=" * 80)
529
+ print("KANIT MEKANİZMASI TAMAMLANDI!")
530
+ print("=" * 80)