Asosiy qismga o'tish

KNN va Naive Bayes

21-bob 14 kod bloki 3008 so'z

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

Sahna: ikkita eng oddiy model

Bu bobda ikkita model bor va ularni birga o'rganish qulay — chunki ikkisi ham o'qitish bosqichiga deyarli ega emas.

  • KNN: hech narsa o'rganmaydi. Bashorat paytida eng yaqin qo'shnilarga qaraydi.
  • Naive Bayes: faqat sanaydi. Ehtimolliklarni hisoblab qo'yadi, keyin Bayes teoremasini qo'llaydi.

Ikkisi ham eski va oddiy. Lekin ikkisi ham hali ham ishlatiladi — aniq joylarda.

KNN: "dangasa" o'rganuvchi

G'oya bir jumlada: yangi kvartiraga eng o'xshash K ta kvartirani topamiz va ularning javobiga qaraymiz.

python28 qator
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
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)

quvur = Pipeline([("sc", StandardScaler()), ("m", KNeighborsClassifier(n_neighbors=5))]).fit(X_tr, y_tr)

# bitta kvartira uchun qo'shnilarini ko'rsatamiz
sc = quvur.named_steps["sc"]
knn = quvur.named_steps["m"]
masofa, indeks = knn.kneighbors(sc.transform(X_te.iloc[[0]]), n_neighbors=5)

print("Tekshirilayotgan kvartira:")
print(X_te.iloc[0].to_string())
print(f"\nHaqiqiy javob: {y_te.iloc[0]}")
print("\nEng yaqin 5 qo'shni:")
qoshnilar = X_tr.iloc[indeks[0]].copy()
qoshnilar["javob"] = y_tr.iloc[indeks[0]].values
qoshnilar["masofa"] = masofa[0].round(3)
show(qoshnilar)
print(f"Qo'shnilar ovozi: {y_tr.iloc[indeks[0]].mean():.1%} 'ha' -> bashorat {quvur.predict(X_te.iloc[[0]])[0]}")
Chiqish
Tekshirilayotgan kvartira:
xonalar           4.00
maydon_m2        79.20
qavat             9.00
qavatlar_soni    10.00
metro_km          2.01
mebel             0.00

Haqiqiy javob: 1

Eng yaqin 5 qo'shni:
Qo'shnilar ovozi: 20.0% 'ha' -> bashorat 0
xonalarmaydon_m2qavatqavatlar_sonimetro_kmmebeljavobmasofa
471.78101.27010.65
489.78112.44000.682
483.29122.94000.765
489.67102.44000.798
490.47102.5000.837

Model hech qanday qoida o'rganmadi. U butun train to'plamini eslab qoldi va bashorat paytida izlaydi.

Shuning uchun KNN "lazy learner" deb ataladi: o'qitish bir zumda, bashorat sekin.

python10 qator
import time
from sklearn.linear_model import LogisticRegression

print(f"{'model':<24} {'fit vaqti':>12} {'predict vaqti':>15}")
print("-" * 54)
for nom, m in [("KNN", Pipeline([("sc", StandardScaler()), ("m", KNeighborsClassifier())])),
               ("LogisticRegression", Pipeline([("sc", StandardScaler()), ("m", LogisticRegression(max_iter=2000))]))]:
    t0 = time.perf_counter(); m.fit(X_tr, y_tr); ft = (time.perf_counter()-t0)*1000
    t0 = time.perf_counter(); m.predict(X_te); pt = (time.perf_counter()-t0)*1000
    print(f"{nom:<24} {ft:>10.2f} ms {pt:>13.2f} ms")
Chiqish
model                       fit vaqti   predict vaqti
------------------------------------------------------
KNN                            1.35 ms          5.19 ms
LogisticRegression             2.86 ms          0.50 ms

KNN ishlab chiqarishda qimmat

Model butun train to'plamini o'zi bilan olib yuradi. 10 million qatorli ma'lumot bo'lsa, server xotirasida 10 million qator turishi kerak.

Va har bir so'rov uchun masofa hisoblanadi. Shuning uchun KNN real vaqtli tizimlarda kamdan-kam ishlatiladi.

Scaling KNN uchun hayotiy

4-bobda aytganmiz, endi o'lchaymiz:

python8 qator
print(f"{'holat':<22} {'CV F1':>9} {'±':>8}")
print("-" * 40)
b1 = cross_val_score(KNeighborsClassifier(), X, y, cv=5, scoring="f1")
b2 = cross_val_score(Pipeline([("sc", StandardScaler()), ("m", KNeighborsClassifier())]),
                     X, y, cv=5, scoring="f1")
print(f"{'scaling YO`Q':<22} {b1.mean():>9.4f} {b1.std():>8.4f}")
print(f"{'scaling BOR':<22} {b2.mean():>9.4f} {b2.std():>8.4f}")
print(f"{'farq':<22} {b2.mean()-b1.mean():>+9.4f}")
Chiqish
holat                      CV F1        ±
----------------------------------------
scaling YO`Q              0.6627   0.0113
scaling BOR               0.6903   0.0141
farq                     +0.0276

Sabab: masofa hisoblanganda maydon_m2 (22–220) mebel (0–1) ni butunlay bosib ketadi. Scaling busiz KNN amalda faqat bitta ustunga qarab ishlaydi.

K ni tanlash — bu bias-variance tutqichi

python12 qator
from sklearn.metrics import f1_score
print(f"{'K':>5} {'train F1':>10} {'test F1':>9} {'CV F1':>9} {'izoh'}")
print("-" * 52)
for k in [1, 3, 5, 15, 50, 200]:
    q = Pipeline([("sc", StandardScaler()), ("m", KNeighborsClassifier(n_neighbors=k))]).fit(X_tr, y_tr)
    tr_f = f1_score(y_tr, q.predict(X_tr))
    te_f = f1_score(y_te, q.predict(X_te))
    cv = cross_val_score(Pipeline([("sc", StandardScaler()),
                                   ("m", KNeighborsClassifier(n_neighbors=k))]),
                         X, y, cv=5, scoring="f1").mean()
    izoh = "K=1: har nuqta o'zini topadi" if k == 1 else ("juda silliq" if k >= 200 else "")
    print(f"{k:>5} {tr_f:>10.4f} {te_f:>9.4f} {cv:>9.4f} {izoh}")
Chiqish
    K   train F1   test F1     CV F1 izoh
----------------------------------------------------
    1     1.0000    0.6398    0.6243 K=1: har nuqta o'zini topadi
    3     0.7789    0.6873    0.6547 
    5     0.7523    0.7385    0.6903 
   15     0.7148    0.7554    0.7064 
   50     0.6903    0.7769    0.7191 
  200     0.6990    0.7917    0.7272 juda silliq

K=1 da train F1 1.0000 — chunki har nuqtaning eng yaqin qo'shnisi o'zi. Bu toza overfitting.

K oshgani sari model silliqlashadi: kam variance, ko'p bias.

python7 qator
show(pd.DataFrame([
    {"K": "kichik (1-3)", "Model": "Juda moslashuvchan", "Xavf": "Overfitting, shovqinga sezgir"},
    {"K": "o'rta (5-20)", "Model": "Muvozanat", "Xavf": "Odatda eng yaxshi"},
    {"K": "katta (100+)", "Model": "Juda silliq", "Xavf": "Underfitting — hamma joyda bir xil javob"},
]))
print()
print("Qoida: K ni JUFT bo'lmagan son qiling (binary classification da teng ovoz bo'lmasligi uchun).")
Chiqish
Qoida: K ni JUFT bo'lmagan son qiling (binary classification da teng ovoz bo'lmasligi uchun).
KModelXavf
kichik (1-3)Juda moslashuvchanOverfitting, shovqinga sezgir
o'rta (5-20)MuvozanatOdatda eng yaxshi
katta (100+)Juda silliqUnderfitting — hamma joyda bir xil javob

Masofa metrikasi

python13 qator
print(f"{'metrika':<16} {'CV F1':>9}  izoh")
print("-" * 58)
for nom, p in [("euclidean", 2), ("manhattan", 1)]:
    b = cross_val_score(Pipeline([("sc", StandardScaler()),
                                  ("m", KNeighborsClassifier(n_neighbors=15, p=p))]),
                        X, y, cv=5, scoring="f1")
    izoh = "sqrt(sum(d^2)) — sukut" if p == 2 else "sum(|d|) — chetki qiymatga chidamli"
    print(f"{nom:<16} {b.mean():>9.4f}  {izoh}")
print()
b_w = cross_val_score(Pipeline([("sc", StandardScaler()),
                                ("m", KNeighborsClassifier(n_neighbors=15, weights="distance"))]),
                      X, y, cv=5, scoring="f1")
print(f"{'weights=distance':<16} {b_w.mean():>9.4f}  yaqin qo'shni ko'proq ovozga ega")
Chiqish
metrika              CV F1  izoh
----------------------------------------------------------
euclidean           0.7064  sqrt(sum(d^2)) — sukut
manhattan           0.7178  sum(|d|) — chetki qiymatga chidamli

weights=distance    0.7017  yaqin qo'shni ko'proq ovozga ega

O'lchov la'nati (curse of dimensionality)

KNN ning eng jiddiy cheklovi: ustunlar ko'payganda masofa ma'nosini yo'qotadi.

python8 qator
r = np.random.default_rng(42)
print("Tasodifiy nuqtalar orasidagi masofa — o'lchov oshganda:")
print(f"{'o`lchov':>8} {'eng yaqin':>11} {'eng uzoq':>10} {'nisbat':>8}")
print("-" * 42)
for m in [2, 10, 50, 200, 1000]:
    P = r.normal(size=(500, m))
    d = np.linalg.norm(P[1:] - P[0], axis=1)
    print(f"{m:>8} {d.min():>11.3f} {d.max():>10.3f} {d.max()/d.min():>8.2f}")
Chiqish
Tasodifiy nuqtalar orasidagi masofa — o'lchov oshganda:
 o`lchov   eng yaqin   eng uzoq   nisbat
------------------------------------------
       2       0.041      4.301   104.36
      10       1.525      6.079     3.99
      50       7.867     13.041     1.66
     200      16.601     21.685     1.31
    1000      42.854     47.704     1.11

Nisbatga qarang. 2 o'lchovda eng uzoq nuqta eng yaqinidan ancha uzoq. 1000 o'lchovda esa ular deyarli teng masofada bo'ladi.

Ya'ni "eng yaqin qo'shni" tushunchasi ma'nosini yo'qotadi — hamma bir xil uzoqlikda.

python14 qator
print("KNN ga shovqin ustunlari qo'shsak:")
print(f"{'qo`shilgan shovqin':>20} {'CV F1':>9}")
print("-" * 32)
for n_sh in [0, 10, 50, 200]:
    if n_sh == 0:
        Xn = X
    else:
        sh = pd.DataFrame(r.normal(size=(len(X), n_sh)),
                          columns=[f"sh_{i}" for i in range(n_sh)], index=X.index)
        Xn = pd.concat([X, sh], axis=1)
    b = cross_val_score(Pipeline([("sc", StandardScaler()),
                                  ("m", KNeighborsClassifier(n_neighbors=15))]),
                        Xn, y, cv=5, scoring="f1")
    print(f"{n_sh:>20} {b.mean():>9.4f}")
Chiqish
KNN ga shovqin ustunlari qo'shsak:
  qo`shilgan shovqin     CV F1
--------------------------------
                   0    0.7064
                  10    0.6953
                  50    0.6417
                 200    0.6410

16-bobda Lasso shovqinga chidamli edi. KNN esa chidamsiz — chunki shovqin ustunlari masofani buzadi.

Naive Bayes: faqat sanash

Ikkinchi model butunlay boshqa g'oyadan chiqadi — Bayes teoremasi:

"Naive" (sodda) so'zi shundan: model xususiyatlarni bir-biridan mustaqil deb hisoblaydi. Bu deyarli har doim noto'g'ri — lekin model baribir ishlaydi.

python13 qator
from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB

print(f"{'model':<24} {'CV F1':>9} {'fit vaqti':>12}")
print("-" * 48)
for nom, m in [("GaussianNB", GaussianNB()),
               ("KNN (K=15)", Pipeline([("sc", StandardScaler()),
                                        ("m", KNeighborsClassifier(n_neighbors=15))])),
               ("LogisticRegression", Pipeline([("sc", StandardScaler()),
                                                ("m", LogisticRegression(max_iter=2000))]))]:
    t0 = time.perf_counter()
    b = cross_val_score(m, X, y, cv=5, scoring="f1")
    dt = (time.perf_counter()-t0)*1000
    print(f"{nom:<24} {b.mean():>9.4f} {dt:>10.1f} ms")
Chiqish
model                        CV F1    fit vaqti
------------------------------------------------
GaussianNB                  0.7291       12.2 ms
KNN (K=15)                  0.7064       34.9 ms
LogisticRegression          0.7210       19.8 ms
python8 qator
show(pd.DataFrame([
    {"Variant": "GaussianNB", "Qanday ma'lumot": "Uzluksiz sonlar",
     "Taxmini": "Har xususiyat normal taqsimlangan", "Misol": "Bo'y, vazn, narx"},
    {"Variant": "MultinomialNB", "Qanday ma'lumot": "Sanoq (count)",
     "Taxmini": "So'z chastotalari", "Misol": "SPAM FILTRI — matn tasnifi"},
    {"Variant": "BernoulliNB", "Qanday ma'lumot": "Binary (0/1)",
     "Taxmini": "So'z bor/yo'q", "Misol": "Qisqa matn"},
]))
VariantQanday ma'lumotTaxminiMisol
GaussianNBUzluksiz sonlarHar xususiyat normal taqsimlanganBo'y, vazn, narx
MultinomialNBSanoq (count)So'z chastotalariSPAM FILTRI — matn tasnifi
BernoulliNBBinary (0/1)So'z bor/yo'qQisqa matn

Naive Bayes ning haqiqiy uyi: matn

Son ustunlarda Naive Bayes o'rtacha ishlaydi. Lekin matn tasnifida u hali ham kuchli.

python23 qator
from sklearn.feature_extraction.text import CountVectorizer

xatlar = [
    "Tabriklaymiz siz yutdingiz pul oling hoziroq",
    "Bepul kredit tez pul oling ariza",
    "Ertaga yigilish soat 10 da bo`ladi",
    "Hisobot tayyor tekshirib bering iltimos",
    "Yutuq pul mukofot bepul oling",
    "Loyiha hujjatlari yuborildi korib chiqing",
    "Bepul bonus hoziroq oling pul",
    "Dars jadvali ozgardi xabar beraman",
]
yorliq = [1, 1, 0, 0, 1, 0, 1, 0]        # 1 = spam

vec = CountVectorizer()
Xt = vec.fit_transform(xatlar)
nb = MultinomialNB().fit(Xt, yorliq)

yangi = ["bepul pul oling hoziroq", "ertaga hisobot tayyor boladi"]
e = nb.predict_proba(vec.transform(yangi))
for matn, p in zip(yangi, e):
    print(f'  "{matn}"')
    print(f"     spam ehtimoli: {p[1]:.4f}  ->  {'SPAM' if p[1] > 0.5 else 'oddiy xat'}")
Chiqish
  "bepul pul oling hoziroq"
     spam ehtimoli: 0.9967  ->  SPAM
  "ertaga hisobot tayyor boladi"
     spam ehtimoli: 0.1111  ->  oddiy xat

Atigi 8 ta misol bilan model ishladi. Bu — Naive Bayes ning asosiy afzalligi: juda kam ma'lumot bilan ham ishga tushadi.

Nol chastota muammosi va smoothing

Agar yangi xatda train da hech qachon uchramagan so'z bo'lsa, uning ehtimoli 0 bo'ladi. Ko'paytmada bitta nol — butun natija nol.

Yechim: Laplace smoothing — har bir sanoqqa kichik son qo'shiladi. sklearn da bu alpha=1.0 sukut bo'yicha yoqilgan.

alpha=0 qo'ysangiz, model birinchi notanish so'zda yiqiladi.

python6 qator
print("Smoothing ning ta'siri:")
notanish = ["mutlaqo notanish sozlar bilan yozilgan xat"]
for a in [1.0, 1e-10]:
    nb_a = MultinomialNB(alpha=a).fit(Xt, yorliq)
    p = nb_a.predict_proba(vec.transform(notanish))[0]
    print(f"  alpha={a:<8} spam ehtimoli = {p[1]:.6f}")
Chiqish
Smoothing ning ta'siri:
  alpha=1.0      spam ehtimoli = 0.500000
  alpha=1e-10    spam ehtimoli = 0.500000

Qachon qaysi birini tanlaysiz

python11 qator
show(pd.DataFrame([
    {"Model": "KNN", "Qachon yaxshi": "Kichik ma'lumot, kam ustun, mahalliy naqsh",
     "Qachon yomon": "Ko'p ustun, katta ma'lumot, real vaqt",
     "Scaling": "SHART"},
    {"Model": "GaussianNB", "Qachon yaxshi": "Juda tez baza kerak bo'lsa",
     "Qachon yomon": "Xususiyatlar kuchli bog'liq bo'lsa",
     "Scaling": "Kerak emas"},
    {"Model": "MultinomialNB", "Qachon yaxshi": "MATN tasnifi, spam, kam ma'lumot",
     "Qachon yomon": "Uzluksiz sonlar",
     "Scaling": "Kerak emas"},
]))
ModelQachon yaxshiQachon yomonScaling
KNNKichik ma'lumot, kam ustun, mahalliy naqshKo'p ustun, katta ma'lumot, real vaqtSHART
GaussianNBJuda tez baza kerak bo'lsaXususiyatlar kuchli bog'liq bo'lsaKerak emas
MultinomialNBMATN tasnifi, spam, kam ma'lumotUzluksiz sonlarKerak emas

Naive Bayes ni baza sifatida ishlating

6-bobda DummyClassifier ni baza qilgan edik. Matn tasnifida esa MultinomialNB yaxshi ikkinchi baza: u bir zumda o'qitiladi va ko'pincha hayratlanarli natija beradi.

Agar sizning murakkab transformer modelingiz Naive Bayes dan oshmasa — muammo modelda emas, ma'lumotda.

Bugun nima o'rgandik

python10 qator
show(pd.DataFrame([
    {"Tushuncha": "KNN g'oyasi", "Bitta jumla": "Hech narsa o'rganmaydi — qo'shnilarga qaraydi"},
    {"Tushuncha": "KNN narxi", "Bitta jumla": "fit tez, predict sekin; butun train ni saqlaydi"},
    {"Tushuncha": "KNN va scaling", "Bitta jumla": "Busiz katta shkalali ustun hammasini bosadi"},
    {"Tushuncha": "K ning roli", "Bitta jumla": "Kichik K overfitting, katta K underfitting"},
    {"Tushuncha": "O'lchov la'nati", "Bitta jumla": "Ustun ko'paysa hamma nuqta teng masofada bo'ladi"},
    {"Tushuncha": "Naive Bayes", "Bitta jumla": "Bayes teoremasi + xususiyatlar mustaqil degan soxta taxmin"},
    {"Tushuncha": "NB ning uyi", "Bitta jumla": "Matn tasnifi — MultinomialNB"},
    {"Tushuncha": "Smoothing", "Bitta jumla": "alpha=1 nol chastota muammosini yechadi"},
]))
TushunchaBitta jumla
KNN g'oyasiHech narsa o'rganmaydi — qo'shnilarga qaraydi
KNN narxifit tez, predict sekin; butun train ni saqlaydi
KNN va scalingBusiz katta shkalali ustun hammasini bosadi
K ning roliKichik K overfitting, katta K underfitting
O'lchov la'natiUstun ko'paysa hamma nuqta teng masofada bo'ladi
Naive BayesBayes teoremasi + xususiyatlar mustaqil degan soxta taxmin
NB ning uyiMatn tasnifi — MultinomialNB
Smoothingalpha=1 nol chastota muammosini yechadi

Keyingi bob — 4-qismning oxirgisi: eng keng ko'chani izlaydigan model.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar