Bagian 2 menutup dengan satu celah yang disebut namanya: aturan satu dimensi buta terhadap kombinasi — checkout kilat bernominal sedang pada jam normal lolos dari z-score, kuantil, maupun aturan jam. Bagian ini menutup celahnya dengan model multi-dimensi. Isolation Forest — algoritme yang mengisolasi anomali lewat pemisahan acak berulang, sudah kenal kita dari Section Anomaly Detection — dilatih pada enam fitur transaksi terskala, diperbandingkan dengan Local Outlier Factor, lalu diadu langsung dengan aturan statistik Bagian 2. Seperti sebelumnya, kolom is_fraud tetap tersegel: model belajar pola keramaian tanpa tahu mana anomali sejati.
Rekayasa Fitur: Membuat Kombinasi Terlihat #
Kekuatan model multi-dimensi berdiri di atas fitur yang dirancang. Empat kolom dasar (nominal, jam, durasi, jumlah item) dilengkapi dua transformasi dan dua fitur rasio. log1p pada nominal dan durasi meredam kemiringan +14,28 yang ditemukan Bagian 1 — tanpa ini, jarak Euclidean di ruang fitur didominasi ekor kanan nominal. Fitur durasi_per_item lahir dari penalaran domain: durasi checkout normal sebanding jumlah item, sehingga rasio yang terlalu kecil menandai proses yang tidak manusiawi cepatnya; nominal_per_item menangkap kebalikannya. Kolom menit dan hari-dalam-minggu sengaja dijatuhkan — eksplorasi Bagian 1 menunjukkan keduanya seragam, dan fitur tanpa sinyal hanya menambah derau pada pengukuran jarak.
Isolation Forest dan Local Outlier Factor Berdampingan #
# Model anomali: Isolation Forest dan Local Outlier Factor pada fitur
# transaksi terskala, dibandingkan dengan aturan statistik Bagian 2.
# Pipeline fitur:
# - fitur dasar : nominal_ribu, jam_transaksi, durasi_detik, jumlah_item
# (menit dan hari_dalam_minggu dijatuhkan: seragam, tanpa sinyal).
# - transformasi log1p pada nominal dan durasi (skewness Bagian 1).
# - fitur turunan durasi_per_item dan nominal_per_item: durasi normal
# sebanding jumlah item, sehingga rasionya membidik pola kombinasi
# yang tidak wajar.
# IsolationForest(n_estimators=200, contamination=0.012, random_state=42):
# skor predict -1 = anomali; decision_function = skor kontinu (makin
# kecil makin anomali). Skrip TIDAK membaca is_fraud. Jalankan dari
# root proyek: python3 src/04_model.py
import numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor
from sklearn.preprocessing import StandardScaler
df = pd.read_csv("data/transaksi.csv")
# ---------- Rekayasa fitur ----------
nominal = df["nominal_ribu"].to_numpy()
jam = df["jam_transaksi"].to_numpy()
durasi = df["durasi_detik"].to_numpy()
item = df["jumlah_item"].to_numpy()
durasi_per_item = durasi / np.maximum(item, 1)
nominal_per_item = nominal / np.maximum(item, 1)
matriks = np.column_stack(
[
np.log1p(nominal),
jam,
np.log1p(durasi),
item,
np.log1p(durasi_per_item),
np.log1p(nominal_per_item),
]
)
NAMA_FITUR = [
"log1p(nominal_ribu)",
"jam_transaksi",
"log1p(durasi_detik)",
"jumlah_item",
"log1p(durasi_per_item)",
"log1p(nominal_per_item)",
]
X = StandardScaler().fit_transform(matriks)
print("== Rekayasa fitur model ==")
print(f"fitur dasar : 4 kolom (menit, hari_dalam_minggu dijatuhkan - seragam)")
print(f"fitur turunan: log1p(nominal), log1p(durasi), 2 fitur rasio")
print(f"total fitur : {len(NAMA_FITUR)} -> {NAMA_FITUR}")
print(f"penskalaan : StandardScaler (mean 0, std 1 per fitur)")
# ---------- Isolation Forest ----------
model = IsolationForest(
n_estimators=200,
contamination=0.012,
random_state=42,
)
model.fit(X)
label_if = model.predict(X) # -1 anomali, 1 normal
skor_if = model.decision_function(X) # makin kecil makin anomali
tertanda_if = label_if == -1
print("\n== Isolation Forest ==")
print(f"parameter : n_estimators=200, contamination=0.012, random_state=42")
print(f"tertandai : {int(tertanda_if.sum())} transaksi "
f"(skor predict -1, {tertanda_if.mean():.2%} dari data)")
print(f"skor decision: min {skor_if.min():.3f}, median {np.median(skor_if):.3f}, "
f"maks {skor_if.max():.3f}")
# ---------- Local Outlier Factor ----------
lof = LocalOutlierFactor(n_neighbors=20)
label_lof = lof.fit_predict(X)
tertanda_lof = label_lof == -1
print("\n== Local Outlier Factor (pembanding) ==")
print(f"parameter : n_neighbors=20")
print(f"tertandai : {int(tertanda_lof.sum())} transaksi "
f"({tertanda_lof.mean():.2%} dari data)")
# ---------- Sepuluh transaksi paling anomali versi model ----------
urut = np.argsort(skor_if)
df_top = df.iloc[urut[:10]][
["id_transaksi", "nominal_ribu", "jam_transaksi", "durasi_detik",
"jumlah_item"]
].copy()
df_top["skor_if"] = skor_if[urut[:10]]
print("\n== Sepuluh transaksi dengan skor Isolation Forest terendah ==")
print(df_top.to_string(index=False))
# ---------- Perbandingan dengan aturan statistik Bagian 2 ----------
rata = float(nominal.mean())
std = float(nominal.std())
q99 = float(np.quantile(nominal, 0.99))
aturan_z = (nominal - rata) / std >= 3.0
aturan_kuantil = nominal >= q99
aturan_jam = (jam >= 2) & (jam <= 5)
skor_stat = (
aturan_z.astype(int) + aturan_kuantil.astype(int) + aturan_jam.astype(int)
)
tertanda_stat = skor_stat >= 1
print("\n== Perbandingan pendekatan ==")
print(f"{'metode':38s} {'tertandai':>10s}")
print(f"{'aturan statistik (skor >= 1)':38s} {int(tertanda_stat.sum()):10d}")
print(f"{'Isolation Forest (contamination 1,2%)':38s} {int(tertanda_if.sum()):10d}")
print(f"{'Local Outlier Factor (n=20)':38s} {int(tertanda_lof.sum()):10d}")
keduanya = tertanda_stat & tertanda_if
hanya_stat = tertanda_stat & ~tertanda_if
hanya_if = ~tertanda_stat & tertanda_if
print(f"\naturan & model sekaligus : {int(keduanya.sum())}")
print(f"hanya aturan statistik : {int(hanya_stat.sum())}")
print(f"hanya Isolation Forest : {int(hanya_if.sum())}")
print(f"tidak keduanya : {int((~tertanda_stat & ~tertanda_if).sum())}")
print("\n== Sebaran skor statistik pada 60 transaksi tertanda model ==")
for s in range(3):
n = int((skor_stat[tertanda_if] == s).sum())
print(f"skor statistik {s}: {n:3d} transaksi")
print("-> transaksi ber skor statistik 0 luput dari seluruh aturan Bagian 2")Output yang Dihasilkan:
== Rekayasa fitur model ==
fitur dasar : 4 kolom (menit, hari_dalam_minggu dijatuhkan - seragam)
fitur turunan: log1p(nominal), log1p(durasi), 2 fitur rasio
total fitur : 6 -> ['log1p(nominal_ribu)', 'jam_transaksi', 'log1p(durasi_detik)', 'jumlah_item', 'log1p(durasi_per_item)', 'log1p(nominal_per_item)']
penskalaan : StandardScaler (mean 0, std 1 per fitur)
== Isolation Forest ==
parameter : n_estimators=200, contamination=0.012, random_state=42
tertandai : 60 transaksi (skor predict -1, 1.20% dari data)
skor decision: min -0.116, median 0.169, maks 0.232
== Local Outlier Factor (pembanding) ==
parameter : n_neighbors=20
tertandai : 94 transaksi (1.88% dari data)
== Sepuluh transaksi dengan skor Isolation Forest terendah ==
id_transaksi nominal_ribu jam_transaksi durasi_detik jumlah_item skor_if
3926 361.6 16 42 8 -0.115590
3645 625.5 19 26 8 -0.114725
3881 450.5 17 29 8 -0.113452
3845 517.0 18 41 8 -0.109419
4085 376.1 11 53 8 -0.107813
4629 658.0 13 27 8 -0.100251
3699 557.7 11 37 8 -0.099983
3654 429.4 17 26 7 -0.099676
3591 580.3 15 41 8 -0.097617
1043 6595.1 21 127 1 -0.095591
== Perbandingan pendekatan ==
metode tertandai
aturan statistik (skor >= 1) 84
Isolation Forest (contamination 1,2%) 60
Local Outlier Factor (n=20) 94
aturan & model sekaligus : 32
hanya aturan statistik : 52
hanya Isolation Forest : 28
tidak keduanya : 4888
== Sebaran skor statistik pada 60 transaksi tertanda model ==
skor statistik 0: 28 transaksi
skor statistik 1: 12 transaksi
skor statistik 2: 20 transaksi
-> transaksi ber skor statistik 0 luput dari seluruh aturan Bagian 2Mari kita bedah arsitektur dan hasilnya:
- Baris 24–50 (matriks fitur): dua rasio dibangun dengan
np.maximum(item, 1)menjaga pembagian dari nol,log1pmeredam kemiringan, danStandardScalermenyetarakan enam dimensi. Tanpa penskalaan, jam (0–23) dan nominal terskala-log akan berjarak berbeda orde — model berbasis pemisahan acak kehilangan arti. - Baris 59–67 (Isolation Forest):
contamination=0.012memberi tahu model seberapa banyak anomali yang diharapkan (1,2% — estimasi dari desain data),random_state=42mengunci determinisme pemisahan acak,n_estimators=200menstabilkan skor.predict()menghasilkan label biner;decision_function()menghasilkan skor kontinu — skor inilah yang dipakai Bagian 4 untuk menggeser ambang. - Baris 77–83 (Local Outlier Factor): pembanding berbasis kepadatan lokal dengan
n_neighbors=20. LOF menandai 94 transaksi — hampir 60% lebih banyak dari IF. - Sepuluh teratas IF: perhatikan baris pertama — transaksi 3926 bernominal 361,6 ribu pada jam 16, durasi 42 detik, 8 item. Tidak ada satu aturan Bagian 2 pun yang akan menandai transaksi ini;
durasi_per_item-lah yang membuatnya menonjol sebagai checkout yang tidak wajar cepatnya. Ini bukti hidup nilai fitur kombinasi. Baris terakhir tabel (transaksi 1043, nominal 6595,1) menunjukkan sisi lain: anomali nominal ekstrem juga tetap terdeteksi model. - Baris 95–119 (perbandingan tiga pendekatan): 84 (aturan) vs 60 (IF) vs 94 (LOF) — tetapi yang lebih penting adalah irisan: hanya 32 transaksi ditandai aturan sekaligus IF, 52 hanya aturan, 28 hanya IF. Dua pendekatan melihat dunia berbeda dan saling melengkapi.
- Sebaran skor statistik pada 60 tertanda IF: 28 dari 60 transaksi tertanda model memiliki skor statistik 0 — luput dari seluruh aturan Bagian 2. Inilah kuantifikasi celah yang diduga di awal bagian ini.
Dua Model, Dua Karakter #
Isolation Forest dan Local Outlier Factor menandai jumlah berbeda (60 vs 94) bukan kebetulan. Isolation Forest mengukur seberapa mudah satu titik dipisahkan dari keramaian lewat pemotongan acak — anomali nominal raksasa atau checkout kilat terisolasi dalam beberapa potongan saja. LOF mengukur seberapa jauh kepadatan lokal satu titik dari tetangganya — sensitif terhadap jembatan antar klaster kepadatan, tetapi pada data 5000 baris dengan parameter n_neighbors=20 ia cenderung memperluas wilayah tandai. Keduanya sama-sama sah; pilihan di antara mereka — atau keputusan menggabungkan — hanya bisa diambil setelah diukur terhadap label. Dan sekarang, seluruh pertanyaan mengarah ke sana: 60 tertanda IF dan 84 tertanda aturan, mana yang lebih benar? Segel is_fraud dibuka di Bagian 4: Evaluasi dan Ambang.
Referensi Resmi #
- IsolationForest — scikit-learn API
— parameter
contamination,decision_function, danpredict. - LocalOutlierFactor — scikit-learn API
— kepadatan lokal dan
n_neighbors. - Outlier detection — scikit-learn User Guide — perbandingan konseptual keluarga metode deteksi anomali.
- numpy.log1p — API — transformasi logaritmik yang aman untuk nilai nol.