K-Nearest Neighbors pada chapter sebelumnya mengambil keputusan dari kepadatan tetangga, dan seperti semua model berbasis jarak, ia menuntut fitur yang diskalakan. Support Vector Machine (SVM) berbagi tuntutan yang sama tetapi membawa filosofi geometris yang berbeda: alih-alih menghitung tetangga, SVM mencari garis — atau bidang pada dimensi lebih tinggi — pemisah dua kelas yang memberi ruang aman selebar mungkin di antara keduanya. Model ini dikenal tangguh pada data berdimensi banyak, akurat pada dataset kecil-menengah, dan memiliki mekanisme bernama kernel untuk menggarap batas pemisah yang melengkung. Pada chapter ini kita memakai data panjang dan berat biji kopi untuk membedah hyperplane, margin, dan peran penskalaan, lalu data percobaan harga di “Toko Kopi Kita” untuk melihat kapan kernel rbf benar-benar dibutuhkan.

Hyperplane dan Margin Maksimum #

Pada klasifikasi dua kelas, tugas SVM adalah menemukan hyperplane — garis pemisah pada dua dimensi, bidang pada tiga dimensi, dan seterusnya — yang memisahkan kedua kelas. Dari tak terhingga banyaknya garis pemisah yang mungkin, SVM memilih satu dengan kriteria khusus:

text
Hyperplane    : w . x + b = 0
Margin        : jarak terpendek hyperplane ke titik terdekat tiap kelas
Tujuan SVM    : maksimalkan lebar margin (2 x jarak tersebut)

w : arah tegak lurus hyperplane (bobot tiap fitur)
b : pergeseran posisi hyperplane
titik-titik yang tepat berada di tepi margin = vektor pendukung

Intuisinya: garis pemisah yang menempel rapat ke salah satu kelas terlalu percaya diri — sedikit derau pada data baru sudah cukup untuk membuat prediksi berpindah kelas. Garis yang berdiri persis di tengah, dengan ruang kosong selebar mungkin di kedua sisinya, memberi toleransi terbesar terhadap variasi data baru. Hanya titik-titik yang berada tepat di tepi margin — dinamai vektor pendukung (support vector) — yang menentukan posisi hyperplane; titik lain yang berada jauh di zona aman boleh dipindahkan tanpa mengubah apa pun. Inilah sebabnya SVM hemat perhatian: model yang tersimpan hanyalah segelintir titik perbatasan itu, bukan seluruh data seperti KNN.

Penskalaan Fitur Sebelum SVM #

Margin dihitung sebagai jarak di ruang fitur, jadi satuan fitur ikut menentukan bentuk geometrinya. Kasus kita: “Toko Kopi Kita” menerima kiriman biji kopi dari beberapa pemasok dan perlu memilah lot arabika dari robusta hanya dari dua ukuran fisik — panjang_mm (milimeter, bernilai 8-12) dan berat_gram (gram, bernilai 0,10-0,24). Polanya: arabika cenderung lebih panjang dan lebih berat. Masalahnya, skala panjang_mm ratusan kali lebih besar daripada berat_gram, sehingga geometri margin nyaris sepenuhnya dibentuk oleh panjang biji:

python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

biji = pd.DataFrame({
    "panjang_mm": [9.4, 10.1, 10.8, 11.2, 9.8, 10.5, 11.6, 9.9, 10.3, 11.0,
                   9.6, 10.7, 11.4, 10.2, 10.9,
                   8.4, 9.1, 8.8, 9.6, 8.2, 9.3, 8.6, 9.8, 8.9, 9.5,
                   8.3, 9.0, 9.7, 8.5, 9.05],
    "berat_gram": [0.21, 0.19, 0.22, 0.20, 0.23, 0.18, 0.24, 0.20, 0.22, 0.19,
                   0.22, 0.21, 0.18, 0.24, 0.155,
                   0.12, 0.14, 0.11, 0.13, 0.15, 0.10, 0.13, 0.12, 0.16, 0.11,
                   0.14, 0.15, 0.14, 0.12, 0.175],
    "arabika": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
})

fitur = ["panjang_mm", "berat_gram"]
X = biji[fitur]
y = biji["arabika"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
                                                   stratify=y, random_state=42)

mentah = SVC(kernel="linear", C=1.0)
mentah.fit(X_latih, y_latih)

skala = StandardScaler()
X_latih_skala = skala.fit_transform(X_latih)
X_uji_skala = skala.transform(X_uji)
diskala = SVC(kernel="linear", C=1.0)
diskala.fit(X_latih_skala, y_latih)

print("Data mentah : akurasi uji %.2f" % mentah.score(X_uji, y_uji))
print("Data diskala: akurasi uji %.2f" % diskala.score(X_uji_skala, y_uji))
print("Vektor pendukung model diskala: %d dari %d titik latih"
      % (diskala.n_support_.sum(), len(X_latih)))

Output yang Dihasilkan:

text
Data mentah : akurasi uji 0.78
Data diskala: akurasi uji 1.00
Vektor pendukung model diskala: 5 dari 21 titik latih

Mari kita telaah baris-baris kode di atas:

  1. SVC(kernel="linear", C=1.0): SVC adalah kelas SVM scikit-learn; kernel="linear" meminta hyperplane berupa garis lurus, dan C mengatur ketatnya margin (kita bahas khusus di bagian berikutnya).
  2. Data mentah hanya mencapai 0,78: rentang panjang_mm sekitar 8-12 sedangkan berat_gram hanya 0,10-0,24. Dalam geometri mentah, selisih 1 mm panjang biji setara selisih satu penuh satuan berat — padahal 1 mm itu jauh lebih bermakna secara fisik. Kelompok arabika pendek dan robusta panjang di zona tumpang tindih pun salah pilah karena berat_gram nyaris tak terdengar oleh margin.
  3. StandardScaler menyeterakan keduanya (z-score, sama seperti chapter KNN): akurasi melonjak ke 1,00 pada 9 lot uji, dan hanya 5 dari 21 titik latih yang berperan sebagai vektor pendukung — sisanya dudung aman di belakang margin.

Kernel: Ketika Garis Lurus Tidak Cukup #

Hyperplane lurus tidak selalu ada. Kasus klasik dari dunia ritel: “Toko Kopi Kita” menguji harga menu andalan dan menemukan bahwa penjualan di atas target (laku=1) justru muncul di dua ujung spektrum — harga promo di bawah Rp12 ribu yang memancing pemburu diskon, dan harga premium di atas Rp26 ribu yang menarik pembeli kualitas — sementara harga menengah Rp14-23 ribu justru kalah bersaing (laku=0). Kelas laku=1 membentuk dua gugus di kiri-kanan dengan pita laku=0 di tengah; tidak ada satu garis lurus pun yang mampu membelah pola seperti ini.

Di sinilah kernel masuk. Kernel adalah trik matematis yang menghitung jarak kompleks di ruang berdimensi lebih tinggi tanpa benar-benar memindahkan data ke sana — dan di ruang itu, batas yang melengkung di dunia asal bisa menjadi lurus. Kernel linear tetap pada garis lurus; kernel rbf (radial basis function) membentuk batas melengkung mengikuti gugus data:

python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

percobaan = pd.DataFrame({
    "harga_gelas": [8500, 9000, 9500, 10000, 10500, 11000, 9000, 10000, 11500,
                    10500, 26500, 27000, 28000, 29000, 30000, 31000, 32000,
                    27500, 14000, 15000, 16000, 17000, 18000, 19000, 20000,
                    21000, 22000, 23000, 14500, 19500],
    "rating_minggu": [4.2, 3.9, 4.5, 4.0, 4.3, 4.6, 4.4, 4.1, 3.8, 4.7,
                      4.5, 4.8, 4.3, 4.6, 4.9, 4.4, 4.7, 4.2,
                      4.0, 4.4, 4.6, 4.2, 4.8, 4.1, 4.5, 4.3, 4.7, 4.0, 4.6, 4.9],
    "laku": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
             1, 1, 1, 1, 1, 1, 1, 1,
             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
})

fitur = ["harga_gelas", "rating_minggu"]
X = percobaan[fitur]
y = percobaan["laku"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
                                                   stratify=y, random_state=42)

skala = StandardScaler()
X_latih_skala = skala.fit_transform(X_latih)
X_uji_skala = skala.transform(X_uji)

print("kernel  | akurasi latih | akurasi uji | vektor pendukung")
for nama in ["linear", "rbf"]:
    model = SVC(kernel=nama, C=1.0)
    model.fit(X_latih_skala, y_latih)
    print("%-7s |          %.2f |        %.2f | %d"
          % (nama, model.score(X_latih_skala, y_latih),
             model.score(X_uji_skala, y_uji), model.n_support_.sum()))

Output yang Dihasilkan:

text
kernel  | akurasi latih | akurasi uji | vektor pendukung
linear  |          0.62 |        0.56 | 18
rbf     |          0.95 |        0.89 | 18

Mari kita telaah hasilnya:

  1. linear mentok di 0,56 pada data uji — bukan karena SVM lemah, melainkan karena bentuk datanya memang tidak bisa dipisah satu garis: ujung kiri dan ujung kanan sama-sama laku=1, pita tengah laku=0. Garis mana pun yang memisahkan dua ujung pasti memotong pita tengah.
  2. rbf mencapai 0,89 dengan membengkokkan batas mengikuti pita tengah — inilah pekerjaan kernel: memindahkan masalah ke ruang dimana pola “dua gugus dengan pita di tengah” menjadi terpisah garis.
  3. Jumlah vektor pendukung yang besar (18 dari 21 titik latih) pada kedua kernel menandakan kelas-kelas ini saling berkelindan di sekitar batas — margin yang bersih sulit ditemukan pada data harga seperti ini.

Parameter C: Menimbang Lebar Margin #

Data nyata jarang rapi sempurna; hampir selalu ada titik yang melanggar zona aman. Parameter C mengatur sikap SVM terhadap pelanggaran itu: C kecil berarti pelanggaran dibiarkan demi margin yang tetap lebar dan tenang, sedangkan C besar berarti setiap pelanggaran ditindak tegas sampai margin menyempit mengikuti titik-titiknya. Kita amati dampaknya pada data biji kopi yang telah diskalakan:

python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

biji = pd.DataFrame({
    "panjang_mm": [9.4, 10.1, 10.8, 11.2, 9.8, 10.5, 11.6, 9.9, 10.3, 11.0,
                   9.6, 10.7, 11.4, 10.2, 10.9,
                   8.4, 9.1, 8.8, 9.6, 8.2, 9.3, 8.6, 9.8, 8.9, 9.5,
                   8.3, 9.0, 9.7, 8.5, 9.05],
    "berat_gram": [0.21, 0.19, 0.22, 0.20, 0.23, 0.18, 0.24, 0.20, 0.22, 0.19,
                   0.22, 0.21, 0.18, 0.24, 0.155,
                   0.12, 0.14, 0.11, 0.13, 0.15, 0.10, 0.13, 0.12, 0.16, 0.11,
                   0.14, 0.15, 0.14, 0.12, 0.175],
    "arabika": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
})
X = biji[["panjang_mm", "berat_gram"]]
y = biji["arabika"]
X_latih, X_uji, y_latih, y_uji = train_test_split(X, y, test_size=0.3,
                                                   stratify=y, random_state=42)

skala = StandardScaler()
X_latih_skala = skala.fit_transform(X_latih)
X_uji_skala = skala.transform(X_uji)

print("     C | akurasi latih | akurasi uji | vektor pendukung")
for c in [0.01, 1.0, 100.0]:
    model = SVC(kernel="linear", C=c)
    model.fit(X_latih_skala, y_latih)
    print("%5.2f |          %.2f |        %.2f | %d"
          % (c, model.score(X_latih_skala, y_latih),
             model.score(X_uji_skala, y_uji), model.n_support_.sum()))

Output yang Dihasilkan:

text
     C | akurasi latih | akurasi uji | vektor pendukung
 0.01 |          0.52 |        0.44 | 20
 1.00 |          1.00 |        1.00 | 5
100.00 |          1.00 |        1.00 | 3

Tabel ini memperlihatkan dua arah kegagalan yang sama tuanya dengan bias-variance:

  1. C=0.01 terlalu pemalu: penalti pelanggaran nyaris nol sehingga SVM memilih margin super lebar yang mengabaikan hampir seluruh struktur data — akurasi latih 0,52 berarti model nyaris tidak belajar (underfitting), dan 20 dari 21 titik menjadi vektor pendukung karena hyperplane tidak berpihak dengan yakin.
  2. C=1.0 sudah optimal di sini: margin cukup tegas untuk mengikuti pola panjang-berat, akurasi uji 1,00, cukup 5 vektor pendukung.
  3. C=100 tidak lagi memperbaiki hasil tetapi menipiskan margin: hanya 3 vektor pendukung yang menopang keputusan — pada data uji yang sedikit berbeda, hyperplane yang terlalu menempel pada segelintir titik ini lebih rapuh. C besar adalah pintu menuju overfitting ketika datanya berkelindan.

Kekuatan dan Kelemahan SVM #

AspekKeterangan
Tangguh di dimensi banyakMargin ditentukan segelintir vektor pendukung; cocok saat fitur jauh lebih banyak dari sampel
Efektif pada data kecil-menengahPunya kecenderungan menggeneralisasi baik walau datanya tidak besar
Wajib penskalaan fiturMargin adalah ukuran jarak; 0,78 lawan 1,00 pada data biji kopi kita
Kernel untuk batas melengkungrbf membenahi pola yang mustahil dipisah garis (0,56 menjadi 0,89)
Penyetelan cukup rumitKombinasi C, kernel, dan parameter kernel (mis. gamma) perlu dicoba bersama
Prediksi melambat pada data besarMelatih SVM pada puluhan ribu baris bisa sangat lambat dibanding model pohon
Sulit dijelaskan langsungTidak ada koefisien sesederhana regresi untuk ditunjukkan ke pemangku kepentingan

Kapan Kita Memakainya #

SVM adalah pilihan kuat ketika jumlah fitur besar relatif terhadap jumlah baris — misalnya hasil pengukuran sensor, data biomedis, atau representasi teks — dan ketika kita menginginkan batas keputusan yang presisi dari data yang tidak terlalu besar. Untuk data tabular besar dengan jutaan baris, keluarga pohon dari chapter-chapter sebelumnya biasanya lebih praktis. Seluruh model kita sejauh ini bekerja dengan angka: fitur numerik yang diskalakan, jarak, margin. Chapter penutup Section ini berpindah ke jenis data yang berbeda wujud — teks bebas berbahasa manusia — dan memperkenalkan model yang justru bekerja lewat probabilitas: Naive Bayes.

Rangkuman #

  • SVM mencari hyperplane pemisah dengan margin maksimum; hanya titik di tepi margin (vektor pendukung) yang menentukan hasil — 5 dari 21 titik latih pada data biji kopi kita.
  • Penskalaan fitur adalah prasyarat: geometri margin digiring oleh fitur berskala besar; akurasi biji kopi melompat dari 0,78 ke 1,00 setelah StandardScaler.
  • Kernel linear menghasilkan batas lurus; kernel rbf membengkokkan batas lewat ruang dimensi lebih tinggi — pada percobaan harga (pola dua gugus dengan pita tengah), rbf 0,89 lawan linear 0,56.
  • C menimbang lebar margin lawan ketegasan pelanggaran: terlalu kecil membuat model nyaris tidak belajar (0,44), terlalu besar menipiskan margin hingga rawan overfitting.
  • SVM kuat pada data dimensi banyak berukuran kecil-menengah, tetapi lambat pada data besar dan tidak butuh penskalaan pada model pohon.

Prediksi SVM berbunyi “kelas mana” tanpa menayangkan rasa yakinnya. Model terakhir Section ini bekerja persis sebaliknya: seluruh keputusannya adalah perkalian probabilitas atas kata-kata — mari kita tutup dengan Naive Bayes pada chapter Naive Bayes .

Referensi #

  1. scikit-learn — Preprocessing data (StandardScaler untuk SVM dan KNN)
  2. scikit-learn — FAQ (memilih dan menyetel model)