Chapter sebelumnya ditutup dengan janji: begitu model menjadi campuran, MLE tidak lagi punya rumus tertutup dan kita membutuhkan prosedur iteratif. Chapter ini menepati janji itu lewat Gaussian Mixture Model (GMM) — model generatif yang menyatakan data sebagai campuran beberapa distribusi normal, dilatih dengan algoritme expectation-maximization (EM). Kita kembali bertemu 180 pelanggan toko ritel dari Chapter k-Means Clustering, tetapi dengan dua perbaikan besar: klaster boleh berbentuk elips dengan ukuran berbeda, dan keanggotaan tidak lagi hitam-putih melainkan soft assignment berupa probabilitas.

Campuran Gaussian: Distribusi Berlapis #

Ide dasarnya satu kalimat: setiap titik data dianggap dihasilkan oleh proses dua langkah — pilih satu komponen k dengan peluang w_k, lalu ambil sampel dari Gaussian N(μ_k, Σ_k) milik komponen tersebut. Distribusi hasilnya adalah campuran berlapis:

text
p(x) = Σ_k  w_k · N(x | μ_k, Σ_k)

Bobot w_k berjumlah satu dan menyatakan porsi tiap komponen; rata-rata μ_k dan kovarians Σ_k membentuk dan orientasi tiap gumpalan. Karena kovarians berupa matriks penuh, satu komponen bisa berupa elips miring yang lebarnya berbeda di tiap arah — kontras dengan k-Means yang hanya mengenal jarak lurus ke titik pusat. Atribut model scikit-learn memetakan langsung ke notasi tersebut: weights_, means_, dan covariances_.

EM: E-step dan M-step #

EM mengulang dua langkah yang saling mengisi sampai log-likelihood berhenti naik. E-step (expectation) menjawab pertanyaan tersembunyi: untuk tiap titik xₙ, seberapa besar tanggung jawab tiap komponen atas titik itu? Nilai tanggung jawab (responsibility) dihitung dengan aturan Bayes:

text
r(n,k) = w_k · N(xₙ | μ_k, Σ_k)  /  Σ_j w_j · N(xₙ | μ_j, Σ_j)

M-step (maximization) kemudian memperbarui parameter dengan tanggung jawab tersebut sebagai bobot: rata-rata komponen menjadi rata-rata tertimbang seluruh titik, kovarians mengikuti simpangan tertimbangnya, dan bobot komponen menjadi total tanggung jawab dibagi jumlah titik. Parameternya kini saling bergantung — rata-rata baru mengubah tanggung jawab, tanggung jawab baru mengubah rata-rata — jadi solusi analitik satu kali tidak berlaku; iterasi bergantian inilah jalan keluarnya.

covariance_type: Empat Pilihan Bentuk #

Parameter covariance_type mengatur seberapa bebas matriks kovarians tiap komponen. Pilihan ini langsung menentukan jumlah parameter bebas — pada data dengan banyak fitur, bedanya besar.

TipeBentuk klasterParameter per komponen (d fitur)Kapan dipakai
fullElips miring bebasd² + dData cukup banyak, bentuk bebas
tiedElips miring sama untuk semuad² + d (dibagi bersama)Komponen sejenis, hemat parameter
diagElips sejajar sumbudKompatibel fitur independen, data sedang
sphericalBulat, radius bebas1Data kecil, mirip k-Means tapi lunak

Chapter ini memakai full agar kontras dengan k-Means terlihat maksimal; keempat pilihan tersedia di GaussianMixture dengan argumen yang sama.

Praktik: Soft Assignment dan Verifikasi Rumus #

Skrip berikut melatih GMM pada 180 pelanggan (fitur kunjungan bulanan dan rata-rata belanja, sama seperti Chapter k-Means Clustering, kini dengan sebaran segmen yang berbeda agar saling tumpang tindih), membaca ketiga atribut parameter, menguji predict_proba pada tiga titik, lalu memverifikasi rumus responsibility secara manual dengan scipy.stats.multivariate_normal.

python
# Gaussian Mixture Model: soft clustering, EM, dan pemilihan k via BIC/AIC.
# Kisah: 180 pelanggan toko ritel, dua fitur, tiga segmen yang tumpang tindih.

import numpy as np
from scipy import stats
from sklearn.datasets import make_blobs
from sklearn.mixture import GaussianMixture

