Asosiy qismga o'tish

Confusion matrix, precision, recall, F1

8-bob 12 kod bloki 2686 so'z

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

Savol: 0.78 nimani yashiradi

0-bobda biz model qurdik va accuracy = 0.7778 oldik. Bazadan 0.27 yuqori — demak model nimanidir o'rgandi.

Lekin agentlikdagi menejer sizdan so'raydi:

"Model 'tez ijaraga beriladi' degan kvartiralarning qanchasi haqiqatan tez ketdi? Chunki men o'sha e'lonlarga reklama puli sarflayman."

accuracy = 0.7778 bu savolga javob bermaydi.

Chunki accuracy — bitta son, va u ikki xil xatoni bir xaltaga soladi:

  • Model "tez ketadi" dedi, lekin kvartira uch oy bo'sh turdi → reklama puli yondi
  • Model "sekin ketadi" dedi, lekin kvartira bir haftada ketdi → imkoniyat qo'ldan ketdi

Birinchi xato pul yo'qotadi. Ikkinchisi foydani qo'ldan chiqaradi. Ular bir xil narsa emas — va accuracy ularni ajratmaydi.

Bu bob ularni ajratadigan asboblarni beradi.

Protokoldagi joyi

Bu — 0-bobdagi protokolning 10-qadami: "metrikani tanlayman". Qadam 9 da model qurdingiz, endi unga qanday baho berishni hal qilasiz.

Modelni qaytadan quramiz

Bob mustaqil bo'lishi kerak — shuning uchun 0-bobdagi modelni shu yerda qaytadan quramiz.

python29 qator
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression

df = pd.read_csv(DATA / "uy_ijara.csv")

son = ["xonalar", "maydon_m2", "qavat", "qavatlar_soni", "metro_km", "mebel", "qurilgan_yil"]
matn = ["tuman"]
X = df[son + matn]
y = 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([
    ("tayyorla", ColumnTransformer([
        ("son", Pipeline([("toldir", SimpleImputer(strategy="median")),
                          ("shkala", StandardScaler())]), son),
        ("matn", OneHotEncoder(handle_unknown="ignore"), matn),
    ])),
    ("model", LogisticRegression(max_iter=2000)),
]).fit(X_tr, y_tr)

bashorat = quvur.predict(X_te)
print(f"test to'plami: {len(y_te)} kvartira")
print(f"accuracy     : {(bashorat == y_te).mean():.4f}")
Chiqish
test to'plami: 360 kvartira
accuracy     : 0.7778

Confusion matrix — to'rtta katak

Accuracy bitta son beradi. Confusion matrix to'rtta son beradi — va shu to'rttasida butun hikoya bor.

python8 qator
from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_te, bashorat)
print("Confusion Matrix:")
print(cm)
print()
tn, fp, fn, tp = cm.ravel()
print(f"TN={tn}  FP={fp}  FN={fn}  TP={tp}")
Chiqish
Confusion Matrix:
[[126  52]
 [ 28 154]]

TN=126  FP=52  FN=28  TP=154

sklearn har doim shu tartibda beradi: yuqori chap TN, yuqori o'ng FP, pastki chap FN, pastki o'ng TP. Buni yodlab oling — chalkashtirsangiz hamma hisob buziladi.

Endi shu to'rtta sonni bizning biznesimiz tilida o'qiymiz:

python10 qator
show(pd.DataFrame([
    {"Belgi": "TN", "Soni": tn, "Model nima dedi": "sekin ketadi", "Haqiqatda": "sekin ketdi",
     "Biznes uchun": "To'g'ri. Reklama sarflanmadi, zarar yo'q"},
    {"Belgi": "FP", "Soni": fp, "Model nima dedi": "TEZ ketadi", "Haqiqatda": "sekin ketdi",
     "Biznes uchun": "Reklama puli YONDI — bo'sh e'longa sarflandi"},
    {"Belgi": "FN", "Soni": fn, "Model nima dedi": "sekin ketadi", "Haqiqatda": "TEZ ketdi",
     "Biznes uchun": "Imkoniyat QO'LDAN KETDI — e'tibor bermadik"},
    {"Belgi": "TP", "Soni": tp, "Model nima dedi": "TEZ ketadi", "Haqiqatda": "tez ketdi",
     "Biznes uchun": "To'g'ri. Reklama o'z joyiga tushdi"},
]))
BelgiSoniModel nima dediHaqiqatdaBiznes uchun
TN126sekin ketadisekin ketdiTo'g'ri. Reklama sarflanmadi, zarar yo'q
FP52TEZ ketadisekin ketdiReklama puli YONDI — bo'sh e'longa sarflandi
FN28sekin ketadiTEZ ketdiImkoniyat QO'LDAN KETDI — e'tibor bermadik
TP154TEZ ketaditez ketdiTo'g'ri. Reklama o'z joyiga tushdi

Mana endi menejerning savoliga javob berish mumkin.

Model 206 ta kvartirani "tez ketadi" dedi (154 + 52). Shundan 154 tasi haqiqatan tez ketdi. Qolgan 52 tasiga reklama puli behuda ketdi.

Bu nisbatning nomi bor: precision.

Precision va recall

Ikkisi ham matritsadan to'g'ridan-to'g'ri hisoblanadi. Formulalarni yodlashdan oldin ularning savolini yodlang.

python8 qator
show(pd.DataFrame([
    {"Metrika": "Precision", "Formula": "TP / (TP + FP)",
     "Savoli": "Model 'ha' deganda, qanchasida to'g'ri chiqdi?",
     "Kim so'raydi": "Pul sarflaydigan odam"},
    {"Metrika": "Recall", "Formula": "TP / (TP + FN)",
     "Savoli": "Bor 'ha' larning qanchasini topdi?",
     "Kim so'raydi": "Imkoniyatni qo'ldan chiqarmaslik kerak bo'lgan odam"},
]))
MetrikaFormulaSavoliKim so'raydi
PrecisionTP / (TP + FP)Model 'ha' deganda, qanchasida to'g'ri chiqdi?Pul sarflaydigan odam
RecallTP / (TP + FN)Bor 'ha' larning qanchasini topdi?Imkoniyatni qo'ldan chiqarmaslik kerak bo'lgan odam

Endi hisoblaymiz — va raqamni matritsadan chiqarib ko'rsatamiz:

python10 qator
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score

p = precision_score(y_te, bashorat)
r = recall_score(y_te, bashorat)
f1 = f1_score(y_te, bashorat)

print(f"Accuracy : {accuracy_score(y_te, bashorat):.4f}  = (TP+TN)/jami = ({tp}+{tn})/{len(y_te)}")
print(f"Precision: {p:.4f}  = TP/(TP+FP) = {tp}/({tp}+{fp})")
print(f"Recall   : {r:.4f}  = TP/(TP+FN) = {tp}/({tp}+{fn})")
print(f"F1       : {f1:.4f}")
Chiqish
Accuracy : 0.7778  = (TP+TN)/jami = (154+126)/360
Precision: 0.7476  = TP/(TP+FP) = 154/(154+52)
Recall   : 0.8462  = TP/(TP+FN) = 154/(154+28)
F1       : 0.7938

Diqqat qiling: recall (0.8462) precision (0.7476) dan yuqori.

Bu bizning modelimiz haqida nimani aytadi? U saxiy — ko'proq kvartiraga "tez ketadi" deyishga moyil. Shuning uchun u haqiqiy imkoniyatlarning ko'pini topadi (yuqori recall), lekin ayni paytda ko'p bo'sh e'longa ham "ha" deydi (pastroq precision).

Agar menejer reklama budjetini tejashni xohlasa — bu model unga mos emas. 8-bobda threshold ni o'zgartirib, modelni qattiqroq qilishni ko'ramiz.

F1 — nega o'rtacha emas, "garmonik o'rtacha"

Precision va recall ikkita son. Bitta son kerak bo'lsa nima qilamiz? Qo'shib ikkiga bo'lamizmi?

Yo'q. Va nega yo'qligini ko'rsataman.

Tasavvur qiling: dangasa model faqat bitta kvartiraga "tez ketadi" dedi va o'sha bittasida to'g'ri chiqdi. Precision = 1.0 (mukammal!). Lekin qolgan 180 ta imkoniyatni o'tkazib yubordi, recall = 0.01.

python12 qator
hollar = [
    ("Muvozanatli model", 0.75, 0.80),
    ("Dangasa model (faqat 1 ta 'ha')", 1.00, 0.01),
    ("Saxiy model (hammaga 'ha')", 0.51, 1.00),
]
qatorlar = []
for nom, pr, rc in hollar:
    oddiy = (pr + rc) / 2
    garmonik = 2 * pr * rc / (pr + rc)
    qatorlar.append({"Model": nom, "Precision": pr, "Recall": rc,
                     "Oddiy o'rtacha": round(oddiy, 3), "F1 (garmonik)": round(garmonik, 3)})
show(pd.DataFrame(qatorlar))
ModelPrecisionRecallOddiy o'rtachaF1 (garmonik)
Muvozanatli model0.750.80.7750.774
Dangasa model (faqat 1 ta 'ha')10.010.5050.02
Saxiy model (hammaga 'ha')0.5110.7550.675

Qarang. Dangasa model uchun oddiy o'rtacha 0.505 — "o'rtacha model" bo'lib ko'rinadi. Lekin u 180 imkoniyatdan 179 tasini o'tkazib yuborgan. U yaroqsiz.

F1 esa 0.02 beradi. Haqiqatni aytadi.

Sabab: garmonik o'rtacha kichik sonning tomonini oladi. Agar bittasi past bo'lsa, F1 ham past bo'ladi. Shuning uchun F1 ni "ikkisi ham yaxshi bo'lishi shart" degan talab deb o'qing.

Precision 1.0 ni ko'rganda xursand bo'lmang

Precision = 1.0000 — bu ko'pincha model juda kam 'ha' degani bilan bog'liq. Bitta bashorat qilib, bittasida to'g'ri chiqsa — precision mukammal.

Shuning uchun precision ni hech qachon yolg'iz o'qimang. U bilan birga recall ni, yoki model nechta 'ha' deganini ko'ring:

bashorat.sum() — model nechta marta 'ha' dedi.

python5 qator
print(f"Model nechta marta 'tez ketadi' dedi : {bashorat.sum()} ta")
print(f"Test to'plamida haqiqatan tez ketgani: {y_te.sum()} ta")
print()
print("Model saxiymi yoki qattiqmi:",
      "SAXIY" if bashorat.sum() > y_te.sum() else "QATTIQ")
Chiqish
Model nechta marta 'tez ketadi' dedi : 206 ta
Test to'plamida haqiqatan tez ketgani: 182 ta

Model saxiymi yoki qattiqmi: SAXIY

classification_report — hammasi bitta jadvalda

Qo'lda hisoblashni o'rgandik. Amalda sklearn hammasini birdan beradi.

python5 qator
from sklearn.metrics import classification_report

print(classification_report(y_te, bashorat,
                            target_names=["sekin ketdi", "tez ketdi"],
                            digits=4))
Chiqish
              precision    recall  f1-score   support

 sekin ketdi     0.8182    0.7079    0.7590       178
   tez ketdi     0.7476    0.8462    0.7938       182

    accuracy                         0.7778       360
   macro avg     0.7829    0.7770    0.7764       360
weighted avg     0.7825    0.7778    0.7766       360

Bu jadvalni qanday o'qish kerak:

  • Har bir sinf uchun alohida qator. tez ketdi qatori — bizni qiziqtirgan sinf.
  • support — o'sha sinfda nechta haqiqiy namuna bor.
  • macro avg — ikki sinfning oddiy o'rtachasi. Har sinfni teng hisoblaydi.
  • weighted avg — sinf hajmiga qarab o'rtacha. Katta sinf ustun keladi.

`macro` va `weighted` farqi — nomutanosib ma'lumotda hayotiy

Bizda sinflar deyarli teng (50.7% / 49.3%), shuning uchun macro va weighted bir-biriga yaqin.

Lekin agar sinflar 95% / 5% bo'lsa, weighted avg katta sinfning yaxshi natijasini ko'rsatadi va kichik sinfning yomon natijasini yashiradi. macro avg esa yashirmaydi.

Qoida: nomutanosib ma'lumotda macro ni o'qing. U sizga yoqmaydigan raqamni ko'rsatadi — lekin u to'g'ri raqam. 8-bob to'liq shu haqda.

