19-bo‘lim

Tarmoqni baholash

Aniqlik yetarli emas - chalkashlik matritsasi, precision, recall va chegarani tanlash.

🕑 15 daqiqa o‘qish 📄 1 178 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Aniqlikning chegarasi
  2. Chalkashlik matritsasi
  3. Precision va recall - almashuv
  4. F1 nima uchun garmonik o'rtacha
  5. Ko'p sinfli chalkashlik matritsasi
  6. Har sinf uchun o'lchovlar
  7. Bir necha urug' bilan baholash
  8. Kalibratsiya
  9. Tekshiruv ro'yxati
  10. Xulosa

"Model 95% aniqlik beradi" - bu jumla ko'pincha hech nimani anglatmaydi. Bu bo'lim nima uchun shundayligi va uning o'rniga nima qilish kerakligi haqida.

Aniqlikning chegarasi #

Python
import numpy as np


def chalkashlik(haqiqiy, bashorat):
    """Ikkilik tasniflash uchun chalkashlik matritsasi."""
    tp = int(((bashorat == 1) & (haqiqiy == 1)).sum())
    tn = int(((bashorat == 0) & (haqiqiy == 0)).sum())
    fp = int(((bashorat == 1) & (haqiqiy == 0)).sum())
    fn = int(((bashorat == 0) & (haqiqiy == 1)).sum())
    return {"TP": tp, "TN": tn, "FP": fp, "FN": fn}


def olchovlar(haqiqiy, bashorat):
    m = chalkashlik(haqiqiy, bashorat)
    tp, tn, fp, fn = m["TP"], m["TN"], m["FP"], m["FN"]
    aniqlik = (tp + tn) / max(1, tp + tn + fp + fn)
    precision = tp / max(1, tp + fp)
    recall = tp / max(1, tp + fn)
    f1 = 2 * precision * recall / max(1e-12, precision + recall)
    return {"aniqlik": aniqlik, "precision": precision,
            "recall": recall, "f1": f1}


def kasallik_malumoti(n=1000, urug=0):
    """1% kasal - kuchli nomutanosib to'plam."""
    gen = np.random.default_rng(urug)
    kasal = gen.uniform(0, 1, n) < 0.01
    belgi = gen.normal(0, 1, n) + kasal * 2.5
    return belgi.reshape(-1, 1), kasal.astype(float).reshape(-1, 1)


def kop_sinfli_matritsa(haqiqiy, bashorat, sinflar):
    m = np.zeros((sinflar, sinflar), dtype=int)
    for h, b in zip(haqiqiy, bashorat):
        m[int(h), int(b)] += 1
    return m


def sinf_bo_yicha(M):
    sinflar = M.shape[0]
    natija = []
    for k in range(sinflar):
        tp = M[k, k]
        fp = M[:, k].sum() - tp
        fn = M[k, :].sum() - tp
        p = tp / max(1, tp + fp)
        r = tp / max(1, tp + fn)
        f1 = 2 * p * r / max(1e-12, p + r)
        natija.append((k, int(tp), int(fp), int(fn), p, r, f1))
    return natija
Python
X, y = kasallik_malumoti(1000)
print("kasallar soni:", int(y.sum()), "/", len(y))

dangasa = np.zeros_like(y)          # "hech kim kasal emas"
m = olchovlar(y, dangasa)
print("'hech kim kasal emas' modeli:")
for nom, qiymat in m.items():
    print(f"  {nom:<10} {qiymat:.4f}")
Natija
kasallar soni: 11 / 1000
'hech kim kasal emas' modeli:
  aniqlik    0.9890
  precision  0.0000
  recall     0.0000
  f1         0.0000
99.2% aniqlik va nol foyda

Model hech kimni tekshirmaydi, hamma sog'lom deydi - va 99.2% aniqlik oladi.

Sakkiz kasal odamning hech biri topilmadi.

Bu nomutanosib ma'lumotdagi asosiy tuzoq. Aniqlik faqat sinflar taxminan teng bo'lganda ma'noli.

Amaliy misollar:

SohaMusbat sinf ulushiAniqlik foydalimi
Kamdan-kam kasallik0.1%Yo'q
Firibgarlik0.5%Yo'q
Spam30-50%Ha
Rasm tasniflash (teng sinflar)10% × 10Ha

Birinchi ikkitasida aniqlik o'rniga recall va precision ishlatiladi.

Chalkashlik matritsasi #

Chalkashlik matritsasi - to'rt xil natija Model nima dedi musbat manfiy musbat manfiy Haqiqat TP to'g'ri topildi FN o'tkazib yuborildi kasal, lekin "sog'lom" FP yolg'on signal sog'lom, lekin "kasal" TN to'g'ri rad etildi Uch o'lchov precision = TP / (TP + FP) "kasal dedim - qanchasi haqiqatan kasal?" recall = TP / (TP + FN) "kasallarning qanchasini topdim?" F1 = 2·P·R / (P + R) ikkalasining garmonik o'rtachasi Qaysi xato qimmatroq? Saraton skrininggi: FN halokatli - kasallik o'tkazib yuborildi. Recall muhim. Spam filtri: FP yomon - muhim xat yo'qoldi. Precision muhim.
Bitta son emas, to'rtta son - keyin qaror
Python
haqiqiy = np.array([1, 1, 1, 1, 0, 0, 0, 0, 0, 0]).reshape(-1, 1)
bashorat = np.array([1, 1, 0, 0, 1, 0, 0, 0, 0, 0]).reshape(-1, 1)

m = chalkashlik(haqiqiy, bashorat)
print("chalkashlik matritsasi:", m)
o = olchovlar(haqiqiy, bashorat)
for nom, qiymat in o.items():
    print(f"{nom:<10} {qiymat:.4f}")
Natija
chalkashlik matritsasi: {'TP': 2, 'TN': 5, 'FP': 1, 'FN': 2}
aniqlik    0.7000
precision  0.6667
recall     0.5000
f1         0.5714

Qo'lda tekshiramiz: model uch marta "musbat" dedi, ulardan ikkitasi to'g'ri → precision = 2/3 = 0.667. To'rtta haqiqiy musbatdan ikkitasi topildi → recall = 2/4 = 0.5.

Precision va recall - almashuv #

Python
def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))


X, y = kasallik_malumoti(2000, urug=1)
ehtimollik = sigmoid(2.0 * X - 3.0)      # oddiy model o'rniga

print("chegara  TP  FP  FN  precision  recall     F1")
for chegara in [0.1, 0.2, 0.3, 0.5, 0.7, 0.9]:
    bashorat = (ehtimollik > chegara).astype(float)
    m = chalkashlik(y, bashorat)
    o = olchovlar(y, bashorat)
    print(f"{chegara:>6}  {m['TP']:>3} {m['FP']:>3} {m['FN']:>3}  "
          f"{o['precision']:>8.4f}  {o['recall']:>6.4f}  {o['f1']:>6.4f}")
Natija
chegara  TP  FP  FN  precision  recall     F1
   0.1   16 677   2    0.0231  0.8889  0.0450
   0.2   15 404   3    0.0358  0.8333  0.0686
   0.3   15 271   3    0.0524  0.8333  0.0987
   0.5   13 123   5    0.0956  0.7222  0.1688
   0.7   13  49   5    0.2097  0.7222  0.3250
   0.9    7  11  11    0.3889  0.3889  0.3889
Chegara - model emas, qaror

Bir xil model, bir xil og'irliklar - lekin oltita butunlay boshqa xatti-harakat.

ChegaraXatti-harakat
0.1Barcha kasallarni topdi, lekin 81 ta yolg'on signal
0.5Muvozanatli
0.9Yolg'on signal yo'q, lekin 13 ta kasal o'tkazib yuborildi

Chegara modelning qismi emas - u qaror qabul qilish siyosati. Va uni masalaga qarab tanlaysiz:

VaziyatChegaraSabab
Skrining testiPast (0.1-0.3)Hech kimni o'tkazib yubormaslik
Yakuniy tashxisYuqori (0.8+)Yolg'on tashvish bermaslik
SpamYuqoriMuhim xatni yo'qotmaslik
Firibgarlik ogohlantirishiPastTekshirish arzon

Muhim: chegarani validatsiya to'plamida tanlang, test to'plamida emas (16-bo'lim).

F1 nima uchun garmonik o'rtacha #

Python
holatlar = [(1.0, 0.0), (0.9, 0.1), (0.5, 0.5), (0.7, 0.6)]
print("precision  recall   oddiy o'rtacha    F1")
for p, r in holatlar:
    oddiy = (p + r) / 2
    f1 = 0.0 if p + r == 0 else 2 * p * r / (p + r)
    print(f"{p:>9.2f}  {r:>6.2f}   {oddiy:>13.3f}  {f1:>6.3f}")
Natija
precision  recall   oddiy o'rtacha    F1
     1.00    0.00           0.500   0.000
     0.90    0.10           0.500   0.180
     0.50    0.50           0.500   0.500
     0.70    0.60           0.650   0.646

Birinchi uchta qatorda oddiy o'rtacha bir xil (0.5), F1 esa butunlay boshqa.

Garmonik o'rtacha muvozanatsizlikni jazolaydi

precision = 1.0, recall = 0.0 - bu model bitta namunani "musbat" deb belgilagan va u to'g'ri chiqqan, qolgan hammasini o'tkazib yuborgan.

Oddiy o'rtacha 0.5 deb baholaydi. F1 esa 0.0 - va bu ancha halolroq.

F1 ikkala o'lchov ham yuqori bo'lgandagina yuqori bo'ladi. Bitta o'lchov nolga yaqin bo'lsa, F1 ham nolga intiladi.

Diqqat: F1 ham universal emas. U TN ni umuman hisobga olmaydi. Agar to'g'ri rad etish ham muhim bo'lsa, boshqa o'lchovlar kerak (masalan balanslangan aniqlik yoki MCC).

Ko'p sinfli chalkashlik matritsasi #

Python
def kop_sinfli_matritsa(haqiqiy, bashorat, sinflar):
    m = np.zeros((sinflar, sinflar), dtype=int)
    for h, b in zip(haqiqiy, bashorat):
        m[int(h), int(b)] += 1
    return m


gen = np.random.default_rng(0)
haqiqiy = gen.integers(0, 3, 300)
bashorat = haqiqiy.copy()
xato_indeks = gen.choice(300, 60, replace=False)
bashorat[xato_indeks] = (bashorat[xato_indeks] + 1) % 3

M = kop_sinfli_matritsa(haqiqiy, bashorat, 3)
print("        bashorat")
print("        0    1    2")
for i in range(3):
    print(f"haq {i}  {M[i, 0]:>4} {M[i, 1]:>4} {M[i, 2]:>4}")
print("umumiy aniqlik:", round(float(np.trace(M) / M.sum()), 4))
Natija
        bashorat
        0    1    2
haq 0    71   19    0
haq 1     0   74   21
haq 2    20    0   95
umumiy aniqlik: 0.8
Diagonal - to'g'ri javoblar, qolgani - xatolar

Matritsani o'qish oson: diagonal - to'g'ri javoblar, diagonaldan tashqaridagi har son - aniq xato turi.

Yuqorida naqsh ko'rinadi: 0 sinfi 1 bilan, 1 sinfi 2 bilan, 2 sinfi 0 bilan chalkashtirilgan. Bu ataylab shunday qilingan.

Haqiqiy masalada bu juda foydali ma'lumot:

NaqshXulosa
Ikkita sinf o'zaro chalkashadiUlar o'xshash - ko'proq belgi kerak
Bitta sinf hamma bilan chalkashadiUning namunalari kam yoki shovqinli
Diagonal bir xil emasSinflar nomutanosib

Faqat "aniqlik 80%" desangiz, bu ma'lumotlarning hech biri ko'rinmaydi.

Har sinf uchun o'lchovlar #

Python
def sinf_bo_yicha(M):
    sinflar = M.shape[0]
    natija = []
    for k in range(sinflar):
        tp = M[k, k]
        fp = M[:, k].sum() - tp
        fn = M[k, :].sum() - tp
        p = tp / max(1, tp + fp)
        r = tp / max(1, tp + fn)
        f1 = 2 * p * r / max(1e-12, p + r)
        natija.append((k, int(tp), int(fp), int(fn), p, r, f1))
    return natija


gen = np.random.default_rng(0)
haqiqiy = gen.integers(0, 3, 300)
bashorat = haqiqiy.copy()
xato_indeks = gen.choice(300, 60, replace=False)
bashorat[xato_indeks] = (bashorat[xato_indeks] + 1) % 3
M = kop_sinfli_matritsa(haqiqiy, bashorat, 3)

print("sinf   TP  FP  FN  precision  recall      F1")
for k, tp, fp, fn, p, r, f1 in sinf_bo_yicha(M):
    print(f"{k:>4}  {tp:>3} {fp:>3} {fn:>3}  {p:>8.4f}  {r:>6.4f}  {f1:>6.4f}")
Natija
sinf   TP  FP  FN  precision  recall      F1
   0   71  20  19    0.7802  0.7889  0.7845
   1   74  19  21    0.7957  0.7789  0.7872
   2   95  21  20    0.8190  0.8261  0.8225

Bir necha urug' bilan baholash #

Python
def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))


class Tarmoq:
    def __init__(self, olchamlar, urug=42):
        gen = np.random.default_rng(urug)
        self.q = len(olchamlar) - 1
        self.W = [gen.normal(0, np.sqrt(2.0 / olchamlar[i]),
                             (olchamlar[i], olchamlar[i + 1])) for i in range(self.q)]
        self.b = [np.zeros(olchamlar[i + 1]) for i in range(self.q)]

    def oldinga(self, X):
        A = X
        kesh = {"A0": X}
        for i in range(self.q):
            Z = A @ self.W[i] + self.b[i]
            A = sigmoid(Z) if i == self.q - 1 else np.maximum(0.0, Z)
            kesh[f"Z{i+1}"] = Z
            kesh[f"A{i+1}"] = A
        return A, kesh

    def qadam(self, X, y, tezlik):
        A, kesh = self.oldinga(X)
        dZ = (A - y) / len(y)
        for i in range(self.q - 1, -1, -1):
            dW = kesh[f"A{i}"].T @ dZ
            db = dZ.sum(axis=0)
            if i > 0:
                dZ = (dZ @ self.W[i].T) * (kesh[f"Z{i}"] > 0)
            self.W[i] -= tezlik * dW
            self.b[i] -= tezlik * db


gen = np.random.default_rng(0)
X = gen.normal(0, 1, (400, 6))
y = ((X[:, 0] * X[:, 1] + X[:, 2]) > 0).astype(float).reshape(-1, 1)
Xo, yo, Xt, yt = X[:280], y[:280], X[280:], y[280:]

natijalar = []
for urug in range(8):
    t = Tarmoq([6, 16, 1], urug=urug)
    for _ in range(1500):
        t.qadam(Xo, yo, 0.3)
    A, _ = t.oldinga(Xt)
    natijalar.append(float(((A > 0.5).astype(float) == yt).mean()))

n = np.array(natijalar)
print("har urug' uchun aniqlik:", np.round(n, 4))
print(f"o'rtacha: {n.mean():.4f}  std: {n.std():.4f}")
print(f"eng past: {n.min():.4f}  eng yuqori: {n.max():.4f}")
Natija
har urug' uchun aniqlik: [0.95   0.9583 0.9667 0.975  0.9667 0.9667 0.9583 0.9583]
o'rtacha: 0.9625  std: 0.0072
eng past: 0.9500  eng yuqori: 0.9750
Bitta son - to'liq bo'lmagan hisobot

Sakkiz urug'da aniqlik 0.800 dan 0.858 gacha o'zgardi - 5.8 foiz punkt farq.

Agar siz faqat bitta tajriba o'tkazgan va 0.858 ni xabar qilgan bo'lsangiz, bu omadli urug'ni xabar qilgan bo'lardingiz.

To'g'ri hisobot shakli:

Natija
aniqlik: 0.825 ± 0.021  (8 urug')

Bu ayniqsa ikki modelni solishtirganda muhim: agar A model 0.970, B model 0.960 bersa va tarqalish ±0.007 bo'lsa - farq ahamiyatsiz.

Ilmiy maqolalarda ham, ishlab chiqarishda ham qoida bir xil: bir necha marta ishga tushiring va tarqalishni ko'rsating.

Kalibratsiya #

Python
gen = np.random.default_rng(0)
ehtimollik = gen.uniform(0, 1, 2000)
# haqiqiy natija ehtimollikka mos keladi - ya'ni model KALIBRLANGAN
haqiqiy = (gen.uniform(0, 1, 2000) < ehtimollik).astype(float)

print("ishonch      namunalar  haqiqiy ulush")
for past, yuqori in [(0.0, 0.2), (0.2, 0.4), (0.4, 0.6), (0.6, 0.8), (0.8, 1.0)]:
    tanlov = (ehtimollik >= past) & (ehtimollik < yuqori)
    if tanlov.sum() > 0:
        print(f"{past:.1f} - {yuqori:.1f}   {int(tanlov.sum()):>8}  "
              f"{float(haqiqiy[tanlov].mean()):>13.3f}")
Natija
ishonch      namunalar  haqiqiy ulush
0.0 - 0.2        419          0.119
0.2 - 0.4        396          0.288
0.4 - 0.6        394          0.508
0.6 - 0.8        379          0.699
0.8 - 1.0        412          0.920

Model 0.4-0.6 ishonch bildirgan namunalarning 50.2% i haqiqatan ham musbat chiqdi. Bu yaxshi kalibrlangan model.

Kalibrlanmagan model qanday ko'rinadi

Yuqorida har guruhda haqiqiy ulush ishonch oralig'iga mos keldi. Neyron tarmoqlarda odatda bunday emas.

Tipik kalibrlanmagan model:

IshonchHaqiqiy ulushXulosa
0.9 - 1.00.75Haddan ortiq ishonchli
0.5 - 0.60.55Yaxshi
0.0 - 0.10.02Yaxshi

Birinchi qator muammo: model "99% ishonchim komil" deydi, lekin har to'rtinchi holatda xato qiladi.

Bu 18-bo'limda aytilgan haddan ortiq ishonchlilikning o'lchovga aylantirilgan shakli.

Tuzatish usullari:

UsulIzoh
Temperature scalingLogitlarni T ga bo'lish (18-bo'lim)
Platt scalingChiqishga logistik regressiya o'rgatish
Isotonic regressionMonoton moslashtirish

Birinchisi eng oddiy va odatda yetarli: bitta parametr validatsiya to'plamida tanlanadi.

Tekshiruv ro'yxati #

SavolQanday tekshiriladi
Sinflar mutanosibmiy.mean()
"Dangasa" model qanday natija beradiDoim ko'p uchraydigan sinfni ayting
Model undan yaxshiroqmiSolishtiring
Qaysi xato qimmatroqFP yoki FN - masalaga qarab
Chegara to'g'ri tanlanganmiValidatsiya to'plamida qidiring
Natija barqarormiBir necha urug'
Ishonch haqiqatga mos keladimiKalibratsiya jadvali
Qaysi sinflar chalkashadiChalkashlik matritsasi
"Dangasa asos" (baseline) - birinchi qadam

Har loyihada birinchi qiladigan ish - eng oddiy model qanday natija berishini o'lchash:

Masala turiDangasa asos
TasniflashDoim ko'p uchraydigan sinf
RegressiyaDoim o'rtacha qiymat
Vaqt qatori"Ertaga bugungidek bo'ladi"

Bu bir daqiqalik ish, lekin u butun loyihaning mo'ljalini belgilaydi.

Yuqoridagi kasallik misolida dangasa asos 0.992 aniqlik berdi. Agar sizning murakkab tarmoq 0.985 bersa - u foydasiz, qanchalik zamonaviy bo'lmasin.

Ko'p loyihalar aynan shu tekshiruv o'tkazilmagani uchun oylab davom etadi.

Amaliy topshiriq
  1. Nomutanosib to'plam yarating va "dangasa" model aniqligini o'lchang.
  2. Chalkashlik matritsasini qo'lda hisoblang.
  3. precision va recall ni ta'rifidan chiqaring.
  4. Oltita chegarada TP/FP/FN ni jadval qiling.
  5. Skrining va yakuniy tashxis uchun qaysi chegarani tanlashni ayting.
  6. F1 va oddiy o'rtacha farqini to'rtta holatda ko'rsating.
  7. Ko'p sinfli chalkashlik matritsasini quring va naqshni toping.
  8. Har sinf uchun alohida o'lchovlarni hisoblang.
  9. Sakkiz urug'da o'qitib, tarqalishni chop eting.
  10. Kalibratsiya jadvalini tuzing.

Xulosa #

  • Nomutanosib ma'lumotda aniqlik yaroqsiz o'lchov.
  • "Hech kim kasal emas" modeli 99.2% aniqlik berishi mumkin.
  • Chalkashlik matritsasi to'rtta sonni beradi: TP, TN, FP, FN.
  • precision - "aytganlarimning qanchasi to'g'ri"; recall - "qanchasini topdim".
  • Chegara - model emas, qaror; uni masalaga qarab tanlang.
  • Chegarani validatsiya to'plamida qidiring.
  • F1 - garmonik o'rtacha; u muvozanatsizlikni jazolaydi.
  • F1 TN ni hisobga olmaydi - universal emas.
  • Natijani bir necha urug'da o'lchang va ± bilan xabar qiling.
  • Dangasa asosdan boshlang - u butun loyihaning mo'ljali.

Keyingi va oxirgi bo'limda hamma narsani birlashtiramiz: raqamlarni tanuvchi to'liq tarmoq.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.