Chapter sebelumnya memperlakukan model sebagai distribusi, tetapi satu pertanyaan belum terjawab: bagaimana cara menggabungkan keyakinan awal dengan bukti baru secara kuantitatif? Teorema Bayes menjawabnya dengan satu baris rumus yang menjadi mesin utama seluruh pandangan probabilistik. Di chapter ini kita menurunkan rumus tersebut dari nol, mengujinya pada kasus penapisan medis yang terkenal karena jebakan base rate, lalu menghubungkannya dengan classifier Naive Bayes yang bekerja dengan cara persis sama — hanya dengan seribu fitur sekaligus.
Rumus yang Membalik Arah Pengondisian #
Kita sering tahu P(bukti | hipotesis) — peluang melihat gejala tertentu jika suatu kondisi memang ada — padahal yang kita butuhkan adalah P(hipotesis | bukti). Teorema Bayes membalik arah pengondisian tersebut:
P(H | E) = P(E | H) · P(H) / P(E)Empat besaran di rumus tersebut punya nama yang akan terus muncul di sepanjang section ini:
| Istilah | Rumus | Peran |
|---|---|---|
| Prior | P(H) | Keyakinan awal sebelum bukti masuk |
| Likelihood | P(E | H) | Peluang bukti jika hipotesis benar |
| Evidence | P(E) | Peluang bukti di seluruh populasi (penorma) |
| Posterior | P(H | E) | Keyakinan terbaru setelah bukti diperhitungkan |
Perhatikan pembagian perannya: evidence berfungsi sebagai penorma sehingga posterior berjumlah satu, dan posterior hari ini menjadi prior untuk bukti berikutnya — teorema Bayes adalah mesin pembaruan keyakinan yang bisa dijalankan berulang kali seiring data mengalir.
Studi Kasus: Hasil Positif pada Penapisan Medis #
Kekuatan teorema Bayes paling terasa ketika intuisi kita meleset. Pertimbangkan pemeriksaan penapisan (screening) untuk suatu kondisi medis dengan karakteristik berikut: prevalensi 1% (satu dari 100 pasien benar-benar mengidap kondisi tersebut), sensitivitas 90% (hasil positif hampir pasti muncul pada pasien yang sakit), dan spesifisitas 95% (hasil negatif hampir pasti muncul pada pasien yang sehat). Intuisi cepat biasanya menebak “hasil positif berarti kemungkinan sakit sekitar 90%”. Perhitungan Bayes berkata lain.
# Teorema Bayes dari nol: penapisan medis dan peran base rate.
# Semua angka dihitung dari empat besaran dasar, tanpa pustaka ML.
# 1) Empat besaran dasar penapisan (screening) kondisi medis tertentu.
prevalensi = 0.01 # P(sakit): 1 dari 100 pasien benar-benar sakit
sensitivitas = 0.90 # P(positif | sakit)
spesifisitas = 0.95 # P(negatif | sehat)
positif_pada_sehat = 1 - spesifisitas # P(positif | sehat)
# 2) Teorema Bayes: P(sakit|positif) = P(positif|sakit) P(sakit) / P(positif)
p_positif = sensitivitas * prevalensi + positif_pada_sehat * (1 - prevalensi)
posterior = sensitivitas * prevalensi / p_positif
print("=== Perhitungan rumus ===")
print(f"P(positif) = {sensitivitas:.2f}*{prevalensi:.2f} + "
f"{positif_pada_sehat:.2f}*{1 - prevalensi:.2f} = {p_positif:.4f}")
print(f"P(sakit | positif) = {sensitivitas * prevalensi:.4f} / "
f"{p_positif:.4f} = {posterior:.4f}")
# 3) Cara kedua: tabel hitung pada 10.000 pasien.
n = 10_000
sakit = int(n * prevalensi)
sehat = n - sakit
positif_benar = int(round(sakit * sensitivitas))
positif_palsu = int(round(sehat * positif_pada_sehat))
total_positif = positif_benar + positif_palsu
print("\n=== Tabel hitung 10.000 pasien ===")
print(f"pasien sakit : {sakit:>6} -> hasil positif {positif_benar}")
print(f"pasien sehat : {sehat:>6} -> hasil positif {positif_palsu}")
print(f"total hasil positif : {total_positif:>6}")
print(f"proporsi sakit di antara positif = {positif_benar}/{total_positif}"
f" = {positif_benar / total_positif:.4f}")
# 4) Update berurutan: posterior hari ini = prior pemeriksaan berikutnya.
# Asumsi: kedua hasil pemeriksaan saling bebas kondisional.
prior = prevalensi
print("\n=== Dua hasil positif berturut-turut ===")
for i in (1, 2):
p_pos = sensitivitas * prior + positif_pada_sehat * (1 - prior)
prior = sensitivitas * prior / p_pos
print(f"setelah positif ke-{i}: P(sakit) = {prior:.4f}")
# 5) Kontras: populasi rujukan klinik dengan prevalensi 30%.
prevalensi_klinik = 0.30
p_pos_klinik = (sensitivitas * prevalensi_klinik
+ positif_pada_sehat * (1 - prevalensi_klinik))
posterior_klinik = sensitivitas * prevalensi_klinik / p_pos_klinik
print(f"\nJika prevalensi 30% (populasi rujukan): "
f"P(sakit | positif) = {posterior_klinik:.4f}")Output yang Dihasilkan:
=== Perhitungan rumus ===
P(positif) = 0.90*0.01 + 0.05*0.99 = 0.0585
P(sakit | positif) = 0.0090 / 0.0585 = 0.1538
=== Tabel hitung 10.000 pasien ===
pasien sakit : 100 -> hasil positif 90
pasien sehat : 9900 -> hasil positif 495
total hasil positif : 585
proporsi sakit di antara positif = 90/585 = 0.1538
=== Dua hasil positif berturut-turut ===
setelah positif ke-1: P(sakit) = 0.1538
setelah positif ke-2: P(sakit) = 0.7660
Jika prevalensi 30% (populasi rujukan): P(sakit | positif) = 0.8852Mari kita bedah hasil eksekusinya:
- Baris 11–17 (rumus Bayes):
p_positifmenghitung evidence lewat hukum total peluang — dua jalan menuju hasil positif: pasien sakit yang terdeteksi (0,009) dan pasien sehat yang salah terdeteksi (0,0495). Posterior 0,1538 muncul dari pembagian 0,009/0,0585. - Baris 19–31 (tabel hitung): cara berpikir yang sama tanpa rumus. Dari 10.000 pasien, hanya 100 sakit dan 90 di antaranya terdeteksi — tetapi 495 pasien sehat ikut terdeteksi karena 5% laju positif palsu. Dari 585 total hasil positif, hanya 90 yang benar: proporsi 0,1538, persis hasil rumus.
- Baris 33–40 (update berurutan): posterior 0,1538 dipakai ulang sebagai prior, dan hasil positif kedua melonjakkan keyakinan ke 0,7660. Bukti yang konsisten menumpuk; satu hasil positif sendirian justru lemah karena terkalahkan base rate.
- Baris 42–48 (prevalensi tinggi): pada populasi rujukan klinik dengan prevalensi 30%, posterior langsung melompat ke 0,8852 — pemeriksaan yang sama, kualitas yang sama, tetapi jawaban berbeda karena prior berbeda.
Intinya: sensitivitas 90% bukan berarti posterior 90%. Ketika kondisi yang dicari jarang (base rate rendah), mayoritas hasil positif berasal dari kelompok besar pasien sehat — fenomena base rate neglect yang oleh khalayak sering luput dari perhitungan.
Naive Bayes: Teorema Bayes dengan Seribu Fitur #
Bagaimana Bayes bekerja ketika buktinya bukan satu gejala, melainkan seribu fitur? Menghitung P(x₁, x₂, ..., xₙ | kelas) secara persis menuntut kita menaksir distribusi bersama seluruh fitur — jumlah parameternya membengkak secara eksponensial. Naive Bayes memotong masalah itu dengan satu asumsi naif: fitur saling bebas kondisional terhadap kelas, sehingga likelihood terfaktorisasi menjadi perkalian P(x₁|kelas) · P(x₂|kelas) · .... Untuk teks, fitur dihitung dari frekuensi kata (bag-of-words) dan variasi MultinomialNB memodelkan tiap kata dengan distribusi multinomial.
Pemetaan istilahnya rapi sekali:
| Besaran Bayes | Contoh penapisan medis | Contoh filter pesan |
|---|---|---|
Prior P(H) | Prevalensi kondisi di populasi | Proporsi pesan promosi di kotak masuk |
Likelihood P(E|H) | Sensitivitas pemeriksaan | Peluang kata “diskon” muncul di pesan promosi |
Posterior P(H|E) | Peluang sakit jika hasil positif | predict_proba label promosi untuk pesan baru |
| Bukti berulang | Pemeriksaan kedua | Kata kedua, ketiga, … ke-n dalam satu pesan |
Praktik: Posterior Pesan dengan MultinomialNB #
Kita terapkan pada 14 pesan berlabel: delapan pesan kerja/pribadi dan enam promosi. Setelah CountVectorizer mengubah teks menjadi matriks hitungan kata, MultinomialNB mempelajari prior kelas dan likelihood tiap kata, lalu predict_proba mengembalikan posterior — angka yang akan kita verifikasi ulang secara manual dari parameter model.
# Naive Bayes untuk klasifikasi pesan: posterior lewat predict_proba,
# lalu diverifikasi manual dari parameter model (murni NumPy).
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
# 1) Dataset kecil: 14 pesan berlabel manual (0 = pribadi/kerja, 1 = promosi).
pesan = [
"rapat tim dijadwalkan kamis jam sembilan",
"mohon kirim laporan bulanan sebelum jumat",
"lampiran berita acara rapat sudah diperbarui",
"konfirmasi kehadiran workshop internal selasa",
"catat notulen rapat lalu kirim ke milis tim",
"makan siang bersama tim kamis ini",
"jadwal wawancara kandidat pindah ke jumat",
"notulen dan berita acara sudah dikirim milis",
"diskon besar akhir pekan belanja sekarang",
"gratis ongkir khusus member baru hari ini",
"klaim voucher belanja senilai lima puluh ribu",
"penawaran eksklusif potongan harga pekan ini",
"undian berhadiah khusus pelanggan setia",
"promo terbatas belanja dua berbayar satu",
]
label = [0] * 8 + [1] * 6
# 2) Bag-of-words + MultinomialNB dengan Laplace smoothing alpha=1.
vec = CountVectorizer()
X = vec.fit_transform(pesan)
model = MultinomialNB(alpha=1.0)
model.fit(X, label)
print("Prior kelas (proporsi data latih):")
for kelas, log_prior in zip(model.classes_, model.class_log_prior_):
print(f" kelas {kelas}: {np.exp(log_prior):.4f}")
# 3) Posterior predict_proba untuk tiga pesan baru.
baru = [
"diskon voucher belanja akhir pekan",
"mohon kirim laporan rapat hari jumat",
"rapat akhir pekan", # kata campuran -> posterior di tengah
]
X_baru = vec.transform(baru)
proba = model.predict_proba(X_baru)
print("\nPosterior predict_proba:")
for teks, p in zip(baru, proba):
print(f" pribadi {p[0]:.4f} | promosi {p[1]:.4f} <- {teks!r}")
# 4) Verifikasi manual: hitung ulang posterior pesan pertama.
x0 = np.asarray(X_baru[0].todense()).ravel()
log_joint = model.class_log_prior_ + x0 @ model.feature_log_prob_.T
log_norm = np.logaddexp.reduce(log_joint) # log-sum-exp
posterior_manual = np.exp(log_joint - log_norm)
print("\nVerifikasi posterior pesan pertama:")
print(f" manual : pribadi {posterior_manual[0]:.6f} "
f"promosi {posterior_manual[1]:.6f}")
print(f" predict_proba : pribadi {proba[0][0]:.6f} "
f"promosi {proba[0][1]:.6f}")
print(f" selisih maks : {np.max(np.abs(posterior_manual - proba[0])):.2e}")
# 5) Kata mana yang paling membedakan kedua kelas?
nama_fitur = np.array(vec.get_feature_names_out())
log_ratio = model.feature_log_prob_[1] - model.feature_log_prob_[0]
urut = np.argsort(log_ratio, kind="stable")
print("\nLima kata paling promosi (log-ratio terbesar):")
for idx in urut[::-1][:5]:
print(f" {nama_fitur[idx]:>12} {log_ratio[idx]:+.3f}")
print("Lima kata paling pribadi (log-ratio terkecil):")
for idx in urut[:5]:
print(f" {nama_fitur[idx]:>12} {log_ratio[idx]:+.3f}")Output yang Dihasilkan:
Prior kelas (proporsi data latih):
kelas 0: 0.5714
kelas 1: 0.4286
Posterior predict_proba:
pribadi 0.0077 | promosi 0.9923 <- 'diskon voucher belanja akhir pekan'
pribadi 0.9794 | promosi 0.0206 <- 'mohon kirim laporan rapat hari jumat'
pribadi 0.3851 | promosi 0.6149 <- 'rapat akhir pekan'
Verifikasi posterior pesan pertama:
manual : pribadi 0.007689 promosi 0.992311
predict_proba : pribadi 0.007689 promosi 0.992311
selisih maks : 0.00e+00
Lima kata paling promosi (log-ratio terbesar):
belanja +1.503
pekan +1.215
khusus +1.215
voucher +0.810
undian +0.810
Lima kata paling pribadi (log-ratio terkecil):
rapat -1.270
tim -1.270
acara -0.982
berita -0.982
jumat -0.982Mari kita bedah bagian-bagian pentingnya:
- Baris 27–31 (pipeline teks):
CountVectorizermengubah tiap pesan menjadi vektor hitungan kata;MultinomialNB(alpha=1.0)kemudian mempelajari prior kelas dan likelihood kata. Parameteralpha=1adalah Laplace smoothing — kata yang tak pernah muncul di satu kelas tetap mendapat peluang kecil yang bukan nol, sehingga satu kata asing tidak meniadakan seluruh hasil kali likelihood. - Baris 33–35 (prior): kelas 0 mendapat prior 0,5714 (8 dari 14 pesan) dan kelas 1 mendapat 0,4286 — persis proporsi data latih, terhubung langsung dengan kolom prior pada tabel di atas.
- Baris 43–47 (posterior): pesan penuh kata promosi mendapat posterior 0,9923; pesan penuh kata kerja hanya menyisakan peluang promosi 0,0206. Pesan “rapat akhir pekan” — satu kata dari tiap dunia — berhenti di 0,6149: posterior yang jujur ragu, persis perilaku yang kita harapkan dari pandangan probabilistik.
- Baris 50–58 (verifikasi manual): baris
model.class_log_prior_ + x0 @ model.feature_log_prob_.Tmenghitung log-probabilitas bersama kedua kelas dari parameter model, lalunp.logaddexp.reducemenormalkan di ranah logaritma. Selisih maksimum terhadappredict_probaadalah 0,00e+00 — keluaran API dan rumus Bayes yang kita tulis sendiri identik. - Baris 62–69 (diagnosis kata): log-ratio
feature_log_prob_[1] - feature_log_prob_[0]memberi peringkat kata. Kata “belanja” (+1,503) dan “pekan” (+1,215) paling menarik ke arah promosi, sedangkan “rapat” dan “tim” (−1,270) paling menarik ke arah pribadi — model Naive Bayes yang dilatih 14 pesan ternyata mudah sekali dibedah.
Satu pertanyaan masih tergantung: angka likelihood seperti feature_log_prob_ itu sendiri datang dari mana? Jawabannya adalah prinsip estimasi yang menyeluruh paling sering dipakai machine learning — cari parameter yang membuat data yang kita amati menjadi paling mungkin. Chapter berikutnya, Maximum Likelihood Estimation, membahas prinsip tersebut sampai ke solusi analitiknya.
Referensi Resmi #
- Naive Bayes — scikit-learn User Guide — penjelasan resmi semua varian Naive Bayes termasuk MultinomialNB dan smoothing.
- sklearn.naive_bayes.MultinomialNB — API Reference
— dokumentasi parameter
alpha, atributclass_log_prior_, danfeature_log_prob_. - sklearn.feature_extraction.text.CountVectorizer — API Reference — transformasi teks menjadi matriks hitungan kata.