Asosiy qismga o'tish

Nomutanosib ma'lumot

7-bob 10 kod bloki 1794 so'z

Nima u?

Nomutanosib (imbalanced) ma'lumot — bir klass ikkinchisidan ancha ko'p bo'lgan ma'lumot. Ko'p klass majority class, kam klass minority class deyiladi.

Real hayotda bu oddiy holat, istisno emas: - Karta firibgarligi: mashhur Kaggle dataseti (Credit Card Fraud) da 284 807 ta to'lovdan atigi 492 tasi firibgarlik, ya'ni 0.17%. - Kasallik skriningi: tekshirilganlarning ko'pchiligi sog'lom. - Zavoddagi nuqsonli mahsulot: 1000 tadan bir nechtasi. - Bizning MNIST: 5 lar 9%.

Va deyarli har doim kam klass — biz qidirayotgan klass.

Nega bu qiyin?

  1. Accuracy aldaydi. 2-bobda ko'rdik: hech narsa qilmaydigan model 91% oldi.
  2. Model ko'p klass tomonga og'adi. Model o'qishda umumiy xatolar sonini kamaytirishga harakat qiladi. Ko'pchilikka ko'p klassni aytsa, xatolar soni allaqachon kam bo'ladi. Shuning uchun model kam klassni aytishdan "qochadi".
  3. O'rganish uchun misol kam. Model kam klassni kam ko'radi.
  4. Bo'lishda kam klass yo'qolishi mumkin. Fold yoki test da kam klass umuman tushmasligi mumkin.
  5. ROC curve haddan tashqari yaxshi ko'rinadi. Buni hozir ko'ramiz.

ROC curve va PR curve

6-bobda ROC curve ni o'rgandik. Uning ikkala o'qi ham qator bo'yicha hisoblanadi: TPR — "haqiqat: 5" qatoridan, FPR — "haqiqat: 5 emas" qatoridan.

PR curve boshqacha: x o'qi — recall, y o'qi — precision. Precision esa ustun bo'yicha hisoblanadi: model "5" degan rasmlar ichidan. Nomutanosib ma'lumotda aynan shu farq hal qiluvchi bo'ladi.

PR curve da yaxshi joy — yuqori o'ng burchak (recall 1, precision 1). Tasodifiy modelning chizig'i 0.5 da emas, 5 lar ulushida (0.09) turadi: tasodifan "5" deydigan modelning precision i shunchaki 5 lar ulushiga teng bo'ladi. PR curve ostidagi yuza PR AUC (yoki average precision) deyiladi.

Ikkala grafikni bitta model uchun yonma-yon chizamiz:

python6 qator
from sklearn.metrics import RocCurveDisplay, PrecisionRecallDisplay

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.5))
RocCurveDisplay.from_predictions(y_train_5, y_scores, ax=ax1, plot_chance_level=True)
PrecisionRecallDisplay.from_predictions(y_train_5, y_scores, ax=ax2, plot_chance_level=True)
plt.show()

grafik

python5 qator
from sklearn.metrics import average_precision_score

pd.DataFrame({"model": [roc_auc_score(y_train_5, y_scores), average_precision_score(y_train_5, y_scores)],
              "tasodifiy model": [0.5, y_train_5.mean()]},
             index=["ROC AUC", "PR AUC (average precision)"])
Chiqish
                               model  tasodifiy model
ROC AUC                     0.960494          0.50000
PR AUC (average precision)  0.810042          0.09035

Model ham, score lar ham bir xil. Lekin: - ROC AUC = 0.960 — juda zo'r ko'rinadi. - PR AUC = 0.810 — yaxshi, lekin modelni hali ancha yaxshilash mumkinligi ko'rinib turibdi.

Nega ROC haddan tashqari yaxshi ko'rsatadi?

Hamma gap maxrajda. Bir xil 687 ta FP ni olaylik:

  • FPR = FP / (hamma "5 emas"lar) = 687 / 54 579 = 1.3%. Maxraj ulkan, shuning uchun 687 ta xato juda kichik ko'rinadi.
  • Precision = TP / (model "5" deganlar) = 3530 / 4217 = 0.837. Ya'ni model "5" degan javoblarining 16% i noto'g'ri.

Xatolar soni bir xil. Faqat ROC ularni katta klassning hajmiga bo'ladi, precision esa model "5" degan javoblar soniga bo'ladi. Ko'p klass qancha katta bo'lsa, FPR shuncha kichik chiqadi va ROC shuncha yaxshi ko'rinadi.

Kitobdagi qoida: kam klass (positive) kam uchrasa yoki FP siz uchun muhim bo'lsa, PR curve ga qarang. Aks holda ROC curve ishlatsa bo'ladi.

Ma'lumotni yanada nomutanosib qilamiz: 1%

9% hali yengil holat. Real masalalarda 1% yoki undan kam ham bo'ladi. Haqiqiy MNIST rasmlaridan 5 lardan faqat 550 tasini tasodifiy qoldiramiz, "5 emas"larning hammasi qoladi:

python5 qator
rng = np.random.default_rng(42)
qoldir = np.concatenate([np.flatnonzero(y_train != "5"),
                         rng.choice(np.flatnonzero(y_train_5), 550, replace=False)])
X_kam, y_kam = X_train[qoldir], y_train_5[qoldir]
pd.Series(y_kam).value_counts().to_frame().assign(ulushi=lambda d: d["count"] / len(y_kam))
Chiqish
       count    ulushi
False  54579  0.990023
True     550  0.009977

Endi 5 lar 1%. Xuddi shu modelni xuddi shu usulda baholaymiz va 9% bilan yonma-yon qo'yamiz. Bir xil kodni ikki marta yozmaslik uchun baholashni kichik funksiyaga olamiz:

python10 qator
kam_scores = cross_val_predict(sgd_clf, X_kam, y_kam, cv=3, method="decision_function")
kam_pred = kam_scores > 0

def baho(y_true, scores, pred):
    return {"accuracy": accuracy_score(y_true, pred), "precision": precision_score(y_true, pred),
            "recall": recall_score(y_true, pred), "ROC AUC": roc_auc_score(y_true, scores),
            "PR AUC": average_precision_score(y_true, scores)}

pd.DataFrame({"5 lar 9%": baho(y_train_5, y_scores, y_train_pred),
              "5 lar 1%": baho(y_kam, kam_scores, kam_pred)})
Chiqish
           5 lar 9%  5 lar 1%
accuracy   0.957033  0.993288
precision  0.837088  0.715311
recall     0.651171  0.543636
ROC AUC    0.960494  0.944941
PR AUC     0.810042  0.617991

Bu jadval nomutanosiblikning butun xavfini ko'rsatadi:

  • Accuracy oshdi: 0.957 → 0.993. Model yaxshilanganga o'xshaydi.
  • Aslida esa precision tushdi (0.837 → 0.715), recall tushdi (0.651 → 0.544). Model yomonlashdi.
  • ROC AUC deyarli o'zgarmadi: 0.960 → 0.945.
  • PR AUC esa sezilarli tushdi: 0.810 → 0.618.

Nega accuracy oshdi? Chunki endi 99% rasm "5 emas". Hech narsa qilmaydigan model 0.990 oladi. Bizning modelimiz undan atigi 0.3 foiz yaxshi. Accuracy faqat ko'p klass qanchalik ko'pligini ko'rsatyapti.

Nega o'zgarishni PR AUC eng to'g'ri ko'rsatdi? Kam klassning o'zi bilan hisoblanadi (precision va recall), shuning uchun "5 emas"larning ko'pligi unga ta'sir qilmaydi.

Matritsaning o'zi esa hammasini ochiq ko'rsatadi:

python1 qator
confusion_matrix(y_kam, kam_pred)
Chiqish
array([[54460,   119],
       [  251,   299]])

550 ta 5 dan 251 tasi o'tkazib yuborilgan (FN). Shu bilan birga 119 ta noto'g'ri signal (FP) bor. Accuracy 99.3% bo'lsa ham, model 5 larning deyarli yarmini topa olmayapti.

Stratification: nega fold larda nisbat saqlanishi kerak?

Ma'lumotimiz tartiblangan: avval 54 579 ta "5 emas", oxirida 550 ta 5. Oddiy KFold ma'lumotni tartib bo'yicha 3 bo'lakka kesadi. Har bir test fold ida nechta 5 bo'lishini sanaymiz:

python5 qator
from sklearn.model_selection import KFold, StratifiedKFold

pd.DataFrame({"KFold": [y_kam[test].sum() for _, test in KFold(3).split(X_kam)],
              "StratifiedKFold": [y_kam[test].sum() for _, test in StratifiedKFold(3).split(X_kam, y_kam)]},
             index=["1-fold", "2-fold", "3-fold"])
Chiqish
        KFold  StratifiedKFold
1-fold      0              184
2-fold      0              183
3-fold    550              183

KFold: 1- va 2-fold da bitta ham 5 yo'q, hamma 550 ta 5 3-fold ga tushdi. 3-fold ni test qilganda, model 1- va 2-fold da o'qitiladi, ya'ni bitta ham 5 ko'rmaydi. Bunday baho ma'nosiz.

StratifiedKFold: har bir fold da 183–184 ta 5. Har bir fold da klasslar nisbati butun ma'lumotdagi bilan bir xil (taxminan 1%).

Nega yuqoridagi natijalarimiz to'g'ri chiqdi? Chunki cross_val_score va cross_val_predict ga classifier va cv=3 berilsa, sklearn o'zi StratifiedKFold ishlatadi. Lekin buni bilish kerak, chunki: - train_test_split da buni o'zingiz yozishingiz kerak: stratify=y. - KFold(...) ni qo'lda yaratsangiz, stratification bo'lmaydi.

class_weight="balanced": nega va qanday ishlaydi?

2-sababni eslang: model umumiy xatoni kamaytiradi va har bir xato bir xil "narx"ga ega. 99% rasm "5 emas" bo'lgani uchun model asosan ularga moslashadi.

class_weight="balanced" har bir klassga vazn beradi: kam klassdagi xato qimmatroq bo'ladi. Formula: vazn = jami namunalar / (klasslar soni × shu klassdagi namunalar).

python3 qator
from sklearn.utils.class_weight import compute_class_weight

pd.Series(compute_class_weight("balanced", classes=np.array([False, True]), y=y_kam), index=["5 emas", "5"])
Chiqish
5 emas     0.505039
5         50.117273
dtype: float64

5 ning vazni 50.1, "5 emas"niki 0.505. Nisbati taxminan 99. Ya'ni o'qitishda bitta 5 dagi xato taxminan 99 ta "5 emas"dagi xatoga teng hisoblanadi. Bu aynan klasslar nisbatini (54 579 / 550 ≈ 99) muvozanatlaydi.

Oddiy model bilan balanced modelni bir xil 1% ma'lumotda solishtiramiz:

python7 qator
sgd_bal = SGDClassifier(class_weight="balanced", random_state=42)
bal_scores = cross_val_predict(sgd_bal, X_kam, y_kam, cv=3, method="decision_function")
bal_pred = bal_scores > 0

pd.DataFrame([confusion_matrix(y_kam, kam_pred).ravel(), confusion_matrix(y_kam, bal_pred).ravel()],
             index=["oddiy", "balanced"], columns=["TN", "FP", "FN", "TP"]
            ).assign(precision=lambda d: d.TP / (d.TP + d.FP), recall=lambda d: d.TP / (d.TP + d.FN))
Chiqish
             TN    FP   FN   TP  precision    recall
oddiy     54460   119  251  299   0.715311  0.543636
balanced  53217  1362  115  435   0.242070  0.790909
  • FN 251 dan 115 ga tushdi, recall 0.544 → 0.791. Model 5 larni ancha ko'p topyapti.
  • FP 119 dan 1362 ga oshdi, precision 0.715 → 0.242. Noto'g'ri signal ko'paydi.

Nega shunday? Vazn qaror chizig'ini "5 emas" tomonga suradi. Bu threshold ni tushirishga juda o'xshaydi. Demak bunda bir narsa yaxshilanib, boshqasi yomonlashdi: biz yana o'sha precision/recall trade-off ning boshqa nuqtasiga o'tdik.

Qaysi biri yaxshi? Bu xatolarning narxiga bog'liq. Kasallikda FN qimmat, shuning uchun balanced yaxshiroq. Spam filtrida FP qimmat, shuning uchun oddiy model yaxshiroq.

Nomutanosiblik bilan ishlashning boshqa usullari: - Threshold ni tanlash — 5-bobda qildik. Ko'pincha eng sodda va yetarli usul. - Resampling — kam klassni ko'paytirish (oversampling, masalan SMOTE) yoki ko'p klassni kamaytirish (undersampling). Bu faqat train qismiga qilinadi, test ga hech qachon qilinmaydi. - Ko'proq ma'lumot yig'ish — kam klassdan yangi misollar topish. Ko'pincha eng kuchli usul.

Oxirgi qadam: test set da bir marta tekshirish

Hamma tanlov train da qilindi: model va threshold (precision 90% uchun 3370). Endi modelni butun train da o'qitamiz va test set ni birinchi va oxirgi marta ishlatamiz:

python3 qator
sgd_clf.fit(X_train, y_train_5)
test_pred = sgd_clf.decision_function(X_test) >= threshold_90
confusion_matrix(y_test_5, test_pred)
Chiqish
array([[9069,   39],
       [ 368,  524]])
python1 qator
pd.Series({"precision": precision_score(y_test_5, test_pred), "recall": recall_score(y_test_5, test_pred)})
Chiqish
precision    0.930728
recall       0.587444
dtype: float64

Test da precision 0.931, recall 0.587. Cross-validation da 0.900 va 0.480 edi. Test natijasi biroz yaxshiroq. Buning sababi: endi model 60 000 ta rasmda o'qitildi, cross-validation da esa har safar 40 000 tada o'qitilgan edi. Raqamlar bir xil bo'lishi shart emas, lekin bir-biriga yaqin. Demak threshold tanlovimiz yangi ma'lumotda ham ishlaydi.

Muhim: bu test natijasini ko'rib, endi threshold ni o'zgartirmaymiz. Aks holda test ham "train"ga aylanib qoladi.


Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar