CNN — o'rganiladigan konvolyutsiya
2-bobda yadrolarni qo'lda yozdik: chet topish uchun shunday, xiralashtirish uchun bunday.
CNN da yadrolar o'rganiladi. Boshida tasodifiy sonlar, keyin tarmoq minglab rasmni ko'rib, foydali yadrolarni o'zi topadi.
Nima uchun oddiy tarmoq yetmaydi
100x100 rangli rasmni to'liq bog'langan (fully connected) qatlamga bersak:
kirish = 100 * 100 * 3 # 30 000 piksel neyronlar = 128 print(f"vaznlar soni: {kirish * neyronlar:,}")
3.8 million vazn — bitta qatlam uchun. Va bu hali boshlanishi.
Uch muammo bor:
- Juda ko'p parametr — o'qitish uchun juda ko'p ma'lumot kerak
- Joylashuv yo'qoladi — qo'shni piksellar bog'liqligi hisobga olinmaydi, rasm shunchaki 30 000 ta alohida son bo'ladi
- Siljish muammosi — mushuk chapda bo'lsa boshqa neyronlar, o'ngda bo'lsa boshqa neyronlar yonadi
CNN uchalasini ham hal qiladi.
Uchta g'oya
1. Lokal bog'lanish. Har bir neyron butun rasmni emas, kichik hududni (masalan 3x3) ko'radi.
2. Vazn ulashish (weight sharing). Bitta yadro butun rasm bo'ylab sirg'anadi. Chapdagi chetni topadigan yadro o'ngdagi chetni ham topadi — bir xil vaznlar bilan.
# To'liq bog'langan: har bir piksel uchun alohida vazn fc_vazn = 100 * 100 * 3 * 128 # Konvolyutsion: faqat yadro vaznlari conv_vazn = 3 * 3 * 3 * 128 # 3x3 yadro, 3 kanal, 128 filtr print(f"FC : {fc_vazn:,}") print(f"Conv : {conv_vazn:,}") print(f"kamayish: {fc_vazn // conv_vazn} barobar")
Natija:
FC : 3,840,000 Conv : 3,456 kamayish: 1111 barobar
Ming barobardan ko'p kam parametr — va natija yaxshiroq. Chunki vazn ulashish tarmoqni "chet har joyda chet" degan haqiqatni o'rganishga majbur qiladi.
3. Pooling. O'lchamni kichraytirish, muhimini saqlash.
PyTorch da qatlamlar
import torch import torch.nn as nn x = torch.randn(1, 3, 64, 64) # (batch, kanal, balandlik, kenglik) conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1) chiqish = conv(x) print("kirish :", tuple(x.shape)) print("chiqish:", tuple(chiqish.shape)) print("vaznlar:", sum(p.numel() for p in conv.parameters()))
Natija:
kirish : (1, 3, 64, 64) chiqish: (1, 16, 64, 64) vaznlar: 448
448 = 16 filtr x (3 kanal x 3 x 3 yadro) + 16 bias.
Kanallar soni 3 dan 16 ga o'sdi, o'lcham esa padding=1 tufayli
saqlandi.
(1, 3, 64, 64) — PyTorch tartibi: batch, kanal, balandlik, kenglik.
Diqqat: OpenCV da (balandlik, kenglik, kanal), PyTorch da
(kanal, balandlik, kenglik). Kanal o'rni har xil. Bu eng
ko'p uchraydigan xato.
O'tkazish:
import numpy as np rasm_cv = np.zeros((64, 64, 3), dtype=np.uint8) # OpenCV: HWC tensor = torch.from_numpy(rasm_cv).permute(2, 0, 1) # PyTorch: CHW print("OpenCV :", rasm_cv.shape) print("PyTorch:", tuple(tensor.shape))
padding — o'lcham nega o'zgaradi
x = torch.randn(1, 1, 32, 32) for p in (0, 1): c = nn.Conv2d(1, 1, kernel_size=3, padding=p) print(f"padding={p} -> {tuple(c(x).shape)}")
Natija:
padding=0 -> (1, 1, 30, 30) padding=1 -> (1, 1, 32, 32)
padding=0 — har konvolyutsiyada 2 piksel yo'qoladi (3x3 yadro
uchun). 10 qatlamdan keyin 20 piksel yo'q bo'ladi.
padding=1 — o'lcham saqlanadi. Chuqur tarmoqlarda deyarli
doim shu ishlatiladi.
Formula:
chiqish = (kirish + 2*padding - yadro) / stride + 1
Pooling — kichraytirish
x = torch.randn(1, 16, 32, 32) pool = nn.MaxPool2d(kernel_size=2, stride=2) print(tuple(pool(x).shape)) # (1, 16, 16, 16)
MaxPool 2x2 hududdan eng kattasini oladi. O'lcham yarmiga tushadi, kanallar soni o'zgarmaydi.
Nega maksimum? Chunki bizni "shu hududda belgi BORMI?" degan savol qiziqtiradi. Eng kuchli javob — maksimum.
LeNet — birinchi ishlaydigan CNN (1998)
class LeNet(nn.Module): def __init__(self, sinflar=10): super().__init__() self.belgilar = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5, padding=2), # 28x28 -> 28x28 nn.ReLU(), nn.MaxPool2d(2), # -> 14x14 nn.Conv2d(6, 16, kernel_size=5), # -> 10x10 nn.ReLU(), nn.MaxPool2d(2), # -> 5x5 ) self.klassifikator = nn.Sequential( nn.Flatten(), nn.Linear(16 * 5 * 5, 120), nn.ReLU(), nn.Linear(120, 84), nn.ReLU(), nn.Linear(84, sinflar), ) def forward(self, x): x = self.belgilar(x) return self.klassifikator(x) model = LeNet() x = torch.randn(4, 1, 28, 28) print("chiqish:", tuple(model(x).shape)) print("parametr:", sum(p.numel() for p in model.parameters()))
Natija:
chiqish: (4, 10) parametr: 61706
61 706 parametr — bugungi standartlar bo'yicha juda kichik. ResNet-50 da 25 million bor. Lekin LeNet 1998 yilda qo'lyozma raqamlarni tanishni hal qilgan edi.
(4, 10) — 4 ta rasm uchun 10 tadan ball (10 ta sinf).
Tuzilish har doim bir xil: konvolyutsiya bloklari belgilarni ajratadi, oxirida to'liq bog'langan qatlamlar qaror qabul qiladi.
16 * 5 * 5 qayerdan keldi? Oxirgi konvolyutsiya 16 kanal,
5x5 o'lcham qoldirdi. Flatten ularni 400 ta songa yoyadi.
Bu sonni qo'lda hisoblash kerak — va o'lcham o'zgarsa xato
chiqadi. Shuning uchun ko'p kod nn.AdaptiveAvgPool2d ishlatadi:
u kirish o'lchamidan qat'i nazar bir xil chiqish beradi.
ResNet — chuqurlik muammosi
LeNet 5 qatlam. Zamonaviy tarmoqlar 50, 100, 150 qatlam.
Lekin 2015 gacha chuqur tarmoqlar yomonroq ishlardi. 56 qatlamli tarmoq 20 qatlamlidan ko'ra ko'proq xato qilardi — o'qitishda ham, testda ham.
Sabab: gradient yo'qolishi. Orqaga tarqalishda gradient har qatlamdan o'tganda kichrayadi. 50 qatlamdan keyin deyarli nol — birinchi qatlamlar o'rganmaydi.
Yechim: skip connection (o'tkazib yuborish).
class QoldiqBlok(nn.Module): def __init__(self, kanal): super().__init__() self.yol = nn.Sequential( nn.Conv2d(kanal, kanal, 3, padding=1), nn.BatchNorm2d(kanal), nn.ReLU(), nn.Conv2d(kanal, kanal, 3, padding=1), nn.BatchNorm2d(kanal), ) self.relu = nn.ReLU() def forward(self, x): return self.relu(self.yol(x) + x) # <-- + x : SKIP
self.yol(x) + x — butun ResNet g'oyasi shu qo'shuvda.
Nega ishlaydi: gradient endi ikki yo'ldan oqadi — konvolyutsiyalar
orqali VA to'g'ridan-to'g'ri + x orqali. Ikkinchi yo'lda gradient
kichraymaydi.
Boshqacha aytganda: blok butun natijani emas, faqat
o'zgarishni (qoldiqni) o'rganadi. Agar blok foydasiz bo'lsa,
u nolga yaqinlashadi va x o'zgarmasdan o'tadi.
blok = QoldiqBlok(16) x = torch.randn(2, 16, 32, 32) print("kirish :", tuple(x.shape)) print("chiqish:", tuple(blok(x).shape))
Kirish va chiqish bir xil o'lcham — aks holda + x ishlamaydi.
Kanal soni o'zgarganda 1x1 konvolyutsiya bilan moslashtiriladi.
BatchNorm nega bor
# parcha nn.BatchNorm2d(kanal)
Har bir batch uchun chiqishni normallashtiradi (o'rtacha 0, dispersiya 1). Natija:
- O'qitish tezroq boradi
- Kattaroq learning rate ishlatish mumkin
- Boshlang'ich vaznlarga kam bog'liq
Deyarli har bir zamonaviy CNN da bor.
Belgilar qanday o'rganiladi
O'qitilgan tarmoqning birinchi qatlam yadrolariga qarasangiz — ular 2-bobdagi qo'lda yozganimizga juda o'xshash: chet topish, burchak topish, rang o'tishlari.
Hech kim ularni dasturlamagan. Tarmoq o'zi topgan, chunki ular foydali.
Chuqurroq qatlamlar murakkabroq narsalarni o'rganadi:
| Qatlam | Nimani topadi |
|---|---|
| 1–2 | chetlar, burchaklar, ranglar |
| 3–4 | tekstura, oddiy shakllar |
| 5+ | obyekt qismlari (g'ildirak, ko'z) |
| oxirgi | butun obyekt |
Shuning uchun transfer learning ishlaydi: birinchi qatlamlar har qanday rasm uchun foydali. Buni keyingi bobda ishlatamiz.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.