Chapter Kalibrasi Probabilitas Model ditutup dengan satu pertanyaan yang menggantung: bagaimana menandai titik yang menurut model hampir mustahil terjadi? Fungsi score_samples pada Gaussian Mixture Model sudah memberi jawabannya — log-kepadatan setiap titik — dan section ini kita balikkan lensanya: alih-alih memodelkan apa yang umum, kita berburu apa yang jarang. Anomaly detection adalah disiplin menandai pola yang menyimpang dari mayoritas data, tanpa atau dengan sedikit label. Kita mulai dari kosakata dasarnya: apa itu anomali, mengapa akurasi klasifikasi biasa menyesatkan saat anomali langka, dan bagaimana scikit-learn memisahkan masalah outlier detection dari novelty detection.

Tiga Wajah Anomali #

Anomali (atau outlier) adalah observasi yang menyimpang begitu jauh dari mekanisme pembangkit data mayoritas sehingga kemunculannya menimbulkan kecurigaan — transaksi Rp3.500.000 pada toko yang rata-rata transaksinya Rp120.000, sensor suhu mesin yang tiba-tiba naik 40 derajat, atau klik 500 halaman dalam satu menit. Literatur membaginya menjadi tiga tipe yang menuntut metode berbeda:

TipeBentukContoh pada data transaksi
Titik (point)Satu baris berdiri sendiri yang ekstremSatu transaksi bernilai 30 kali rata-rata
Kolektif (collective)Sekumpulan baris yang masing-masing wajar, namun tidak sebagai grup30 transaksi kecil berurutan dari satu akun dalam 5 menit
Kontekstual (contextual)Nilai wajar pada konteks lain, tidak wajar pada konteks iniTransaksi Rp500.000 wajar siang hari, mencurigakan pukul 03.00

Tipe titik paling mudah dideteksi dan menjadi fokus sebagian besar chapter section ini. Tipe kolektial dan kontekstual menuntut fitur turunan — selisih waktu antar transaksi, jam kejadian, rasio terhadap riwayat akun — sebelum algoritme apa pun bisa melihatnya.

Kenapa Klasifikasi Biasa Gagal pada Kelas Langka #

Cara paling naif: labeli transaksi lama sebagai “normal”/“anomali”, lalu latih classifier biasa. Pendekatan ini terganggu oleh aritmetika sederhana. Jika hanya 1% transaksi yang anomali, model yang selalu menebak “normal” langsung mencatat akurasi 99% — tanpa mendeteksi satu anomali pun. Metrik yang menghargami mayoritas menjadi tidak bermakna; yang dibutuhkan adalah presisi dan recall pada kelas langka, materi Chapter Evaluasi Anomaly Detection.

Masalah kedua lebih fundamental: anomali yang benar-benar baru sering tidak punya contoh. Pola penyalahgunaan tahun depan belum terjadi hari ini, sehingga data berlabelnya secara definisi tidak bisa ada. Karena itu anomaly detection umumnya diframe ulang menjadi tiga pengaturan:

PengaturanData yang tersediaPertanyaan yang dijawab
SupervisedLabel anomali/normal lengkap dan berimbang cukupTransaksi baru mirip anomali yang mana?
Unsupervised (outlier detection)Data mentah campuran, tanpa labelTitik mana yang paling menyimpang dari kerumunan?
Semi-supervised (novelty detection)Data bersih normal saja saat latihApakah titik baru berasal dari distribusi normal yang sama?

scikit-learn memakai istilah ini dengan ketat: outlier detection berarti data latih sudah berisi anomali dan kita memisahkannya dari data itu juga; novelty detection berarti data latih diasumsikan bersih dan kita menguji titik baru. Sebagian estimator punya mode khusus untuk keduanya — LocalOutlierFactor punya parameter novelty=True yang mengubah aturan pemakaiannya.

Praktik: Menyuntik Anomali dan Baseline Kepadatan #

Teori anomali paling mudah diuji lewat eksperimen terkontrol: bangun data normal yang mekanismenya kita ketahui, suntik anomali di posisi yang kita pilih sendiri, lalu ukur seberapa baik sebuah detektor menemukannya kembali. Kita pakai skenario toko daring “TokoKita” dengan dua fitur: nilai belanja (ribu rupiah) dan jumlah item per keranjang. Baseline detektornya paling sederhana yang bisa dibayangkan — Gaussian tunggal dari section sebelumnya, dengan score_samples sebagai skor kepadatan.

python
# Konsep anomali: 200 transaksi 2 fitur, 10 anomali disuntik sengaja,
# lalu baseline kepadatan sederhana (Gaussian tunggal via score_samples).

import numpy as np
from sklearn.mixture import GaussianMixture

rng = np.random.default_rng(42)

# 1) Data transaksi toko daring "TokoKita" -- dua fitur:
#    nilai belanja (ribu rupiah) dan jumlah item per keranjang.
normal = rng.normal(loc=[120.0, 8.0], scale=[25.0, 3.0], size=(190, 2))

# 2) Sepuluh anomali titik (point anomaly): belanja sangat besar
#    dengan keranjang sangat kecil -- pola yang tidak pernah
#    muncul pada data normal di atas.
anomali = rng.uniform(low=[300.0, 1.0], high=[480.0, 3.0], size=(10, 2))

X = np.vstack([normal, anomali])
y_anomali = np.array([0] * 190 + [1] * 10)  # 1 = anomali suntikan

print("Ringkasan data:")
print(f"  total transaksi : {len(X)}")
print(f"  transaksi normal (nilai rata-rata): {normal[:, 0].mean():.1f} ribu Rp")
print(f"  anomali suntikan (nilai rata-rata): {anomali[:, 0].mean():.1f} ribu Rp")

# 3) Seberapa jauh tiap titik dari pusat kerumunan normal?
pusat = normal.mean(axis=0)
jarak = np.linalg.norm(X - pusat, axis=1)
print(f"\nJarak ke pusat data normal (rata-rata):")
print(f"  transaksi normal : {jarak[y_anomali == 0].mean():.1f}")
print(f"  anomali suntikan : {jarak[y_anomali == 1].mean():.1f}")

# 4) Baseline kepadatan: Gaussian tunggal, skor = log-kepadatan.
#    Semakin rendah skor, semakin "mustahil" titik menurut model.
gmm = GaussianMixture(n_components=1, random_state=42).fit(X)
skor = gmm.score_samples(X)

# 5) Ambang = kontaminasi 5%: 10 skor terendah ditandai anomali.
ambang = np.percentile(skor, 5.0)
flag = skor < ambang
benar = int(((flag) & (y_anomali == 1)).sum())
salah = int(((flag) & (y_anomali == 0)).sum())
print(f"\nBaseline kepadatan, kontaminasi 5% (ambang log-kepadatan {ambang:.3f}):")
print(f"  ditandai anomali        : {int(flag.sum())} transaksi")
print(f"  di antaranya benar anomali : {benar}, salah tandai normal : {salah}")

# 6) Lihat langsung 10 skor terendah: apakah semuanya anomali suntikan?
urut = np.argsort(skor)
print("\n10 skor kepadatan terendah:")
print("  nilai_ribu  item  skor_log  asli")
for i in urut[:10]:
    jenis = "anomali" if y_anomali[i] == 1 else "normal"
    print(f"  {X[i, 0]:>9.1f}  {X[i, 1]:>4.1f}  {skor[i]:>7.2f}  {jenis}")

Output yang Dihasilkan:

text
Ringkasan data:
  total transaksi : 200
  transaksi normal (nilai rata-rata): 121.0 ribu Rp
  anomali suntikan (nilai rata-rata): 381.4 ribu Rp

Jarak ke pusat data normal (rata-rata):
  transaksi normal : 19.7
  anomali suntikan : 260.5

Baseline kepadatan, kontaminasi 5% (ambang log-kepadatan -11.121):
  ditandai anomali        : 10 transaksi
  di antaranya benar anomali : 9, salah tandai normal : 1

10 skor kepadatan terendah:
  nilai_ribu  item  skor_log  asli
      479.5   2.8   -22.10  anomali
      448.6   1.6   -19.50  anomali
      440.6   1.2   -18.95  anomali
      419.8   2.3   -17.31  anomali
      382.5   1.9   -14.94  anomali
      169.9  16.7   -13.37  normal
      354.3   2.8   -13.19  anomali
      351.1   2.7   -13.04  anomali
      319.2   3.0   -11.46  anomali
      316.3   2.8   -11.39  anomali

Mari kita bedah hasil eksekusinya:

  1. Baris 12–17 (data normal): rng.normal membangkitkan 190 keranjang belanja dua fitur — nilai belanja menyebar di sekitar Rp120 ribu dan jumlah item di sekitar 8. Karena kita sendiri yang membangkitkannya, “kebenaran” tiap titik tidak diperdebatkan lagi.
  2. Baris 19–23 (anomali suntikan): sepuluh titik diambil dari wilayah nilai Rp300–480 ribu dengan 1–3 item. Perhatikan bahwa anomali ini tidak ekstrem di kedua dimensi sekaligus — 2 item masih terjadi pada data normal — melainkan ekstrem pada kombinasi nilainya yang besar dengan keranjangnya yang kecil.
  3. Baris 37–42 (baseline kepadatan): GaussianMixture(n_components=1) adalah kepadatan paling sederhana yang bisa dipasang; score_samples mengembalikan log-kepadatan tiap titik — benang merah langsung dari chapter kalibrasi. Semakin negatif skor, semakin rendah kepadatan model menaksir titik tersebut.
  4. Baris 44–51 (ambang kontaminasi): np.percentile(skor, 5.0) memotong 5% skor terendah — asumsi “sekitar 5% data adalah anomali” itulah parameter contamination. Hasilnya 9 dari 10 anomali suntikan ditemukan, dengan 1 transaksi normal ikut tertandai.
  5. Baris 53–62 (tabel skor): baris keenam memperlihatkan kejujuran eksperimen ini — transaksi normal bernilai Rp169,9 ribu dengan 16,7 item (keranjang besar namun wajar) mendapat skor kepadatan −13,37, lebih rendah daripada dua anomali suntikan bernilai Rp319 ribu. Gaussian tunggal berbentuk elips simetris sehingga sudut “banyak item” dianggap sama mustahilnya dengan sudut “nilai besar + item sedikit”.

Kontaminasi: Menebak Proporsi yang Tidak Diketahui #

Hampir semua detektor scikit-learn menerima parameter contamination — dugaan proporsi anomali dalam data. Parameter ini tidak mengubah skor, hanya menggeser ambang keputusan: contamination=0.05 berarti 5% skor paling ekstrem dilabeli anomali oleh predict. Di dunia nyata angka ini jarang diketahui; meninjau manual beberapa transaksi wajar jauh lebih murah daripada membiarkan anomali lolos, sehingga praktikum umumnya memilih kontaminasi sedikit lebih tinggi lalu menyaring hasilnya dengan aturan bisnis.

Eksperimen baseline ini juga menetapkan tolok ukur section selanjutnya: Gaussian tunggal menemukan 9 dari 10 anomali dan salah menandai 1 dari 190 transaksi normal. Metode berikutnya harus mengalahkan angka itu dengan asumsi lebih longgar — mulai dari yang paling murah dan paling bisa dijelaskan: ambang statistik univariat. Kita bahas pada Chapter Deteksi Statistik: Z-Score dan IQR.

Referensi Resmi #