Qaysi metrikani tanlaysiz

Bu — muhandislik va biznes qarori, matematik emas. Jadval:

python14 qator
show(pd.DataFrame([
    {"Vaziyat": "Reklama budjeti cheklangan", "Ustun qo'yiladi": "Precision",
     "Nega": "Bo'sh e'longa pul sarflanmasin (FP qimmat)"},
    {"Vaziyat": "Har bir imkoniyat qimmat", "Ustun qo'yiladi": "Recall",
     "Nega": "Tez ketadigan kvartirani o'tkazib yubormaylik (FN qimmat)"},
    {"Vaziyat": "Kasallikni aniqlash", "Ustun qo'yiladi": "Recall",
     "Nega": "Kasalni sog'lom deb yuborish — eng xavfli xato"},
    {"Vaziyat": "Spam filtri", "Ustun qo'yiladi": "Precision",
     "Nega": "Muhim xatni spam ga tashlash — eng yomon xato"},
    {"Vaziyat": "Ikkisi ham muhim", "Ustun qo'yiladi": "F1",
     "Nega": "Muvozanat; bittasi past bo'lsa F1 ham past"},
    {"Vaziyat": "Sinflar juda nomutanosib", "Ustun qo'yiladi": "macro F1 / PR-AUC",
     "Nega": "Accuracy ham, weighted ham aldaydi (8-bob)"},
]))
VaziyatUstun qo'yiladiNega
Reklama budjeti cheklanganPrecisionBo'sh e'longa pul sarflanmasin (FP qimmat)
Har bir imkoniyat qimmatRecallTez ketadigan kvartirani o'tkazib yubormaylik (FN qimmat)
Kasallikni aniqlashRecallKasalni sog'lom deb yuborish — eng xavfli xato
Spam filtriPrecisionMuhim xatni spam ga tashlash — eng yomon xato
Ikkisi ham muhimF1Muvozanat; bittasi past bo'lsa F1 ham past
Sinflar juda nomutanosibmacro F1 / PR-AUCAccuracy ham, weighted ham aldaydi (8-bob)

Darsda beriladigan savol

Talabalardan so'rang: "Bizning agentlikda FP qimmatmi, FN qimmatmi?"

To'g'ri javob yo'q — bu kompaniyaning strategiyasiga bog'liq. Agar reklama arzon va kvartira ko'p bo'lsa, FN qimmat (recall muhim). Agar reklama qimmat bo'lsa, FP qimmat (precision muhim).

Muhim nuqta: bu savolga model javob bermaydi. Siz javob berasiz. Keyin metrikani tanlaysiz.

Accuracy qachon aldaydi — qisqa ogohlantirish

Bizda sinflar teng, shuning uchun accuracy ancha halol ishladi. Lekin nomutanosib holatni sun'iy yasab ko'rsataman — bu 8-bobga kirish.

python17 qator
import numpy as np

# Nomutanosib holat yasaymiz: "tez ketdi" ulushini ~5% ga tushiramiz
np.random.seed(42)
tez = y_te[y_te == 1].index
sekin = y_te[y_te == 0].index
tanlangan = list(sekin) + list(np.random.choice(tez, size=int(len(sekin) * 0.05), replace=False))

y_nom = y_te.loc[tanlangan]
print(f"Yangi to'plam: {len(y_nom)} qator, 'tez ketdi' ulushi {y_nom.mean():.1%}")
print()

dangasa = np.zeros(len(y_nom), dtype=int)   # hammaga "sekin ketadi" deydi
print(f"DANGASA model (hammaga 'sekin'):")
print(f"  Accuracy : {accuracy_score(y_nom, dangasa):.4f}   <-- chiroyli!")
print(f"  Recall   : {recall_score(y_nom, dangasa, zero_division=0):.4f}   <-- BITTA ham topmadi")
print(f"  F1       : {f1_score(y_nom, dangasa, zero_division=0):.4f}")
Chiqish
Yangi to'plam: 186 qator, 'tez ketdi' ulushi 4.3%

DANGASA model (hammaga 'sekin'):
  Accuracy : 0.9570   <-- chiroyli!
  Recall   : 0.0000   <-- BITTA ham topmadi
  F1       : 0.0000

Accuracy 0.9570. Hisobotga yozsa, chiroyli ko'rinadi.

Va bu model bitta ham tez ketadigan kvartirani topmadi. U butunlay foydasiz.

Agar siz faqat accuracy ni ko'rsatsangiz — bu yolg'on hisobot bo'ladi.

Bugun nima o'rgandik

python9 qator
show(pd.DataFrame([
    {"Tushuncha": "Confusion matrix", "Bitta jumla bilan": "To'rtta katak: TN, FP, FN, TP — butun hikoya shunda"},
    {"Tushuncha": "FP va FN", "Bitta jumla bilan": "Ikki xil xato, ikki xil narx — accuracy ularni ajratmaydi"},
    {"Tushuncha": "Precision", "Bitta jumla bilan": "Model 'ha' deganda qanchasida to'g'ri — TP/(TP+FP)"},
    {"Tushuncha": "Recall", "Bitta jumla bilan": "Bor 'ha' larning qanchasini topdi — TP/(TP+FN)"},
    {"Tushuncha": "F1", "Bitta jumla bilan": "Garmonik o'rtacha; bittasi past bo'lsa F1 ham past"},
    {"Tushuncha": "macro vs weighted", "Bitta jumla bilan": "Nomutanosibda macro ni o'qi — weighted yashiradi"},
    {"Tushuncha": "Metrika tanlash", "Bitta jumla bilan": "Biznes qarori: FP qimmatmi yoki FN?"},
]))
TushunchaBitta jumla bilan
Confusion matrixTo'rtta katak: TN, FP, FN, TP — butun hikoya shunda
FP va FNIkki xil xato, ikki xil narx — accuracy ularni ajratmaydi
PrecisionModel 'ha' deganda qanchasida to'g'ri — TP/(TP+FP)
RecallBor 'ha' larning qanchasini topdi — TP/(TP+FN)
F1Garmonik o'rtacha; bittasi past bo'lsa F1 ham past
macro vs weightedNomutanosibda macro ni o'qi — weighted yashiradi
Metrika tanlashBiznes qarori: FP qimmatmi yoki FN?

Bizning modelimizning yakuniy pasporti:

python8 qator
show(pd.DataFrame([
    {"Metrika": "Accuracy", "Qiymat": f"{accuracy_score(y_te, bashorat):.4f}", "O'qilishi": "Umumiy to'g'rilik"},
    {"Metrika": "Precision", "Qiymat": f"{p:.4f}", "O'qilishi": f"'Ha' deganlarining {p:.0%} i to'g'ri"},
    {"Metrika": "Recall", "Qiymat": f"{r:.4f}", "O'qilishi": f"Imkoniyatlarning {r:.0%} ini topdi"},
    {"Metrika": "F1", "Qiymat": f"{f1:.4f}", "O'qilishi": "Muvozanat bahosi"},
    {"Metrika": "FP (yongan reklama)", "Qiymat": str(fp), "O'qilishi": "Shuncha bo'sh e'longa pul ketdi"},
    {"Metrika": "FN (qo'ldan ketgan)", "Qiymat": str(fn), "O'qilishi": "Shuncha imkoniyat sezilmadi"},
]))
MetrikaQiymatO'qilishi
Accuracy0.7778Umumiy to'g'rilik
Precision0.7476'Ha' deganlarining 75% i to'g'ri
Recall0.8462Imkoniyatlarning 85% ini topdi
F10.7938Muvozanat bahosi
FP (yongan reklama)52Shuncha bo'sh e'longa pul ketdi
FN (qo'ldan ketgan)28Shuncha imkoniyat sezilmadi

Keyingi bob

Bu bobda modelning bitta chegarasidagi (threshold 0.5) natijasini o'lchadik.

8-bobda shu chegarani o'zgartiramiz — va modelni qayta o'qitmasdan precision bilan recall o'rtasida tanlov qilishni ko'ramiz. Hamda nomutanosib ma'lumotda ROC egri chizig'ining nega aldashini va PR egri chizig'ining nega haqiqatni ko'rsatishini o'rganamiz.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar