Asosiy qismga o'tish

Rasm klassifikatsiyasi — to'liq o'qitish

5-bob 14 kod bloki 1889 so'z

Endi CNN ni haqiqiy ma'lumotda o'qitamiz. Bu bobdan keyin sizda ishlaydigan model bo'ladi.

Ma'lumot: MNIST

MNIST — qo'lyozma raqamlar (0–9). 60 000 o'qitish, 10 000 test. 28x28, kulrang.

Nega MNIST? Chunki u kichik va tez. CPU da ham bir necha daqiqada o'qitiladi. G'oyani tushunish uchun ideal.

python14 qator
import torch
from torchvision import datasets, transforms

ozgartirish = transforms.Compose([
    transforms.ToTensor(),                      # 0-255 -> 0.0-1.0, HWC -> CHW
    transforms.Normalize((0.1307,), (0.3081,)), # MNIST o'rtachasi va dispersiyasi
])

oqitish = datasets.MNIST("data", train=True, download=True,
                         transform=ozgartirish)
test = datasets.MNIST("data", train=False, transform=ozgartirish)

print("o'qitish:", len(oqitish))
print("test    :", len(test))

Normalize nega kerak

python2 qator
# parcha
transforms.Normalize((0.1307,), (0.3081,))

Bu (piksel - 0.1307) / 0.3081 amalini bajaradi. Natijada qiymatlar o'rtachasi 0, dispersiyasi 1 atrofida bo'ladi.

Nega muhim: neyron tarmoq 0 atrofidagi sonlar bilan ancha yaxshi o'qiydi. 0–255 oralig'ida gradientlar juda katta bo'lib ketadi.

0.1307 va 0.3081 — bu MNIST ning haqiqiy o'rtachasi va standart og'ishi. Har bir dataset uchun o'zi hisoblanadi.

DataLoader — partiyalarga bo'lish

python9 qator
from torch.utils.data import DataLoader

oqitish_yuk = DataLoader(oqitish, batch_size=64, shuffle=True)
test_yuk = DataLoader(test, batch_size=1000)

rasmlar, javoblar = next(iter(oqitish_yuk))
print("partiya:", tuple(rasmlar.shape))
print("javoblar:", tuple(javoblar.shape))
print("qiymat oralig'i:", float(rasmlar.min()), float(rasmlar.max()))

shuffle=True faqat o'qitish uchun. Test da tartib muhim emas, lekin aralashtirish keraksiz ish.

batch_size=64 — 64 ta rasm birga qayta ishlanadi. Katta partiya tezroq, lekin ko'proq xotira. CPU da 32–64 yaxshi.

Model

python27 qator
import torch.nn as nn

class RaqamModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.belgilar = nn.Sequential(
            nn.Conv2d(1, 32, 3, padding=1),   # 28x28
            nn.ReLU(),
            nn.MaxPool2d(2),                   # 14x14
            nn.Conv2d(32, 64, 3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2),                   # 7x7
        )
        self.klassifikator = nn.Sequential(
            nn.Flatten(),
            nn.Linear(64 * 7 * 7, 128),
            nn.ReLU(),
            nn.Dropout(0.25),
            nn.Linear(128, 10),
        )

    def forward(self, x):
        return self.klassifikator(self.belgilar(x))


model = RaqamModel()
print("parametr:", sum(p.numel() for p in model.parameters()))

Natija: 421 642 parametr. LeNet dan (61 706) olti barobar katta, chunki bu yerda 32 va 64 filtr bor.

Dropout(0.25) — o'qitishda neyronlarning 25% ini tasodifiy o'chiradi. Bu overfittingga qarshi: model bitta neyronga tayanib qolmaydi.

Test paytida dropout avtomatik o'chadi (model.eval() bilan).

O'qitish sikli

python30 qator
import torch.optim as optim

qurilma = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(qurilma)

yoqotish_f = nn.CrossEntropyLoss()
optimizator = optim.Adam(model.parameters(), lr=0.001)


def bir_epoch(model, yuk, qurilma, optimizator, yoqotish_f):
    model.train()                      # dropout YOQILADI
    jami_yoqotish = 0.0
    togri = 0
    jami = 0

    for rasmlar, javoblar in yuk:
        rasmlar = rasmlar.to(qurilma)
        javoblar = javoblar.to(qurilma)

        optimizator.zero_grad()        # eski gradientlarni tozalash
        chiqish = model(rasmlar)
        yoqotish = yoqotish_f(chiqish, javoblar)
        yoqotish.backward()            # gradientlarni hisoblash
        optimizator.step()             # vaznlarni yangilash

        jami_yoqotish += yoqotish.item() * rasmlar.size(0)
        togri += (chiqish.argmax(1) == javoblar).sum().item()
        jami += rasmlar.size(0)

    return jami_yoqotish / jami, togri / jami

optimizator.zero_grad() — ENG KO'P UNUTILADIGAN QATOR.

PyTorch gradientlarni to'playdi. Tozalamasangiz, oldingi partiyaning gradientlari qo'shilib ketadi va o'qitish buziladi. Xato chiqmaydi — natija shunchaki yomon bo'ladi.

Uchta qadam har doim shu tartibda:

python2 qator
zero_grad()  ->  backward()  ->  step()
tozalash        hisoblash       yangilash

Baholash

python12 qator
@torch.no_grad()
def baho(model, yuk, qurilma):
    model.eval()                       # dropout O'CHADI
    togri = 0
    jami = 0
    for rasmlar, javoblar in yuk:
        rasmlar = rasmlar.to(qurilma)
        javoblar = javoblar.to(qurilma)
        chiqish = model(rasmlar)
        togri += (chiqish.argmax(1) == javoblar).sum().item()
        jami += javoblar.size(0)
    return togri / jami

@torch.no_grad() — gradient hisoblanmaydi. Xotira tejaydi va tezroq ishlaydi. Baholashda gradient kerak emas.

model.eval() va model.train() — ikkalasini ham unutmang. eval() qilmasangiz dropout test paytida ham ishlaydi va natija tasodifiy bo'lib qoladi.

Ishga tushirish

python7 qator
# parcha — vaqt oladi, o'zingiz ishga tushiring
for epoch in range(1, 4):
    yoq, oqitish_aniq = bir_epoch(
        model, oqitish_yuk, qurilma, optimizator, yoqotish_f)
    test_aniq = baho(model, test_yuk, qurilma)
    print(f"epoch {epoch}: yo'qotish {yoq:.4f}  "
          f"o'qitish {oqitish_aniq:.4f}  test {test_aniq:.4f}")

Kutilgan natija (CPU da har epoch ~1-2 daqiqa):

matn4 qator
parametr: 421642 | qurilma: cuda
epoch 1: yo'qotish 0.1597  o'qitish 0.9504  test 0.9831
epoch 2: yo'qotish 0.0551  o'qitish 0.9831  test 0.9875
epoch 3: yo'qotish 0.0391  o'qitish 0.9876  test 0.9891

Bu raqamlar haqiqiy ishga tushirishdan olingan. Sizda biroz farq qilishi mumkin — vaznlar tasodifiy boshlanadi va shuffle har safar boshqacha. Lekin 3-epochda test aniqligi 0.98–0.99 oralig'ida bo'lishi kerak.

99% aniqlik — uchta epoch dan keyin. MNIST oson dataset.

Raqamlarni QANDAY o'qish kerak

Uchta ustunga birga qarang:

Yo'qotish tushyaptimi? Yo'q bo'lsa — learning rate juda katta yoki juda kichik.

O'qitish aniqligi test aniqligidan ANCHA yuqorimi? Masalan o'qitish 0.99, test 0.85 — bu overfitting. Model yodlab olgan.

Bizning natijada test aniqligi o'qitishdan yuqori — bu g'alati emas: dropout o'qitishda ishlaydi va aniqlikni pasaytiradi, test paytida esa o'chadi.

Ikkalasi ham past bo'lsa — model juda oddiy yoki kam o'qidi (underfitting).

Chalkashlik matritsasi — QAYSI sinf xato

Aniqlik bitta son: 0.9891. U nima noto'g'ri ketganini aytmaydi.

Chalkashlik matritsasi aytadi: qaysi sinf qaysi sinf bilan adashtirilgan.

python30 qator
import torch


@torch.no_grad()
def chalkashlik_matritsasi(model, yuk, qurilma, sinflar=10):
    """Qator = haqiqiy sinf, ustun = model bashorati."""
    model.eval()
    M = torch.zeros(sinflar, sinflar, dtype=torch.long)
    for rasmlar, javoblar in yuk:
        bashorat = model(rasmlar.to(qurilma)).argmax(1).cpu()
        for h, b in zip(javoblar, bashorat):
            M[h, b] += 1
    return M


def matritsani_chiqar(M):
    n = M.size(0)
    print("     " + "".join(f"{i:>6}" for i in range(n)))
    for i in range(n):
        qator = "".join(f"{int(M[i, j]):>6}" for j in range(n))
        print(f"  {i}: {qator}")


def eng_yomon_chalkashlik(M, nechta=5):
    """Diagonal EMAS eng katta qiymatlar — eng ko'p adashish."""
    n = M.size(0)
    juftlar = [(int(M[i, j]), i, j)
               for i in range(n) for j in range(n) if i != j]
    juftlar.sort(reverse=True)
    return juftlar[:nechta]

Diagonal — to'g'ri javoblar. Diagonaldan tashqaridagi har bir son — xato.

Ishlatish

python7 qator
# parcha — o'qitilgan model kerak
M = chalkashlik_matritsasi(model, test_yuk, qurilma)
matritsani_chiqar(M)

print("\nEng ko'p adashish:")
for son, haqiqiy, bashorat in eng_yomon_chalkashlik(M):
    print(f"  {haqiqiy} -> {bashorat} deb o'qildi: {son} marta")

Haqiqiy natija (yuqoridagi model, 3 epoch):

python8 qator
Eng ko'p adashish:
  9 -> 5 deb o'qildi: 8 marta
  5 -> 3 deb o'qildi: 7 marta
  9 -> 7 deb o'qildi: 6 marta
  9 -> 4 deb o'qildi: 6 marta
  6 -> 5 deb o'qildi: 6 marta

aniqlik: 0.9902 | eng yomon sinf: 9 (0.9782)

Bu — aniqlik bermaydigan ma'lumot. Umumiy aniqlik 0.9902, lekin 9 raqami 0.9782 — o'rtachadan sezilarli past.

Va e'tibor bering: birinchi beshlikdan uchtasi 9 bilan bog'liq. Model 9 ni 5, 7 va 4 bilan adashtiradi. Bu tasodif emas — qo'lda yozilgan 9 ning dumi turli tomonga qiyshaysa, u shu raqamlarga o'xshab qoladi.

Keyingi qadam aniq: 9 raqami uchun ko'proq ma'lumot kerak. Aniqlik bu xulosani bermaydi, chalkashlik matritsasi beradi.

Nima qilish kerak

Chalkashlik matritsasi keyingi qadamni aytadi:

Ko'rinish Ma'nosi Qilinadigan ish
Bitta juftlik ko'p adashadi shaklan o'xshash shu ikkisidan ko'proq ma'lumot
Bitta qator butunlay yomon shu sinf kam o'qitilgan shu sinf uchun ma'lumot qo'shing
Bir ustun juda "og'ir" model shu sinfga moyil sinf balansini tekshiring

Aniqlik "yaxshi/yomon" deydi. Chalkashlik matritsasi "nima qilish kerak" deydi.

Sinf bo'yicha aniqlik

python7 qator
def sinf_boyicha_aniqlik(M):
    """Har bir sinf uchun alohida aniqlik."""
    natija = {}
    for i in range(M.size(0)):
        jami = int(M[i].sum())
        natija[i] = int(M[i, i]) / jami if jami else 0.0
    return natija

Umumiy aniqlik 0.99 bo'lsa ham, bitta sinf 0.85 bo'lishi mumkin. O'rtacha buni yashiradi.

Hujjat vazifasida bu ayniqsa muhim: "summa" maydonini 85% aniqlik bilan o'qish — bu yaroqsiz, garchi umumiy aniqlik 99% bo'lsa ham.

Saqlash va yuklash

python6 qator
# parcha
torch.save(model.state_dict(), "raqam_model.pt")

yangi = RaqamModel()
yangi.load_state_dict(torch.load("raqam_model.pt"))
yangi.eval()

state_dict() saqlang, butun modelni emas. Butun modelni saqlasangiz (torch.save(model, ...)) — u sinf ta'rifiga bog'liq bo'ladi va kodni o'zgartirsangiz yuklanmaydi.

eval() ni unutmang — yuklangandan keyin model train rejimida bo'ladi.

MNIST dan keyin nima

MNIST juda oson. Keyingi qadam:

  • Fashion-MNIST — kiyim rasmlari, xuddi shu format, ancha qiyin
  • CIFAR-10 — 32x32 rangli, 10 sinf, real fotosuratlar

Kodni deyarli o'zgartirmasdan sinab ko'ring — faqat datasets.MNIST o'rniga datasets.CIFAR10 yozing va birinchi konvolyutsiyada Conv2d(1, ...) ni Conv2d(3, ...) qiling (rangli rasm — 3 kanal).

Aniqlik keskin tushadi. Mana shu yerda transfer learning kerak bo'ladi — keyingi bob.


Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar