Aykırı Değerler (Outliers) — Bölüm 2: Çok Boyutlu Veriler ve PyOD Algoritmaları
Bölüm 1’de tek bir sütun içindeki aykırı değerleri IQR ve Z-Score ile yakalamayı, maskeleme etkisini ve IQR’ın neden daha dayanıklı…
Aykırı Değerler (Outliers) — Bölüm 2: Çok Boyutlu Veriler ve PyOD Algoritmaları
Bölüm 1’de tek bir sütun içindeki aykırı değerleri IQR ve Z-Score ile yakalamayı, maskeleme etkisini ve IQR’ın neden daha dayanıklı olduğunu incelemiştik.

Görsel yazara aittir.
Gerçek dünya veri setlerinde onlarca, hatta yüzlerce sütun var. Sütunlar tek başına aykırılık içermezken bir araya geldiğinde çok boyutlu aykırılık (multivariate outlier) oluşturabilirler. Bu yazıda çok boyutlu aykırılıkların derinliklerine iniyoruz.
Başlamadan önce sıkça akla gelen o soruyu yanıtlayalım: Scikit-Learn içinde de IsolationForest veya LocalOutlierFactor var, neden PyOD kullanıyoruz? Scikit-Learn sadece 3–4 model sunarken, PyOD içinde 40'tan fazla modern algoritma barındırır ve birden fazla modelin tahminini tek bir satırda birleştirerek ortak bir karar mekanizması kurmayı çok kolaylaştırır. Bugün 3 farklı model kullanacağız ama farklı modelleri incelemek ve nasıl kullanacağını görmek için PyOD dokümantasyonunu inceleyebilirsiniz. Hazırsanız başlayalım.
Sentetik Veri Oluşturma
Tüm modellerin aynı şartlarda test edilmesi için PyOD içerisinde yer alan generate_data fonksiyonuyla %1 anomali içeren 10 boyutlu bir veri seti oluşturuyoruz. Modellerimiz 10.000 satır arasında sadece en aykırı 100 noktayı bulmaya çalışacak.
import numpy as np
import pandas as pd
from pyod.utils.data import generate_data
# 10k satır, 10 boyut, %1 Anomali Oranı
contamination = 0.01
n_train = 10000
n_test = 2000 # Modellerin hiç görmediği test verisi
X_train, X_test, y_train, y_test = generate_data(
n_train=n_train,
n_test=n_test,
n_features=10,
contamination=contamination,
random_state=42
)
print(f"Eğitim Seti Boyutu: {X_train.shape}")
print(f"Eğitim Setindeki Gerçek Anomali Sayısı: {np.sum(y_train)} / {n_train} (Tam %1)")
print(f"Test Setindeki Gerçek Anomali Sayısı: {np.sum(y_test)} / {n_test}")
Modellerin Analizi ve Görselleştirilmesi
Çok boyutlu verilerde tahminleri ekrana çizdirebilmek için PCA modelini bir görselleştirme motoru olarak kullanacağız. Her modelin tahminlerini gerçek sınıflarla kıyaslayarak “nerede hata yapmışlar” grafik üzerinden okuyacağız.
Isolation Forest (Ağaç Tabanlı Yaklaşım)
- Nasıl Çalışır?: Veriyi rastgele özelliklerden rastgele kesim noktalarıyla dallara böler. Normal veriler çok sıkışık kümelendiği için ağacın derinliklerinde (deep leaves) kaybolurken, aykırı değerler gruplardan uzakta ve yalnız olduğundan çok daha az bölünmeyle (ağacın köküne daha yakın) hızlıca izole edilir.
- En İyi Çalıştığı Veri Tipi: Doğrusal olmayan karmaşık ilişkiler içeren, yüksek boyutlu ve kategorik/sayısal karışık veri setlerinde çok başarılıdır. Bankacılıkta dolandırıcılık tespiti (fraud) sistemlerinin vazgeçilmezidir.
- Teorisini merak edenler için akademik makale: Liu et al. — Isolation Forest
from pyod.models.iforest import IForest
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
# Model Eğitimi
iforest = IForest(contamination=contamination, random_state=42).fit(X_train)
y_pred_if = iforest.labels_
# PCA ile 2 Boyuta İndirgeme
pca = PCA(n_components=2, random_state=42)
X_pca = pca.fit_transform(X_train)
df_res = pd.DataFrame(X_pca, columns=['PC1', 'PC2'])
df_res['Gerçek'] = y_train
df_res['Tahmin'] = y_pred_if
# Karar Sınırları İçin Izgara (Mesh Grid) Oluşturma
xx, yy = np.meshgrid(np.linspace(df_res['PC1'].min()-1, df_res['PC1'].max()+1, 100),
np.linspace(df_res['PC2'].min()-1, df_res['PC2'].max()+1, 100))
# Izgaradaki noktaları 10 boyutlu uzaya geri projekte edip skor üretme
grid_2d = np.c_[xx.ravel(), yy.ravel()]
grid_10d = pca.inverse_transform(grid_2d)
Z_if = iforest.decision_function(grid_10d).reshape(xx.shape)
# Grafik: Gerçek vs Karar Sınırları Karşılaştırması
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 1) Gerçek Dağılım
sns.scatterplot(x='PC1', y='PC2', hue='Gerçek', palette={0: 'blue', 1: 'red'}, data=df_res, ax=axes[0])
axes[0].set_title('Gerçek Dağılım')
axes[0].legend(handles=axes[0].get_legend_handles_labels()[0], labels=[f'Normal ({(y_train==0).sum()})', f'Gerçek Aykırı ({(y_train==1).sum()})'])
# 2) Karar Sınırları ve Tahmin
axes[1].contourf(xx, yy, Z_if, levels=20, cmap='Oranges', alpha=0.4)
sns.scatterplot(x='PC1', y='PC2', hue='Tahmin', palette={0: 'blue', 1: 'purple'}, data=df_res, ax=axes[1])
axes[1].set_title('Isolation Forest Karar Sınırları')
axes[1].legend(handles=axes[1].get_legend_handles_labels()[0], labels=[f'Normal ({(y_pred_if==0).sum()})', f'Aykırı İlan Edilen ({(y_pred_if==1).sum()})'])
plt.show()

