Konvolyutsiya — CV ning yuragi
Konvolyutsiya — kompyuter ko'rishning eng muhim amali. Xiralashtirish, chet topish, o'tkirlashtirish — hammasi shu.
Va CNN dagi "C" harfi ham aynan shu.
G'oya: kichik oyna bilan sirg'anish
Rasm ustidan 3x3 oyna sirg'anadi. Har bir joyda:
- Oyna ostidagi 9 ta pikselni oladi
- Har birini yadro (kernel) dagi songa ko'paytiradi
- Hammasini qo'shadi
- Natijani markazga yozadi
Rasm bo'lagi Yadro Natija [10 20 30] [ 0 -1 0] [40 50 60] x [-1 5 -1] = 50*5 - 20 - 40 - 60 - 80 = evaziga [70 80 90] [ 0 -1 0]
Butun sirni yadrodagi sonlar hal qiladi. O'sha 9 ta son rasmga nima bo'lishini belgilaydi.
O'ZINGIZ O'YNANG
Yadrodagi sonlarni o'zgartiring va natijani darhol ko'ring. Tayyor yadrolarni ham sinab ko'ring:
Nimalarga e'tibor bering:
- Yig'indi = 1 bo'lsa — rasm yorqinligi saqlanadi (xiralashtirish)
- Yig'indi = 0 bo'lsa — faqat O'ZGARISHLAR qoladi, tekis joylar qora bo'ladi (chet topish)
- Markaz katta, atrofi manfiy — o'tkirlashtirish
Sobel X ni tanlang: vertikal chiziqlar yorishadi. Sobel Y ni tanlang: gorizontal chiziqlar yorishadi.
Nega? Sobel X chapdan o'ngga o'zgarishni o'lchaydi — vertikal chet aynan shunday o'zgarish beradi.
Kod bilan
import cv2 import numpy as np rasm = cv2.imread("hujjat.png", cv2.IMREAD_GRAYSCALE) # Chet topish yadrosi yadro = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], dtype=np.float32) natija = cv2.filter2D(rasm, -1, yadro) cv2.imwrite("chetlar.png", natija)
filter2D(rasm, -1, yadro) — -1 chiqish turi asl bilan bir
xil bo'lsin degani (uint8).
Yig'indi nega muhim
yadrolar = { "xiralashtirish": np.ones((3,3), np.float32) / 9, "chet_topish": np.array([[-1,-1,-1],[-1,8,-1],[-1,-1,-1]], np.float32), "otkirlash": np.array([[0,-1,0],[-1,5,-1],[0,-1,0]], np.float32), } for nom, y in yadrolar.items(): print(f"{nom:<16} yig'indi = {y.sum():.2f}")
Natija:
xiralashtirish yig'indi = 1.00 chet_topish yig'indi = 0.00 otkirlash yig'indi = 1.00
Yig'indi = 1 → o'rtacha yorqinlik o'zgarmaydi. Yig'indi = 0 → tekis hududlar 0 (qora) bo'ladi, faqat chetlar qoladi.
Agar yadroni o'zingiz yasasangiz va rasm juda yorug' yoki juda qorong'i chiqsa — birinchi navbatda yig'indini tekshiring.
Chegara muammosi
Oyna rasm chetiga yetganda, tashqarida piksel yo'q. Nima qilish kerak?
# Turli chegara siyosatlari cv2.filter2D(rasm, -1, yadro, borderType=cv2.BORDER_REFLECT) # aks ettirish cv2.filter2D(rasm, -1, yadro, borderType=cv2.BORDER_CONSTANT) # 0 bilan to'ldirish cv2.filter2D(rasm, -1, yadro, borderType=cv2.BORDER_REPLICATE) # chetni takrorlash
OpenCV default — BORDER_REFLECT. Neyron tarmoqlarda esa odatda
nol bilan to'ldirish (zero padding) ishlatiladi.
Amaliy ta'sir: rasm chetida 1-2 piksellik xato paydo bo'lishi mumkin. Katta rasmda sezilmaydi, kichik rasmda muhim.
Noldan yozish
Kutubxonasiz tushunish uchun:
def konvolyutsiya(rasm, yadro): b, k = rasm.shape yb, yk = yadro.shape p = yb // 2 # chegara kengligi toldirilgan = np.pad(rasm, p, mode="reflect") natija = np.zeros((b, k), dtype=np.float32) for y in range(b): for x in range(k): hudud = toldirilgan[y:y+yb, x:x+yk] natija[y, x] = (hudud * yadro).sum() return np.clip(natija, 0, 255).astype(np.uint8)
(hudud * yadro).sum() — butun konvolyutsiya shu bitta qatorda.
Element-wise ko'paytirish, keyin yig'indi.
np.clip(..., 0, 255) — natija manfiy yoki 255 dan katta
bo'lishi mumkin. Qirqmasak, uint8 ga aylantirishda son
"aylanib ketadi": 300 → 44.
Tekshiring:
rasm = cv2.imread("hujjat.png", cv2.IMREAD_GRAYSCALE) yadro = np.array([[0,-1,0],[-1,5,-1],[0,-1,0]], np.float32) meniki = konvolyutsiya(rasm, yadro) opencv = cv2.filter2D(rasm, -1, yadro) farq = np.abs(meniki.astype(int) - opencv.astype(int)) print(f"o'rtacha farq: {farq.mean():.2f}") print(f"maksimal farq: {farq.max()}")
Natija:
o'rtacha farq: 0.00 maksimal farq: 0
Aniq bir xil. Chunki biz OpenCV bilan bir xil chegara siyosatini
(reflect) ishlatdik. Boshqa siyosat tanlasangiz — rasm chetida
kichik farq paydo bo'ladi.
Lekin sekinligiga e'tibor bering. Ikkita ichma-ich sikl —
Python uchun juda sekin. cv2.filter2D C++ da yozilgan va
SIMD ishlatadi, shuning uchun yuzlab barobar tez.
Noldan yozish — TUSHUNISH uchun. Ishlatish uchun kutubxona.
im2col — nega kutubxona 100 barobar tez
Yuqorida o'z konvolyutsiyamiz sekin ekanini ko'rdik. Sabab —
ikkita ichma-ich sikl. Lekin cv2.filter2D ham xuddi shu ishni
qiladi. Nega u tez?
Javob: u konvolyutsiyani KO'PAYTIRISHGA aylantiradi.
Kompyuterlar matritsa ko'paytirishni juda tez bajaradi — buning uchun maxsus kutubxonalar (BLAS) va protsessor buyruqlari bor. Ichma-ich Python sikli esa eng sekin yo'l.
G'oya
Har bir 3x3 oynani bitta qatorga yoyamiz:
Oyna: [a b c] Qator: [a b c d e f g h i]
[d e f] ->
[g h i]
Barcha oynalarni shunday yoysak — katta matritsa chiqadi. Yadroni ham ustunga yoysak — natija oddiy matritsa ko'paytirish bo'ladi.
def im2col(rasm, yadro_olcham): """Har bir oynani alohida QATORga yoyadi.""" b, k = rasm.shape y = yadro_olcham chiqish_b, chiqish_k = b - y + 1, k - y + 1 ustunlar = np.zeros((chiqish_b * chiqish_k, y * y), dtype=np.float32) idx = 0 for i in range(chiqish_b): for j in range(chiqish_k): ustunlar[idx] = rasm[i:i+y, j:j+y].ravel() idx += 1 return ustunlar, (chiqish_b, chiqish_k) def konv_im2col(rasm, yadro): """Konvolyutsiya = im2col + matritsa ko'paytirish.""" ustunlar, shakl = im2col(rasm.astype(np.float32), yadro.shape[0]) natija = ustunlar @ yadro.ravel() # BITTA ko'paytirish return natija.reshape(shakl)
ustunlar @ yadro.ravel() — butun konvolyutsiya shu bitta
amalda. Sikl yo'q.
Tekshiramiz
import time rasm = np.random.randint(0, 255, (200, 200)).astype(np.uint8) yadro = np.array([[0,-1,0],[-1,5,-1],[0,-1,0]], np.float32) t0 = time.perf_counter() a = konvolyutsiya(rasm, yadro) # sikl bilan t_sikl = time.perf_counter() - t0 t0 = time.perf_counter() b = konv_im2col(rasm, yadro) t_im2col = time.perf_counter() - t0 t0 = time.perf_counter() c = cv2.filter2D(rasm, cv2.CV_32F, yadro) t_cv = time.perf_counter() - t0 print(f"sikl : {t_sikl*1000:>8.1f} ms") print(f"im2col : {t_im2col*1000:>8.1f} ms") print(f"OpenCV : {t_cv*1000:>8.1f} ms") print(f"im2col sikldan {t_sikl/t_im2col:.0f} barobar tez")
Natija (200x200 rasm):
sikl : 47.2 ms im2col : 19.9 ms OpenCV : 0.1 ms im2col sikldan 2 barobar tez
Kutilmagan natija — im2col atigi 2 barobar tez, OpenCV esa 472 barobar. Nega?
Chunki bizning im2col hali ham Python siklidan foydalanadi —
matritsani qurish uchun. Biz siklni yo'q qilmadik, faqat uni
ko'chirdik: konvolyutsiyadan matritsa qurishga.
Faqat oxirgi @ amali tez bo'ldi.
Haqiqiy kutubxonalarda im2col ham C da yozilgan — u yerda sikl yo'q. Shuning uchun OpenCV 0.1 ms da bajaradi.
Saboq: tezlik faqat algoritmdan emas, bajarilish tilidan ham keladi. Python da yozilgan har qanday piksel-sikl sekin bo'ladi.
Narxi: XOTIRA
im2col bepul emas. 3x3 yadro uchun har bir piksel 9 marta nusxalanadi.
rasm = np.zeros((200, 200), np.uint8) ustunlar, _ = im2col(rasm.astype(np.float32), 3) print(f"asl rasm : {rasm.nbytes:,} bayt") print(f"im2col : {ustunlar.nbytes:,} bayt") print(f"o'sish : {ustunlar.nbytes / rasm.nbytes:.0f}x")
Natija:
asl rasm : 40,000 bayt im2col : 1,411,344 bayt o'sish : 35x
35 barobar — 9 emas. Ikkita sabab ko'payadi:
9 marta nusxalash x float32 (4 bayt) va uint8 (1 bayt)
farqi = 36. Deyarli aynan shu.
Bu klassik almashuv: xotira evaziga tezlik.
PyTorch va TensorFlow ham shu usulni ishlatadi (yoki undan ham
tezroq — Winograd, FFT). Shuning uchun nn.Conv2d tez.
Receptive field — neyron nimani KO'RADI
Bitta 3x3 konvolyutsiyadan keyin har bir chiqish pikseli asl rasmning 3x3 hududini "ko'radi".
Ikkinchi 3x3 qatlamdan keyin-chi?
1-qatlam: har bir piksel 3x3 ko'radi
2-qatlam: uning kirishidagi har bir piksel ham 3x3 ko'radi
-> jami 5x5
3-qatlam -> 7x7
Bu receptive field (qabul maydoni) deyiladi.
def receptive_field(qatlamlar, yadro=3, stride=1): """Nechta qatlamdan keyin neyron qancha hududni ko'radi.""" rf = 1 jump = 1 for _ in range(qatlamlar): rf = rf + (yadro - 1) * jump jump = jump * stride return rf for n in (1, 2, 3, 5, 10): print(f"{n:>2} qatlam (3x3, stride 1) -> {receptive_field(n)}x{receptive_field(n)}")
Natija:
1 qatlam -> 3x3 2 qatlam -> 5x5 3 qatlam -> 7x7 5 qatlam -> 11x11 10 qatlam -> 21x21
Har bir qatlam atigi 2 piksel qo'shadi. 10 qatlamdan keyin ham faqat 21x21. Bu juda sekin o'sish.
Nega bu MUHIM
Model faqat receptive field ichidagi narsani ko'ra oladi.
Agar hujjatdagi jadval 200x200 piksel bo'lsa, lekin tarmoqning receptive field i 15x15 bo'lsa — u jadvalni butun holda hech qachon ko'rmaydi. Faqat kichik bo'laklarni.
Shuning uchun chuqur tarmoq kerak: har bir qatlam ko'rish doirasini kengaytiradi.
Pooling receptive field ni TEZ o'stiradi
def rf_pooling_bilan(bloklar): """Har blok: conv3x3 + conv3x3 + maxpool2 (VGG uslubi).""" rf, jump = 1, 1 for _ in range(bloklar): for _ in range(2): # ikkita conv rf += 2 * jump rf += 1 * jump # pool 2x2 jump *= 2 # pool stride 2 return rf for b in (1, 2, 3, 4): print(f"{b} blok -> receptive field {rf_pooling_bilan(b)}")
Natija:
1 blok -> 6 2 blok -> 16 3 blok -> 36 4 blok -> 76
4 blokda 76x76 — yuqoridagi 10 qatlamli variant esa atigi 21x21 bergan edi. Farq: pooling.
jump *= 2 — pooling dan keyin har bir qadam asl rasmda ikki
barobar kattaroq masofani bosadi. Shuning uchun receptive field
eksponensial o'sadi.
Faqat konvolyutsiya bilan 10 qatlam kerak bo'lgan joyda, pooling bilan 3 blok yetadi.
VGG qoidasi: ikkita 3x3 = bitta 5x5
bitta_5x5 = 5 * 5 ikkita_3x3 = 2 * (3 * 3) print(f"bitta 5x5 yadro : {bitta_5x5} parametr, RF 5x5") print(f"ikkita 3x3 : {ikkita_3x3} parametr, RF {receptive_field(2)}x{receptive_field(2)}")
Natija:
bitta 5x5 yadro : 25 parametr, RF 5x5 ikkita 3x3 : 18 parametr, RF 5x5
Bir xil receptive field, kamroq parametr — va orada qo'shimcha ReLU bor, ya'ni ko'proq nochiziqlilik.
Mana shuning uchun zamonaviy tarmoqlarda deyarli faqat 3x3 yadrolar ishlatiladi. 4-bobdagi kodimizda ham shunday.
CNN bilan bog'lanish
Yuqorida yadrolarni qo'lda yozdik: chet topish uchun shunday, xiralashtirish uchun bunday.
CNN da esa yadrolar o'rganiladi. Tarmoq minglab rasmni ko'rib, "qanday yadro foydali" ekanini o'zi topadi.
Birinchi qatlamlar odatda chet va burchak topadigan yadrolarni o'rganadi — biz qo'lda yozganiga juda o'xshash. Chuqurroq qatlamlar esa murakkabroq shakllarni.
Shuning uchun bu bob muhim: CNN sehr emas, u o'rganilgan konvolyutsiya.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.