Mucahit S.
Upload leakage_map.py with huggingface_hub
137a6b0 verified
"""
===============================================================================
SIZINTI HARİTASI (LEAKAGE MAP): Sızıntının Ağ Üzerinde Nasıl Aktığını Göster
===============================================================================
HİÇ YAPILMAMIŞ ÖZGÜN KATKI:
Random split yaptığınızda test düğümlerinin komşuları eğitim setinde olabiliyor.
GraphSAGE gibi GNN'ler komşuluktan bilgi topladığı için, bu komşuluk üzerinden
geleceğin bilgisi geçmişe SIZIYOR.
Biz bunu ilk kez ölçüyor ve haritasını çıkarıyoruz:
1. KOMŞULUK SIZINTI ORANI: Her test düğümü için komşularının yüzde kaçı
eğitim setinde? (Yüksek = çok sızıntı)
2. SIZINTI-BAŞARI KORELASYONU: Komşu sızıntısı yüksek olan düğümler daha mı
doğru tahmin ediliyor? (Korelasyon varsa = sızıntı kanıtı)
3. TEMPORAL SIZINTI: Test düğümünün komşuları farklı bir zaman diliminden mi?
(Farklı zaman = temporal bilgi sızıntısı)
4. YAPI DUYARLILIĞI: Komşu sızıntısı kaldırıldığında (test düğümlerinin
eğitim komşularıyla bağları kesildiğinde) performans ne kadar düşüyor?
(Düşüş = model sızıntıya bağımlıydı)
===============================================================================
"""
import os, json, warnings
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
from sklearn.model_selection import train_test_split
import lightgbm as lgb
warnings.filterwarnings('ignore')
np.random.seed(42)
FIGDIR = '/app/figures_proof'
OUTDIR = '/app/results_proof'
# ─── VERİ ───
feat_df = pd.read_csv('/app/data/elliptic_txs_features.csv', header=None)
class_df = pd.read_csv('/app/data/elliptic_txs_classes.csv')
edge_df = pd.read_csv('/app/data/elliptic_txs_edgelist.csv')
txids = feat_df.iloc[:, 0].values
timesteps = feat_df.iloc[:, 1].values.astype(int)
features = feat_df.iloc[:, 2:].values.astype(np.float32)
N = len(txids)
id_map = {tid: i for i, tid in enumerate(txids)}
label_map = {'1': 1, '2': 0, 'unknown': -1}
labels = np.array([label_map[str(c)] for c in class_df['class'].values])
# Komşuluk sözlüğü oluştur
print("Komşuluk sözlüğü oluşturuluyor...")
neighbors = {i: set() for i in range(N)}
for _, row in edge_df.iterrows():
s = id_map.get(row['txId1'])
d = id_map.get(row['txId2'])
if s is not None and d is not None:
neighbors[s].add(d)
neighbors[d].add(s)
print(f" Toplam düğüm: {N}, ortalama komşu: {np.mean([len(v) for v in neighbors.values()]):.1f}")
labeled_mask = labels >= 0
X = features[labeled_mask]
y = labels[labeled_mask]
ts = timesteps[labeled_mask]
labeled_indices = np.where(labeled_mask)[0] # Global indeksler
# ─── RANDOM SPLIT ───
idx_all = np.arange(len(y))
train_idx, test_idx = train_test_split(idx_all, test_size=0.2, random_state=42, stratify=y)
train_global = set(labeled_indices[train_idx])
test_global = labeled_indices[test_idx]
# ─── Model eğit ───
scaler = StandardScaler()
X_tr = scaler.fit_transform(X[train_idx])
X_te = scaler.transform(X[test_idx])
model = lgb.LGBMClassifier(n_estimators=300, max_depth=10, scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1)
model.fit(X_tr, y[train_idx])
pred = model.predict(X_te)
proba = model.predict_proba(X_te)[:, 1]
correct = (pred == y[test_idx])
print(f"\nRandom split F1: {f1_score(y[test_idx], pred):.4f}")
# ═════════════════════════════════════════════════════════════════════════
# ÖLÇÜM 1: KOMŞULUK SIZINTI ORANI
# ═════════════════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("ÖLÇÜM 1: KOMŞULUK SIZINTI ORANI")
print("Her test düğümünün komşularının yüzde kaçı eğitim setinde?")
print("=" * 70)
leakage_ratios = []
neighbor_counts = []
train_neighbor_ts_diffs = []
for i, global_idx in enumerate(test_global):
nbrs = neighbors[global_idx]
if len(nbrs) == 0:
leakage_ratios.append(0.0)
neighbor_counts.append(0)
train_neighbor_ts_diffs.append(0.0)
continue
train_nbrs = nbrs & train_global
ratio = len(train_nbrs) / len(nbrs)
leakage_ratios.append(ratio)
neighbor_counts.append(len(nbrs))
# Zamansal mesafe: test düğümü ile eğitim komşuları arası
test_ts = timesteps[global_idx]
if len(train_nbrs) > 0:
nbr_ts = [timesteps[n] for n in train_nbrs]
avg_diff = np.mean(np.abs(np.array(nbr_ts) - test_ts))
train_neighbor_ts_diffs.append(avg_diff)
else:
train_neighbor_ts_diffs.append(0.0)
leakage_ratios = np.array(leakage_ratios)
neighbor_counts = np.array(neighbor_counts)
train_neighbor_ts_diffs = np.array(train_neighbor_ts_diffs)
print(f" Ortalama sızıntı oranı: {leakage_ratios.mean():.3f}")
print(f" Median sızıntı oranı: {np.median(leakage_ratios):.3f}")
print(f" Sızıntısız düğüm (0.0): {(leakage_ratios == 0).sum()} ({(leakage_ratios == 0).mean()*100:.1f}%)")
print(f" Tam sızıntı (1.0): {(leakage_ratios == 1).sum()} ({(leakage_ratios == 1).mean()*100:.1f}%)")
# ═════════════════════════════════════════════════════════════════════════
# ÖLÇÜM 2: SIZINTI-BAŞARI KORELASYONU
# ═════════════════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("ÖLÇÜM 2: SIZINTI-BAŞARI KORELASYONU")
print("Komşu sızıntısı yüksek düğümler daha mı kolay tahmin ediliyor?")
print("=" * 70)
# Sızıntı oranı gruplarına göre performans
bins = [0.0, 0.2, 0.4, 0.6, 0.8, 1.01]
bin_labels = ['0-20%', '20-40%', '40-60%', '60-80%', '80-100%']
leak_bins = pd.cut(leakage_ratios, bins=bins, labels=bin_labels, include_lowest=True)
print(f"\n Sızıntı grubuna göre doğruluk:")
bin_results = []
for bl in bin_labels:
mask = (leak_bins == bl)
if mask.sum() < 10:
continue
acc = correct[mask].mean()
n = mask.sum()
n_ill = y[test_idx][mask].sum()
f1_bin = f1_score(y[test_idx][mask], pred[mask], zero_division=0) if len(np.unique(y[test_idx][mask])) > 1 else 0
bin_results.append({'bin': bl, 'accuracy': acc, 'f1': f1_bin, 'n': n, 'n_illicit': n_ill})
print(f" Sızıntı {bl:>8s}: doğruluk={acc:.4f}, F1={f1_bin:.4f} (n={n}, illicit={n_ill})")
# Korelasyon
corr = np.corrcoef(leakage_ratios[neighbor_counts > 0],
correct[neighbor_counts > 0].astype(float))[0, 1]
print(f"\n Sızıntı oranı ↔ doğru tahmin korelasyonu: r = {corr:.4f}")
if abs(corr) > 0.05:
print(f" ⚠️ Pozitif korelasyon: Sızıntı yüksek → tahmin daha kolay")
# ═════════════════════════════════════════════════════════════════════════
# ÖLÇÜM 3: TEMPORAL SIZINTI
# ═════════════════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("ÖLÇÜM 3: TEMPORAL SIZINTI")
print("Test düğümünün eğitim komşuları farklı zaman diliminden mi?")
print("=" * 70)
# Test düğümü ile eğitim komşuları arasındaki zamansal mesafe
has_train_nbrs = train_neighbor_ts_diffs > 0
print(f" Ortalama zamansal mesafe (test ↔ eğitim komşu): {train_neighbor_ts_diffs[has_train_nbrs].mean():.2f} timestep")
print(f" Aynı timestep'ten komşu oranı: {(train_neighbor_ts_diffs == 0).sum() / max(has_train_nbrs.sum(), 1) * 100:.1f}%")
# TEMPORAL SPLIT İLE KARŞILAŞTIR
print("\n Temporal split'te sızıntı oranı:")
cutoff = 39
temp_train_global = set(np.where((labels >= 0) & (timesteps <= cutoff))[0])
temp_test_global = np.where((labels >= 0) & (timesteps > cutoff))[0]
temp_leak = []
for global_idx in temp_test_global:
nbrs = neighbors[global_idx]
if len(nbrs) == 0:
temp_leak.append(0.0)
continue
temp_leak.append(len(nbrs & temp_train_global) / len(nbrs))
temp_leak = np.array(temp_leak)
print(f" Random split ortalama sızıntı oranı: {leakage_ratios.mean():.3f}")
print(f" Temporal split ortalama sızıntı oranı: {temp_leak.mean():.3f}")
print(f" FARK: Random split {leakage_ratios.mean() / max(temp_leak.mean(), 0.001):.1f}x daha fazla sızıntı!")
# ═════════════════════════════════════════════════════════════════════════
# ÖLÇÜM 4: YAPI DUYARLILIĞI TESTİ
# ═════════════════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("ÖLÇÜM 4: YAPI DUYARLILIĞI (Sızıntıya Bağımlılık)")
print("Aggregated özellikler (komşuluk bilgisi) çıkarılınca ne oluyor?")
print("=" * 70)
# Özellik 0-93: local (düğümün kendi özellikleri)
# Özellik 94-165: aggregated (komşuluk bilgisi)
X_local = features[labeled_mask][:, :93] # Sadece local özellikler
X_agg = features[labeled_mask][:, 93:] # Sadece aggregated özellikler
# Local ile eğit (sızıntıdan etkilenmez)
scaler_l = StandardScaler()
X_tr_l = scaler_l.fit_transform(X_local[train_idx])
X_te_l = scaler_l.transform(X_local[test_idx])
model_l = lgb.LGBMClassifier(n_estimators=300, max_depth=10, scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1)
model_l.fit(X_tr_l, y[train_idx])
pred_l = model_l.predict(X_te_l)
f1_local = f1_score(y[test_idx], pred_l)
# Aggregated ile eğit (komşuluk = sızıntı kanalı)
scaler_a = StandardScaler()
X_tr_a = scaler_a.fit_transform(X_agg[train_idx])
X_te_a = scaler_a.transform(X_agg[test_idx])
model_a = lgb.LGBMClassifier(n_estimators=300, max_depth=10, scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1)
model_a.fit(X_tr_a, y[train_idx])
pred_a = model_a.predict(X_te_a)
f1_agg = f1_score(y[test_idx], pred_a)
# Tüm özelliklerle
f1_all = f1_score(y[test_idx], pred)
# Temporal split ile aynı testi yap
tr_t = np.where((labels[labeled_mask] >= 0) & (ts <= cutoff))[0]
te_t = np.where((labels[labeled_mask] >= 0) & (ts > cutoff))[0]
if len(tr_t) > 0 and len(te_t) > 0 and len(np.unique(y[te_t])) > 1:
# Local - temporal
scaler_lt = StandardScaler()
model_lt = lgb.LGBMClassifier(n_estimators=300, max_depth=10, scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1)
model_lt.fit(scaler_lt.fit_transform(X_local[tr_t]), y[tr_t])
f1_local_temp = f1_score(y[te_t], model_lt.predict(scaler_lt.transform(X_local[te_t])))
# Aggregated - temporal
scaler_at = StandardScaler()
model_at = lgb.LGBMClassifier(n_estimators=300, max_depth=10, scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1)
model_at.fit(scaler_at.fit_transform(X_agg[tr_t]), y[tr_t])
f1_agg_temp = f1_score(y[te_t], model_at.predict(scaler_at.transform(X_agg[te_t])))
# All - temporal
scaler_allt = StandardScaler()
model_allt = lgb.LGBMClassifier(n_estimators=300, max_depth=10, scale_pos_weight=10, random_state=42, n_jobs=-1, verbose=-1)
model_allt.fit(scaler_allt.fit_transform(X[tr_t]), y[tr_t])
f1_all_temp = f1_score(y[te_t], model_allt.predict(scaler_allt.transform(X[te_t])))
print(f"\n RANDOM SPLIT:")
print(f" Tüm özellikler: F1 = {f1_all:.4f}")
print(f" Sadece local (93 feat): F1 = {f1_local:.4f}")
print(f" Sadece aggregated (72): F1 = {f1_agg:.4f}")
print(f" Aggregated'ın katkısı: +{f1_all - f1_local:.4f}")
print(f"\n TEMPORAL SPLIT:")
print(f" Tüm özellikler: F1 = {f1_all_temp:.4f}")
print(f" Sadece local (93 feat): F1 = {f1_local_temp:.4f}")
print(f" Sadece aggregated (72): F1 = {f1_agg_temp:.4f}")
print(f" Aggregated'ın katkısı: +{f1_all_temp - f1_local_temp:.4f}")
agg_boost_random = f1_all - f1_local
agg_boost_temporal = f1_all_temp - f1_local_temp
print(f"\n KILIT BULGU:")
print(f" Aggregated özelliklerin random split'te ekstra katkısı: +{agg_boost_random:.4f}")
print(f" Aggregated özelliklerin temporal split'te ekstra katkısı: +{agg_boost_temporal:.4f}")
if agg_boost_random > agg_boost_temporal + 0.01:
print(f" ⚠️ Aggregated özellikler random split'te {agg_boost_random/max(agg_boost_temporal, 0.001):.1f}x daha fazla katkı sağlıyor!")
print(f" Bu, komşuluk bilgisinin random split'te sızıntı kanalı olduğunun kanıtıdır.")
# ═════════════════════════════════════════════════════════════════════════
# FİGÜRLER
# ═════════════════════════════════════════════════════════════════════════
print("\nFİGÜRLER OLUŞTURULUYOR...")
sns.set_theme(style='whitegrid', font_scale=1.1)
# ── FİGÜR: Sızıntı Haritası ──
fig, axes = plt.subplots(2, 2, figsize=(18, 14))
# 1: Sızıntı oranı dağılımı
axes[0, 0].hist(leakage_ratios, bins=30, color='#FF6B6B', edgecolor='white', alpha=0.8)
axes[0, 0].set_xlabel('Komşuluk Sızıntı Oranı', fontsize=12)
axes[0, 0].set_ylabel('Düğüm Sayısı', fontsize=12)
axes[0, 0].set_title('Her Test Düğümünün Komşuluk Sızıntı Oranı\n(Random Split)', fontsize=13, fontweight='bold')
axes[0, 0].axvline(x=leakage_ratios.mean(), color='black', linewidth=2, linestyle='--',
label=f'Ortalama: {leakage_ratios.mean():.2f}')
axes[0, 0].legend(fontsize=11)
# 2: Sızıntı oranı vs doğruluk
if len(bin_results) > 0:
br_df = pd.DataFrame(bin_results)
axes[0, 1].bar(br_df['bin'], br_df['f1'], color='#4ECDC4', edgecolor='black')
axes[0, 1].set_xlabel('Komşuluk Sızıntı Oranı', fontsize=12)
axes[0, 1].set_ylabel('Illicit F1 Score', fontsize=12)
axes[0, 1].set_title('Sızıntı ↑ = Tahmin Doğruluğu ↑ ?\n(Korelasyon varsa sızıntı kanıtı)', fontsize=13, fontweight='bold')
# 3: Random vs Temporal sızıntı karşılaştırması
data_compare = [
('Random Split', leakage_ratios.mean()),
('Temporal Split', temp_leak.mean()),
]
axes[1, 0].bar([d[0] for d in data_compare], [d[1] for d in data_compare],
color=['#FF6B6B', '#4ECDC4'], edgecolor='black')
axes[1, 0].set_ylabel('Ortalama Komşuluk Sızıntı Oranı', fontsize=12)
axes[1, 0].set_title('Random vs Temporal: Komşuluk Sızıntısı', fontsize=13, fontweight='bold')
for i, (name, val) in enumerate(data_compare):
axes[1, 0].text(i, val + 0.01, f'{val:.3f}', ha='center', fontsize=13, fontweight='bold')
# 4: Yapı duyarlılığı
feat_types = ['Sadece Local\n(93 özellik)', 'Sadece Aggregated\n(72 özellik)', 'Tümü\n(165 özellik)']
random_vals = [f1_local, f1_agg, f1_all]
temporal_vals = [f1_local_temp, f1_agg_temp, f1_all_temp]
x = np.arange(len(feat_types))
axes[1, 1].bar(x - 0.18, random_vals, 0.32, label='Random Split', color='#FF6B6B')
axes[1, 1].bar(x + 0.18, temporal_vals, 0.32, label='Temporal Split', color='#4ECDC4')
axes[1, 1].set_xticks(x)
axes[1, 1].set_xticklabels(feat_types, fontsize=10)
axes[1, 1].set_ylabel('Illicit F1 Score', fontsize=12)
axes[1, 1].set_title('Yapı Duyarlılığı: Aggregated Özellikler\nRandom Split\'te Daha mı Çok Katkı Sağlıyor?', fontsize=13, fontweight='bold')
axes[1, 1].legend(fontsize=11)
plt.suptitle('SIZINTI HARİTASI: Bilgi Ağ Üzerinden Nasıl Sızıyor?', fontsize=16, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig(f'{FIGDIR}/fig7_leakage_map.png', dpi=150, bbox_inches='tight')
plt.close()
print(" ✓ Figür 7: Sızıntı Haritası")
# Sonuçları kaydet
results = {
'avg_leakage_ratio_random': float(leakage_ratios.mean()),
'avg_leakage_ratio_temporal': float(temp_leak.mean()),
'leakage_ratio_multiplier': float(leakage_ratios.mean() / max(temp_leak.mean(), 0.001)),
'correlation_leakage_accuracy': float(corr),
'f1_local_random': float(f1_local),
'f1_agg_random': float(f1_agg),
'f1_all_random': float(f1_all),
'f1_local_temporal': float(f1_local_temp),
'f1_agg_temporal': float(f1_agg_temp),
'f1_all_temporal': float(f1_all_temp),
'agg_boost_random': float(agg_boost_random),
'agg_boost_temporal': float(agg_boost_temporal),
}
with open(f'{OUTDIR}/leakage_map_results.json', 'w') as f:
json.dump(results, f, indent=2)
print("\n✓ Tamamlandı!")