Görsel yazara aittir.
Isolation Forest grafiğine baktığımızda, merkezdeki yoğun mavi kümenin dış çeperindeki bazı normal noktaları da mora boyayarak False Positive (Yalancı Anomali) ürettiğini lejanttaki sayılardan görebilirsiniz. Arka plandaki koyu turuncu dalgalara dikkat ederseniz modelin uzayı doğrusal ve keskin parçalara ayırdığını fark edersiniz. Bunun sebebi, ağacın rastgele kesimler yaparken merkez kümenin sınır çizgilerindeki hafif seyrek alanları “aykırı” sanarak erken izole etmesidir.
Local Outlier Factor — LOF (Yoğunluk Tabanlı Yaklaşım)
- Nasıl Çalışır?: Bir noktanın yerel yoğunluğunu en yakın $k$ komşusunun yoğunluğuyla kıyaslar. Eğer bir noktanın yoğunluğu, komşularının yoğunluğundan belirgin şekilde düşükse, o nokta seyrekte kalmış demektir ve yüksek bir LOF skoru alır.
- En İyi Çalıştığı Veri Tipi: Verinin içinde farklı yoğunluklara sahip birden fazla doğal küme varsa harika çalışır. Örneğin coğrafi veriler veya müşteri segmentasyon grupları.
- Teorisini merak edenler için akademik makale: Breunig et al. — LOF: Identifying Density-Based Local Outliers
from pyod.models.lof import LOF
# Model Eğitimi
lof = LOF(contamination=contamination, n_neighbors=30).fit(X_train)
y_pred_lof = lof.labels_
Z_lof = lof.decision_function(grid_10d).reshape(xx.shape)
# Grafik Karşılaştırması
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 1) Gerçek Dağılım
sns.scatterplot(x='PC1', y='PC2', hue='Gerçek', palette={0: 'blue', 1: 'red'}, data=df_res, ax=axes[0])
axes[0].legend(handles=axes[0].get_legend_handles_labels()[0], labels=[f'Normal ({(y_train==0).sum()})', f'Gerçek Aykırı ({(y_train==1).sum()})'])
axes[0].set_title('Gerçek Dağılım')
# 2) LOF Karar Sınırları ve Tahmin
axes[1].contourf(xx, yy, Z_lof, levels=20, cmap='Greens', alpha=0.4) # Yoğunluk haritası arka planda
sns.scatterplot(x='PC1', y='PC2', hue=y_pred_lof, palette={0: 'blue', 1: 'green'}, data=df_res, ax=axes[1])
axes[1].legend(handles=axes[1].get_legend_handles_labels()[0][:2], labels=[f'Normal ({(y_pred_lof==0).sum()})', f'Aykırı İlan Edilen ({(y_pred_lof==1).sum()})'])
axes[1].set_title('LOF Tahmini ve Yoğunluk Haritası')
# Aykırıları kırmızı çember ile çevreleme katmanı
outliers = df_res[y_pred_lof == 1]
axes[1].scatter(outliers['PC1'], outliers['PC2'], s=100, facecolors='none', edgecolors='red', linewidths=1.5)
plt.show()

Görsel yazara aittir.
LOF tamamen yerel yoğunluğa odaklandığı için, ana kümeden uzakta duran ama şans eseri yan yana düşmüş 2–3 anomali noktasını “kendi içinde bir küme/yoğunluk” sanarak ıskalayabilir (False Negative). Grafikteki koyu yeşil arka plan kıvrımlarına bakarsanız, LOF’un ağaçlar gibi düz çizgiler çekmek yerine, noktaların etrafında dairesel ve organik yoğunluk çeperleri (komşuluk bulutları) oluşturduğunu görebilirsiniz. Grafiğin çeperlerinde kalan bazı yeşil noktalar, komşuluk ilişkileri yüzünden ana gruptan uzak olmalarına rağmen anomali sınır çizgisine takılmıştır.
HBOS (Histogram-Based Outlier Score)
- Nasıl Çalışır?: Değişkenlerin birbiriyle bağımsız olduğunu varsayar. Her sütun için ayrı ayrı histogramlar çizer ve bir noktanın histogramlardaki sıklığını hesaplar. Eğer bir nokta tüm sütunların histogramlarında en az frekansa sahip kutulara düşüyorsa, anomali skoru tavan yapar. Mesafe hesabı yapmadığı için tam bir hız canavarıdır.
- En İyi Çalıştığı Veri Tipi: Milyonlarca satırdan oluşan devasa büyük veri (Big Data) setlerinde ve değişkenler arasında güçlü/doğrusal bağların olmadığı durumlarda ilk hızlı filtre olarak mükemmeldir.
- Teorisini merak edenler için orijinal akademik makale: Goldstein et al. — Histogram-based Outlier Score
from pyod.models.hbos import HBOS
# Model Eğitimi
hbos = HBOS(contamination=contamination).fit(X_train)
y_pred_hbos = hbos.labels_
Z_hbos = hbos.decision_function(grid_10d).reshape(xx.shape)
# Grafik: Gerçek vs Tahmin Karşılaştırması
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 1) Gerçek Dağılım Grafiği
sns.scatterplot(x='PC1', y='PC2', hue='Gerçek', palette={0: 'blue', 1: 'red'}, data=df_res, ax=axes[0])
axes[0].set_title('Gerçek Dağılım')
axes[0].legend(handles=axes[0].get_legend_handles_labels()[0], labels=[f'Normal ({(y_train==0).sum()})', f'Gerçek Aykırı ({(y_train==1).sum()})'])
# 2) HBOS Karar Sınırları ve Tahmin
axes[1].contourf(xx, yy, Z_hbos, levels=20, cmap='Purples', alpha=0.4) # Matris taraması
sns.scatterplot(x='PC1', y='PC2', hue=y_pred_hbos, palette={0: 'blue', 1: 'purple'}, data=df_res, ax=axes[1])
axes[1].set_title('HBOS Karar Sınırları')
axes[1].legend(handles=axes[1].get_legend_handles_labels()[0][:2], labels=[f'Normal ({(y_pred_hbos==0).sum()})', f'Aykırı İlan Edilen ({(y_pred_hbos==1).sum()})'])
# Tahminde aykırıları çembere aldığımız kısım
outliers = df_res[y_pred_hbos == 1]
axes[1].scatter(outliers['PC1'], outliers['PC2'], s=100, facecolors='none', edgecolors='red', linewidths=1.5)
plt.show()

Görsel yazara aittir.
HBOS değişkenler arası korelasyonu yok saydığı için, iki değişkenin ortaklaşa yarattığı doğrusal ilişkili anomalileri kaçırır. Grafikte mor noktaların ve arka plandaki koyu mor sınırların daha köşeli/kutu şeklinde hizalandığını göreceksiniz çünkü her boyut bağımsız birer histogram üzerinden kesildiği için, bu aralıklar üst üste bindiğinde uzayda tam bir dik açılı ızgara matrisi (grid) oluşturur. 10 boyutlu uzaydaki o dik kesimler PCA düzlemine bükülerek yansısa da köşeli karakterini hala net bir şekilde koruyor.
Modellerin Karşılaştırılması
Tüm modelleri aynı 10.000 satırlık veri setinde çalıştırdıktan sonra, hem milisaniye cinsinden eğitim sürelerini görebileceğimiz hem de orijinal veri setiyle yan yana performanslarını analiz edebileceğimiz kod:
import time
# Süre Ölçümlü Eğitimler
start = time.time(); IForest(contamination=contamination, random_state=42).fit(X_train); if_time = (time.time() - start) * 1000
start = time.time(); LOF(contamination=contamination, n_neighbors=30).fit(X_train); lof_time = (time.time() - start) * 1000
start = time.time(); HBOS(contamination=contamination).fit(X_train); hbos_time = (time.time() - start) * 1000
# 4'lü Panel Çizimi
fig, axes = plt.subplots(1, 4, figsize=(24, 5), sharey=True)
# 1. Orijinal Veri
sns.scatterplot(x='PC1', y='PC2', hue='Gerçek', palette={0: 'blue', 1: 'red'}, data=df_res, ax=axes[0])
axes[0].set_title('Orijinal Veri (Gerçek Dağılım)')
axes[0].legend(handles=axes[0].get_legend_handles_labels()[0], labels=[f'Normal ({(y_train==0).sum()})', f'Aykırı ({(y_train==1).sum()})'])
# 2. Isolation Forest
axes[1].contourf(xx, yy, Z_if, levels=20, cmap='Oranges', alpha=0.3)
sns.scatterplot(x='PC1', y='PC2', hue=y_pred_if, palette={0: 'blue', 1: 'purple'}, data=df_res, ax=axes[1])
axes[1].set_title(f'Isolation Forest ({if_time:.2f} ms)')
axes[1].legend(handles=axes[1].get_legend_handles_labels()[0], labels=[f'Normal ({(y_pred_if==0).sum()})', f'Aykırı ({(y_pred_if==1).sum()})'])
# 3. LOF
axes[2].contourf(xx, yy, Z_lof, levels=20, cmap='Greens', alpha=0.3)
sns.scatterplot(x='PC1', y='PC2', hue=y_pred_lof, palette={0: 'blue', 1: 'green'}, data=df_res, ax=axes[2])
axes[2].scatter(df_res[y_pred_lof == 1]['PC1'], df_res[y_pred_lof == 1]['PC2'], s=100, facecolors='none', edgecolors='red', linewidths=1.5)
axes[2].set_title(f'LOF ({lof_time:.2f} ms)')
axes[2].legend(handles=axes[2].get_legend_handles_labels()[0][:2], labels=[f'Normal ({(y_pred_lof==0).sum()})', f'Aykırı ({(y_pred_lof==1).sum()})'])
# 4. HBOS
axes[3].contourf(xx, yy, Z_hbos, levels=20, cmap='Purples', alpha=0.3)
sns.scatterplot(x='PC1', y='PC2', hue=y_pred_hbos, palette={0: 'blue', 1: 'purple'}, data=df_res, ax=axes[3])
axes[3].scatter(df_res[y_pred_hbos == 1]['PC1'], df_res[y_pred_hbos == 1]['PC2'], s=100, facecolors='none', edgecolors='red', linewidths=1.5)
axes[3].set_title(f'HBOS ({hbos_time:.2f} ms)')
axes[3].legend(handles=axes[3].get_legend_handles_labels()[0][:2], labels=[f'Normal ({(y_pred_hbos==0).sum()})', f'Aykırı ({(y_pred_hbos==1).sum()})'])
plt.suptitle('Çok Boyutlu Uzayda Algoritmaların Performans, Geometri ve Süre Karşılaştırması', y=1.05, fontsize=14)
plt.tight_layout()
plt.show()

Görsel yazara aittir.
- HBOS: Mesafe veya ağaç bölünmesi hesaplamadığı için mikrosaniyeler içinde çalışır ve yarışı açık ara önde bitirir. Büyük veride ilk aşama filtresi olarak harikadır.
- Isolation Forest: Kararlılık, hız ve doğruluk dengesinde sektörün hükümdarıdır. Milisaniyeler içinde karmaşık yapıları çözer.
- LOF: Satır sayısı arttıkça mesafe hesaplama yükü katlanarak artar ve süre olarak geride kalır. Ancak mikro-kümelenmelerde alternatifsizdir.
Gerçek Hayatta Eşik Değer (Contamination) Nasıl Seçilir?
Laboratuvar ortamında contamination=0.01 diyerek işin içinden çıkmak kolaydır; çünkü sentetik veride ne kadar aykırı değer olduğunu zaten biliyoruz. Ancak gerçek bir üretim ortamında (production) veya temizlenmemiş bir veri setinde bu oranı bilmeyiz. Ezbere bir değer girmek ise ya yanlış alarma (False Positive) ya da kaçakları ıskalamaya (False Negative) yol açar.
Sektörde bu eşik değeri belirlemek ve kalibre etmek için 3 altın strateji kullanılır:
Saha Uzmanlığı & Tarihsel Veri (Domain Knowledge)
Veri bilimi sadece matematikten ibaret değildir; iş mantığı (business logic) her zaman önceliklidir. Model kurulmadan önce projenin geçmiş istatistiklerine bakılır:
- E-Ticaret / Bankacılık: Tarihsel olarak kredi kartı dolandırıcılığı (fraud) oranları genellikle %0.1 ile %0.3 arasındadır.
- Üretim / Fabrika: Bir üretim bandındaki makinelerin parça hata veya arıza çıkarma oranı tarihsel olarak %0.5'tir.
- Uygulama: Modelin ilk contamination parametresi bu tarihsel oranlara eşitlenerek (contamination=0.002 gibi) bir baseline oluşturulur.
Dirsek Yöntemi ile Skor Analizi (The Elbow Method)
Saha uzmanlığının olmadığı veya yeni başlanan projelerde matematik devreye girer. Modellerin ham anomali skorlarını (decisionscores) kullanarak şu adımları izleriz:
- Modeli herhangi bir contamination değeri vermeden (veya varsayılan değerle) eğitiriz.
- Her bir gözlemin aldığı ham anomali skorlarını küçükten büyüğe sıralarız.
- Bu sıralı skorları bir çizgi grafiğine (Line Plot) dökeriz.
# Modeli varsayılan değerle eğitip skorları sıralama
raw_scores = model.decision_scores_
sorted_scores = np.sort(raw_scores)
# Dirsek Grafiği Çizimi
plt.figure(figsize=(9, 4))
plt.plot(sorted_scores, color='purple', lw=2.5)
plt.axhline(y=np.percentile(sorted_scores, 99), color='red', linestyle='--', label='Yol Ayrımı (%1 Sınırı)')
plt.title('Anomali Skor Dağılımı ve Eşik Seçimi (Elbow Analizi)')
plt.ylabel('Ham Anomali Skoru'); plt.legend(); plt.grid(True); plt.show()

Görsel yazara aittir.
Manuel Çapa Noktaları (Labeled Anchor Points)
Verimiz tamamen etiketsiz (unlabeled) olsa bile, saha ekiplerinin geriye dönük incelemelerinde “kesinlikle anomali/hata” dediği 10–15 tane geçmiş örnek satırı manuel olarak bulup kenara ayırırız.
- Uygulama: Modeli farklı contamination değerleriyle (örn: 0.005, 0.01, 0.02, 0.05) test ederiz.
- Amaç: Kenara ayırdığımız o “kesin anomali” olan çapa noktalarının tamamını yakalamaya başladığımız en düşük contamination değerini seçeriz. Böylece modeli gerçek dünyadaki bilinen hatalara karşı kalibre etmiş oluruz.
- Daha fazla bilgi almak isterseniz sektördeki mühendislerin sıfır etiketli verilerde yarı-gözetimli (semi-supervised) yöntemlerle contamination parametresini nasıl kalibre ettiklerini anlatan vaka analizlerini (case studies) bu **linkten **okuyabilirsiniz.
Günün sonunda çok boyutlu verilerde anomali avlamak, sadece en doğru algoritmayı seçmekten ibaret değildir; verinin geometrisini anlamak ve iş mantığıyla o doğru eşik değerini kalibre edebilme sanatıdır. Bir sonraki yazıda görüşmek üzere.
Bu yazı, veri bilimi topluluğuna katkı sağlamak amacıyla ortaklaşa kaleme alınmıştır.
[embed]Barış Yıldırım - Medium Read writing from Barış Yıldırım on Medium.medium.com
메타데이터
- post_id
- bca3dcb51524
- slug
- aykırı-değerler-outliers-bölüm-2-çok-boyutlu-veriler-ve-pyod-algoritmaları-bca3dcb51524
- url
- https://medium.com/kahve-yan%C4%B1-veri/ayk%C4%B1r%C4%B1-de%C4%9Ferler-outliers-b%C3%B6l%C3%BCm-2-%C3%A7ok-boyutlu-veriler-ve-pyod-algoritmalar%C4%B1-bca3dcb51524
- canonical_url
- https://medium.com/kahve-yan%C4%B1-veri/ayk%C4%B1r%C4%B1-de%C4%9Ferler-outliers-b%C3%B6l%C3%BCm-2-%C3%A7ok-boyutlu-veriler-ve-pyod-algoritmalar%C4%B1-bca3dcb51524
- author_url
- https://medium.com/@batubilgili1907.bb
- status
- ok
- fetched_at
- 2026-07-10 06:10:56