Dua algoritme sebelumnya memandang klasifikasi lewat geometri: garis pemisah dan jarak antar-titik. Naive Bayes memandangnya lewat peluang: “jika sebuah email memuat kata gratis dan diskon, berapa besar kemungkinan email itu spam?” Pendekatan ini bukan sekadar teori — penyaring spam berbasis Naive Bayes menjadi standar industri sejak awal 2000-an dan tetap dipakai hingga kini karena cepat, stabil pada data kecil, dan mudah diinterpretasikan. Chapter ini membedah teorema Bayes versi klasifikasi, asumsi kemerdekaan fitur yang memberinya julukan naive, serta tiga varian scikit-learn untuk tiga jenis data.

Teorema Bayes untuk Klasifikasi #

Intuisinya adalah membalik arah penalaran. Kita tahu dari data latih seberapa sering kata gratis muncul di email spam — itu likelihood. Yang kita inginkan adalah arah sebaliknya: probabilitas sebuah email adalah spam given kata-kata di dalamnya — itu posterior. Teorema Bayes menjembatani keduanya (materi lengkapnya ada di Chapter Probabilitas Dasar untuk Machine Learning):

text
P(kelas | x) = P(x | kelas) * P(kelas) / P(x)

Tiga komponen penting untuk klasifikasi: P(kelas) adalah prior — peluang awal setiap kelas sebelum melihat fitur apa pun; P(x | kelas) adalah likelihood fitur pada kelas tersebut; penyebut P(x) berfungsi sebagai faktor penormalisasi sehingga seluruh posterior berjumlah 1. Karena penyebut sama untuk semua kelas, klasifikasi cukup menghitung skor per kelas lalu memilih yang terbesar (argmax) — tidak perlu menghitung penyebut sama sekali.

Asumsi “Naive” #

Masalah muncul saat menghitung P(x | kelas) untuk fitur yang banyak: secara teori kita perlu distribusi bersama seluruh kombinasi fitur, dan jumlah parameternya melonjak secara kombinatorial. Naive Bayes menembus kebuntuan itu dengan satu asumsi berani: semua fitur saling bebas (independen) diberikan kelas. Konsekuensinya, likelihood gabungan cukup dikalikan dari likelihood per fitur:

text
P(x1, x2, ..., xd | kelas) = P(x1 | kelas) * P(x2 | kelas) * ... * P(xd | kelas)

Kata “naive” (polos) menempel karena asumsi ini hampir selalu salah di dunia nyata: kata gratis dan diskon jelas kerap muncul bersama dalam email spam, sehingga keduanya tidak bebas. Uniknya, hasil klasifikasi tetap bagus. Penyebabnya: kita hanya butuh kelas dengan skor terbesar, bukan nilai posterior yang akurat — kesalahan akibat kemerdekaan palsu cenderung serupa di semua kelas sehingga peringkat antar kelas masih sering benar. Asumsi yang salah rupanya cukup memaafkan untuk keputusan biner.

Tiga Varian untuk Tiga Jenis Data #

Asumsi kemerdekaan menentukan bentuk likelihood per fitur, dan di situlah varian-varian Naive Bayes berbeda. GaussianNB memodelkan tiap fitur kontinu sebagai kurva Gaussian (normal) per kelas — parameternya hanya rerata dan varians. MultinomialNB bekerja pada hitungan (counts): berapa kali sebuah kata muncul, cocok untuk representasi bag-of-words. BernoulliNB memakai fitur biner: kata itu muncul atau tidak.

VarianTipe fiturModel likelihoodContoh pemakaian
GaussianNBKontinuKurva Gaussian per kelasSensor, ukuran numerik
MultinomialNBHitungan (counts)Distribusi multinomialKlasifikasi teks, hitungan kata
BernoulliNBBiner (ada/tidak)Uji coba BernoulliKehadiran fitur biner

Prior juga bisa diatur: secara bawaan scikit-learn memperkirakan P(kelas) dari proporsi kelas di data latih, tetapi parameter priors memungkinkan kita menyuntikkan pengetahuan domain — misalnya meningkatkan prior spam ketika gelombang spam sedang tinggi. Mari kita lihat semua bagian ini bekerja pada dua data berbeda:

python
# Naive Bayes: GaussianNB untuk fitur kontinu dan MultinomialNB untuk hitungan kata.
# Kisah deteksi spam dilanjutkan pada dua jenis data yang berbeda.

import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB, MultinomialNB

# ---------- Bagian 1: GaussianNB pada fitur kontinu ----------
X, y = make_classification(
    n_samples=80,
    n_features=2,
    n_informative=2,
    n_redundant=0,
    n_clusters_per_class=1,
    random_state=42,
)

X_latih, X_uji, y_latih, y_uji = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

model_gauss = GaussianNB()
model_gauss.fit(X_latih, y_latih)

print("GaussianNB pada 80 email (fitur kontinu):")
print(f"  akurasi uji        : {model_gauss.score(X_uji, y_uji):.3f}")
print(f"  class_prior_ [ham, spam] : {model_gauss.class_prior_[0]:.3f}, "
      f"{model_gauss.class_prior_[1]:.3f}")
print("  theta_ (rerata fitur per kelas):")
for kelas, nama in enumerate(["ham ", "spam"]):
    print(f"    {nama}: {model_gauss.theta_[kelas][0]:8.3f}   "
          f"{model_gauss.theta_[kelas][1]:8.3f}")
print("  var_ (varians fitur per kelas):")
for kelas, nama in enumerate(["ham ", "spam"]):
    print(f"    {nama}: {model_gauss.var_[kelas][0]:8.3f}   "
          f"{model_gauss.var_[kelas][1]:8.3f}")

# ---------- Bagian 2: MultinomialNB pada hitungan kata ----------
# Empat email mini direpresentasikan sebagai hitungan kata (bag-of-words).
kosa_kata = ["rapat", "laporan", "gratis", "diskon", "klik"]
X_kata = np.array([
    [2, 1, 0, 0, 0],  # ham
    [1, 2, 0, 0, 0],  # ham
    [0, 0, 2, 1, 1],  # spam
    [0, 0, 1, 0, 2],  # spam
])
y_kata = np.array([0, 0, 1, 1])

model_kata = MultinomialNB(alpha=1.0)
model_kata.fit(X_kata, y_kata)

# Email baru memuat kata "rapat", "laporan", dan "gratis" masing-masing satu kali.
email_baru = np.array([[1, 1, 1, 0, 0]])

print("\nMultinomialNB pada 4 email mini (hitungan kata):")
print("  P(kata | kelas) setelah smoothing Laplace (alpha=1):")
p_kata = np.exp(model_kata.feature_log_prob_)
for kelas, nama in enumerate(["ham ", "spam"]):
    peluang = "  ".join(f"{p:5.3f}" for p in p_kata[kelas])
    print(f"    {nama}: {peluang}")
print(f"  kosa kata          : {kosa_kata}")

prediksi = model_kata.predict(email_baru)[0]
proba = model_kata.predict_proba(email_baru)[0]
print(f"\n  email baru (rapat=1, laporan=1, gratis=1)")
print(f"  prediksi           : {'spam' if prediksi == 1 else 'ham'}")
print(f"  P(ham)  P(spam)    : {proba[0]:.3f}, {proba[1]:.3f}")

Output yang Dihasilkan:

text
GaussianNB pada 80 email (fitur kontinu):
  akurasi uji        : 0.950
  class_prior_ [ham, spam] : 0.500, 0.500
  theta_ (rerata fitur per kelas):
    ham :    0.870     -1.023
    spam:    0.964      0.894
  var_ (varians fitur per kelas):
    ham :    0.797      0.165
    spam:    0.089      0.746

MultinomialNB pada 4 email mini (hitungan kata):
  P(kata | kelas) setelah smoothing Laplace (alpha=1):
    ham : 0.364  0.364  0.091  0.091  0.091
    spam: 0.083  0.083  0.333  0.167  0.333
  kosa kata          : ['rapat', 'laporan', 'gratis', 'diskon', 'klik']

  email baru (rapat=1, laporan=1, gratis=1)
  prediksi           : ham
  P(ham)  P(spam)    : 0.839, 0.161

Mari kita telaah baris-baris kode di atas:

  1. Baris 10–17 (make_classification): 80 email dengan dua fitur kontinu dibuat deterministik dengan random_state=42; proporsi kelas dijaga seimbang lewat stratify pada pemisahan data di baris 19–21.
  2. Baris 23–24 (GaussianNB().fit): pelatihan GaussianNB hanya memperkirakan rerata, varians, dan prior per kelas — perhitungan sekali jalan tanpa iterasi, salah satu alasan Naive Bayes termasuk classifier tercepat.
  3. Baris 26–37 (cetak parameter): atribut class_prior_ memuat prior kedua kelas; theta_ adalah rerata tiap fitur per kelas; var_ adalah variansnya. Ketiganya adalah seluruh “pengetahuan” model.
  4. Baris 41–48 (email mini): empat email direpresentasikan sebagai vektor hitungan kata — baris pertama berarti email memuat dua kata rapat dan satu kata laporan. Data sekecil ini sengaja dipilih agar aritmetikanya bisa dihitung tangan.
  5. Baris 50–51 (MultinomialNB(alpha=1.0)): parameter alpha adalah kekuatan Laplace smoothing — penambahan satu hitungan bayangan pada setiap kata, dibahas pada callout di bawah.
  6. Baris 58–61 (feature_log_prob_): scikit-learn menyimpan logaritma P(kata | kelas) demi stabilitas numerik; np.exp mengembalikannya ke skala probabilitas agar bisa dibaca.
  7. Baris 64–68 (predict dan predict_proba): email baru berisi kata rapat, laporan, dan gratis sekali masing-masing; keluaran menunjukkan keputusan final beserta posterior kedua kelas.

Bagian GaussianNB memperlihatkan isi kepala model secara utuh. Prior tercetak 0,500 dan 0,500 — sesuai data latih yang seimbang. Rerata fitur kedua (baris kedua theta_) terpisah jelas: -1,023 untuk ham dan +0,894 untuk spam, dengan varians yang kecil di masing-masing kelas; fitur inilah motor pengklasifikasi. Sebaliknya rerata fitur pertama nyaris berimpit (0,870 vs 0,964) sehingga fitur tersebut hampir tidak membantu memisahkan kelas — model tetap bekerja karena likelihood kedua fitur dikalikan, bukan dipilih salah satu. Akurasi uji 0,950 pada 20 email uji menjadi umpan balik kewarasan.

Bagian MultinomialNB memperlihatkan aritmetika Bayes secara telanjang. Tabel P(kata | kelas) terbaca persis seperti intuisi: kata rapat berpeluang 0,364 di kelas ham tetapi hanya 0,083 di kelas spam; kata gratis kebalikannya (0,091 vs 0,333). Email baru berisi satu rapat, satu laporan, dan satu gratis. Skor ham sebanding dengan 0,364 × 0,364 × 0,091 ≈ 0,012, sedangkan skor spam sebanding dengan 0,083 × 0,083 × 0,333 ≈ 0,0023 — rasio sekitar 5 banding 1 untuk ham, sehingga posterior tercetak 0,839 dan 0,161. Dua kata kantor mengalahkan satu kata promosi; itulah seluruh logika penyaring spam klasik.

Pemecahan berulang itu tepat menjadi chapter berikutnya: alih-alih satu rumus peluang untuk semua data, Decision Tree membagi ruang fitur menjadi wilayah-wilayah kecil dengan aturan bertingkat seperti layaknya diagram alur.