# 1) Data sintetis: tiga segmen dengan sebaran berbeda agar saling tumpang tindih.
PUSAT_SEG = [[2.0, 2.0], [7.0, 4.0], [5.0, 9.0]]
X, y_asli = make_blobs(
    n_samples=180, centers=PUSAT_SEG, cluster_std=[0.8, 1.2, 1.8],
    random_state=42,
)

# 2) Latih GMM: k=3, kovarians penuh, EM dijalankan dari 5 inisialisasi.
gmm = GaussianMixture(
    n_components=3, covariance_type="full", n_init=5, random_state=42
).fit(X)

print("Parameter hasil EM:")
print(f"  bobot komponen : {gmm.weights_.round(3).tolist()}")
for c in range(3):
    mu = gmm.means_[c]
    cov_diag = np.diag(gmm.covariances_[c])
    print(f"  komponen {c}: mean ({mu[0]:.3f}, {mu[1]:.3f}) "
          f"kovarians diag {np.round(cov_diag, 3).tolist()}")

# 3) Soft assignment: predict_proba untuk tiga pelanggan pilihan.
titik_uji = np.array([[2.0, 2.0], [6.0, 6.5], [9.0, 9.0]])
proba = gmm.predict_proba(titik_uji)
print("\nRespons (probabilitas keanggotaan) tiga titik:")
nama_titik = ["pusat segmen A", "di antara segmen", "tepi segmen longgar"]
for nama, x, p in zip(nama_titik, titik_uji, proba):
    print(f"  ({x[0]:.1f}, {x[1]:.1f}) {nama:>17}: "
          f"[{p[0]:.3f}, {p[1]:.3f}, {p[2]:.3f}]")

# 4) Verifikasi rumus: r_k = w_k N(x|mu_k,S_k) / sum_j w_j N(x|mu_j,S_j).
dens = np.array([
    gmm.weights_[k]
    * stats.multivariate_normal(gmm.means_[k], gmm.covariances_[k]).pdf(titik_uji)
    for k in range(3)
]).T
manual = dens / dens.sum(axis=1, keepdims=True)
print(f"\nSelisih maks vs rumus manual: {np.max(np.abs(manual - proba)):.2e}")

# 5) Titik ambigu vs tegas pada seluruh data: ambang soft assignment.
proba_semua = gmm.predict_proba(X)
maks = proba_semua.max(axis=1)
print(f"\npelanggan ambigu (respons maks < 0.80): {(maks < 0.80).sum()} dari {len(X)}")
print(f"pelanggan tegas   (respons maks > 0.95): {(maks > 0.95).sum()} dari {len(X)}")

# 6) Pemilihan k dengan BIC dan AIC: k = 1..6.
print("\n k        BIC        AIC")
bic_list, aic_list = [], []
for k in range(1, 7):
    g = GaussianMixture(
        n_components=k, covariance_type="full", n_init=5, random_state=42
    ).fit(X)
    bic_list.append(g.bic(X))
    aic_list.append(g.aic(X))
    print(f"{k:>2}  {g.bic(X):>10.1f}  {g.aic(X):>9.1f}")
print(f"k terbaik menurut BIC: {int(np.argmin(bic_list)) + 1}")
print(f"k terbaik menurut AIC: {int(np.argmin(aic_list)) + 1}")

# 7) Log-kepadatan tiap titik: pintu masuk ke skor anomali.
print("\nLog-kepadatan (score_samples) tiga titik uji:")
for x, ld in zip(titik_uji, gmm.score_samples(titik_uji)):
    print(f"  ({x[0]:.1f}, {x[1]:.1f}): {ld:.3f}")

Output yang Dihasilkan:

text
Parameter hasil EM:
  bobot komponen : [0.344, 0.333, 0.323]
  komponen 0: mean (7.003, 4.246) kovarians diag [1.258, 1.52]
  komponen 1: mean (1.899, 1.973) kovarians diag [0.478, 0.605]
  komponen 2: mean (5.140, 8.907) kovarians diag [2.875, 1.781]

Respons (probabilitas keanggotaan) tiga titik:
  (2.0, 2.0)    pusat segmen A: [0.000, 1.000, 0.000]
  (6.0, 6.5)  di antara segmen: [0.600, 0.000, 0.400]
  (9.0, 9.0) tepi segmen longgar: [0.003, 0.000, 0.997]

Selisih maks vs rumus manual: 3.33e-16

pelanggan ambigu (respons maks < 0.80): 2 dari 180
pelanggan tegas   (respons maks > 0.95): 170 dari 180

 k        BIC        AIC
 1      1746.3     1730.3
 2      1570.3     1535.2
 3      1552.9     1498.6
 4      1568.4     1495.0
 5      1592.3     1499.7
 6      1614.2     1502.5
k terbaik menurut BIC: 3
k terbaik menurut AIC: 4

Log-kepadatan (score_samples) tiga titik uji:
  (2.0, 2.0): -2.325
  (6.0, 6.5): -4.817
  (9.0, 9.0): -6.407

Mari kita bedah hasil eksekusinya:

  1. Baris 21–27 (atribut parameter): bobot [0.344, 0.333, 0.323] nyaris merata — masuk akal karena tiap segmen berisi 60 pelanggan. Rata-rata komponen (1,899; 1,973), (7,003; 4,246), dan (5,140; 8,907) berdekatan dengan pusat segmen yang ditanam. Diagonal kovarians memperlihatkan asimetri yang tidak bisa ditangkap k-Means: komponen 1 rapat (diag 0,478; 0,605) sedangkan komponen 2 melebar (diag 2,875; 1,781) — cermin dari cluster_std 0,8 vs 1,8.
  2. Baris 30–36 (soft assignment): titik (2,0; 2,0) di jantung segmen A mendapat responsibility [0,000; 1,000; 0,000]. Bintang demonstrasi ada di (6,0; 6,5): responsibility [0,600; 0,000; 0,400] — pelanggan itu 60% milik komponen 0 dan 40% milik komponen 2, jenis keputusan yang tidak bisa diberikan label keras k-Means.
  3. Baris 39–45 (verifikasi rumus): dens menghitung w_k · N(x | μ_k, Σ_k) per komponen dengan scipy.stats.multivariate_normal, lalu dinormalkan per baris. Selisih maksimum terhadap predict_proba hanya 3,33e-16 — batas presisi float; predict_proba GMM adalah rumus Bayes yang kita tulis sendiri.
  4. Baris 48–51 (ambigu vs tegas): dari 180 pelanggan, 170 pelanggan memiliki responsibility maksimum di atas 0,95 dan hanya 2 pelanggan berada di zona abu-abu di bawah 0,80. Data kita memang tidak terlalu tumpang tindih; pada data nyata dengan segmen bercampur, baris seperti ini menjadi alat ukur berapa banyak pelanggan yang layak ditinjau manual.
  5. Baris 56–64 (BIC/AIC): BIC mencapai minimum di k=3 (1552,9) — tepat jumlah segmen yang ditanam — sedangkan AIC memilih k=4 (1495,0). Pembahasannya ada di bagian berikutnya.
  6. Baris 67–69 (log-kepadatan): score_samples memberi log-kepadatan model pada tiap titik: −2,325 di pusat segmen, −6,407 di tepi. Titik dengan kepadatan sangat rendah adalah kandidat anomali — jembatan menuju Section Anomaly Detection di lanjutan Course ini.

Memilih k dengan BIC dan AIC #

Kedua kriteria menyeimbangkan kecocokan model dengan jumlah parameternya, keduanya dihitung dari log-likelihood maksimum yang sama:

text
AIC = -2·loglik + 2p        BIC = -2·loglik + p·ln(n)

dengan p jumlah parameter bebas dan n jumlah data. AIC menghukum tiap parameter dengan konstanta 2; BIC menghukumnya dengan ln(n) — pada n = 180 hukuman itu sekitar 5,2 per parameter, jauh lebih berat. Konsekuensinya terlihat telak pada output kita: k=4 memang menaikkan log-likelihood (AIC turun ke 1495,0), tetapi enam parameter tambahannya membuat BIC naik ke 1568,4. BIC memilih 3 — sesuai kebenaran generator — AIC memilih 4 — model tambahannya membelah segmen longgar menjadi dua. Tidak ada yang “salah” di sini: AIC memang dikenal condong memilih model lebih besar, dan praktikum seperti ini melatih mata kita membaca hasil keduanya secara berdampingan.

GMM menutup satu lembar penting: kini kita sanggup membangun distribusi atas data itu sendiri. Namun di seluruh Course ini keluaran yang paling sering kita konsumsi adalah predict_proba dari classifier — dan angka-angka itu punya masalah tersendiri yang belum pernah kita ukur: apakah 0,8 dari model benar-benar berarti 80%? Chapter penutup section ini, Kalibrasi Probabilitas Model, mengajari cara mengukur dan memperbaiki kesesuaian tersebut.

Referensi Resmi #