Asosiy qismga o'tish

Support Vector Machine (SVM)

22-bob 15 kod bloki 3618 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Sahna: eng keng ko'cha

Ikki sinfni ajratadigan chiziq chizish kerak. Lekin cheksiz ko'p chiziq bu ishni bajaradi — qaysi birini tanlaysiz?

Logistic regression (15-bob) bitta javob beradi, daraxt (17-bob) boshqa javob beradi. SVM esa aniq mezon qo'yadi:

Ikki sinf orasidagi eng KENG ko'chani topaman, va chiziqni o'sha ko'chaning o'rtasidan o'tkazaman.

Ko'chaning kengligi — margin. Uning chetiga tegib turgan nuqtalar — support vektorlar. Modelning nomi shundan.

Margin nima va nega muhim

python29 qator
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC, LinearSVC
from sklearn.datasets import make_blobs

X_d, y_d = make_blobs(n_samples=60, centers=2, cluster_std=1.1, random_state=7)

svm = SVC(kernel="linear", C=1000).fit(X_d, y_d)   # C katta = qattiq margin
w, b0 = svm.coef_[0], svm.intercept_[0]

rasm, ax = plt.subplots(figsize=(9, 5.5))
ax.scatter(X_d[:, 0], X_d[:, 1], c=y_d, cmap="coolwarm", s=45, edgecolors="k", linewidths=.5)
xx = np.linspace(X_d[:, 0].min() - 1, X_d[:, 0].max() + 1, 100)
yy = -(w[0] * xx + b0) / w[1]
chet = 1 / np.linalg.norm(w)
ax.plot(xx, yy, "k-", lw=2, label="qaror chizig'i")
ax.plot(xx, yy + chet * np.sqrt(1 + (w[0]/w[1])**2), "k--", lw=1, label="margin cheti")
ax.plot(xx, yy - chet * np.sqrt(1 + (w[0]/w[1])**2), "k--", lw=1)
ax.scatter(svm.support_vectors_[:, 0], svm.support_vectors_[:, 1],
           s=260, facecolors="none", edgecolors="green", linewidths=2.2,
           label=f"support vektorlar ({len(svm.support_vectors_)} ta)")
ax.legend(fontsize=9); ax.set_title("SVM: eng keng ko'cha", fontweight="bold")
ax.grid(alpha=.3)
fig("svm_margin.png", caption="Qora chiziq — qaror chegarasi. Shtrix chiziqlar — margin chetlari. Yashil doiralar — support vektorlar.")

print(f"Jami nuqta        : {len(X_d)}")
print(f"Support vektorlar : {len(svm.support_vectors_)}")
print(f"Margin kengligi   : {2/np.linalg.norm(w):.4f}")
Chiqish
Jami nuqta        : 60
Support vektorlar : 3
Margin kengligi   : 3.9591

Qora chiziq — qaror chegarasi. Shtrix chiziqlar — margin chetlari. Yashil doiralar — support vektorlar.

Qora chiziq — qaror chegarasi. Shtrix chiziqlar — margin chetlari. Yashil doiralar — support vektorlar.

Diqqat qiling: 60 nuqtadan faqat bir nechtasi modelni aniqlaydi. Qolgan nuqtalarni o'chirib tashlasangiz, chiziq o'zgarmaydi.

python6 qator
# isbot: support vektorlardan boshqasini olib tashlaymiz
sv_idx = svm.support_
svm2 = SVC(kernel="linear", C=1000).fit(X_d[sv_idx], y_d[sv_idx])
print(f"Butun ma'lumotda  : w = {svm.coef_[0].round(4)}, b = {svm.intercept_[0]:.4f}")
print(f"Faqat SV larda    : w = {svm2.coef_[0].round(4)}, b = {svm2.intercept_[0]:.4f}")
print(f"Bir xilmi         : {bool(np.allclose(svm.coef_, svm2.coef_, atol=1e-3))}")
Chiqish
Butun ma'lumotda  : w = [ 0.4893 -0.1257], b = 2.8536
Faqat SV larda    : w = [ 0.4892 -0.126 ], b = 2.8549
Bir xilmi         : True

Nega bu nazariy jihatdan chiroyli

Eng keng margin — eng ishonchli chegara. Agar yangi nuqta bir oz siljigan bo'lsa ham, keng ko'cha uni to'g'ri tomonda ushlab qoladi.

Shuning uchun SVM kichik ma'lumotda yaxshi umumlashtiradi: u ma'lumotning hamma nuqtasiga emas, faqat chegaradagi nuqtalarga tayanadi.

C parametri: qattiq va yumshoq margin

Haqiqiy ma'lumotda sinflar toza ajralmaydi. Shunda SVM ba'zi nuqtalarning ko'chaga kirishiga ruxsat beradi — lekin jarima bilan.

C — o'sha jarimaning kuchi.

python18 qator
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

df = pd.read_csv(DATA / "uy_ijara.csv")
son = ["xonalar", "maydon_m2", "qavat", "qavatlar_soni", "metro_km", "mebel"]
X, y = df[son], df["ijaraga_berildi_30k"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

print(f"{'C':>8} {'margin':>10} {'SV soni':>9} {'CV F1':>9} {'izoh'}")
print("-" * 54)
for C in [0.01, 0.1, 1, 10, 100]:
    q = Pipeline([("sc", StandardScaler()), ("m", SVC(kernel="linear", C=C))]).fit(X_tr, y_tr)
    b = cross_val_score(Pipeline([("sc", StandardScaler()), ("m", SVC(kernel="linear", C=C))]),
                        X, y, cv=5, scoring="f1")
    m = q.named_steps["m"]
    izoh = "yumshoq margin" if C <= 0.1 else ("qattiq margin" if C >= 100 else "")
    print(f"{C:>8} {'keng' if C <= 0.1 else 'tor':>10} {m.n_support_.sum():>9} {b.mean():>9.4f} {izoh}")
Chiqish
       C     margin   SV soni     CV F1 izoh
------------------------------------------------------
    0.01       keng       625    0.7242 yumshoq margin
     0.1       keng       590    0.7247 yumshoq margin
       1        tor       583    0.7233 
      10        tor       583    0.7239 
     100        tor       584    0.7239 qattiq margin
python6 qator
show(pd.DataFrame([
    {"C": "kichik (0.01-0.1)", "Margin": "KENG", "Xatoga munosabat": "Ruxsat beradi",
     "Natija": "Soddaroq model, ko'p support vektor", "Xavf": "Underfitting"},
    {"C": "katta (100+)", "Margin": "TOR", "Xatoga munosabat": "Qattiq jazolaydi",
     "Natija": "Murakkab chegara, kam support vektor", "Xavf": "Overfitting"},
]))
CMarginXatoga munosabatNatijaXavf
kichik (0.01-0.1)KENGRuxsat beradiSoddaroq model, ko'p support vektorUnderfitting
katta (100+)TORQattiq jazolaydiMurakkab chegara, kam support vektorOverfitting

`C` — 16-bobdagi `C` bilan BIR XIL mantiq

16-bobda aytganmiz: LogisticRegression(C=...) da C = 1/alpha.

SVM da ham xuddi shunday: C katta = regularizatsiya KUCHSIZ.

Ya'ni Ridge(alpha=100) va SVC(C=0.01) ikkisi ham "kuchli jarima" degani. Chalkashtirmaslik uchun eslab qoling: alpha jarimani oshiradi, C jarimani kamaytiradi.

Kernel trick: SVM ning super kuchi

Hozirgacha kernel="linear" ishlatdik. Lekin SVM ning asosiy kuchi boshqa joyda.

15-bobdagi halqa ma'lumotini eslaysizmi? Chiziqli model 0.5350 berdi.

python20 qator
r = np.random.default_rng(42)
n = 600
burchak = r.uniform(0, 2 * np.pi, n)
radius = np.where(r.random(n) < 0.5, r.normal(1, .25, n), r.normal(3, .3, n))
Xh = np.c_[radius * np.cos(burchak), radius * np.sin(burchak)]
yh = (radius > 2).astype(int)

from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier

print(f"{'model':<34} {'CV accuracy':>13} {'±':>8}")
print("-" * 58)
for nom, m in [
    ("LogisticRegression (15-bob)", LogisticRegression()),
    ("DecisionTree (17-bob)", DecisionTreeClassifier(max_depth=6, random_state=42)),
    ("SVM kernel='linear'", SVC(kernel="linear")),
    ("SVM kernel='rbf'", SVC(kernel="rbf")),
]:
    b = cross_val_score(Pipeline([("sc", StandardScaler()), ("m", m)]), Xh, yh, cv=5)
    print(f"{nom:<34} {b.mean():>13.4f} {b.std():>8.4f}")
Chiqish
model                                CV accuracy        ±
----------------------------------------------------------
LogisticRegression (15-bob)               0.5350   0.0220
DecisionTree (17-bob)                     0.9883   0.0085
SVM kernel='linear'                       0.6400   0.0266
SVM kernel='rbf'                          1.0000   0.0000

kernel="rbf" halqani deyarli mukammal ajratdi — bitta parametr o'zgartirib.

Qanday? Kernel ma'lumotni yuqori o'lchovga ko'chiradi, u yerda sinflar chiziqli ajraladi, keyin natijani qaytaradi. Va buni haqiqatan ko'chirmasdan qiladi — faqat skalyar ko'paytmalarni boshqacha hisoblaydi. Shuning uchun "trick" deb ataladi.

python10 qator
show(pd.DataFrame([
    {"Kernel": "linear", "Qachon": "Ustunlar ko'p, ma'lumot matnli (TF-IDF)",
     "Parametrlar": "C", "Tezlik": "Tez"},
    {"Kernel": "rbf", "Qachon": "SUKUT TANLOV — murakkab chegara kerak bo'lsa",
     "Parametrlar": "C, gamma", "Tezlik": "Sekinroq"},
    {"Kernel": "poly", "Qachon": "Polinom bog'lanish ma'lum bo'lsa",
     "Parametrlar": "C, degree, gamma", "Tezlik": "Sekin"},
    {"Kernel": "sigmoid", "Qachon": "Kamdan-kam ishlatiladi",
     "Parametrlar": "C, gamma", "Tezlik": "Sekin"},
]))
KernelQachonParametrlarTezlik
linearUstunlar ko'p, ma'lumot matnli (TF-IDF)CTez
rbfSUKUT TANLOV — murakkab chegara kerak bo'lsaC, gammaSekinroq
polyPolinom bog'lanish ma'lum bo'lsaC, degree, gammaSekin
sigmoidKamdan-kam ishlatiladiC, gammaSekin

gamma: rbf kernel ning radiusi

python11 qator
print(f"{'gamma':>10} {'CV accuracy':>13} {'izoh'}")
print("-" * 48)
for g in [0.001, 0.01, 0.1, 1, 10, 100]:
    b = cross_val_score(Pipeline([("sc", StandardScaler()), ("m", SVC(kernel="rbf", gamma=g))]),
                        Xh, yh, cv=5)
    izoh = "juda silliq (underfit)" if g <= 0.001 else ("har nuqta o'z oroli (overfit)" if g >= 100 else "")
    print(f"{g:>10} {b.mean():>13.4f} {izoh}")
print()
print("'scale' (sukut) — ma'lumot dispersiyasiga qarab avtomatik tanlanadi:")
b = cross_val_score(Pipeline([("sc", StandardScaler()), ("m", SVC(kernel='rbf', gamma='scale'))]), Xh, yh, cv=5)
print(f"  gamma='scale'  CV accuracy = {b.mean():.4f}")
Chiqish
     gamma   CV accuracy izoh
------------------------------------------------
     0.001        0.5033 juda silliq (underfit)
      0.01        0.5383 
       0.1        1.0000 
         1        1.0000 
        10        0.9967 
       100        0.9767 har nuqta o'z oroli (overfit)

'scale' (sukut) — ma'lumot dispersiyasiga qarab avtomatik tanlanadi:
  gamma='scale'  CV accuracy = 1.0000

gamma har bir support vektorning "ta'sir radiusi"ni belgilaydi:

  • kichik gamma — katta radius, silliq chegara
  • katta gamma — kichik radius, har nuqta o'z atrofini himoya qiladi (overfitting)

`C` va `gamma` ni BIRGA sozlash kerak

Ular bir-biriga bog'liq: katta C + katta gamma = kafolatlangan overfitting.

Shuning uchun SVM uchun GridSearchCV da ikkisini birga qidiriladi (23-bob):

json1 qator
{"m__C": [0.1, 1, 10, 100], "m__gamma": [0.001, 0.01, 0.1, 1]}

Bu 16 kombinatsiya — SVM ning sozlash narxi shunda.

Scaling SVM uchun hayotiy

python5 qator
print("SVM — scaling bilan va busiz:")
for nom, m in [("scaling YO'Q", SVC()),
               ("scaling BOR", Pipeline([("sc", StandardScaler()), ("m", SVC())]))]:
    b = cross_val_score(m, X, y, cv=5, scoring="f1")
    print(f"  {nom:14s} CV F1 = {b.mean():.4f} ±{b.std():.4f}")
Chiqish
SVM — scaling bilan va busiz:
  scaling YO'Q   CV F1 = 0.7263 ±0.0056
  scaling BOR    CV F1 = 0.7308 ±0.0135

Sabab KNN bilan bir xil (20-bob): SVM masofaga tayanadi, shuning uchun katta shkalali ustun hammasini bosib ketadi.

Tezlik: SVM ning asosiy cheklovi

python13 qator
import time
from sklearn.datasets import make_classification

print(f"{'qatorlar':>10} {'SVC (rbf)':>12} {'LinearSVC':>12} {'LogisticReg':>13}")
print("-" * 52)
for n_q in [500, 2000, 8000]:
    Xb, yb = make_classification(n_samples=n_q, n_features=20, n_informative=10, random_state=42)
    satr = [f"{n_q:>10}"]
    for m in [SVC(kernel="rbf"), LinearSVC(dual="auto", max_iter=5000), LogisticRegression(max_iter=2000)]:
        t0 = time.perf_counter()
        Pipeline([("sc", StandardScaler()), ("m", m)]).fit(Xb, yb)
        satr.append(f"{(time.perf_counter()-t0)*1000:>11.0f}ms")
    print(" ".join(satr))
Chiqish
  qatorlar    SVC (rbf)    LinearSVC   LogisticReg
----------------------------------------------------
       500           3ms           1ms           3ms
      2000          22ms           2ms           2ms
      8000         259ms           8ms          11ms

Jadvalga qarang: qatorlar ko'paygan sari SVC ning vaqti tez o'sadi.

Sabab: kernel SVM ning murakkabligi

dan

gacha. Shuning uchun:

python5 qator
show(pd.DataFrame([
    {"Qatorlar": "< 10 000", "Tanlov": "SVC(kernel='rbf')", "Izoh": "Kernel ning kuchidan foydalanish mumkin"},
    {"Qatorlar": "10 000 - 100 000", "Tanlov": "LinearSVC yoki SGDClassifier", "Izoh": "Kernel juda qimmat"},
    {"Qatorlar": "> 100 000", "Tanlov": "LogisticRegression / LightGBM", "Izoh": "SVM umuman mos emas"},
]))
QatorlarTanlovIzoh
< 10 000SVC(kernel='rbf')Kernel ning kuchidan foydalanish mumkin
10 000 - 100 000LinearSVC yoki SGDClassifierKernel juda qimmat
> 100 000LogisticRegression / LightGBMSVM umuman mos emas

SVM 2010-yillarda qirol edi — bugun emas

Chuqur o'rganishdan oldin SVM eng kuchli model hisoblangan. Bugun esa tabular ma'lumotda boosting (19-bob) uni deyarli har doim yengadi, va u tezroq ishlaydi.

SVM hali ham mos keladigan joylar: - Kichik ma'lumot (yuzlab qator) — kam parametr, yaxshi umumlashtirish - Ustunlar qatorlardan ko'p (m > n) — masalan genetika, matn - Matn tasnifi TF-IDF bilan — LinearSVC hali ham kuchli baza

Yangi loyihada esa birinchi tanlov sifatida boosting yoki logistic regression dan boshlaysiz.

Probability: SVM ehtimollik bermaydi

python13 qator
svc = Pipeline([("sc", StandardScaler()), ("m", SVC())]).fit(X_tr, y_tr)
try:
    svc.predict_proba(X_te[:3])
except AttributeError as e:
    print("SVC().predict_proba() ->", type(e).__name__)
    print(f"  {str(e)[:70]}")
print()
svc_p = Pipeline([("sc", StandardScaler()), ("m", SVC(probability=True, random_state=42))]).fit(X_tr, y_tr)
print("probability=True bilan:")
print(f"  {svc_p.predict_proba(X_te[:3])[:, 1].round(4)}")
print()
print("decision_function — SVM ning TABIIY chiqishi (chegaradan masofa):")
print(f"  {svc.decision_function(X_te[:3]).round(4)}")
Chiqish
SVC().predict_proba() -> AttributeError
  This 'Pipeline' has no attribute 'predict_proba'

probability=True bilan:
  [0.2973 0.771  0.7154]

decision_function — SVM ning TABIIY chiqishi (chegaradan masofa):
  [-0.9156  1.2765  0.9675]

`probability=True` qimmatga tushadi

SVM tabiiy ravishda ehtimollik bermaydi — u faqat chegaradan masofani beradi.

probability=True qo'ysangiz, sklearn ichida 5-fold cross-validation o'tkazib Platt kalibrlashni o'rgatadi. Ya'ni o'qitish vaqti ~5 barobar oshadi.

8-bobdagi threshold surish uchun esa decision_function() yetarli — u ham tartiblangan ball beradi.

Variantlar

python7 qator
show(pd.DataFrame([
    {"Klass": "SVC", "Vazifa": "Classification", "Kernel": "Bor", "Qachon": "n < 10 000"},
    {"Klass": "LinearSVC", "Vazifa": "Classification", "Kernel": "Yo'q (faqat chiziqli)", "Qachon": "Katta n, matn"},
    {"Klass": "SVR", "Vazifa": "Regression", "Kernel": "Bor", "Qachon": "Kichik regression"},
    {"Klass": "LinearSVR", "Vazifa": "Regression", "Kernel": "Yo'q", "Qachon": "Katta regression"},
    {"Klass": "OneClassSVM", "Vazifa": "Anomaliya aniqlash", "Kernel": "Bor", "Qachon": "Faqat 'normal' ma'lumot bo'lsa"},
]))
KlassVazifaKernelQachon
SVCClassificationBorn < 10 000
LinearSVCClassificationYo'q (faqat chiziqli)Katta n, matn
SVRRegressionBorKichik regression
LinearSVRRegressionYo'qKatta regression
OneClassSVMAnomaliya aniqlashBorFaqat 'normal' ma'lumot bo'lsa

4-qism yakuni: hamma algoritm bir jadvalda

python28 qator
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from xgboost import XGBClassifier

print(f"{'model':<28} {'CV F1':>9} {'±':>8} {'scaling':>9}")
print("-" * 60)
natijalar = []
for nom, m, sc_kerak in [
    ("LogisticRegression (15)", LogisticRegression(max_iter=2000), True),
    ("DecisionTree (17)", DecisionTreeClassifier(max_depth=6, min_samples_leaf=20, random_state=42), False),
    ("RandomForest (18)", RandomForestClassifier(n_estimators=200, random_state=42), False),
    ("XGBoost (19)", XGBClassifier(n_estimators=200, verbosity=0, random_state=42), False),
    ("KNN (20)", KNeighborsClassifier(n_neighbors=15), True),
    ("GaussianNB (20)", GaussianNB(), False),
    ("SVM rbf (21)", SVC(), True),
]:
    quvur = Pipeline([("sc", StandardScaler()), ("m", m)]) if sc_kerak else m
    b = cross_val_score(quvur, X, y, cv=5, scoring="f1")
    natijalar.append((nom, b.mean(), b.std()))
    print(f"{nom:<28} {b.mean():>9.4f} {b.std():>8.4f} {'ha' if sc_kerak else 'yo`q':>9}")

print()
eng = max(natijalar, key=lambda x: x[1])
tarqalish = max(n[2] for n in natijalar)
yaqin = [n[0] for n in natijalar if eng[1] - n[1] < tarqalish]
print(f"Eng baland     : {eng[0]} ({eng[1]:.4f})")
print(f"Tarqalish ichida: {len(yaqin)} ta model -> {', '.join(yaqin)}")
Chiqish
model                            CV F1        ±   scaling
------------------------------------------------------------
LogisticRegression (15)         0.7210   0.0111        ha
DecisionTree (17)               0.6715   0.0246      yo`q
RandomForest (18)               0.6824   0.0185      yo`q
XGBoost (19)                    0.6715   0.0292      yo`q
KNN (20)                        0.7064   0.0191        ha
GaussianNB (20)                 0.7291   0.0080      yo`q
SVM rbf (21)                    0.7308   0.0135        ha

Eng baland     : SVM rbf (21) (0.7308)
Tarqalish ichida: 4 ta model -> LogisticRegression (15), KNN (20), GaussianNB (20), SVM rbf (21)

Oxirgi qatorga diqqat qiling. 9-bob qoidasi bo'yicha, tarqalish ichidagi modellar orasida farq ishonchli emas.

Bizning ma'lumot kichik (1200 qator) va oson. Shuning uchun murakkab modellar oddiylaridan ustunlik qilmaydi — bu amaliyotda juda ko'p uchraydigan holat.

Bugun nima o'rgandik

python10 qator
show(pd.DataFrame([
    {"Tushuncha": "Margin", "Bitta jumla": "Ikki sinf orasidagi eng keng ko'cha"},
    {"Tushuncha": "Support vektorlar", "Bitta jumla": "Faqat chegaradagi nuqtalar modelni aniqlaydi"},
    {"Tushuncha": "C", "Bitta jumla": "Katta C = tor margin = kuchsiz regularizatsiya"},
    {"Tushuncha": "Kernel trick", "Bitta jumla": "Yuqori o'lchovga ko'chirmasdan chiziqsiz chegara"},
    {"Tushuncha": "gamma", "Bitta jumla": "Support vektorning ta'sir radiusi; C bilan birga sozlanadi"},
    {"Tushuncha": "Scaling", "Bitta jumla": "SHART — masofaga tayanadi"},
    {"Tushuncha": "Tezlik", "Bitta jumla": "O(n^2)-O(n^3) — 10 000 qatordan keyin mos emas"},
    {"Tushuncha": "Ehtimollik", "Bitta jumla": "Tabiiy ravishda yo'q; probability=True 5x qimmat"},
]))
TushunchaBitta jumla
MarginIkki sinf orasidagi eng keng ko'cha
Support vektorlarFaqat chegaradagi nuqtalar modelni aniqlaydi
CKatta C = tor margin = kuchsiz regularizatsiya
Kernel trickYuqori o'lchovga ko'chirmasdan chiziqsiz chegara
gammaSupport vektorning ta'sir radiusi; C bilan birga sozlanadi
ScalingSHART — masofaga tayanadi
TezlikO(n^2)-O(n^3) — 10 000 qatordan keyin mos emas
EhtimollikTabiiy ravishda yo'q; probability=True 5x qimmat

Shu bilan 4-qism tugadi. Siz sakkizta algoritmni ko'rdingiz va ularning har birining qachon mos kelishini bilasiz.

Eng muhim xulosa esa boshqa: sizning ma'lumotingizda ularning ko'pi bir xil natija beradi. Shuning uchun algoritm tanlash — ishning kichik qismi. Asosiy ish ma'lumotda (1-qism), baholashda (2-qism) va xato tahlilida (10-bob).

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar