aidoc.uz

4-qism · Ma'lumotlar bilan ishlash·22-bob

Jupyter va scikit-learn: birinchi ML modelingiz

Jupyter bilan to'g'ri ishlash, ML ish jarayoni (workflow), train/test bo'linishi, baseline, Pipeline, metrikalar va confusion matrix, cross-validation, GridSearchCV, data leakage, matn tasnifi (TF-IDF) va modelni saqlash.

Shu paytgacha o'rgangan hamma narsa — Python, NumPy, Pandas, vizualizatsiya — shu bobda bitta maqsad uchun birlashadi: ma'lumotdan o'rganadigan model qurish. Biz scikit-learn'dan foydalanamiz — klassik ML'ning standart kutubxonasi. Bu bob ML nazariyasi kursi emas (u keyingi qadamingiz), balki Python muhandisi nuqtai nazaridan ML ish jarayonini to'g'ri qurish: qanday qilib o'zingizni aldamaslik, natijalarni takrorlanuvchan qilish va xatolardan qochish.

▶ Video dars· freeCodeCamp.org· 2:09:22· ingliz tilida
Scikit-learn Crash Course - Machine Learning Library for Python

scikit-learn bo'yicha amaliy kurs: model, pipeline, GridSearchCV, metrikalar. Uzun — bobni o'qib, keyin kerakli qismlarini ko'ring.

YouTube'da ochish ↗

1. Jupyter: tajriba muhiti#

Jupyter Notebook — kod, natija, grafik va matnni bitta hujjatda birlashtiruvchi muhit. Ma'lumotlarni o'rganish va tajribalar uchun ideal.

uv add --dev jupyterlab ipykernel
uv run jupyter lab

VS Code'da .ipynb faylni ochib, yuqori o'ng burchakda loyihaning .venv kernelini tanlang — alohida server kerak emas.

Foydali "sehrli" buyruqlar#

%timeit np.sort(arr)              # kichik kodni ko'p marta o'lchash
%%time                            # butun hujayra vaqti (hujayraning birinchi qatori)
%load_ext autoreload
%autoreload 2                     # .py modullaringizdagi o'zgarishlar avtomatik qayta yuklansin
%matplotlib inline
!uv add scikit-learn              # terminal buyrug'i
df?                               # hujjat; df?? — manba kod

%autoreload 2 — eng muhimi: kodni .py modullarida yozib, notebook'da faqat chaqirsangiz, modulni o'zgartirganingizda kernel'ni qayta ishga tushirish shart bo'lmaydi.

Jupyter'ning yashirin xavfi: yashirin holat#

Hujayralarni istalgan tartibda ishlatish mumkin. Bu qulay, lekin xavfli:

[1] df = load()            ← 1-marta
[5] df = df[df.x > 0]      ← filtrlandi
[3] model.fit(df)          ← qaysi df? 3-hujayra 5-dan KEYIN ishlagan
[5] df = df[df.x > 0]      ← yana bir marta — endi ikki marta filtrlangan!

Notebook ekranda ko'rinadigan holat va xotiradagi haqiqiy holat farq qilishi mumkin. Qoidalar:

  1. Muntazam 'Restart & Run All'

    Notebook boshidan oxirigacha xatosiz ishlamasa — u natija emas, qoralama.
  2. O'zgaruvchini qayta yozmang

    df = df[...] o'rniga df_clean = df[...] — har bosqich o'z nomi bilan.
  3. Takrorlanuvchi kodni modulga chiqaring

    Funksiyalar src/ da, notebook'da — faqat chaqiruv va tahlil. Testlar modulga yoziladi (16-bob).
  4. Seed'larni boshida qotiring

    rng = np.random.default_rng(42), random_state=42.
  5. Notebook'ni Git'ga toza qo'ying

    Chiqishlarni tozalang (nbstripout) — aks holda diff'lar o'qib bo'lmas bo'ladi.

Notebook'lar — tajriba uchun. Production kodi — modullarda, testlar bilan.

2. ML ish jarayoni#

1. Masalani aniqlash
Nimani bashorat qilamiz? Qanday metrika bilan muvaffaqiyatni o'lchaymiz?
2. Ma'lumot va EDA
Yuklash, tushunish, tozalash (20–21-boblar)
3. Bo'lish: train / validation / test
Test qismini oxirigacha QULFLANG
4. Baseline
Eng sodda model — undan yaxshiroq bo'lishimiz kerak
5. Pipeline + model
Preprocessing va model bitta obyektda
6. Cross-validation va sozlash
Faqat train ichida
7. Testda yakuniy baho — bir marta
Va xatolar tahlili

3. scikit-learn'ning yagona API'si#

scikit-learn'dagi har bir "estimator" bir xil interfeysga ega (12-bobda shu API'ni o'zimiz yozgan edik!):

MetodNima qiladiKimda
fit(X, y)Ma'lumotdan o'rganadi, self qaytaradiHammasi
predict(X)BashoratModellar
predict_proba(X)Sinflar ehtimoliKlassifikatorlar
transform(X)Ma'lumotni o'zgartiradiPreprocessorlar (scaler, vectorizer)
fit_transform(X)fit + transformPreprocessorlar
score(X, y)Standart metrikaModellar

X — 2D jadval (n_samples, n_features) (NumPy massiv yoki DataFrame), y — 1D javoblar (n_samples,). O'qitishda topilgan atributlar _ bilan tugaydi: model.coef_, scaler.mean_.

4. Ma'lumot va bo'lish#

O'rnatilgan dataset — ko'krak bezi o'smalarini tasniflash (30 ta sonli xususiyat, 569 namuna):

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
 
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X.shape, y.value_counts().to_dict()      # (569, 30), {1: 357, 0: 212}  — 1: xavfsiz, 0: xavfli
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,
    stratify=y,            # sinflar nisbati ikkala qismda bir xil bo'lsin
    random_state=42,       # takrorlanuvchanlik
)
X_train.shape, X_test.shape              # (455, 30), (114, 30)

5. Baseline — "aqlsiz" model bilan boshlang#

from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score
 
dummy = DummyClassifier(strategy="most_frequent").fit(X_train, y_train)
accuracy_score(y_test, dummy.predict(X_test))       # 0.632

Hech narsa o'rganmagan, doim "xavfsiz" deydigan model — 63% aniqlik. Demak "bizning model 85% aniq" — o'z-o'zidan hech narsa demaydi; baseline bilan solishtirish kerak. Nomutanosib datasetlarda (masalan, 99% oddiy, 1% firibgarlik) baseline 99% beradi — va accuracy umuman yaroqsiz metrikaga aylanadi.

6. Pipeline: preprocessing + model#

Logistik regressiya (LR) xususiyatlar bir xil shkalada bo'lishini yaxshi ko'radi (19-bobdagi z-score). Buni to'g'ri qilishning yagona to'g'ri yo'li — Pipeline:

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
 
pipe = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
pipe.fit(X_train, y_train)        # scaler FAQAT train'ning o'rtacha/std'sini o'rganadi
pred = pipe.predict(X_test)       # test o'sha (train'dagi) parametrlar bilan o'zgartiriladi
 
accuracy_score(y_test, pred)      # 0.982

Scaler'siz xuddi shu LR — 0.965. Pipeline'ning qiymati faqat qulaylikda emas: u data leakagedan himoya qiladi (8-bo'lim).

7. Metrikalar: accuracy yetarli emas#

from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score
 
print(classification_report(y_test, pred, target_names=["malignant", "benign"], digits=3))
              precision    recall  f1-score   support
 
   malignant      0.976     0.976     0.976        42
      benign      0.986     0.986     0.986        72
 
    accuracy                          0.982       114
   macro avg      0.981     0.981     0.981       114
weighted avg      0.982     0.982     0.982       114
from sklearn.metrics import ConfusionMatrixDisplay
 
ConfusionMatrixDisplay.from_predictions(y_test, pred, display_labels=["malignant", "benign"], cmap="Blues")

Confusion matrix: 41 ta xavfli o'sma to'g'ri, 1 tasi xato "xavfsiz" deb topilgan; 71 ta xavfsiz to'g'ri, 1 tasi xato "xavfli" deb topilgan

MetrikaSavolFormula
AccuracyHammasidan nechtasi to'g'ri?(TP+TN) / hammasi
Precision"Ha" deganlarimizning nechtasi haqiqatan "ha"?TP / (TP+FP)
RecallHaqiqiy "ha"larning nechtasini topdik?TP / (TP+FN)
F1Precision va recall muvozanati2·P·R / (P+R)
ROC AUCEhtimollar bo'yicha tartiblash sifati (chegaradan mustaqil)roc_auc_score(y, proba)

Qaysi metrika muhim — masalaga bog'liq: bu misolda xavfli o'smani "xavfsiz" deb o'tkazib yuborish (confusion matrix'dagi 1 ta xato) — eng qimmat xato, shuning uchun "malignant" sinfi uchun recall asosiy metrika. Spam filtrda esa muhim xatni spamga tushirish yomonroq — u yerda precision muhim.

Ehtimollar va chegara#

proba = pipe.predict_proba(X_test)[:, 1]     # "benign" ehtimoli
roc_auc_score(y_test, proba)                 # 0.995
 
# Standart chegara 0.5. Xavfli holatlarni ko'proq ushlash uchun:
pred_cautious = (proba > 0.8).astype(int)    # "xavfsiz" deyish uchun ko'proq ishonch talab qilamiz

8. Data leakage — eng xavfli xato#

Data leakage — modelga o'qitish paytida test haqidagi ma'lumot "sizib" o'tishi. Natija: tajribada ajoyib raqamlar, real dunyoda — muvaffaqiyatsizlik. Eng ishonarli namoyish — sof shovqin ustida:

import numpy as np
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.model_selection import cross_val_score
 
rng = np.random.default_rng(0)
X_noise = rng.normal(size=(200, 5000))     # 5000 ta tasodifiy "xususiyat"
y_noise = rng.integers(0, 2, 200)          # tasodifiy javoblar — o'rganadigan hech narsa YO'Q
 
# ❌ Xususiyatlarni BUTUN ma'lumot bo'yicha tanlab, keyin CV
X_selected = SelectKBest(f_classif, k=20).fit_transform(X_noise, y_noise)
cross_val_score(LogisticRegression(max_iter=1000), X_selected, y_noise, cv=5).mean()   # 0.795 (!)
 
# ✅ Tanlash pipeline ichida — har fold'da faqat train qismida
leak_free = make_pipeline(SelectKBest(f_classif, k=20), LogisticRegression(max_iter=1000))
cross_val_score(leak_free, X_noise, y_noise, cv=5).mean()                               # 0.51

Tasodifiy shovqinda 79.5% "aniqlik" — chunki xususiyatlar test qismlarini ham "ko'rib" tanlangan. To'g'ri usul tanga tashlash darajasini (51%) ko'rsatadi — haqiqat shu.

Leakage turiMisolHimoya
Preprocessing butun datadaScaler/imputer/feature selection testni ham ko'rganHammasi Pipeline ichida
Vaqt bo'yichaKelajak ma'lumoti bilan o'tmishni bashorat qilishVaqt bo'yicha bo'lish (TimeSeriesSplit)
Guruh bo'yichaBir mijozning yozuvlari ham train, ham testdaGroupKFold, mijoz bo'yicha bo'lish (6-bob)
Javobdan kelib chiqqan xususiyat"Qaytarilgan_summa" ustuni "qaytarildimi?" ni bashorat qilishdaHar bir xususiyat bashorat paytida mavjudmi — tekshiring
DublikatlarBir xil matn train va testdaBo'lishdan oldin dublikatlarni olib tashlang

9. Cross-validation va giperparametrlarni sozlash#

Bitta train/validation bo'linishi tasodifga bog'liq. K-fold cross-validation — train'ni K qismga bo'lib, har biri navbat bilan validation bo'ladi:

from sklearn.model_selection import StratifiedKFold, cross_val_score
 
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X_train, y_train, cv=cv, scoring="accuracy")
scores              # [0.967 0.989 0.978 0.989 0.967]
scores.mean(), scores.std()     # (0.978, 0.0098)

Natijani har doim o'rtacha ± tarqoqlik bilan bering: "97.8% ± 1.0%". Ikki model farqi tarqoqlikdan kichik bo'lsa — ular amalda teng.

GridSearchCV#

from sklearn.model_selection import GridSearchCV
 
grid = GridSearchCV(
    make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)),
    param_grid={"logisticregression__C": [0.01, 0.1, 1, 10]},   # <qadam_nomi>__<parametr>
    cv=cv,
    scoring="f1",
)
grid.fit(X_train, y_train)
grid.best_params_          # {'logisticregression__C': 0.1}
grid.best_score_           # 0.986 — CV bo'yicha
grid.score(X_test, y_test) # 0.979 — testda, BIR MARTA

Bu 7-bobdagi itertools.product grid search'ning professional versiyasi: har bir kombinatsiya uchun to'liq cross-validation, eng yaxshisi butun train'da qayta o'qitiladi. Katta grid uchun RandomizedSearchCV yoki Optuna.

Boshqa model sinab ko'rish#

from sklearn.ensemble import RandomForestClassifier
 
rf = RandomForestClassifier(n_estimators=300, random_state=42, n_jobs=-1).fit(X_train, y_train)
accuracy_score(y_test, rf.predict(X_test))       # 0.947
 
sorted(zip(rf.feature_importances_, X.columns), reverse=True)[:3]
# [(0.137, 'worst perimeter'), (0.137, 'worst area'), (0.116, 'worst concave points')]

Murakkabroq model har doim yaxshiroq emas: bu datasetda oddiy, to'g'ri shkalalangan LR random forest'dan yaxshiroq. Avval sodda model, keyin murakkablik — faqat o'lchov ko'rsatsa.

10. Matn tasnifi: TF-IDF + LR#

LLM'lardan oldin matn tasnifi shunday qilingan — va bugun ham kichik, tez, arzon baseline sifatida juda foydali:

from sklearn.feature_extraction.text import TfidfVectorizer
 
texts = [
    "mahsulot juda zo'r, tez yetkazib berishdi", "sifati a'lo, hammaga tavsiya qilaman",
    "narxiga arziydi, juda mamnunman", "ajoyib xizmat, rahmat",
    "zo'r telefon, batareyasi uzoq ishlaydi", "kuryer xushmuomala, hammasi joyida",
    "juda yoqdi, yana buyurtma beraman", "sifatli va chiroyli",
    "umuman yoqmadi, pulimni qaytaring", "yetkazib berish juda kech keldi",
    "sifati yomon, bir haftada buzildi", "rasmdagidan butunlay boshqacha",
    "xizmat yomon, javob berishmadi", "qutisi yirtilgan, mahsulot singan",
    "narxi qimmat, sifati past", "hech kimga tavsiya qilmayman",
]
labels = ["pos"] * 8 + ["neg"] * 8
 
UZ_TOKEN = r"(?u)\b\w[\w']*\w\b|\b\w\b"     # o'zbekcha apostrofli so'zlarni bo'lmaslik uchun
 
clf = make_pipeline(
    TfidfVectorizer(token_pattern=UZ_TOKEN, ngram_range=(1, 2), sublinear_tf=True),
    LogisticRegression(C=10),
)
clf.fit(texts, labels)
clf.predict(["juda zo'r xizmat, rahmat", "sifati yomon va kech keldi"])   # ['pos', 'neg']

16 ta misol — albatta o'yinchoq. Real loyihada minglab belgilangan misollar kerak; ularni yaratishning zamonaviy usuli — LLM bilan belgilash (17-bobdagi parallel tasnif!) va keyin kichik, tez modelni o'qitish ("distillatsiya"). Yoki aksincha: TF-IDF baseline'ni LLM bilan solishtirib, qimmat LLM chaqiruvi o'zini oqlaydimi — o'lchash.

11. Modelni saqlash va yuklash#

import joblib
 
joblib.dump(pipe, "model.joblib")        # butun pipeline: scaler + model
loaded = joblib.load("model.joblib")
loaded.predict(X_test[:3])

12. Amaliyot: to'liq, takrorlanuvchan o'qitish skripti#

"""train.py — modelni o'qitadi, baholaydi va artefaktlarni saqlaydi."""
 
import json
from datetime import datetime, timezone
from pathlib import Path
 
import joblib
import sklearn
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.model_selection import GridSearchCV, StratifiedKFold, train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
 
SEED = 42
OUT = Path("artifacts")
 
 
def main() -> None:
    X, y = load_breast_cancer(return_X_y=True, as_frame=True)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=SEED)
 
    search = GridSearchCV(
        make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)),
        {"logisticregression__C": [0.01, 0.1, 1, 10]},
        cv=StratifiedKFold(5, shuffle=True, random_state=SEED),
        scoring="f1",
    ).fit(X_train, y_train)
 
    model = search.best_estimator_
    proba = model.predict_proba(X_test)[:, 1]
    report = classification_report(y_test, model.predict(X_test), output_dict=True)
 
    OUT.mkdir(exist_ok=True)
    joblib.dump(model, OUT / "model.joblib")
    meta = {
        "created": datetime.now(timezone.utc).isoformat(),
        "sklearn": sklearn.__version__,
        "seed": SEED,
        "best_params": search.best_params_,
        "cv_f1": round(search.best_score_, 4),
        "test_accuracy": round(report["accuracy"], 4),
        "test_roc_auc": round(float(roc_auc_score(y_test, proba)), 4),
        "features": list(X.columns),
    }
    (OUT / "meta.json").write_text(json.dumps(meta, indent=2, ensure_ascii=False), encoding="utf-8")
    print(json.dumps({k: meta[k] for k in ("best_params", "cv_f1", "test_accuracy", "test_roc_auc")}))
 
 
if __name__ == "__main__":
    main()
{"best_params": {"logisticregression__C": 0.1}, "cv_f1": 0.9861, "test_accuracy": 0.9737, "test_roc_auc": 0.9957}

Seed'lar qotirilgan, test bir marta ishlatilgan, model + metama'lumot birga saqlangan, versiyalar yozilgan. Olti oydan keyin "bu model qanday yaratilgan edi?" savoliga meta.json javob beradi.

Nima noto'g'ri ketishi mumkin#

XatoOqibatTuzatish
Testga qarab modelni sozlashOptimistik, yolg'on natijaSozlash — CV bilan train ichida; test — bir marta
Scaler/selector butun datada fitData leakagePipeline
Baseline yo'q"85%" ning ma'nosi noma'lumDummyClassifier bilan boshlang
Nomutanosib datada accuracyChalg'ituvchi bahoPrecision/recall/F1, PR AUC
stratify yo'qKichik sinf test'da kam/yo'qstratify=y
random_state yo'qHar ishga tushirishda boshqa natijaSeed'larni qotiring
Bitta CV natijasiTasodifiy farqni "yutuq" deb o'ylashO'rtacha ± std
Notebook yashirin holatiTakrorlab bo'lmaydigan natijaRestart & Run All
Begona joblib/pickle yuklashKod bajarilishi xavfiFaqat o'zingiznikini
O'zbekcha matn standart tokenizer bilanApostrofli so'zlar buziladitoken_pattern, normalizatsiya
Amaliy topshiriq

1. To'liq ish jarayoni

sklearn.datasets.load_wine (3 sinfli) uchun bu bobdagi butun jarayonni takrorlang: bo'lish (stratify), baseline, pipeline, 5-fold CV (o'rtacha ± std), GridSearchCV, testda yakuniy baho, confusion matrix grafigi. Qaysi sinflar ko'proq chalkashtiriladi?

Maslahatni ko'rsatish

load_wine (3 sinf); DummyClassifier; Pipeline(StandardScaler, LogisticRegression); GridSearchCV C bo'yicha; classification_report; ConfusionMatrixDisplay.

Amaliy topshiriq

2. Leakage'ni o'zingiz toping

20-bobdagi orders.csv dan "reyting ≥ 4 bo'ladimi?" degan tasniflash masalasini tuzing. Avval ataylab xato yo'l bilan qiling: rating NaN'larini butun data mediana bilan to'ldiring, revenue / city_mean_revenue xususiyatini butun datadan hisoblang. Keyin hammasini Pipeline + ColumnTransformer ichiga ko'chiring. Natijalar farqini tushuntiring. (Bu sun'iy datasetda reyting tasodifiy — to'g'ri natija baseline'ga yaqin bo'lishi kerak!)

Maslahatni ko'rsatish

SimpleImputer'ni pipeline ichiga qo'ying; ColumnTransformer bilan sonli va kategoriyali ustunlarni alohida qayta ishlang.

Amaliy topshiriq

3. Chegarani tanlash

Breast cancer modeli uchun precision_recall_curve chizing va "malignant" sinfi uchun recall ≥ 99% bo'ladigan chegarani toping. Bu chegarada precision qancha? Bunday chegarani testda emas, validationda tanlash kerakligini hisobga oling.

Maslahatni ko'rsatish

precision_recall_curve(y_test, proba) → precision, recall, thresholds; recall >= 0.99 bo'lgan eng katta threshold.

Amaliy topshiriq

4. TF-IDF va LLM solishtirmasi

O'zingiz 100 ta o'zbekcha sharh yozing (yoki LLM bilan yarating va qo'lda tekshiring), 70/30 bo'ling. TF-IDF + LR ni o'qiting. Keyin 23-bobni o'qib bo'lgach, xuddi shu test qismini Claude bilan tasniflang va aniqlik, narx, kechikishni jadvalda solishtiring (20-bob amaliyoti).

Yodda saqlang
  • 01Jupyter — tajriba uchun: autoreload, Restart & Run All, kodni modullarga chiqaring.
  • 02Ish jarayoni: masala va metrika → EDA → bo'lish → baseline → pipeline → CV → test bir marta.
  • 03scikit-learn API: fit / predict / predict_proba / transform; X — 2D, y — 1D.
  • 04Har doim baseline (DummyClassifier) bilan solishtiring.
  • 05Pipeline preprocessing'ni modelga bog'laydi va data leakage'dan himoya qiladi.
  • 06Accuracy'dan tashqari: precision, recall, F1, ROC AUC, confusion matrix — masalaga mosini tanlang.
  • 07Cross-validation natijasi — o'rtacha ± std; GridSearchCV — train ichida sozlash.
  • 08Model + metama'lumot + versiyalar birga saqlansin; joblib — faqat ishonchli fayllar.

Mustahkamlash

Test va amaliy masalalar

Avval testni ishlang — har javobdan keyin izoh chiqadi. Keyin masalalarni shu yerning o'zida yechib, brauzerdagi haqiqiy Python bilan avtomatik tekshiring. Natijalaringiz kurs progressi sahifasida jamlanadi.

Test: topshirilmaganMasalalar: 0/3

Bilimni tekshirish

Bob bo'yicha test

12 ta savol · har javobdan keyin to'g'ri yoki xatoligi va izoh ko'rsatiladi · oxirida natija foizda.

Amaliy masalalar

Kodni yozing va «Tekshirish»ni bosing (yoki Ctrl/⌘ + Enter). Kodingiz shu brauzerda saqlanib turadi.

01To'g'ri pipeline

build_model() — StandardScaler + LogisticRegression(max_iter=1000) pipeline'ini qaytarsin. evaluate(X, y, seed=42) — train_test_split(test_size=0.2, stratify=y, random_state=seed), pipeline'ni train'da o'qitib, test accuracy'ni qaytarsin.

02Cross-validation hisoboti

cv_report(model, X, y, k=5) — StratifiedKFold(k, shuffle=True, random_state=0) bilan cross_val_score qilib, (o'rtacha, std) ni 3 xona yaxlitlab qaytarsin.

03Confusion matrix va metrikalar

report(y_true, y_pred) — {"cm": [[TN, FP], [FN, TP]], "precision": ..., "recall": ...} qaytarsin (sklearn'ning confusion_matrix, precision_score, recall_score; cm — Python ro'yxatlari, metrikalar 3 xona).