Rasm klassifikatsiyasi — to'liq o'qitish
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.
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
# 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
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
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
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:
zero_grad() -> backward() -> step() tozalash hisoblash yangilash
Baholash
@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
# 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):
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.
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
# 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):
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
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
# 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.