Deteksi Anomali Tingkat Lanjut (Isolation Forest & Autoencoder)
Deteksi anomali tanpa label untuk predictive maintenance — Isolation Forest, Autoencoder dengan reconstruction error, penentuan ambang, dan arsitektur hybrid laten + Isolation Forest.
Notebook lab (pratinjau)
Unduh lalu jalankan di Jupyter, VS Code, atau Google Colab.
Lab Minggu 16 — Deteksi Anomali: Isolation Forest, Autoencoder & Hybrid
Skenario: Kamu data scientist di pabrik. Mesin baru dipasang 5 sensor (suhu, getaran, tekanan, arus, rpm). Belum ada catatan kerusakan (cold-start). Tugas: bangun detektor anomali yang belajar dari data operasi normal.
Tujuan lab:
- Eksplorasi data sensor & jenis anomali.
- Isolation Forest (rumus + scikit-learn).
- Autoencoder dengan PyTorch, skor = reconstruction error.
- Hybrid: laten autoencoder + Isolation Forest.
- Bandingkan performa dengan label (yang hanya dipakai untuk evaluasi).
import sys
sys.path.insert(0, "..")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score
from common.datasets import sensor_mesin
FITUR = ["suhu", "getaran", "tekanan", "arus", "rpm"]
df = sensor_mesin()
print(df.shape, "| anomali:", df.anomali.sum())
df.head()
1. Eksplorasi
Label anomali hanya untuk evaluasi akhir. Model tidak boleh melihatnya.
df.groupby("anomali")[FITUR].mean().round(3)
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
pasangan = [("rpm", "tekanan"), ("suhu", "arus"), ("rpm", "getaran")]
for ax, (a, b) in zip(axes, pasangan):
ax.scatter(df[a], df[b], c=df.anomali, cmap="coolwarm", s=8)
ax.set_xlabel(a); ax.set_ylabel(b)
plt.suptitle("Merah = anomali (hanya untuk visualisasi)"); plt.show()
Perhatikan: sebagian anomali (relasional) tidak ekstrem di satu sensor, tetapi kombinasinya aneh (rpm normal, tekanan tinggi, arus rendah).
2. Skema cold-start
Ambil 1000 baris normal pertama sebagai "data operasi harian" untuk latihan. Sisanya data uji (berisi semua anomali).
idx_latih = df.index[df.anomali == 0][:1000]
X_latih = df.loc[idx_latih, FITUR].to_numpy()
uji = df.drop(index=idx_latih)
X_uji, y_uji = uji[FITUR].to_numpy(), uji.anomali.to_numpy()
print(X_latih.shape, X_uji.shape, y_uji.sum())
def laporan(nama, skor_latih, skor_uji, q=99):
ambang = np.percentile(skor_latih, q)
pred = (skor_uji > ambang).astype(int)
return {"model": nama,
"precision": precision_score(y_uji, pred, zero_division=0),
"recall": recall_score(y_uji, pred),
"f1": f1_score(y_uji, pred),
"roc_auc": roc_auc_score(y_uji, skor_uji)}
3. Isolation Forest
3a. Rumus c(n) dan skor s(x, n)
EULER = 0.5772156649
def c_n(n):
if n <= 1: return 0.0
if n == 2: return 1.0
return 2 * (np.log(n - 1) + EULER) - 2 * (n - 1) / n
print("c(256) =", round(c_n(256), 3))
h = np.linspace(0, 20, 100)
plt.plot(h, 2 ** (-h / c_n(256)))
plt.axhline(0.5, ls="--", c="gray"); plt.axvline(c_n(256), ls="--", c="gray")
plt.xlabel("E[h(x)] (rata-rata panjang jalur)"); plt.ylabel("skor s"); plt.title("Skor anomali vs panjang jalur"); plt.show()
3b. scikit-learn
iso = IsolationForest(n_estimators=200, random_state=0).fit(X_latih)
s_latih_if = -iso.score_samples(X_latih) # makin besar makin anomali
s_uji_if = -iso.score_samples(X_uji)
hasil = [laporan("Isolation Forest", s_latih_if, s_uji_if)]
pd.DataFrame(hasil).round(3)
Distribusi skor titik normal vs anomali pada data uji:
plt.hist(s_uji_if[y_uji == 0], bins=40, alpha=0.6, label="normal", density=True)
plt.hist(s_uji_if[y_uji == 1], bins=20, alpha=0.6, label="anomali", density=True)
plt.axvline(np.percentile(s_latih_if, 99), c="k", ls="--", label="ambang p99")
plt.legend(); plt.title("Distribusi skor Isolation Forest"); plt.show()
4. Autoencoder (PyTorch)
import torch
from torch import nn
torch.manual_seed(0)
scaler = StandardScaler().fit(X_latih)
Z_latih = torch.tensor(scaler.transform(X_latih), dtype=torch.float32)
Z_uji = torch.tensor(scaler.transform(X_uji), dtype=torch.float32)
class Autoencoder(nn.Module):
def __init__(self, d=5, laten=3):
super().__init__()
self.encoder = nn.Sequential(nn.Linear(d, 8), nn.ReLU(), nn.Linear(8, laten))
self.decoder = nn.Sequential(nn.ReLU(), nn.Linear(laten, 8), nn.ReLU(), nn.Linear(8, d))
def forward(self, x):
return self.decoder(self.encoder(x))
ae = Autoencoder()
opt = torch.optim.Adam(ae.parameters(), lr=1e-2)
loss_fn = nn.MSELoss()
riwayat = []
for epoch in range(300):
opt.zero_grad()
loss = loss_fn(ae(Z_latih), Z_latih)
loss.backward(); opt.step()
riwayat.append(loss.item())
plt.plot(riwayat); plt.yscale("log"); plt.xlabel("epoch"); plt.ylabel("MSE"); plt.title("Kurva latih autoencoder"); plt.show()
@torch.no_grad()
def error_rekon(Z):
return ((ae(Z) - Z) ** 2).mean(dim=1).numpy()
e_latih, e_uji = error_rekon(Z_latih), error_rekon(Z_uji)
hasil.append(laporan("Autoencoder (MSE)", e_latih, e_uji))
pd.DataFrame(hasil).round(3)
Error per sensor membantu diagnosis: sensor mana yang paling "tidak cocok"?
with torch.no_grad():
err_sensor = ((ae(Z_uji) - Z_uji) ** 2).numpy()
pd.DataFrame(err_sensor[y_uji == 1], columns=FITUR).head(8).round(2)
5. Hybrid: laten + error → Isolation Forest
with torch.no_grad():
L_latih = ae.encoder(Z_latih).numpy()
L_uji = ae.encoder(Z_uji).numpy()
F_latih = np.column_stack([L_latih, e_latih])
F_uji = np.column_stack([L_uji, e_uji])
iso_h = IsolationForest(n_estimators=200, random_state=0).fit(F_latih)
hasil.append(laporan("Hybrid (laten+MSE → IF)", -iso_h.score_samples(F_latih), -iso_h.score_samples(F_uji)))
tabel = pd.DataFrame(hasil).set_index("model").round(3)
tabel
tabel[["precision", "recall", "f1"]].plot.bar(figsize=(8, 4), rot=0, ylim=(0, 1.05))
plt.title("Perbandingan detektor (ambang persentil 99)"); plt.show()
6. Pengaruh ambang
qs = [90, 95, 97, 99, 99.5, 99.9]
rows = []
for q in qs:
ambang = np.percentile(e_latih, q)
pred = (e_uji > ambang).astype(int)
rows.append({"q": q, "alarm": pred.sum(),
"precision": precision_score(y_uji, pred, zero_division=0),
"recall": recall_score(y_uji, pred)})
pd.DataFrame(rows).round(3)
✍️ Coba sendiri
- Ubah ukuran laten autoencoder menjadi 1, 2, dan 4. Bagaimana recall & precision berubah?
- Latih Isolation Forest hanya pada fitur
suhudangetaran. Anomali jenis apa yang terlewat? - Pakai
contamination=0.02diIsolationForestlalupredict. Bandingkan dengan ambang persentil. - Kalau biaya 1 kerusakan terlewat = Rp 50 juta dan 1 alarm palsu = Rp 500 ribu, ambang persentil berapa yang paling murah? (pakai tabel bagian 6)
- Jelaskan mengapa hybrid lebih menarik untuk 120 sensor dibanding 5 sensor.
# 1.
# 2.
# 3.
# 4.
Jawaban no. 5:
- ...
Selanjutnya
Kerjakan kuis dan exercise.py, lalu jalankan python check.py 16 dari folder utama.