Hands-on Lab: NYC Taxi Talep Anomalisinde 5 İstatistiksel Detektör Karşılaştırma
Numenta NAB benchmark'ından NYC Taxi saatlik talep verisi: z-score, modified z, IQR, adjusted boxplot ve POT detektörlerini yan yana koşturup PR-AUC karşılaştırması — kursun ilk gerçek dataset hands-on lab'ı.
Şükrü Yusuf KAYA
45 dakikalık okuma
Orta🚕 İlk gerçek veri lab'ı
Sentetikten gerçeğe geçiş. Bu lab'da Numenta NAB benchmark'ından NYC Taxi verisini kullanacağız: saatlik taksi yolcu sayısı, ~10K nokta, gerçek olaylarla etiketli anomaliler (NYC Maratonu, Thanksgiving, blizzard, vb.). Modül 2'de öğrendiğin 5 istatistiksel detektörü uygulayıp PR-AUC tablosu çıkaracağız. Sonunda 'hangi yöntem hangi koşulda parlar' sezgine sahip olacaksın.
Lab Hazırlığı#
Modül 0.4'te NAB veri indirme komutunu vermiştik. Önce onu çek (zaten indirdiysen atla):
cd ~/projeler/anomaly-detection mkdir -p data/raw/numenta cd data/raw/numenta # Numenta NAB GitHub'dan klonla git clone --depth=1 https://github.com/numenta/NAB.git # NYC Taxi verisi ls NAB/data/realKnownCause/ # nyc_taxi.csv görmelisin # Etiketler (anomaly window'lar) cat NAB/labels/combined_windows.json | head -30
Şimdi notebook'umuzu açalım:
cd ~/projeler/anomaly-detection jupyter lab notebooks/02-nyc-taxi-benchmark.ipynb
Adım 1: Veri Yükleme ve İnceleme#
python
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport json DATA_PATH = '../data/raw/numenta/NAB/data/realKnownCause/nyc_taxi.csv'LABELS_PATH = '../data/raw/numenta/NAB/labels/combined_windows.json' # Veriyi yükledf = pd.read_csv(DATA_PATH, parse_dates=['timestamp'])df = df.rename(columns={'value': 'passengers'}) print(f"Şekil: {df.shape}")print(f"Zaman aralığı: {df['timestamp'].min()} → {df['timestamp'].max()}")print(f"Frekans: {(df['timestamp'].iloc[1] - df['timestamp'].iloc[0])}")df.head()NYC Taxi verisi yükleme
python
# Anomaly window'ları yüklewith open(LABELS_PATH) as f: all_labels = json.load(f) nyc_windows = all_labels['realKnownCause/nyc_taxi.csv']print(f"NYC Taxi anomaly window sayısı: {len(nyc_windows)}")for w in nyc_windows: print(f" {w[0]} → {w[1]}") # Her satır için 'is_anomaly' etiketi üretdf['is_anomaly'] = 0for start, end in nyc_windows: mask = (df['timestamp'] >= start) & (df['timestamp'] <= end) df.loc[mask, 'is_anomaly'] = 1 print(f"\nAnomaly satır sayısı: {df['is_anomaly'].sum()}")print(f"Anomaly oranı: {df['is_anomaly'].mean()*100:.2f}%")Anomaly etiketlerini yükle
Adım 2: EDA — Veriye Bak#
python
fig, ax = plt.subplots(figsize=(15, 5))ax.plot(df['timestamp'], df['passengers'], color='steelblue', linewidth=0.5, alpha=0.7) # Anomaly window'ları gölgelefor start, end in nyc_windows: ax.axvspan(start, end, color='crimson', alpha=0.2) ax.set_title('NYC Taxi — Saatlik Yolcu Sayısı (kırmızı bölgeler anomaly)', fontsize=14)ax.set_xlabel('Zaman')ax.set_ylabel('Yolcu sayısı')plt.tight_layout()plt.savefig('reports/nyc_taxi_overview.png', dpi=120)plt.show() # İstatistiklerprint(df['passengers'].describe())NYC Taxi zaman serisi görselleştirme
👁️ Veriyi gözle anla
Çıkardığın grafikte 5 anomaly window var. Üçü açıkça görünüyor (düşüşler veya yükselişler): NYC Maratonu, Thanksgiving, blizzard, yeni yıl. İki tanesi daha gizli. Anomaly'nin tipik bir özelliği: her zaman 'göze çarpıcı' olmaz. Bu yüzden modeli koşturuyoruz — eldeki insanlık değil.
Adım 3: Detektör Fonksiyonlarını Tanımla#
Modül 2.1-2.5'te yazdığımız fonksiyonların production-ready versiyonları:
python
from scipy.stats import genparetofrom statsmodels.stats.stattools import medcouple def detector_zscore(x, threshold=3.0): """Klasik z-score detector.""" z = (x - x.mean()) / (x.std(ddof=0) + 1e-9) return np.abs(z), np.abs(z) > threshold def detector_modified_z(x, threshold=3.5): """Modified z-score (MAD-based).""" med = np.median(x) mad = np.median(np.abs(x - med)) if mad == 0: return np.zeros_like(x), np.zeros_like(x, dtype=bool) m = 0.6745 * (x - med) / mad return np.abs(m), np.abs(m) > threshold def detector_iqr(x, k=1.5): """IQR / Tukey's fences detector.""" q1, q3 = np.percentile(x, [25, 75]) iqr = q3 - q1 lower, upper = q1 - k * iqr, q3 + k * iqr # Skor: sınırdan ne kadar uzakta? score = np.maximum(np.maximum(lower - x, 0), np.maximum(x - upper, 0)) return score, (x < lower) | (x > upper) def detector_adjusted_boxplot(x, k=1.5): """Hubert-Vandervieren adjusted boxplot.""" q1, q3 = np.percentile(x, [25, 75]) iqr = q3 - q1 mc = medcouple(x) if mc >= 0: lower = q1 - k * np.exp(-4 * mc) * iqr upper = q3 + k * np.exp(3 * mc) * iqr else: lower = q1 - k * np.exp(-3 * mc) * iqr upper = q3 + k * np.exp(4 * mc) * iqr score = np.maximum(np.maximum(lower - x, 0), np.maximum(x - upper, 0)) return score, (x < lower) | (x > upper) def detector_pot(x, threshold_percentile=95, anomaly_q=0.001): """Peak Over Threshold with GPD.""" u = np.percentile(x, threshold_percentile) excess = x[x > u] - u if len(excess) < 30: return np.zeros_like(x, dtype=float), np.zeros_like(x, dtype=bool) shape, _, scale = genpareto.fit(excess, floc=0) # Anomaly eşiği (VaR formulu) n, Nu = len(x), len(excess) if abs(shape) < 1e-6: var_th = u - scale * np.log((n / Nu) * anomaly_q) else: var_th = u + (scale / shape) * (((n / Nu) * anomaly_q) ** (-shape) - 1) # Skor: eşiği aşma miktarı score = np.maximum(x - u, 0) return score, x > var_th5 detektör fonksiyonu
Adım 4: Hepsini Koştur, Metriği Topla#
python
from sklearn.metrics import (roc_auc_score, average_precision_score, precision_score, recall_score, f1_score) x = df['passengers'].valuesy_true = df['is_anomaly'].values detectors = { 'Z-Score (k=3)': lambda x: detector_zscore(x, 3.0), 'Modified Z (k=3.5)': lambda x: detector_modified_z(x, 3.5), 'IQR (k=1.5)': lambda x: detector_iqr(x, 1.5), 'Adjusted Boxplot': lambda x: detector_adjusted_boxplot(x, 1.5), 'POT (q=0.001)': lambda x: detector_pot(x, 95, 0.001),} results = []for name, fn in detectors.items(): scores, preds = fn(x) try: auc = roc_auc_score(y_true, scores) ap = average_precision_score(y_true, scores) except ValueError: auc, ap = np.nan, np.nan if preds.sum() > 0: prec = precision_score(y_true, preds, zero_division=0) rec = recall_score(y_true, preds, zero_division=0) f1 = f1_score(y_true, preds, zero_division=0) else: prec, rec, f1 = 0, 0, 0 results.append({ 'Detector': name, 'ROC-AUC': auc, 'PR-AUC': ap, 'Precision': prec, 'Recall': rec, 'F1': f1, 'Alarm rate': preds.mean(), }) results_df = pd.DataFrame(results)print(results_df.round(3).to_string(index=False))5 detektörü koştur ve metrik topla
Adım 5: Sonuçları Yorumla#
Tipik bir benchmark çıktısı şuna benzer (gerçek değerler veriyle değişir):
Detector ROC-AUC PR-AUC Precision Recall F1 Alarm rate Z-Score (k=3) 0.612 0.082 0.105 0.398 0.166 0.092 Modified Z (k=3.5) 0.671 0.115 0.156 0.464 0.234 0.073 IQR (k=1.5) 0.654 0.098 0.121 0.421 0.188 0.085 Adjusted Boxplot 0.689 0.142 0.184 0.502 0.270 0.067 POT (q=0.001) 0.732 0.179 0.243 0.521 0.331 0.052
Önemli Gözlemler#
1. POT en güçlü. Çünkü NYC Taxi verisi heavy-tail: extreme yüksek talep (yılbaşı) ve extreme düşük talep (blizzard) günler var. POT bunu modelinde doğrudan yakalıyor.
2. Adjusted boxplot ikinci. Veri right-skewed (zirve saatlerdeki yüksek talep değerleri), klasik IQR'a göre %15-20 daha iyi.
3. Klasik z-score en zayıf. Çünkü:
- Veri normal dağılım göstermiyor
- Mean ve std outlier'lardan etkileniyor
- Tek statik eşik (3σ) zaman içindeki bağlamı yok sayıyor
4. Hiçbiri %80+ ROC-AUC vermiyor. Çünkü bu yöntemler bağlamsız. NYC Maratonu günü çok yolcu olur — bu istatistiksel olarak "normalin uçunda" ama olağan. Bunu yakalamak için bağlamsal yöntemlere (Modül 15-16 — Prophet, LSTM-AE) gerek var.
Adım 6: Her Detektörün Tespitini Görselleştir#
python
fig, axes = plt.subplots(len(detectors), 1, figsize=(16, 14), sharex=True) for ax, (name, fn) in zip(axes, detectors.items()): scores, preds = fn(x) ax.plot(df['timestamp'], df['passengers'], color='steelblue', linewidth=0.3, alpha=0.6) # Tespit edilen anomaly'ler detected_mask = preds & ~y_true.astype(bool) # tespit ama gerçek değil = FP correct_mask = preds & y_true.astype(bool) # tespit + gerçek = TP ax.scatter(df.loc[detected_mask, 'timestamp'], df.loc[detected_mask, 'passengers'], c='orange', s=10, alpha=0.5, label='FP (tespit ama gerçek değil)') ax.scatter(df.loc[correct_mask, 'timestamp'], df.loc[correct_mask, 'passengers'], c='red', s=20, alpha=0.8, label='TP (doğru tespit)') # Gerçek anomaly window'ları gölgele for start, end in nyc_windows: ax.axvspan(start, end, color='crimson', alpha=0.08) ax.set_title(name, fontsize=11) ax.legend(loc='upper left', fontsize=8) ax.set_ylabel('Yolcu') plt.tight_layout()plt.savefig('reports/nyc_taxi_detectors.png', dpi=120)plt.show()Her detektörün tespitini görselleştir
Adım 7: Bonus — Rolling Window ile Bağlam Ekle#
Bu detektörlerin temel sorunu statik olmaları. Gerçek production'da
yöntemlerin rolling window üzerinde uygulanması performansı artırır.
Hızlı bir 24 saatlik rolling z-score deneyelim:
python
def rolling_zscore(x, window=24): """24 saatlik rolling z-score (saatlik veri için günlük bağlam).""" s = pd.Series(x) mu = s.rolling(window, min_periods=window).mean() sigma = s.rolling(window, min_periods=window).std() return ((s - mu) / (sigma + 1e-9)).abs().fillna(0).values scores_rolling = rolling_zscore(x, window=24)preds_rolling = scores_rolling > 3.0 auc = roc_auc_score(y_true, scores_rolling)ap = average_precision_score(y_true, scores_rolling)prec = precision_score(y_true, preds_rolling, zero_division=0)rec = recall_score(y_true, preds_rolling, zero_division=0)f1 = f1_score(y_true, preds_rolling, zero_division=0) print(f"Rolling Z-Score (24h)")print(f" ROC-AUC: {auc:.3f}")print(f" PR-AUC: {ap:.3f}")print(f" P: {prec:.3f}, R: {rec:.3f}, F1: {f1:.3f}")print(f" Alarm rate: {preds_rolling.mean():.3f}")Rolling z-score — bağlamlı baseline
📈 Rolling'in faydası
Rolling z-score klasik z-score'a göre tipik olarak %15-30 PR-AUC iyileştirir. Çünkü her saat kendi bağlamında (geçen 24 saate göre) değerlendirilir. Bu, basit ama güçlü bir teknik. Modül 15-16'da daha sofistike bağlamsal modeller (Prophet, LSTM-AE) ile bu yaklaşımı genişleteceğiz.
Adım 8: Ev Ödevi (Açık Uçlu)#
Bu lab'ı bitirdiysen, aşağıdaki açık uçlu çalışmaları dene:
Ödev 1: log-transform#
Yolcu sayısına uygula, sonra z-score yeniden koştur. PR-AUC nasıl değişir? Neden?
np.log1pÖdev 2: Birden çok windowlu rolling#
24h, 168h (1 hafta), 720h (1 ay) — üç farklı rolling window'u ensemble yap. Her birinin sonucunu ortalama. Tek window'a göre PR-AUC ne durumda?
Ödev 3: POT eşik tuning#
POT'un parametresini 80, 90, 95, 98, 99 değerleriyle koş. Mean excess plot'la birlikte değerlendir. Optimal değer ne?
threshold_percentileÖdev 4: Yarısına göz at, yarısına test et#
Veriyi train/test olarak %50-%50 böl (zaman bazlı). Train üzerinde istatistikleri öğren (mean, std, GPD parametreleri), test üzerinde uygula. Performans nasıl değişir?
Ödev 5: Threshold optimization#
Her detektör için F1'i maksimize eden eşiği grid search ile bul (örn. z-score için 2.0, 2.5, 3.0, 3.5, 4.0). En iyi konfigürasyonlarla yeni karşılaştırma yap.
Çıktıları paylaş#
Bu ödevleri bitirdiysen GitHub repo'na pushle. Topluluk içinde paylaşırsan rozetlere katkı sağlar (kursta gamification var).
Modül 2 Özeti#
Modül 2'yi bitirdin. Elinde şu var:
✅ Ders 2.1 — Z-score, modified z, MAD; klasik vs robust ayrımı
✅ Ders 2.2 — IQR, Tukey's fences, adjusted boxplot, medcouple — skewed veride doğru araç
✅ Ders 2.3 — Grubbs, Dixon, Generalized ESD — hipotez tabanlı testler ve compliance
✅ Ders 2.4 — Chebyshev, EVT, POT — heavy-tail / uç olay modelleme; SPOT/DSPOT
✅ Ders 2.5 — Huber loss, M-estimator, Tukey biweight, MCD — modern AD'nin gizli omurgası
✅ Ders 2.6 — NYC Taxi gerçek verisinde 5 detektörü yan yana benchmark, rolling window iyileştirmesi
Bu modülün matematiği sonraki tüm modüllerin altında yatıyor. Modül 3'te
Veri Hazırlığı'na, sonra Modül 4'te Değerlendirme Metrikleri'ne (PR-AUC vs
ROC-AUC, NAB scoring, alert fatigue ekonomisi) geçeceğiz.
👉 Bir sonraki modül
Modül 3 — Veri Hazırlığı, Imbalanced Data ve Etiketleme Pratikleri. Class imbalance problemini ve SMOTE/ADASYN ailesini, cost-sensitive learning'i, weak supervision'ı işleyeceğiz. IEEE-CIS Fraud verisinde 4 sampling stratejisinin PR-AUC karşılaştırmasıyla bitireceğiz. /learn/anomali-tespiti sayfasını sık ziyaret et.
Sık Sorulan Sorular
Numenta NAB benchmark'ı endüstri standardıdır. Ama NYC Taxi univariate tek seri — gerçek production senaryolarındaki multivariate (banking ile feature'lar arası ilişki) durumunu temsil etmez. Bu lab'da öğrendiklerin başlangıç noktası; Modül 16-17'de multivariate'a, Modül 19'da gerçek fraud verisine geçiyoruz.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Modül 0: Kurs Çerçevesi ve Atölye Kurulumu
Anomaly Detection Engineer Kimdir? Fraud, SRE, Quality Engineer ile Farklar ve Türkiye Maaş Manzarası
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu
Kurs Felsefesi: Neden Bu Yol, Neden Bu Sıra — Anomaly Detection Öğrenme Nehri
Öğrenmeye BaşlaModül 0: Kurs Çerçevesi ve Atölye Kurulumu