Confusion matrix, precision, recall, F1
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.
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}")
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.
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}")
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:
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"}, ]))
| Belgi | Soni | Model nima dedi | Haqiqatda | Biznes uchun |
|---|---|---|---|---|
| TN | 126 | sekin ketadi | sekin ketdi | To'g'ri. Reklama sarflanmadi, zarar yo'q |
| FP | 52 | TEZ ketadi | sekin ketdi | Reklama puli YONDI — bo'sh e'longa sarflandi |
| FN | 28 | sekin ketadi | TEZ ketdi | Imkoniyat QO'LDAN KETDI — e'tibor bermadik |
| TP | 154 | TEZ ketadi | tez ketdi | To'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.
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"}, ]))
| Metrika | Formula | Savoli | Kim so'raydi |
|---|---|---|---|
| Precision | TP / (TP + FP) | Model 'ha' deganda, qanchasida to'g'ri chiqdi? | Pul sarflaydigan odam |
| Recall | TP / (TP + FN) | Bor 'ha' larning qanchasini topdi? | Imkoniyatni qo'ldan chiqarmaslik kerak bo'lgan odam |
Endi hisoblaymiz — va raqamni matritsadan chiqarib ko'rsatamiz:
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}")
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.
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))
| Model | Precision | Recall | Oddiy o'rtacha | F1 (garmonik) |
|---|---|---|---|---|
| Muvozanatli model | 0.75 | 0.8 | 0.775 | 0.774 |
| Dangasa model (faqat 1 ta 'ha') | 1 | 0.01 | 0.505 | 0.02 |
| Saxiy model (hammaga 'ha') | 0.51 | 1 | 0.755 | 0.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.
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")
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.
from sklearn.metrics import classification_report print(classification_report(y_te, bashorat, target_names=["sekin ketdi", "tez ketdi"], digits=4))
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 ketdiqatori — 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:
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)"}, ]))
| Vaziyat | Ustun qo'yiladi | Nega |
|---|---|---|
| Reklama budjeti cheklangan | Precision | Bo'sh e'longa pul sarflanmasin (FP qimmat) |
| Har bir imkoniyat qimmat | Recall | Tez ketadigan kvartirani o'tkazib yubormaylik (FN qimmat) |
| Kasallikni aniqlash | Recall | Kasalni sog'lom deb yuborish — eng xavfli xato |
| Spam filtri | Precision | Muhim xatni spam ga tashlash — eng yomon xato |
| Ikkisi ham muhim | F1 | Muvozanat; bittasi past bo'lsa F1 ham past |
| Sinflar juda nomutanosib | macro F1 / PR-AUC | Accuracy 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.
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}")
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
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?"}, ]))
| Tushuncha | Bitta jumla bilan |
|---|---|
| Confusion matrix | To'rtta katak: TN, FP, FN, TP — butun hikoya shunda |
| FP va FN | Ikki xil xato, ikki xil narx — accuracy ularni ajratmaydi |
| Precision | Model 'ha' deganda qanchasida to'g'ri — TP/(TP+FP) |
| Recall | Bor 'ha' larning qanchasini topdi — TP/(TP+FN) |
| F1 | Garmonik o'rtacha; bittasi past bo'lsa F1 ham past |
| macro vs weighted | Nomutanosibda macro ni o'qi — weighted yashiradi |
| Metrika tanlash | Biznes qarori: FP qimmatmi yoki FN? |
Bizning modelimizning yakuniy pasporti:
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"}, ]))
| Metrika | Qiymat | O'qilishi |
|---|---|---|
| Accuracy | 0.7778 | Umumiy to'g'rilik |
| Precision | 0.7476 | 'Ha' deganlarining 75% i to'g'ri |
| Recall | 0.8462 | Imkoniyatlarning 85% ini topdi |
| F1 | 0.7938 | Muvozanat bahosi |
| FP (yongan reklama) | 52 | Shuncha bo'sh e'longa pul ketdi |
| FN (qo'ldan ketgan) | 28 | Shuncha 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.