Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Loyiha xaritasi
- 2.2. Ma'lumot quvuri
- 2.3. Model va o'rgatish
- 2.4. Taqqoslash va qaror
- 2.5. Test, sinf bo'yicha recall va Grad-CAM tekshiruvi
- 2.6. Topshirish paketi va hisobot
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Konfig, papkadan ma'lumot va tekshiruvlar
- Misol 2 — Trainer, eng yaxshi holat va seedlar
- Misol 3 — MLP va ko'pchilik bazaviysi bilan juftlashgan taqqoslash
- Misol 4 — Test, Grad-CAM, topshirish paketi va hisobot
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
22.14-dars: Amaliyot — sanoat nuqsonlarini aniqlash loyihasi
22-QISM — KOMPYUTER KO'RISH · 14-dars
1. Kirish va motivatsiya
22-qismda kompyuter ko'rishning asosiy qurilish bloklarini ko'rdik: rasm tensor sifatida, konvolyutsiya va pooling, birinchi CNN, ResNet bloki va GAP, BatchNorm2d, augmentatsiya, transfer learning, papkadan o'qiladigan Dataset, Grad-CAM, obyekt aniqlash va segmentatsiya. Endi ularni bitta ishlaydigan loyihaga yig'amiz.
Vazifa — ishlab chiqarish liniyasidagi metall sirt rasmlarida nuqson turini aniqlash. To'rt sinf bor: nuqsonsiz, tirnalish (to'g'ri, yorug' chiziq), dog' (dumaloq qorong'i joy) va yoriq (siniq, qorong'i chiziq). Ma'lumot nomutanosib: rasmlarning 55 foizi nuqsonsiz, yoriqlar esa atigi 10 foiz atrofida. Eng xavfli nuqson ham aynan yoriq — u keyinchalik detal sinishiga olib keladi.
Loyiha 21.12-darsdagi skeletni saqlaydi: konfig, testni qulflash, tez tekshiruvlar, Trainer va eng yaxshi holat, bir necha seed, bazaviy bilan juftlashgan taqqoslash, testni bir marta ochish va weights_only=True bilan yuklanadigan paket. Farq — endi har bosqich rasmga moslashgan: mean/std piksellar bo'yicha, augmentatsiya geometrik, model konvolyutsion, tekshiruv esa Grad-CAM bilan.
Ikki savolga alohida e'tibor beramiz. Birinchisi: qaysi xato qimmatroq? Nuqsonli detalni "nuqsonsiz" deb o'tkazib yuborish yolg'on signaldan ko'p marta qimmat — shuning uchun umumiy aniqlik emas, sinf bo'yicha recall asosiy o'lchov. Ikkinchisi: model to'g'ri sababga ko'ra to'g'rimi? Yuqori ball berib, nuqson o'rniga fon teksturasiga qarayotgan model ishlab chiqarishda birinchi yorug'lik o'zgarishida sinadi.
Real vaziyat. Keramik plitka zavodida nuqson klassifikatori testda 96% aniqlik ko'rsatdi va ishga tushirildi. Bir oydan keyin reklamatsiyalar ko'paydi: yoriqli plitkalarning uchdan biri "nuqsonsiz" deb o'tib ketayotgan edi. Aniqlik yuqori edi, chunki yoriqlar ma'lumotning 4 foizi edi. Sinf bo'yicha recall hisoblanganda yoriq uchun u 0.64 chiqdi — bu raqam dastlabki hisobotda umuman yo'q edi. Ushbu darsdagi loyiha aynan shu raqamni birinchi o'ringa qo'yadi.
Bu darsda to'liq kompyuter ko'rish loyihasini quramiz.
Bu darsda:
- Papkadan
Datasetgacha: bo'lish,mean/std, augmentatsiya - Kichik ResNet, sinf vazni,
Trainerva eng yaxshi holat - Bir necha seed
- MLP va ko'pchilik bazaviysi bilan juftlashgan taqqoslash
- Test bir marta: sinf bo'yicha recall va nuqsonni o'tkazib yuborish narxi
- Grad-CAM bilan tekshiruv
- Topshirish paketi va hisobot
- Tuzoqlar
ℹ Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
2. Nazariya — chuqur tushuntirish
2.1. Loyiha xaritasi
Konfig (frozen dataclass)
|
v
Ma'lumot: papka/<sinf>/<raqam>.png (22.10 uslubi)
|
v
Bo'lish 60/20/20, stratifikatsiya -> TEST QULFLANADI
|
v
mean/std FAQAT o'quvdan -> PapkaDataset (+ augmentatsiya faqat o'quvda)
|
v
Tez tekshiruvlar + bitta batch testi 21.9-bob
|
v
Kichik ResNet + sinf vazni + Trainer + EngYaxshisi (21.5, 22.6-22.7)
|
v
3 seed -> o'rtacha +- std
|
v
Juftlashgan CV: Ko'pchilik vs MLP vs CNN -> qaror 21.12-bob
|
v
Test BIR MARTA -> sinf bo'yicha recall -> Grad-CAM tekshiruvi 22.11-bob
|
v
Paket (weights_only) -> Bashoratchi -> hisobot21.12-darsdagi xarita bilan solishtirsak, tuzilma deyarli bir xil. Bu tasodif emas: yaxshi loyiha skeleti ma'lumot turiga bog'liq emas. Jadval o'rnida rasm, Embedding o'rnida konvolyutsiya, AUC o'rnida macro-recall — lekin "testni qulflash → tayyorlashni o'quvda qurish → tekshirish → o'rgatish → juftlashgan qaror → test bir marta → paket" zanjiri o'zgarmaydi.
Rasm loyihasiga xos ikkita qo'shimcha qadam bor: augmentatsiya (faqat o'quvda va faqat yorliqni saqlaydigan o'zgarishlar) va vizual tekshiruv (Grad-CAM). Ikkinchisi jadval loyihasida yo'q edi — u yerda belgilar ahamiyatini tekshirish mumkin edi, lekin "model qayerga qarayapti" savoli rasmda ancha o'tkirroq turadi.
Skelet 21.12 niki — rasmga xos qo'shimchalar augmentatsiya va Grad-CAM.
2.2. Ma'lumot quvuri
1. PAPKA:
papka/nuqsonsiz/00003.png, papka/yoriq/00017.png, ...
sinf = papka nomi; fayllar tartiblangan (sorted) -> deterministik
kichik ma'lumot: bir marta o'qib, xotirada saqlash (N, 1, H, W)
2. BO'LISH (stratifikatsiya, har sinfdan bir xil ulush):
ish / test = 80 / 20; ish -> o'quv / validatsiya = 75 / 25
yoriq (10%) har bo'lakda ~10% bo'lishi SHART
test indekslari shu yerda qulflanadi
3. NORMALLASH:
mean, std = x[o'quv].mean(), x[o'quv].std() <- FAQAT o'quvdan
x_norm = (x - mean) / std <- hamma bo'lakka
paketga ham aynan shu ikki son yoziladi
4. AUGMENTATSIYA (faqat o'quvda, 22.8):
gorizontal / vertikal akslantirish, 90 gradusga burish
- sirt nuqsonining YO'NALISHI yo'q -> yorliq o'zgarmaydi
- piksellar to'plami o'zgarmaydi (faqat joyi) -> mean/std buzilmaydi
yorqinlikni kuchli o'zgartirish XAVFLI:
tirnalish (yorug') va yoriq (qorong'i) farqi aynan yorqinlikda
5. TEKSHIRUV:
normallangan o'quv: o'rtacha ~0, std ~1
boshlang'ich loss ~ ln(4) = 1.386
32 namunani yodlay oladimi (bitta batch testi)Stratifikatsiya nomutanosib ma'lumotda majburiy. Oddiy tasodifiy bo'lishda 280 ta test rasmida yoriqlar soni o'rtacha 27 atrofida, lekin taxminan 17 dan 37 gacha tebranishi mumkin (binomial standart og'ish ~5) — va yoriq recall i shu kichik songa bog'liq. 1-misolda uchala bo'lakda ham yoriq ulushi 0.096-0.100 oralig'ida chiqdi.
Augmentatsiyani tanlashda yagona savol: "bu o'zgarish yorliqni saqlaydimi?". Sirt nuqsoni uchun yo'nalish ahamiyatsiz — akslantirish va 90 gradusga burish xavfsiz. 1-misol buni tekshirdi: augmentatsiya faqat piksellar joyini o'zgartiradi, ularning to'plamini emas. Yorqinlikni o'zgartirish esa bu ma'lumotda xavfli: tirnalishni yoriqdan ajratadigan belgi aynan yorug' yoki qorong'iligi.
mean/std va augmentatsiya faqat o'quvda; augmentatsiya faqat yorliqni saqlaydigan o'zgarishlardan tuziladi.
2.3. Model va o'rgatish
KICHIK ResNet (22.6-22.7):
conv3x3(1 -> 8) + BN + ReLU 24x24
ResBlok(8) 24x24 (x + F(x))
pastga: conv3x3 stride 2 (8 -> 16) 12x12
ResBlok(16)
pastga: conv3x3 stride 2 (16 -> 32) 6x6
ResBlok(32)
GAP -> Linear(32 -> 4)
~30 ming parametr
NOMUTANOSIBLIK - SINF VAZNI:
w_k = N / (K * n_k) (sklearn "balanced" bilan bir xil)
F.cross_entropy(logit, y, weight=w)
kam sinf xatosi qimmatroq -> recall muvozanatlashadi
METRIKA - MACRO-RECALL:
har sinf recall ining o'rtachasi
"hammasi nuqsonsiz" modeli: 0.25 (aniqlik esa 0.55!)
TRAINER 21.5-bob:
AdamW + CosineAnnealingLR
EngYaxshisi(monitor="val_mrecall", sabr=4)
-> copy.deepcopy(state_dict()) va oxirida tiklash
SEEDLAR:
3 seed -> o'rtacha +- std
har seedda: model init, DataLoader generator, augmentatsiya generatorModel ataylab kichik: ~30 ming parametr va uchta ResNet bloki. 24x24 rasm va ming atrofidagi misol uchun bu yetarli, katta model esa faqat yodlash xavfini oshiradi. Stride 2 li konvolyutsiya pooling o'rnida o'lchamni kichraytiradi, oxirida GAP — klassifikatsiya uchun joylashuv kerak emas (22.12-darsdagi lokalizatsiyadan farqli).
Metrika tanlovi — loyihaning eng muhim qarori. Aniqlik bu ma'lumotda aldaydi: "hammasi nuqsonsiz" modeli 55% aniqlik oladi. Macro-recall har sinf recall ini teng vaznda o'rtachalaydi, shuning uchun u "hamma yoriqlarni o'tkazib yuborish" ni darhol jazolaydi — o'sha bazaviy model uchun u atigi 0.25.
Eng yaxshi holat nega kerakligini 2-misol ko'rsatdi: eng yaxshi val macro-recall 0.8711 8-davrda bo'ldi, oxirgi davrda esa 0.8600 — deepcopy siz hisobot va paket oxirgi davr modeliniki bo'lardi. 3-davrda esa val macro-recall vaqtincha 0.4424 gacha tushgan edi — BatchNorm li kichik tarmoqlarda boshlang'ich davrlardagi bunday sakrashlar odatiy.
Nomutanosib sinflarda metrika macro-recall, loss esa sinf vazni bilan; eng yaxshi holat deepcopy bilan saqlanadi.
2.4. Taqqoslash va qaror
BIR XIL 3 TA FOLD (ish = o'quv + validatsiya, test TEGILMAYDI):
Ko'pchilik - doim "nuqsonsiz" (eng sodda)
MLP - piksellar tekislanadi (fazoviy tuzilma yo'q)
CNN - kichik ResNet
HAR FOLDDA:
mean/std - fold o'quvidan
belgilangan davrlar soni, erta to'xtatishsiz
(fold validatsiyasi ham tanlashga, ham baholashga ishlatilmasin)
JUFTLASHGAN FARQ:
d_f = mrecall_CNN,f - mrecall_MLP,f
SE = std(d) / sqrt(3)
sezilarli: |mean(d)| > 2 * SE
QOIDA: eng yaxshisidan SEZILARLI yomon bo'lmagan ENG SODDA model
soddalik tartibi: Ko'pchilik < MLP < CNNMLP — halol bazaviy: u ham tarmoq, ham o'sha optimizator va sinf vazni bilan o'rgatiladi. Farqi bitta — u rasmni 576 ta mustaqil son deb ko'radi va qo'shni piksellar orasidagi bog'liqlikdan foydalana olmaydi. Nuqson rasmning istalgan joyida va istalgan yo'nalishda bo'lishi mumkin; konvolyutsiyaning og'irlik bo'lishishi 22.2-bob aynan shu holat uchun yaratilgan.
3-misolda CNN uchala foldda ham MLP dan yaxshi chiqdi: o'rtacha farq +0.2132, 2*SE = 0.0646. Qoida bo'yicha tanlov — CNN. Diqqat qiling: bu natija oldindan ma'lum emas edi. Agar nuqsonlar doim rasmning bir joyida bo'lganda yoki farq faqat umumiy yorqinlikda bo'lganda, MLP ham yetarli bo'lishi mumkin edi — va qoida uni tanlardi.
Uch fold — kam. SE ning o'zi ham noaniq bahoga ega. Bu yerda farq 2*SE dan uch barobardan ko'p bo'lgani uchun xulosa ishonchli; farq chegaraga yaqin bo'lganda 5 fold yoki takroriy CV kerak bo'ladi (18-qism).
Qarorni juftlashgan farq va SE belgilaydi; soddaroq model sezilarli yomon bo'lmasa — u tanlanadi.
2.5. Test, sinf bo'yicha recall va Grad-CAM tekshiruvi
TEST BIR MARTA:
macro-recall + aniqlik
SINF BO'YICHA RECALL - asosiy jadval
chalkashlik matritsasi qatorlari: yoriq -> [nuqsonsiz, tirnalish, dog', yoriq]
XATO NARXI (biznes tili):
nuqsonni o'tkazib yuborish = P(bashorat "nuqsonsiz" | nuqsonli)
-> nuqsonli detal mijozga ketadi: reklamatsiya, xavfsizlik
yolg'on signal = P(bashorat nuqsonli | nuqsonsiz)
-> sog'lom detal qo'lda tekshiruvga: ish vaqti
odatda o'tkazib yuborish 10-100 marta qimmatroq
GRAD-CAM TEKSHIRUVI 22.11-bob:
to'g'ri topilgan nuqsonli test rasmlari uchun
CAM energiyasining qancha qismi nuqson niqobida (+1 piksel)?
solishtirish: niqob yuzasi (tasodifiy qarash shuncha beradi)
nisbat > 2 -> model nuqsonga qarayapti
nisbat ~ 1 -> model fonga yoki teksturaga qarayapti (xavfli!)Test oxirida bir marta ochiladi va qarorni o'zgartirmaydi. Uning vazifasi — haqiqiy xatolar tarkibini ko'rsatish. 4-misolda test macro-recall 0.9003 chiqdi, lekin sinf bo'yicha jadval muhimroq narsani ko'rsatdi: yoriq recall i 0.778 — to'rt sinf ichida eng past. 27 ta yoriqdan 6 tasi "nuqsonsiz" deb o'tib ketgan. Umuman olganda nuqsonli detallarning 0.079 qismi o'tkazib yuborilgan.
Bu raqam ishlab chiqarish uchun hisobotdagi eng muhim qator. Uni kamaytirishning bir necha yo'li bor: qaror chegarasini o'zgartirish (masalan, "nuqsonsiz" ehtimolligi 0.9 dan past bo'lsa — qo'lda tekshiruv), yoriq uchun sinf vaznini oshirish, yoriq misollarini ko'proq yig'ish. Qaysi biri to'g'ri — o'tkazib yuborish va yolg'on signal narxlariga bog'liq; bu biznes bilan birga hal qilinadigan savol.
Grad-CAM tekshiruvi modelning nima uchun to'g'ri ekanini tekshiradi. Sintetik ma'lumotda har nuqsonning niqobi ma'lum, shuning uchun "model nuqsonga qarayaptimi?" savoliga raqam bilan javob berish mumkin. 4-misolda CAM energiyasi niqob sohasida uning yuzasiga nisbatan 2.4 barobar zich chiqdi — model asosan nuqson piksellariga qarayapti. Real loyihada niqob bo'lmasa, bir necha o'nlab CAM rasmini ko'z bilan ko'rish ham xuddi shu maqsadga xizmat qiladi.
Hisobotda umumiy ball emas, sinf bo'yicha recall va o'tkazib yuborish ulushi; Grad-CAM esa to'g'ri javob to'g'ri sababga ko'ra ekanini tasdiqlaydi.
2.6. Topshirish paketi va hisobot
paket = {
"format": 1,
"konfig": asdict(k), # kanal, davrlar, ...
"holat": model.state_dict(),
"kirish": {"mean": 0.4985, "std": 0.0926, "R": 24, "kanal": 1},
"sinflar": ["nuqsonsiz", "tirnalish", "dog'", "yoriq"], # TARTIB muhim!
"metrika": {"val_mrecall": ..., "test_mrecall": ...,
"test_recall": {"yoriq": ..., ...}},
"versiyalar": {"torch": str(torch.__version__), "python": ...},
"nazorat": {"x": 8 ta xom rasm, "logit": ularning logitlari},
}
torch.save(paket, yol)
torch.load(yol, weights_only=True) # hammasi xavfsiz turlar
BASHORATCHI:
paketdan model quradi -> eval() -> nazorat logitlarini qayta hisoblab tekshiradi
bashorat(fayllar): PNG -> [0, 1] -> (x - mean) / std -> softmax -> (sinf, ehtimol)
HISOBOT:
VAZIFA, METRIKA (macro-recall - nega), DIZAYN (bo'lish, test bir marta)
QAROR: juftlashgan CV (CNN vs MLP, farq +- SE)
TEST: macro-recall, SINF BO'YICHA recall, o'tkazib yuborish ulushi
TEKSHIRUV: Grad-CAM nisbati
PAKET: format, weights_only, nazorat
CHEKLOVLAR: sintetik ma'lumot, bitta tekstura, kamera sharoitlariPaketdagi eng nozik element — sinflar ro'yxati va uning tartibi. Model chiqishining 3-indeksi "yoriq" ekanini faqat shu ro'yxat aytadi. Papka nomlaridan tartibni qayta tiklashga urinish (masalan, sorted(os.listdir(...))) boshqa kompyuterda yoki yangi sinf qo'shilganda boshqacha natija berishi mumkin. 1-misolda papkalar alifbo tartibida ['dog', 'nuqsonsiz', 'tirnalish', 'yoriq'] chiqdi — model tartibi esa boshqa.
Nazorat namunasi 21.12-darsdagi kabi paketning o'zini o'zi tekshirishini ta'minlaydi. Bu yerda muhim tafsilot: nazorat xom rasmlarda (normallashdan oldin) saqlanadi, shuning uchun u mean/std ning ham to'g'ri yuklanganini tekshiradi.
Hisobot faqat yaxshi raqamlardan iborat bo'lmasligi kerak. 4-misoldagi Bashoratchi namunasida yoriqli rasm "nuqsonsiz" deb bashorat qilindi (0.649 ishonch bilan) — va bu test jadvalidagi eng past recall bilan mos keladi. Hisobotdagi "ZAIF JOY" qatori aynan shu uchun bor.
Paketda sinflar ro'yxati, mean/std va nazorat namunasi — ularsiz model boshqa joyda boshqacha ishlaydi.
2.7. Tuzoqlar
Asosiy tuzoqlar: aniqlikni nomutanosib sinflarda asosiy metrika qilish; stratifikatsiyasiz bo'lish (kam sinf testda tasodifiy sonda); mean/std ni butun ma'lumotda hisoblash; validatsiya va testga augmentatsiya qo'llash; yorliqni o'zgartiradigan augmentatsiya (bu yerda — kuchli yorqinlik o'zgarishi); sinf vaznisiz o'rgatib, kam sinfni "yo'qotish"; eng yaxshi holatni havola bilan saqlash; bitta seed natijasini hisobot qilish; bazaviyni boshqa foldlarda yoki boshqa sozlamalarda baholash; fold validatsiyasini ham erta to'xtatishga, ham baholashga ishlatish; testni bir necha marta ochish; sinflar tartibini papka nomlaridan qayta tiklash; paketga TorchVersion yoki Path obyektini qo'yish; Grad-CAM siz "model ishlayapti" deb xulosa qilish; hisobotda faqat umumiy ballni ko'rsatish.
3. Tez ma'lumotnoma
k = Konfig()
seed_everything(k.seed)
fayllar, y, x = papkadan_oqi(papka) # (N, 1, R, R)
ish, te = bolish(y, 0.2, k.seed) # test QULF
tr_i, va_i = bolish(y[ish], 0.25, k.seed); tr, va = ish[tr_i], ish[va_i]
mean, std = x[tr].mean().item(), x[tr].std().item() # faqat o'quvdan
ds_tr = PapkaDataset(x[tr], y[tr], mean, std, augment=True, seed=k.seed)
ds_va = PapkaDataset(x[va], y[va], mean, std)
model = NuqsonCNN(k.kanal)
tarix, eyx = orgat(model, k, ds_tr, ds_va, y[tr]) # sinf vazni + EngYaxshisi
# juftlashgan CV: d = mrecall_CNN - mrecall_MLP; |mean(d)| > 2 * SE
p = model((x[te] - mean) / std).argmax(1) # test BIR MARTA
recall = {s: (p[y[te] == i] == i).float().mean() for i, s in enumerate(SINFLAR)}
cam, _ = grad_cam(model, (x[te] - mean) / std) # tekshiruv
torch.save(paket, yol); Bashoratchi(yol) # weights_only=TrueAmaliyot xulosasi
konfig -> papka -> stratifikatsiyali bo'lish (test qulf) -> mean/std o'quvdan
augmentatsiya faqat o'quvda va faqat yorliqni saqlaydigan
kichik ResNet + sinf vazni + Trainer + EngYaxshisi (deepcopy) -> 3 seed
juftlashgan CV: Ko'pchilik < MLP < CNN -> eng sodda munosib model
test bir marta: sinf bo'yicha recall, o'tkazib yuborish ulushi
Grad-CAM tekshiruvi -> paket (sinflar, mean/std, nazorat) -> hisobot4. Batafsil misollar
Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
Misol 1 — Konfig, papkadan ma'lumot va tekshiruvlar
"""1-qadam: ma'lumot quvuri (papka -> Dataset) va o'rgatishdan oldingi tekshiruvlar."""
import math
import random
import shutil
import tempfile
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from PIL import Image
from torch.utils.data import DataLoader, Dataset
SINFLAR = ["nuqsonsiz", "tirnalish", "dog'", "yoriq"]
ULUSHLAR = [0.55, 0.20, 0.15, 0.10] # nomutanosib
R = 24
@dataclass(frozen=True)
class Konfig:
seed: int = 42
n: int = 1400
lr: float = 3e-3
batch: int = 64
davrlar: int = 10
kanal: int = 8
weight_decay: float = 1e-4
sabr: int = 4
def seed_everything(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
def yarat(n, seed):
"""Sintetik sanoat nuqsonlari: tekstura + tirnalish / dog' / yoriq.
Qaytaradi: rasmlar (n, R, R) [0, 1], yorliqlar (n,), niqoblar (n, R, R)."""
rng = np.random.default_rng(seed)
yy, xx = np.mgrid[0:R, 0:R].astype(float)
y = rng.choice(4, size=n, p=ULUSHLAR)
rasmlar = np.zeros((n, R, R))
niqoblar = np.zeros((n, R, R), dtype=bool)
for i in range(n):
th, f = rng.uniform(0, np.pi), rng.uniform(0.15, 0.3)
fz = rng.uniform(0, 6.3)
img = 0.5 + 0.08 * np.sin(2 * np.pi * f * (xx * np.cos(th)
+ yy * np.sin(th)) + fz)
img += rng.normal(0, 0.07, (R, R))
kontrast = rng.uniform(0.1, 0.25)
m = np.zeros((R, R), dtype=bool)
if y[i] == 1: # tirnalish: to'g'ri, yorug'
y0, x0 = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, np.pi)
for t in np.linspace(-8, 8, 60):
py = int(round(y0 + t * np.sin(a)))
px = int(round(x0 + t * np.cos(a)))
if 0 <= py < R and 0 <= px < R:
m[py, px] = True
img[m] += kontrast
elif y[i] == 2: # dog': dumaloq, qorong'i
cy, cx = rng.uniform(5, R - 5, 2)
r = rng.uniform(1.8, 3.5)
g = np.exp(-((yy - cy) ** 2 + (xx - cx) ** 2) / (2 * r ** 2))
img -= kontrast * g
m = g > 0.4
elif y[i] == 3: # yoriq: siniq, qorong'i
py, px = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, 2 * np.pi)
for _ in range(16):
a += rng.normal(0, 0.7)
py, px = py + np.sin(a), px + np.cos(a)
if 0 <= int(py) < R and 0 <= int(px) < R:
m[int(py), int(px)] = True
img[m] -= kontrast
rasmlar[i] = np.clip(img, 0, 1)
niqoblar[i] = m
return rasmlar, y, niqoblar
def papkaga_yoz(papka, rasmlar, y):
"""ImageFolder uslubi: papka/<sinf>/<raqam>.png"""
for nom in SINFLAR:
(papka / nom.replace("'", "")).mkdir(parents=True, exist_ok=True)
for i, (r, k) in enumerate(zip(rasmlar, y)):
Image.fromarray((r * 255).round().astype(np.uint8)).save(
papka / SINFLAR[k].replace("'", "") / f"{i:05d}.png")
def papkadan_oqi(papka):
"""Fayl ro'yxati, yorliqlar va rasmlar (bir marta o'qib, xotirada)."""
fayllar, yorliqlar = [], []
for k, nom in enumerate(SINFLAR):
for f in sorted((papka / nom.replace("'", "")).glob("*.png")):
fayllar.append(f)
yorliqlar.append(k)
x = np.stack([np.asarray(Image.open(f), dtype=np.float32) / 255.0
for f in fayllar])
return fayllar, np.array(yorliqlar), torch.tensor(x)[:, None]
def bolish(y, ulush, seed):
"""Stratifikatsiyalangan bo'lish: har sinfdan bir xil ulush."""
rng = np.random.default_rng(seed)
a, b = [], []
for k in np.unique(y):
idx = rng.permutation(np.flatnonzero(y == k))
m = int(round(ulush * len(idx)))
b.extend(idx[:m])
a.extend(idx[m:])
return np.sort(a), np.sort(b)
class PapkaDataset(Dataset):
"""Xotiradagi rasmlar; mean/std tashqaridan (o'quvdan) beriladi."""
def __init__(self, x, y, mean, std, augment=False, seed=0):
self.x, self.y = x, torch.as_tensor(y)
self.mean, self.std, self.augment = mean, std, augment
self.g = torch.Generator().manual_seed(seed)
def __len__(self):
return len(self.y)
def __getitem__(self, i):
x = self.x[i]
if self.augment: # nuqson yo'nalishsiz
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-1)
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-2)
k = int(torch.randint(0, 4, (1,), generator=self.g))
x = torch.rot90(x, k, (-2, -1))
return (x - self.mean) / self.std, self.y[i]
class ResBlok(nn.Module):
def __init__(self, k):
super().__init__()
self.c1 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b1 = nn.BatchNorm2d(k)
self.c2 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b2 = nn.BatchNorm2d(k)
def forward(self, x):
h = F.relu(self.b1(self.c1(x)))
return F.relu(x + self.b2(self.c2(h)))
def pastga(k_in, k_out):
return nn.Sequential(nn.Conv2d(k_in, k_out, 3, stride=2, padding=1,
bias=False),
nn.BatchNorm2d(k_out), nn.ReLU())
class NuqsonCNN(nn.Module):
"""Kichik ResNet: 24 -> 12 -> 6, GAP, 4 sinf."""
def __init__(self, k=16):
super().__init__()
self.tana = nn.Sequential(
nn.Conv2d(1, k, 3, padding=1, bias=False), nn.BatchNorm2d(k),
nn.ReLU(), ResBlok(k), pastga(k, 2 * k), ResBlok(2 * k),
pastga(2 * k, 4 * k), ResBlok(4 * k))
self.bosh = nn.Linear(4 * k, len(SINFLAR))
def forward(self, x):
return self.bosh(self.tana(x).mean((2, 3)))
class MLP(nn.Module):
"""Bazaviy: piksellar tekislanadi, fazoviy tuzilma e'tiborsiz."""
def __init__(self, yashirin=128):
super().__init__()
self.tarmoq = nn.Sequential(
nn.Flatten(), nn.Linear(R * R, yashirin), nn.ReLU(),
nn.Linear(yashirin, len(SINFLAR)))
def forward(self, x):
return self.tarmoq(x)
def main() -> None:
k = Konfig()
seed_everything(k.seed)
papka = Path(tempfile.mkdtemp(prefix="nuqson_"))
try:
print("=== 1. Ma'lumot: papkaga yozish va qayta o'qish ===")
rasmlar, y, _ = yarat(k.n, seed=7)
papkaga_yoz(papka, rasmlar, y)
fayllar, yorliq, x = papkadan_oqi(papka)
print(f" papkalar: {sorted(p.name for p in papka.iterdir())}")
print(f" fayllar: {len(fayllar)}, namuna: "
f"{fayllar[0].relative_to(papka).as_posix()}")
print(f" xotiradagi tensor: {tuple(x.shape)}, "
f"[{x.min().item():.2f}, {x.max().item():.2f}]")
soni = np.bincount(yorliq, minlength=4)
for nom, s in zip(SINFLAR, soni):
print(f" {nom:<10} {s:>5} ({s / len(yorliq):.1%})")
print("\n=== 2. Bo'lish: 60/20/20, stratifikatsiya, test QULF ===")
ish, te = bolish(yorliq, 0.2, k.seed)
tr_i, va_i = bolish(yorliq[ish], 0.25, k.seed)
tr, va = ish[tr_i], ish[va_i]
print(f" kesishma yo'q: {len(set(tr) | set(va) | set(te)) == len(yorliq)}")
for nom, ii in [("o'quv", tr), ("validatsiya", va), ("test", te)]:
print(f" {nom:<12} {len(ii):>5} yoriq ulushi "
f"{(yorliq[ii] == 3).mean():.3f}")
print("\n=== 3. mean/std FAQAT o'quvdan ===")
mean, std = x[tr].mean().item(), x[tr].std().item()
print(f" o'quv: mean {mean:.4f}, std {std:.4f}")
ds_tr = PapkaDataset(x[tr], yorliq[tr], mean, std, augment=True,
seed=k.seed)
ds_va = PapkaDataset(x[va], yorliq[va], mean, std)
x0, y0 = ds_va[0]
xs = torch.stack([ds_tr[i][0] for i in range(len(ds_tr))])
print(f" bitta namuna: {tuple(x0.shape)}, {x0.dtype}, "
f"yorliq {int(y0)}")
print(f" normallangan o'quv: o'rtacha {xs.mean().item():+.3f}, "
f"std {xs.std().item():.3f}")
print("\n=== 4. Augmentatsiya: yorliqni saqlaydimi? ===")
asl = (x[tr][0, 0] - mean) / std
variantlar = [ds_tr[0][0][0] for _ in range(8)]
farqli = sum(not torch.allclose(v, asl) for v in variantlar)
print(f" 8 marta o'qildi: {farqli} tasi asl rasmdan farq qiladi")
piksellar = asl.flatten().sort().values
bir_xil = all(torch.equal(v.flatten().sort().values, piksellar)
for v in variantlar)
print(f" piksellar to'plami o'zgarmadi (faqat joyi): {bir_xil}")
print(" flip va 90 gradusli burish nuqson turini o'zgartirmaydi")
print("\n=== 5. Tez tekshiruvlar ===")
g = torch.Generator().manual_seed(k.seed)
dl = DataLoader(ds_tr, batch_size=k.batch, shuffle=True, generator=g)
xb, yb = next(iter(dl))
model = NuqsonCNN(k.kanal)
model.eval()
with torch.no_grad():
ch = model(xb)
boshl = F.cross_entropy(ch, yb).item()
param = sum(p.numel() for p in model.parameters())
tekshiruvlar = [
("kirish shakli", tuple(xb.shape),
tuple(xb.shape)[1:] == (1, R, R)),
("chiqish shakli", tuple(ch.shape),
tuple(ch.shape) == (k.batch, 4)),
("yorliqlar", sorted(set(yb.tolist())),
set(yb.tolist()) <= {0, 1, 2, 3}),
("boshlang'ich loss", round(boshl, 3),
abs(boshl - math.log(4)) < 0.3),
("parametrlar", param, param > 0),
]
print(f" {'tekshiruv':<18} {'qiymat':>18} {'holat':>6}")
for nom, q, ok in tekshiruvlar:
print(f" {nom:<18} {str(q):>18} {'OK' if ok else 'XATO':>6}")
print(f" kutilgan boshlang'ich loss ln(4) = {math.log(4):.3f}")
print("\n=== 6. Bitta batch testi ===")
model.train()
opt = torch.optim.Adam(model.parameters(), lr=1e-2)
xk, yk = xb[:32], yb[:32]
for _ in range(150):
opt.zero_grad()
loss = F.cross_entropy(model(xk), yk)
loss.backward()
opt.step()
print(f" 32 namuna, 150 qadam: loss {loss.item():.4f}")
print(f" natija: {'OTDI' if loss.item() < 0.05 else 'OTMADI'}")
print(" ⭐ Quvur tayyor - o'rgatishga o'tish mumkin")
finally:
shutil.rmtree(papka, ignore_errors=True)
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Ma'lumot: papkaga yozish va qayta o'qish ===
papkalar: ['dog', 'nuqsonsiz', 'tirnalish', 'yoriq']
fayllar: 1400, namuna: nuqsonsiz/00003.png
xotiradagi tensor: (1400, 1, 24, 24), [0.00, 1.00]
nuqsonsiz 770 (55.0%)
tirnalish 280 (20.0%)
dog' 213 (15.2%)
yoriq 137 (9.8%)
=== 2. Bo'lish: 60/20/20, stratifikatsiya, test QULF ===
kesishma yo'q: True
o'quv 840 yoriq ulushi 0.098
validatsiya 280 yoriq ulushi 0.100
test 280 yoriq ulushi 0.096
=== 3. mean/std FAQAT o'quvdan ===
o'quv: mean 0.4985, std 0.0926
bitta namuna: (1, 24, 24), torch.float32, yorliq 0
normallangan o'quv: o'rtacha +0.000, std 1.000
=== 4. Augmentatsiya: yorliqni saqlaydimi? ===
8 marta o'qildi: 8 tasi asl rasmdan farq qiladi
piksellar to'plami o'zgarmadi (faqat joyi): True
flip va 90 gradusli burish nuqson turini o'zgartirmaydi
=== 5. Tez tekshiruvlar ===
tekshiruv qiymat holat
kirish shakli (64, 1, 24, 24) OK
chiqish shakli (64, 4) OK
yorliqlar [0, 1, 2, 3] OK
boshlang'ich loss 1.429 OK
parametrlar 30492 OK
kutilgan boshlang'ich loss ln(4) = 1.386
=== 6. Bitta batch testi ===
32 namuna, 150 qadam: loss 0.0002
natija: OTDI
⭐ Quvur tayyor - o'rgatishga o'tish mumkinNima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — Trainer, eng yaxshi holat va seedlar
"""2-qadam: Trainer, eng yaxshi holat (deepcopy) va bir necha seed."""
import copy
import random
from dataclasses import dataclass, replace
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
SINFLAR = ["nuqsonsiz", "tirnalish", "dog'", "yoriq"]
ULUSHLAR = [0.55, 0.20, 0.15, 0.10] # nomutanosib
R = 24
@dataclass(frozen=True)
class Konfig:
seed: int = 42
n: int = 1400
lr: float = 3e-3
batch: int = 64
davrlar: int = 10
kanal: int = 8
weight_decay: float = 1e-4
sabr: int = 4
def seed_everything(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
def yarat(n, seed):
"""Sintetik sanoat nuqsonlari: tekstura + tirnalish / dog' / yoriq.
Qaytaradi: rasmlar (n, R, R) [0, 1], yorliqlar (n,), niqoblar (n, R, R)."""
rng = np.random.default_rng(seed)
yy, xx = np.mgrid[0:R, 0:R].astype(float)
y = rng.choice(4, size=n, p=ULUSHLAR)
rasmlar = np.zeros((n, R, R))
niqoblar = np.zeros((n, R, R), dtype=bool)
for i in range(n):
th, f = rng.uniform(0, np.pi), rng.uniform(0.15, 0.3)
fz = rng.uniform(0, 6.3)
img = 0.5 + 0.08 * np.sin(2 * np.pi * f * (xx * np.cos(th)
+ yy * np.sin(th)) + fz)
img += rng.normal(0, 0.07, (R, R))
kontrast = rng.uniform(0.1, 0.25)
m = np.zeros((R, R), dtype=bool)
if y[i] == 1: # tirnalish: to'g'ri, yorug'
y0, x0 = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, np.pi)
for t in np.linspace(-8, 8, 60):
py = int(round(y0 + t * np.sin(a)))
px = int(round(x0 + t * np.cos(a)))
if 0 <= py < R and 0 <= px < R:
m[py, px] = True
img[m] += kontrast
elif y[i] == 2: # dog': dumaloq, qorong'i
cy, cx = rng.uniform(5, R - 5, 2)
r = rng.uniform(1.8, 3.5)
g = np.exp(-((yy - cy) ** 2 + (xx - cx) ** 2) / (2 * r ** 2))
img -= kontrast * g
m = g > 0.4
elif y[i] == 3: # yoriq: siniq, qorong'i
py, px = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, 2 * np.pi)
for _ in range(16):
a += rng.normal(0, 0.7)
py, px = py + np.sin(a), px + np.cos(a)
if 0 <= int(py) < R and 0 <= int(px) < R:
m[int(py), int(px)] = True
img[m] -= kontrast
rasmlar[i] = np.clip(img, 0, 1)
niqoblar[i] = m
return rasmlar, y, niqoblar
def bolish(y, ulush, seed):
"""Stratifikatsiyalangan bo'lish: har sinfdan bir xil ulush."""
rng = np.random.default_rng(seed)
a, b = [], []
for k in np.unique(y):
idx = rng.permutation(np.flatnonzero(y == k))
m = int(round(ulush * len(idx)))
b.extend(idx[:m])
a.extend(idx[m:])
return np.sort(a), np.sort(b)
class PapkaDataset(Dataset):
"""Xotiradagi rasmlar; mean/std tashqaridan (o'quvdan) beriladi."""
def __init__(self, x, y, mean, std, augment=False, seed=0):
self.x, self.y = x, torch.as_tensor(y)
self.mean, self.std, self.augment = mean, std, augment
self.g = torch.Generator().manual_seed(seed)
def __len__(self):
return len(self.y)
def __getitem__(self, i):
x = self.x[i]
if self.augment: # nuqson yo'nalishsiz
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-1)
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-2)
k = int(torch.randint(0, 4, (1,), generator=self.g))
x = torch.rot90(x, k, (-2, -1))
return (x - self.mean) / self.std, self.y[i]
class ResBlok(nn.Module):
def __init__(self, k):
super().__init__()
self.c1 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b1 = nn.BatchNorm2d(k)
self.c2 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b2 = nn.BatchNorm2d(k)
def forward(self, x):
h = F.relu(self.b1(self.c1(x)))
return F.relu(x + self.b2(self.c2(h)))
def pastga(k_in, k_out):
return nn.Sequential(nn.Conv2d(k_in, k_out, 3, stride=2, padding=1,
bias=False),
nn.BatchNorm2d(k_out), nn.ReLU())
class NuqsonCNN(nn.Module):
"""Kichik ResNet: 24 -> 12 -> 6, GAP, 4 sinf."""
def __init__(self, k=16):
super().__init__()
self.tana = nn.Sequential(
nn.Conv2d(1, k, 3, padding=1, bias=False), nn.BatchNorm2d(k),
nn.ReLU(), ResBlok(k), pastga(k, 2 * k), ResBlok(2 * k),
pastga(2 * k, 4 * k), ResBlok(4 * k))
self.bosh = nn.Linear(4 * k, len(SINFLAR))
def forward(self, x):
return self.bosh(self.tana(x).mean((2, 3)))
class MLP(nn.Module):
"""Bazaviy: piksellar tekislanadi, fazoviy tuzilma e'tiborsiz."""
def __init__(self, yashirin=128):
super().__init__()
self.tarmoq = nn.Sequential(
nn.Flatten(), nn.Linear(R * R, yashirin), nn.ReLU(),
nn.Linear(yashirin, len(SINFLAR)))
def forward(self, x):
return self.tarmoq(x)
def macro_recall(y, p):
return float(np.mean([(p[y == k] == k).mean()
for k in range(len(SINFLAR))]))
class EngYaxshisi:
def __init__(self, monitor, sabr):
self.monitor, self.sabr = monitor, sabr
def fit_boshi(self, tr):
self.eng, self.holat, self.davr, self.hisob = -np.inf, None, 0, 0
def davr_oxiri(self, tr, davr, log):
if log[self.monitor] > self.eng:
self.eng, self.davr, self.hisob = log[self.monitor], davr, 0
self.holat = copy.deepcopy(tr.model.state_dict())
else:
self.hisob += 1
tr.toxtash = self.hisob >= self.sabr
def fit_oxiri(self, tr):
tr.model.load_state_dict(self.holat)
class Trainer:
def __init__(self, model, opt, sched, vazn, callbacklar):
self.model, self.opt, self.sched, self.vazn = model, opt, sched, vazn
self.callbacklar, self.tarix, self.toxtash = callbacklar, [], False
def _davr(self, dl, orgatish):
self.model.train(orgatish)
jami, n, plar, ylar = 0.0, 0, [], []
with torch.set_grad_enabled(orgatish):
for x, y in dl:
ch = self.model(x)
loss = F.cross_entropy(ch, y, weight=self.vazn)
if orgatish:
self.opt.zero_grad()
loss.backward()
self.opt.step()
jami += loss.item() * len(y)
n += len(y)
plar.append(ch.argmax(1))
ylar.append(y)
y, p = torch.cat(ylar).numpy(), torch.cat(plar).numpy()
return {"loss": jami / n, "mrecall": macro_recall(y, p)}
def fit(self, dl_tr, dl_va, davrlar):
for cb in self.callbacklar:
cb.fit_boshi(self)
for davr in range(1, davrlar + 1):
t, v = self._davr(dl_tr, True), self._davr(dl_va, False)
self.sched.step()
log = {"train_loss": t["loss"], "val_loss": v["loss"],
"val_mrecall": v["mrecall"]}
self.tarix.append({"davr": davr, **log})
for cb in self.callbacklar:
cb.davr_oxiri(self, davr, log)
if self.toxtash:
break
for cb in self.callbacklar:
cb.fit_oxiri(self)
return self.tarix
def sinf_vazni(y):
soni = np.bincount(y, minlength=len(SINFLAR))
w = len(y) / (len(SINFLAR) * soni)
return torch.tensor(w, dtype=torch.float32)
def orgat(model, k, ds_tr, ds_va, y_tr):
seed_everything(k.seed)
opt = torch.optim.AdamW(model.parameters(), lr=k.lr,
weight_decay=k.weight_decay)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, k.davrlar)
g = torch.Generator().manual_seed(k.seed)
dl_tr = DataLoader(ds_tr, batch_size=k.batch, shuffle=True, generator=g)
dl_va = DataLoader(ds_va, batch_size=256)
eyx = EngYaxshisi("val_mrecall", k.sabr)
tarix = Trainer(model, opt, sched, sinf_vazni(y_tr), [eyx]).fit(
dl_tr, dl_va, k.davrlar)
return tarix, eyx
def tayyorla(k):
"""1-misoldagi quvur (papka bosqichisiz - vaqtni tejash uchun)."""
rasmlar, yorliq, _ = yarat(k.n, seed=7)
x = torch.tensor(rasmlar, dtype=torch.float32)[:, None]
ish, te = bolish(yorliq, 0.2, k.seed)
tr_i, va_i = bolish(yorliq[ish], 0.25, k.seed)
tr, va = ish[tr_i], ish[va_i]
mean, std = x[tr].mean().item(), x[tr].std().item()
return x, yorliq, tr, va, mean, std
def main() -> None:
k = Konfig()
x, yorliq, tr, va, mean, std = tayyorla(k)
ds_va = PapkaDataset(x[va], yorliq[va], mean, std)
def yurish(seed):
kk = replace(k, seed=seed)
seed_everything(seed)
model = NuqsonCNN(kk.kanal)
ds_tr = PapkaDataset(x[tr], yorliq[tr], mean, std, augment=True,
seed=seed)
tarix, eyx = orgat(model, kk, ds_tr, ds_va, yorliq[tr])
return model, tarix, eyx
print("=== 1. Sinf vaznlari (o'quvdan) ===")
w = sinf_vazni(yorliq[tr])
print(f" {dict(zip(SINFLAR, [round(v, 2) for v in w.tolist()]))}")
print(" kam uchraydigan sinf (yoriq) xatosi qimmatroq")
print("\n=== 2. Bitta yurish (seed 0) ===")
model, tarix, eyx = yurish(0)
print(f" {len(tarix)} davr, eng yaxshi val macro-recall {eyx.eng:.4f} "
f"({eyx.davr}-davr)")
print(f" {'davr':>5} {'train_loss':>11} {'val_loss':>9} "
f"{'val_mrecall':>12}")
for q in tarix:
if q["davr"] in (1, 3, eyx.davr, len(tarix)):
print(f" {q['davr']:>5} {q['train_loss']:>11.4f} "
f"{q['val_loss']:>9.4f} {q['val_mrecall']:>12.4f}")
print("\n=== 3. Tiklangan holat tekshiruvi ===")
model.eval()
with torch.no_grad():
p = model((x[va] - mean) / std).argmax(1).numpy()
tiklangan = macro_recall(yorliq[va], p)
print(f" tiklangan model val macro-recall: {tiklangan:.4f}")
print(f" callback eslagan: {eyx.eng:.4f}")
print(f" mos: {abs(tiklangan - eyx.eng) < 1e-9}")
oxirgi = tarix[-1]["val_mrecall"]
if eyx.davr < len(tarix):
print(f" oxirgi davr {oxirgi:.4f} edi - deepcopy siz shu "
"qolardi")
print("\n=== 4. Uch seed ===")
ballar = [eyx.eng]
for s in (1, 2):
ballar.append(yurish(s)[2].eng)
ballar = np.array(ballar)
print(f" seedlar 0, 1, 2: {ballar.round(4).tolist()}")
print(f" o'rtacha {ballar.mean():.4f} +- {ballar.std(ddof=1):.4f}")
print(" ⭐ Hisobotga o'rtacha va std yoziladi, eng yaxshi seed emas")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Sinf vaznlari (o'quvdan) ===
{'nuqsonsiz': 0.45, 'tirnalish': 1.25, "dog'": 1.64, 'yoriq': 2.56}
kam uchraydigan sinf (yoriq) xatosi qimmatroq
=== 2. Bitta yurish (seed 0) ===
10 davr, eng yaxshi val macro-recall 0.8711 (8-davr)
davr train_loss val_loss val_mrecall
1 0.9413 0.8911 0.6262
3 0.5543 1.5838 0.4424
8 0.3336 0.3944 0.8711
10 0.2999 0.3431 0.8600
=== 3. Tiklangan holat tekshiruvi ===
tiklangan model val macro-recall: 0.8711
callback eslagan: 0.8711
mos: True
oxirgi davr 0.8600 edi - deepcopy siz shu qolardi
=== 4. Uch seed ===
seedlar 0, 1, 2: [0.8711, 0.8727, 0.8938]
o'rtacha 0.8792 +- 0.0127
⭐ Hisobotga o'rtacha va std yoziladi, eng yaxshi seed emasNima ko'rsatdi: 2.3-bo'lim.
Misol 3 — MLP va ko'pchilik bazaviysi bilan juftlashgan taqqoslash
"""3-qadam: bir xil foldlarda CNN va MLP - juftlashgan taqqoslash va qaror."""
import copy
import random
from dataclasses import dataclass
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
SINFLAR = ["nuqsonsiz", "tirnalish", "dog'", "yoriq"]
ULUSHLAR = [0.55, 0.20, 0.15, 0.10] # nomutanosib
R = 24
@dataclass(frozen=True)
class Konfig:
seed: int = 42
n: int = 1400
lr: float = 3e-3
batch: int = 64
davrlar: int = 10
kanal: int = 8
weight_decay: float = 1e-4
sabr: int = 4
def seed_everything(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
def yarat(n, seed):
"""Sintetik sanoat nuqsonlari: tekstura + tirnalish / dog' / yoriq.
Qaytaradi: rasmlar (n, R, R) [0, 1], yorliqlar (n,), niqoblar (n, R, R)."""
rng = np.random.default_rng(seed)
yy, xx = np.mgrid[0:R, 0:R].astype(float)
y = rng.choice(4, size=n, p=ULUSHLAR)
rasmlar = np.zeros((n, R, R))
niqoblar = np.zeros((n, R, R), dtype=bool)
for i in range(n):
th, f = rng.uniform(0, np.pi), rng.uniform(0.15, 0.3)
fz = rng.uniform(0, 6.3)
img = 0.5 + 0.08 * np.sin(2 * np.pi * f * (xx * np.cos(th)
+ yy * np.sin(th)) + fz)
img += rng.normal(0, 0.07, (R, R))
kontrast = rng.uniform(0.1, 0.25)
m = np.zeros((R, R), dtype=bool)
if y[i] == 1: # tirnalish: to'g'ri, yorug'
y0, x0 = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, np.pi)
for t in np.linspace(-8, 8, 60):
py = int(round(y0 + t * np.sin(a)))
px = int(round(x0 + t * np.cos(a)))
if 0 <= py < R and 0 <= px < R:
m[py, px] = True
img[m] += kontrast
elif y[i] == 2: # dog': dumaloq, qorong'i
cy, cx = rng.uniform(5, R - 5, 2)
r = rng.uniform(1.8, 3.5)
g = np.exp(-((yy - cy) ** 2 + (xx - cx) ** 2) / (2 * r ** 2))
img -= kontrast * g
m = g > 0.4
elif y[i] == 3: # yoriq: siniq, qorong'i
py, px = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, 2 * np.pi)
for _ in range(16):
a += rng.normal(0, 0.7)
py, px = py + np.sin(a), px + np.cos(a)
if 0 <= int(py) < R and 0 <= int(px) < R:
m[int(py), int(px)] = True
img[m] -= kontrast
rasmlar[i] = np.clip(img, 0, 1)
niqoblar[i] = m
return rasmlar, y, niqoblar
def bolish(y, ulush, seed):
"""Stratifikatsiyalangan bo'lish: har sinfdan bir xil ulush."""
rng = np.random.default_rng(seed)
a, b = [], []
for k in np.unique(y):
idx = rng.permutation(np.flatnonzero(y == k))
m = int(round(ulush * len(idx)))
b.extend(idx[:m])
a.extend(idx[m:])
return np.sort(a), np.sort(b)
class PapkaDataset(Dataset):
"""Xotiradagi rasmlar; mean/std tashqaridan (o'quvdan) beriladi."""
def __init__(self, x, y, mean, std, augment=False, seed=0):
self.x, self.y = x, torch.as_tensor(y)
self.mean, self.std, self.augment = mean, std, augment
self.g = torch.Generator().manual_seed(seed)
def __len__(self):
return len(self.y)
def __getitem__(self, i):
x = self.x[i]
if self.augment: # nuqson yo'nalishsiz
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-1)
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-2)
k = int(torch.randint(0, 4, (1,), generator=self.g))
x = torch.rot90(x, k, (-2, -1))
return (x - self.mean) / self.std, self.y[i]
class ResBlok(nn.Module):
def __init__(self, k):
super().__init__()
self.c1 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b1 = nn.BatchNorm2d(k)
self.c2 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b2 = nn.BatchNorm2d(k)
def forward(self, x):
h = F.relu(self.b1(self.c1(x)))
return F.relu(x + self.b2(self.c2(h)))
def pastga(k_in, k_out):
return nn.Sequential(nn.Conv2d(k_in, k_out, 3, stride=2, padding=1,
bias=False),
nn.BatchNorm2d(k_out), nn.ReLU())
class NuqsonCNN(nn.Module):
"""Kichik ResNet: 24 -> 12 -> 6, GAP, 4 sinf."""
def __init__(self, k=16):
super().__init__()
self.tana = nn.Sequential(
nn.Conv2d(1, k, 3, padding=1, bias=False), nn.BatchNorm2d(k),
nn.ReLU(), ResBlok(k), pastga(k, 2 * k), ResBlok(2 * k),
pastga(2 * k, 4 * k), ResBlok(4 * k))
self.bosh = nn.Linear(4 * k, len(SINFLAR))
def forward(self, x):
return self.bosh(self.tana(x).mean((2, 3)))
class MLP(nn.Module):
"""Bazaviy: piksellar tekislanadi, fazoviy tuzilma e'tiborsiz."""
def __init__(self, yashirin=128):
super().__init__()
self.tarmoq = nn.Sequential(
nn.Flatten(), nn.Linear(R * R, yashirin), nn.ReLU(),
nn.Linear(yashirin, len(SINFLAR)))
def forward(self, x):
return self.tarmoq(x)
def macro_recall(y, p):
return float(np.mean([(p[y == k] == k).mean()
for k in range(len(SINFLAR))]))
class EngYaxshisi:
def __init__(self, monitor, sabr):
self.monitor, self.sabr = monitor, sabr
def fit_boshi(self, tr):
self.eng, self.holat, self.davr, self.hisob = -np.inf, None, 0, 0
def davr_oxiri(self, tr, davr, log):
if log[self.monitor] > self.eng:
self.eng, self.davr, self.hisob = log[self.monitor], davr, 0
self.holat = copy.deepcopy(tr.model.state_dict())
else:
self.hisob += 1
tr.toxtash = self.hisob >= self.sabr
def fit_oxiri(self, tr):
tr.model.load_state_dict(self.holat)
class Trainer:
def __init__(self, model, opt, sched, vazn, callbacklar):
self.model, self.opt, self.sched, self.vazn = model, opt, sched, vazn
self.callbacklar, self.tarix, self.toxtash = callbacklar, [], False
def _davr(self, dl, orgatish):
self.model.train(orgatish)
jami, n, plar, ylar = 0.0, 0, [], []
with torch.set_grad_enabled(orgatish):
for x, y in dl:
ch = self.model(x)
loss = F.cross_entropy(ch, y, weight=self.vazn)
if orgatish:
self.opt.zero_grad()
loss.backward()
self.opt.step()
jami += loss.item() * len(y)
n += len(y)
plar.append(ch.argmax(1))
ylar.append(y)
y, p = torch.cat(ylar).numpy(), torch.cat(plar).numpy()
return {"loss": jami / n, "mrecall": macro_recall(y, p)}
def fit(self, dl_tr, dl_va, davrlar):
for cb in self.callbacklar:
cb.fit_boshi(self)
for davr in range(1, davrlar + 1):
t, v = self._davr(dl_tr, True), self._davr(dl_va, False)
self.sched.step()
log = {"train_loss": t["loss"], "val_loss": v["loss"],
"val_mrecall": v["mrecall"]}
self.tarix.append({"davr": davr, **log})
for cb in self.callbacklar:
cb.davr_oxiri(self, davr, log)
if self.toxtash:
break
for cb in self.callbacklar:
cb.fit_oxiri(self)
return self.tarix
def sinf_vazni(y):
soni = np.bincount(y, minlength=len(SINFLAR))
w = len(y) / (len(SINFLAR) * soni)
return torch.tensor(w, dtype=torch.float32)
def orgat(model, k, ds_tr, ds_va, y_tr):
seed_everything(k.seed)
opt = torch.optim.AdamW(model.parameters(), lr=k.lr,
weight_decay=k.weight_decay)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, k.davrlar)
g = torch.Generator().manual_seed(k.seed)
dl_tr = DataLoader(ds_tr, batch_size=k.batch, shuffle=True, generator=g)
dl_va = DataLoader(ds_va, batch_size=256)
eyx = EngYaxshisi("val_mrecall", k.sabr)
tarix = Trainer(model, opt, sched, sinf_vazni(y_tr), [eyx]).fit(
dl_tr, dl_va, k.davrlar)
return tarix, eyx
def stratified_foldlar(y, n_fold, seed):
rng = np.random.default_rng(seed)
fold = np.zeros(len(y), dtype=int)
for k in np.unique(y):
idx = rng.permutation(np.flatnonzero(y == k))
fold[idx] = np.arange(len(idx)) % n_fold
return fold
def fold_bahosi(yasovchi, k, x, y, tr, va):
"""Fold o'quvida mean/std, belgilangan davrlar (erta to'xtatishsiz)."""
mean, std = x[tr].mean().item(), x[tr].std().item()
seed_everything(k.seed)
model = yasovchi()
ds_tr = PapkaDataset(x[tr], y[tr], mean, std, augment=True, seed=k.seed)
ds_va = PapkaDataset(x[va], y[va], mean, std)
opt = torch.optim.AdamW(model.parameters(), lr=k.lr,
weight_decay=k.weight_decay)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, k.davrlar)
g = torch.Generator().manual_seed(k.seed)
dl_tr = DataLoader(ds_tr, batch_size=k.batch, shuffle=True, generator=g)
dl_va = DataLoader(ds_va, batch_size=256)
tarix = Trainer(model, opt, sched, sinf_vazni(y[tr]), []).fit(
dl_tr, dl_va, k.davrlar)
return tarix[-1]["val_mrecall"]
def main() -> None:
k = Konfig()
rasmlar, yorliq, _ = yarat(k.n, seed=7)
x = torch.tensor(rasmlar, dtype=torch.float32)[:, None]
ish, _ = bolish(yorliq, 0.2, k.seed) # test QULF - tegilmaydi
x_ish, y_ish = x[ish], yorliq[ish]
fold = stratified_foldlar(y_ish, 3, seed=0)
print("=== 1. Uch model, bir xil 3 ta fold ===")
modellar = {
"Ko'pchilik": None, # doim "nuqsonsiz"
"MLP": lambda: MLP(128),
"CNN": lambda: NuqsonCNN(k.kanal),
}
natija = {m: [] for m in modellar}
for f in range(3):
tr, va = np.flatnonzero(fold != f), np.flatnonzero(fold == f)
natija["Ko'pchilik"].append(
macro_recall(y_ish[va], np.zeros(len(va), dtype=int)))
for m in ("MLP", "CNN"):
natija[m].append(fold_bahosi(modellar[m], k, x_ish, y_ish,
tr, va))
print(f" {'fold':>5}" + "".join(f"{m:>12}" for m in natija))
for f in range(3):
print(f" {f + 1:>5}" + "".join(f"{natija[m][f]:>12.4f}"
for m in natija))
print("\n=== 2. O'rtachalar (macro-recall) ===")
for m, b in natija.items():
b = np.array(b)
print(f" {m:<11} {b.mean():.4f} +- {b.std(ddof=1):.4f}")
print("\n=== 3. Juftlashgan farq ===")
d = np.array(natija["CNN"]) - np.array(natija["MLP"])
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" CNN - MLP: foldlar {d.round(4).tolist()}")
print(f" o'rtacha {d.mean():+.4f}, SE {se:.4f}, 2*SE {2 * se:.4f}")
print(f" sezilarli: {abs(d.mean()) > 2 * se}")
print("\n=== 4. Qaror: eng yaxshisidan sezilarli yomon bo'lmagan")
print(" ENG SODDA model ===")
oddiydan = ["Ko'pchilik", "MLP", "CNN"]
eng = max(natija, key=lambda m: np.mean(natija[m]))
print(f" o'rtacha bo'yicha eng yaxshi: {eng}")
for m in oddiydan:
if m == eng:
tanlov, sabab = m, "eng yaxshisining o'zi"
break
dd = np.array(natija[eng]) - np.array(natija[m])
se_m = dd.std(ddof=1) / np.sqrt(len(dd))
print(f" {m:<11}: {eng} dan {dd.mean():+.4f} past, "
f"2*SE = {2 * se_m:.4f}")
if dd.mean() <= 2 * se_m:
tanlov, sabab = m, f"{eng} dan sezilarli yomon emas"
break
print(f" TANLOV: {tanlov} ({sabab})")
print(" ⭐ Qaror foldlardan, test hali ham yopiq")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Uch model, bir xil 3 ta fold ===
fold Ko'pchilik MLP CNN
1 0.2500 0.6830 0.9238
2 0.2500 0.6575 0.9075
3 0.2500 0.7066 0.8555
=== 2. O'rtachalar (macro-recall) ===
Ko'pchilik 0.2500 +- 0.0000
MLP 0.6824 +- 0.0245
CNN 0.8956 +- 0.0357
=== 3. Juftlashgan farq ===
CNN - MLP: foldlar [0.2408, 0.25, 0.1489]
o'rtacha +0.2132, SE 0.0323, 2*SE 0.0646
sezilarli: True
=== 4. Qaror: eng yaxshisidan sezilarli yomon bo'lmagan
ENG SODDA model ===
o'rtacha bo'yicha eng yaxshi: CNN
Ko'pchilik : CNN dan +0.6456 past, 2*SE = 0.0412
MLP : CNN dan +0.2132 past, 2*SE = 0.0646
TANLOV: CNN (eng yaxshisining o'zi)
⭐ Qaror foldlardan, test hali ham yopiqNima ko'rsatdi: 2.4-bo'lim.
Misol 4 — Test, Grad-CAM, topshirish paketi va hisobot
"""4-qadam: test bir marta, sinf bo'yicha recall, Grad-CAM, paket va hisobot."""
import copy
import random
import shutil
import sys
import tempfile
from dataclasses import asdict, dataclass
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from PIL import Image
from torch.utils.data import DataLoader, Dataset
FORMAT = 1
SINFLAR = ["nuqsonsiz", "tirnalish", "dog'", "yoriq"]
ULUSHLAR = [0.55, 0.20, 0.15, 0.10] # nomutanosib
R = 24
@dataclass(frozen=True)
class Konfig:
seed: int = 42
n: int = 1400
lr: float = 3e-3
batch: int = 64
davrlar: int = 10
kanal: int = 8
weight_decay: float = 1e-4
sabr: int = 4
def seed_everything(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
def yarat(n, seed):
"""Sintetik sanoat nuqsonlari: tekstura + tirnalish / dog' / yoriq.
Qaytaradi: rasmlar (n, R, R) [0, 1], yorliqlar (n,), niqoblar (n, R, R)."""
rng = np.random.default_rng(seed)
yy, xx = np.mgrid[0:R, 0:R].astype(float)
y = rng.choice(4, size=n, p=ULUSHLAR)
rasmlar = np.zeros((n, R, R))
niqoblar = np.zeros((n, R, R), dtype=bool)
for i in range(n):
th, f = rng.uniform(0, np.pi), rng.uniform(0.15, 0.3)
fz = rng.uniform(0, 6.3)
img = 0.5 + 0.08 * np.sin(2 * np.pi * f * (xx * np.cos(th)
+ yy * np.sin(th)) + fz)
img += rng.normal(0, 0.07, (R, R))
kontrast = rng.uniform(0.1, 0.25)
m = np.zeros((R, R), dtype=bool)
if y[i] == 1: # tirnalish: to'g'ri, yorug'
y0, x0 = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, np.pi)
for t in np.linspace(-8, 8, 60):
py = int(round(y0 + t * np.sin(a)))
px = int(round(x0 + t * np.cos(a)))
if 0 <= py < R and 0 <= px < R:
m[py, px] = True
img[m] += kontrast
elif y[i] == 2: # dog': dumaloq, qorong'i
cy, cx = rng.uniform(5, R - 5, 2)
r = rng.uniform(1.8, 3.5)
g = np.exp(-((yy - cy) ** 2 + (xx - cx) ** 2) / (2 * r ** 2))
img -= kontrast * g
m = g > 0.4
elif y[i] == 3: # yoriq: siniq, qorong'i
py, px = rng.uniform(6, R - 6, 2)
a = rng.uniform(0, 2 * np.pi)
for _ in range(16):
a += rng.normal(0, 0.7)
py, px = py + np.sin(a), px + np.cos(a)
if 0 <= int(py) < R and 0 <= int(px) < R:
m[int(py), int(px)] = True
img[m] -= kontrast
rasmlar[i] = np.clip(img, 0, 1)
niqoblar[i] = m
return rasmlar, y, niqoblar
def bolish(y, ulush, seed):
"""Stratifikatsiyalangan bo'lish: har sinfdan bir xil ulush."""
rng = np.random.default_rng(seed)
a, b = [], []
for k in np.unique(y):
idx = rng.permutation(np.flatnonzero(y == k))
m = int(round(ulush * len(idx)))
b.extend(idx[:m])
a.extend(idx[m:])
return np.sort(a), np.sort(b)
class PapkaDataset(Dataset):
"""Xotiradagi rasmlar; mean/std tashqaridan (o'quvdan) beriladi."""
def __init__(self, x, y, mean, std, augment=False, seed=0):
self.x, self.y = x, torch.as_tensor(y)
self.mean, self.std, self.augment = mean, std, augment
self.g = torch.Generator().manual_seed(seed)
def __len__(self):
return len(self.y)
def __getitem__(self, i):
x = self.x[i]
if self.augment: # nuqson yo'nalishsiz
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-1)
if torch.rand(1, generator=self.g) < 0.5:
x = x.flip(-2)
k = int(torch.randint(0, 4, (1,), generator=self.g))
x = torch.rot90(x, k, (-2, -1))
return (x - self.mean) / self.std, self.y[i]
class ResBlok(nn.Module):
def __init__(self, k):
super().__init__()
self.c1 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b1 = nn.BatchNorm2d(k)
self.c2 = nn.Conv2d(k, k, 3, padding=1, bias=False)
self.b2 = nn.BatchNorm2d(k)
def forward(self, x):
h = F.relu(self.b1(self.c1(x)))
return F.relu(x + self.b2(self.c2(h)))
def pastga(k_in, k_out):
return nn.Sequential(nn.Conv2d(k_in, k_out, 3, stride=2, padding=1,
bias=False),
nn.BatchNorm2d(k_out), nn.ReLU())
class NuqsonCNN(nn.Module):
"""Kichik ResNet: 24 -> 12 -> 6, GAP, 4 sinf."""
def __init__(self, k=16):
super().__init__()
self.tana = nn.Sequential(
nn.Conv2d(1, k, 3, padding=1, bias=False), nn.BatchNorm2d(k),
nn.ReLU(), ResBlok(k), pastga(k, 2 * k), ResBlok(2 * k),
pastga(2 * k, 4 * k), ResBlok(4 * k))
self.bosh = nn.Linear(4 * k, len(SINFLAR))
def forward(self, x):
return self.bosh(self.tana(x).mean((2, 3)))
class MLP(nn.Module):
"""Bazaviy: piksellar tekislanadi, fazoviy tuzilma e'tiborsiz."""
def __init__(self, yashirin=128):
super().__init__()
self.tarmoq = nn.Sequential(
nn.Flatten(), nn.Linear(R * R, yashirin), nn.ReLU(),
nn.Linear(yashirin, len(SINFLAR)))
def forward(self, x):
return self.tarmoq(x)
def macro_recall(y, p):
return float(np.mean([(p[y == k] == k).mean()
for k in range(len(SINFLAR))]))
class EngYaxshisi:
def __init__(self, monitor, sabr):
self.monitor, self.sabr = monitor, sabr
def fit_boshi(self, tr):
self.eng, self.holat, self.davr, self.hisob = -np.inf, None, 0, 0
def davr_oxiri(self, tr, davr, log):
if log[self.monitor] > self.eng:
self.eng, self.davr, self.hisob = log[self.monitor], davr, 0
self.holat = copy.deepcopy(tr.model.state_dict())
else:
self.hisob += 1
tr.toxtash = self.hisob >= self.sabr
def fit_oxiri(self, tr):
tr.model.load_state_dict(self.holat)
class Trainer:
def __init__(self, model, opt, sched, vazn, callbacklar):
self.model, self.opt, self.sched, self.vazn = model, opt, sched, vazn
self.callbacklar, self.tarix, self.toxtash = callbacklar, [], False
def _davr(self, dl, orgatish):
self.model.train(orgatish)
jami, n, plar, ylar = 0.0, 0, [], []
with torch.set_grad_enabled(orgatish):
for x, y in dl:
ch = self.model(x)
loss = F.cross_entropy(ch, y, weight=self.vazn)
if orgatish:
self.opt.zero_grad()
loss.backward()
self.opt.step()
jami += loss.item() * len(y)
n += len(y)
plar.append(ch.argmax(1))
ylar.append(y)
y, p = torch.cat(ylar).numpy(), torch.cat(plar).numpy()
return {"loss": jami / n, "mrecall": macro_recall(y, p)}
def fit(self, dl_tr, dl_va, davrlar):
for cb in self.callbacklar:
cb.fit_boshi(self)
for davr in range(1, davrlar + 1):
t, v = self._davr(dl_tr, True), self._davr(dl_va, False)
self.sched.step()
log = {"train_loss": t["loss"], "val_loss": v["loss"],
"val_mrecall": v["mrecall"]}
self.tarix.append({"davr": davr, **log})
for cb in self.callbacklar:
cb.davr_oxiri(self, davr, log)
if self.toxtash:
break
for cb in self.callbacklar:
cb.fit_oxiri(self)
return self.tarix
def sinf_vazni(y):
soni = np.bincount(y, minlength=len(SINFLAR))
w = len(y) / (len(SINFLAR) * soni)
return torch.tensor(w, dtype=torch.float32)
def orgat(model, k, ds_tr, ds_va, y_tr):
seed_everything(k.seed)
opt = torch.optim.AdamW(model.parameters(), lr=k.lr,
weight_decay=k.weight_decay)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, k.davrlar)
g = torch.Generator().manual_seed(k.seed)
dl_tr = DataLoader(ds_tr, batch_size=k.batch, shuffle=True, generator=g)
dl_va = DataLoader(ds_va, batch_size=256)
eyx = EngYaxshisi("val_mrecall", k.sabr)
tarix = Trainer(model, opt, sched, sinf_vazni(y_tr), [eyx]).fit(
dl_tr, dl_va, k.davrlar)
return tarix, eyx
def grad_cam(model, xn):
"""Oxirgi blok xaritasi bo'yicha Grad-CAM, (N, R, R) ga kattalashtirilgan."""
model.eval()
xarita = model.tana(xn)
xarita.retain_grad()
logit = model.bosh(xarita.mean((2, 3)))
sinf = logit.argmax(1)
logit[torch.arange(len(xn)), sinf].sum().backward()
w = xarita.grad.mean((2, 3), keepdim=True)
cam = F.relu((w * xarita).sum(1, keepdim=True)).detach()
cam = F.interpolate(cam, size=(R, R), mode="bilinear",
align_corners=False)[:, 0]
return cam, sinf
class Bashoratchi:
def __init__(self, yol):
p = torch.load(yol, weights_only=True)
if p["format"] != FORMAT:
raise ValueError("format mos emas")
self.sinflar = p["sinflar"]
self.mean, self.std = p["kirish"]["mean"], p["kirish"]["std"]
self.model = NuqsonCNN(p["konfig"]["kanal"])
self.model.load_state_dict(p["holat"])
self.model.eval()
with torch.no_grad():
q = self.model((p["nazorat"]["x"] - self.mean) / self.std)
self.nazorat_ok = bool(torch.allclose(q, p["nazorat"]["logit"],
atol=1e-5))
def bashorat(self, fayllar):
x = torch.stack([torch.tensor(np.asarray(Image.open(f),
dtype=np.float32) / 255.0)
for f in fayllar])[:, None]
with torch.no_grad():
p = torch.softmax(self.model((x - self.mean) / self.std), 1)
return [(self.sinflar[int(i)], float(v))
for v, i in zip(*p.max(1))]
def main() -> None:
k = Konfig()
rasmlar, yorliq, niqoblar = yarat(k.n, seed=7)
x = torch.tensor(rasmlar, dtype=torch.float32)[:, None]
ish, te = bolish(yorliq, 0.2, k.seed)
tr_i, va_i = bolish(yorliq[ish], 0.25, k.seed)
tr, va = ish[tr_i], ish[va_i]
mean, std = x[tr].mean().item(), x[tr].std().item()
print("=== 1. Yakuniy model (3-qadam qarori: CNN) ===")
seed_everything(k.seed)
model = NuqsonCNN(k.kanal)
ds_tr = PapkaDataset(x[tr], yorliq[tr], mean, std, augment=True,
seed=k.seed)
ds_va = PapkaDataset(x[va], yorliq[va], mean, std)
tarix, eyx = orgat(model, k, ds_tr, ds_va, yorliq[tr])
print(f" val macro-recall {eyx.eng:.4f} ({eyx.davr}-davr, "
f"{len(tarix)} davr)")
print("\n=== 2. Testni BIR MARTA ochamiz ===")
model.eval()
xn_te = (x[te] - mean) / std
with torch.no_grad():
p = model(xn_te).argmax(1).numpy()
y_te = yorliq[te]
mr = macro_recall(y_te, p)
print(f" test macro-recall {mr:.4f}, aniqlik {(p == y_te).mean():.4f}")
print(f" {'sinf':<10} {'n':>4} {'recall':>7} chalkashlik qatori")
recall = {}
for kk, nom in enumerate(SINFLAR):
m = y_te == kk
recall[nom] = float((p[m] == kk).mean())
qator = np.bincount(p[m], minlength=4).tolist()
print(f" {nom:<10} {int(m.sum()):>4} {recall[nom]:>7.3f} {qator}")
nuqsonli = y_te > 0
otkazildi = float((p[nuqsonli] == 0).mean())
yolgon = float((p[~nuqsonli] > 0).mean())
print(f" nuqsonni o'tkazib yuborish (nuqsonli -> nuqsonsiz): "
f"{otkazildi:.3f}")
print(f" yolg'on signal (nuqsonsiz -> nuqsonli): {yolgon:.3f}")
eng_yomon = min(recall, key=recall.get)
print(f" eng past recall: {eng_yomon} ({recall[eng_yomon]:.3f})")
print("\n=== 3. Grad-CAM: model nuqsonga qarayaptimi? ===")
togri = np.flatnonzero((p == y_te) & (y_te > 0))
cam, _ = grad_cam(model, xn_te[togri].clone().requires_grad_(False))
niqob = torch.tensor(niqoblar[te][togri], dtype=torch.float32)
kengroq = F.max_pool2d(niqob[:, None], 3, 1, 1)[:, 0] # +1 piksel
energiya = (cam * kengroq).sum((1, 2)) / cam.sum((1, 2)).clamp(min=1e-8)
yuza = kengroq.mean((1, 2))
print(f" {'sinf':<10} {'n':>4} {'CAM niqobda':>12} {'niqob yuzasi':>13}")
for kk in (1, 2, 3):
m = torch.tensor(y_te[togri] == kk)
print(f" {SINFLAR[kk]:<10} {int(m.sum()):>4} "
f"{energiya[m].mean().item():>12.3f} "
f"{yuza[m].mean().item():>13.3f}")
nisbat = energiya.mean().item() / yuza.mean().item()
print(f" CAM energiyasi niqob yuzasidan {nisbat:.1f} barobar zich")
if nisbat > 2:
print(" model asosan nuqson piksellariga qarayapti - OK")
else:
print(" DIQQAT: model nuqsondan boshqa joyga qarayapti")
papka = Path(tempfile.mkdtemp(prefix="nuqson_paket_"))
try:
print("\n=== 4. Topshirish paketi ===")
nazorat_x = x[te[:8]]
with torch.no_grad():
nazorat_logit = model((nazorat_x - mean) / std)
paket = {
"format": FORMAT,
"konfig": asdict(k),
"holat": model.state_dict(),
"kirish": {"mean": mean, "std": std, "R": R, "kanal": 1},
"sinflar": list(SINFLAR),
"metrika": {"val_mrecall": round(float(eyx.eng), 4),
"test_mrecall": round(mr, 4),
"test_recall": {s: round(v, 4)
for s, v in recall.items()}},
"versiyalar": {"torch": str(torch.__version__),
"python": sys.version.split()[0]},
"nazorat": {"x": nazorat_x, "logit": nazorat_logit},
}
yol = papka / "nuqson_model.pt"
torch.save(paket, yol)
print(f" kalitlar: {sorted(paket)}")
print(f" hajm: {yol.stat().st_size / 1024:.1f} KB")
print("\n=== 5. Bashoratchi (weights_only=True) ===")
b = Bashoratchi(yol)
print(f" nazorat tekshiruvi: {'OK' if b.nazorat_ok else 'XATO'}")
yangi = papka / "yangi"
yangi.mkdir()
fayllar = []
for j in range(4):
i = te[np.flatnonzero(y_te == j)[0]]
f = yangi / f"rasm_{j}.png"
Image.fromarray((rasmlar[i] * 255).round().astype(np.uint8)).save(f)
fayllar.append(f)
for f, (sinf, ehtimol), j in zip(fayllar, b.bashorat(fayllar),
range(4)):
print(f" {f.relative_to(papka).as_posix():<16} haqiqiy "
f"{SINFLAR[j]:<10} -> {sinf:<10} ({ehtimol:.3f})")
print("\n=== 6. Yakuniy hisobot ===")
print(" VAZIFA: 24x24 sirt rasmida nuqson turini aniqlash (4 sinf)")
print(" METRIKA: macro-recall (sinflar teng vaznda)")
print(" DIZAYN: stratifikatsiya 60/20/20; mean/std o'quvdan; test "
"bir marta")
print(f" TEST: macro-recall {mr:.4f}; nuqsonni o'tkazib yuborish "
f"{otkazildi:.3f}")
print(f" ZAIF JOY: {eng_yomon} recall {recall[eng_yomon]:.3f}")
print(f" TEKSHIRUV: Grad-CAM nuqsonda {nisbat:.1f}x zich")
print(f" PAKET: format {FORMAT}, weights_only=True, nazorat "
f"{'OK' if b.nazorat_ok else 'XATO'}")
print(" CHEKLOVLAR: sintetik ma'lumot; bitta tekstura turi;")
print(" real kamerada yorug'lik va fokus o'zgarishi tekshirilmagan")
print(" ⭐ 22-qism yakunlandi: rasmdan topshiriladigan modelgacha")
finally:
shutil.rmtree(papka, ignore_errors=True)
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Yakuniy model (3-qadam qarori: CNN) ===
val macro-recall 0.8718 (9-davr, 10 davr)
=== 2. Testni BIR MARTA ochamiz ===
test macro-recall 0.9003, aniqlik 0.9250
sinf n recall chalkashlik qatori
nuqsonsiz 154 0.942 [145, 3, 0, 6]
tirnalish 56 0.929 [4, 52, 0, 0]
dog' 43 0.953 [0, 0, 41, 2]
yoriq 27 0.778 [6, 0, 0, 21]
nuqsonni o'tkazib yuborish (nuqsonli -> nuqsonsiz): 0.079
yolg'on signal (nuqsonsiz -> nuqsonli): 0.058
eng past recall: yoriq 0.778-bob
=== 3. Grad-CAM: model nuqsonga qarayaptimi? ===
sinf n CAM niqobda niqob yuzasi
tirnalish 52 0.245 0.117
dog' 41 0.363 0.134
yoriq 21 0.222 0.096
CAM energiyasi niqob yuzasidan 2.4 barobar zich
model asosan nuqson piksellariga qarayapti - OK
=== 4. Topshirish paketi ===
kalitlar: ['format', 'holat', 'kirish', 'konfig', 'metrika', 'nazorat', 'sinflar', 'versiyalar']
hajm: 157.0 KB
=== 5. Bashoratchi (weights_only=True) ===
nazorat tekshiruvi: OK
yangi/rasm_0.png haqiqiy nuqsonsiz -> nuqsonsiz 0.867-bob
yangi/rasm_1.png haqiqiy tirnalish -> tirnalish 0.995-bob
yangi/rasm_2.png haqiqiy dog' -> dog' 0.659-bob
yangi/rasm_3.png haqiqiy yoriq -> nuqsonsiz 0.649-bob
=== 6. Yakuniy hisobot ===
VAZIFA: 24x24 sirt rasmida nuqson turini aniqlash (4 sinf)
METRIKA: macro-recall (sinflar teng vaznda)
DIZAYN: stratifikatsiya 60/20/20; mean/std o'quvdan; test bir marta
TEST: macro-recall 0.9003; nuqsonni o'tkazib yuborish 0.079
ZAIF JOY: yoriq recall 0.778
TEKSHIRUV: Grad-CAM nuqsonda 2.4x zich
PAKET: format 1, weights_only=True, nazorat OK
CHEKLOVLAR: sintetik ma'lumot; bitta tekstura turi;
real kamerada yorug'lik va fokus o'zgarishi tekshirilmagan
⭐ 22-qism yakunlandi: rasmdan topshiriladigan modelgachaNima ko'rsatdi: 2.5, 2.6-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Aniqlik 92% — model tayyor" | Sinf bo'yicha recall ga qarang |
| "Augmentatsiya qancha ko'p — shuncha yaxshi" | Faqat yorliqni saqlaydiganlari |
"mean/std ni butun ma'lumotda hisoblash qulay" |
Faqat o'quvdan, paketga ham shu |
| "CNN rasmda har doim MLP dan yaxshi" | Juftlashgan farq hal qiladi |
| "Test yaxshi chiqsa — model to'g'ri sababga ko'ra ishlaydi" | Grad-CAM bilan tekshiring |
| "Sinflar tartibini papkadan tiklash mumkin" | Paketga ro'yxat yoziladi |
| "Nuqsonni o'tkazish va yolg'on signal teng" | O'tkazib yuborish ko'p marta qimmat |
| "Bitta seed yetarli" | O'rtacha va std |
6. Keng tarqalgan xatolar va yechimlari
1. Aniqlik bilan hisobot
print((p == y).mean()) # ⚠️ 55% "hammasi nuqsonsiz"
print(macro_recall(y, p), recall_sinf_boyicha) # ✅2. Stratifikatsiyasiz bo'lish
te = rng.permutation(n)[:280] # ⚠️ yoriqlar soni tasodifiy
ish, te = bolish(y, 0.2, seed) # ✅ har sinfdan 20%3. Validatsiyada augmentatsiya
PapkaDataset(x[va], y[va], mean, std, augment=True) # ⚠️
PapkaDataset(x[va], y[va], mean, std) # ✅4. Yorliqni buzadigan augmentatsiya
x = x * rng.uniform(0.5, 1.5) # ⚠️ tirnalish/yoriq farqi yorqinlikda
x = torch.rot90(x.flip(-1), k, (-2, -1)) # ✅ yo'nalish ahamiyatsiz5. Sinf vaznisiz nomutanosib o'rgatish
F.cross_entropy(logit, y) # ⚠️ yoriq "yo'qoladi"
F.cross_entropy(logit, y, weight=sinf_vazni(y_tr)) # ✅6. Sinflar tartibi paketsiz
sinflar = sorted(os.listdir(papka)) # ⚠️ ['dog', 'nuqsonsiz', ...]
sinflar = paket["sinflar"] # ✅ model tartibi7. Paketda obyekt
{"torch": torch.__version__, "papka": Path(...)} # ⚠️ weights_only rad etadi
{"torch": str(torch.__version__)} # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 21.5, 21.7, 21.11, 21.12-darslar (o'tilgan): Trainer, checkpoint, loyiha tuzilishi va birinchi to'liq loyiha skeleti
- 18-qism (o'tilgan): Juftlashgan taqqoslash, recall va xato narxi
- 22.1-22.13-darslar (o'tilgan): Butun qism — rasm, CNN, augmentatsiya, Grad-CAM
- NLP qismida: Xuddi shu skelet matn bilan: token, embedding, ketma-ketlik modellari
- MLOps qismida: Paketni xizmatga aylantirish, kamera sharoitlari o'zgarishini (drift) kuzatish
8. Eng yaxshi amaliyotlar
Metrikani xato narxidan tanlang — nuqson loyihasida sinf bo'yicha recall.
Stratifikatsiyalangan bo'lish va testni birinchi qadamda qulflang.
mean/stdfaqat o'quvdan, paketga ham aynan shu qiymatlar.Augmentatsiyani "yorliqni saqlaydimi?" savoli bilan tanlang.
Sinf vazni yoki boshqa nomutanosiblik choralari.
Eng yaxshi holatni
deepcopyqiling va 3 seed bering.Bazaviyni bir xil foldlarda, juftlashgan farq bilan taqqoslang.
Grad-CAM bilan "to'g'ri sababga ko'ra to'g'rimi" ni tekshiring va zaif joyni hisobotga yozing.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # 4 sinfda boshlang'ich loss taxminan?
2. # test qachon qulflanadi?
3. # mean/std qaysi bo'lakdan?
4. # bu loyihada qaysi augmentatsiyalar xavfsiz?
5. # nega yorqinlik augmentatsiyasi xavfli?
6. # "hammasi nuqsonsiz" modelining macro-recall i?
7. # sinf vazni formulasi?
8. # juftlashgan taqqoslashda sezilarlilik mezoni?
9. # teng natijada MLP va CNN dan qaysi biri?
10. # hisobotdagi eng muhim qator (ishlab chiqarish uchun)?
11. # Grad-CAM nisbati ~1 bo'lsa nima degani?
12. # sinflar tartibi qayerdan olinadi?Javoblar
ln(4) = 1.386- Birinchi qadamda, bo'lish paytida
- Faqat o'quv bo'lagidan
- Akslantirish va 90 gradusga burish
- Tirnalish (yorug') va yoriq (qorong'i) farqi yorqinlikda
0.25w_k = N / (K * n_k)|mean(d)| > 2 * SE- MLP (soddaroq)
- Nuqsonni o'tkazib yuborish ulushi va yoriq recall i
- Model nuqsonga emas, fonga yoki teksturaga qarayapti
- Paketdagi
"sinflar"ro'yxatidan
Vazifa 2: Xatolarni tuzating
1. mean, std = x.mean(), x.std()
2. ds_va = PapkaDataset(x[va], y[va], mean, std, augment=True)
3. loss = F.cross_entropy(logit, y) # yoriq 10%
4. sinflar = sorted(os.listdir(papka)) # Bashoratchi ichida
5. print(f"test aniqligi {aniqlik:.3f}") # hisobotdagi yagona qatorJavoblar
1. mean, std = x[tr].mean(), x[tr].std()
2. ds_va = PapkaDataset(x[va], y[va], mean, std)
3. loss = F.cross_entropy(logit, y, weight=sinf_vazni(y_tr))
4. sinflar = paket["sinflar"]
5. print(f"macro-recall {mr:.3f}, yoriq recall {recall['yoriq']:.3f}, "
f"o'tkazib yuborish {otkazildi:.3f}")Vazifa 3: Ma'lumot quvuri
Modellang:
- Papkaga yozish va qayta o'qish
- Stratifikatsiyalangan bo'lish
mean/stdva normallash tekshiruvi- Augmentatsiya yorliqni saqlashini tekshirish
Vazifa 4: O'rgatish
Modellang:
- Sinf vazni bilan va vaznsiz — yoriq recall i qanday o'zgaradi?
EngYaxshisiva tiklash tekshiruvi- 3 seed
- Jurnal yozuvi (konfig + metrika)
Vazifa 5: Taqqoslash
Modellang:
- 5 fold bilan qayta taqqoslash
- MLP ga yashirin qatlam qo'shing — farq qisqaradimi?
- Juftlashgan farq va SE
- Qaror qoidasi
Vazifa 6: Topshirish
Modellang:
- Test bir marta va sinf bo'yicha recall
- "Nuqsonsiz" ehtimolligi 0.9 dan past bo'lsa — qo'lda tekshiruv: o'tkazib yuborish va tekshiruv yuki qanday o'zgaradi?
- Grad-CAM nisbati sinf bo'yicha
- Paket,
Bashoratchiva hisobot
Vazifa 7: O'ylash
Loyihangiz ishga tushdi. Ikki haftadan keyin zavod liniyaga yangi, yorqinroq yoritgich o'rnatdi. Model hisobotlarida hech qanday xato yo'q, lekin nazoratchi "yoriqlar ko'proq o'tib ketyapti" deydi. Nima bo'lgan bo'lishi mumkin, qanday tekshirasiz va nima qilasiz?
Javob
Qisqa javob: bu ma'lumot drifti — kirish rasmlarining taqsimoti o'zgargan, model esa eski taqsimotda o'rgatilgan. Xato bermaydi, faqat sifat jimgina tushadi.
Nima uchun aynan yoriqlar:
1. Normallash qotirilgan. Paketdagi mean = 0.4985 va std = 0.0926 eski yoritgich bo'yicha hisoblangan. Yorqinroq rasmlarda (x - mean) / std natijasi butunlay siljiydi — model hech qachon ko'rmagan qiymatlar oralig'iga tushadi.
2. Yoriq — qorong'i nuqson. Tirnalish va yoriqni ajratadigan asosiy belgi yorqinlik (2.2-bo'lim). Kuchli yorug'likda qorong'i chiziqlar kontrasti pasayadi, yoriqlar fonga "singib" ketadi. Test jadvalida ham yoriq eng zaif sinf edi (0.778) — chegaradagi misollar birinchi bo'lib yo'qoladi.
3. Augmentatsiya bu holatni qamramagan. Biz yorqinlik augmentatsiyasini ataylab ishlatmadik (yorliqni buzishi mumkin edi). Demak model yorqinlik o'zgarishiga chidamli bo'lishni o'rganmagan.
Qanday tekshirasiz:
- Yangi rasmlarning o'rtacha yorqinligi va std sini paketdagi
mean/stdbilan solishtiring — bu eng arzon drift signali. - Bashoratlar taqsimotini kuzating: "nuqsonsiz" ulushi oshganmi? (Yoriqlar nuqsonsizga o'tsa, aynan shu ko'rinadi.)
- Yangi yoritgich ostida bir necha o'nta nuqsonli detalni qo'lda yorliqlab, sinf bo'yicha recall ni qayta hisoblang.
- Grad-CAM: yangi rasmlarda model hali ham nuqsonga qarayaptimi?
Nima qilasiz:
- Qisqa muddat: "nuqsonsiz" ehtimolligi chegarasini ko'tarib, shubhali detallarni qo'lda tekshiruvga yuborish — o'tkazib yuborish qimmat.
- O'rta muddat: yangi yoritgich ostida ma'lumot yig'ib, yorliqlash va qayta o'rgatish; normallashni har rasm bo'yicha (per-image) qilishni ko'rib chiqish; yorliqni buzmaydigan yumshoq yorqinlik augmentatsiyasini sinab ko'rish.
- Uzoq muddat: kirish statistikasi va bashorat taqsimotini doimiy kuzatish (MLOps qismida), liniyadagi har qanday apparat o'zgarishi haqida jamoaga xabar berish jarayoni.
Muhim nuans: "model hisobotlarida xato yo'q" — drift ning eng xavfli xususiyati. Paketdagi nazorat namunasi faqat model o'zgarmaganini tasdiqlaydi, dunyo o'zgarmaganini emas. Shuning uchun hisobotdagi "CHEKLOVLAR: real kamerada yorug'lik va fokus o'zgarishi tekshirilmagan" qatori rasmiyatchilik emas, aynan shu vaziyat haqidagi ogohlantirish edi.
Nimani mustahkamlaydi: 2.2, 2.5, 2.6-bo'limlar.
Xulosa
Bu darsda to'liq kompyuter ko'rish loyihasini qurdik: papkadan paketgacha.
Eng muhim uch fikr:
Loyiha skeleti 21.12 niki, rasmga xos qo'shimchalar esa augmentatsiya va vizual tekshiruv. 1-misolda 1400 ta PNG papkalardan o'qildi, stratifikatsiya har bo'lakda yoriq ulushini
0.096-0.100da ushladi,mean/stdfaqat o'quvdan olindi va augmentatsiya piksellar to'plamini o'zgartirmasligi tekshirildi. Tez tekshiruvlar va bitta batch testi o'rgatishdan oldin quvurni tasdiqladi.Qaror juftlashgan farq bilan, test esa xatolar tarkibini ko'rsatish uchun. Uch seedda CNN val macro-recall i
0.8792 +- 0.0127bo'ldi. Bir xil foldlarda CNN MLP dan+0.2132ga yaxshi chiqdi (2*SE = 0.0646), shuning uchun qoida CNN ni tanladi. Test bir marta ochildi: macro-recall0.9003, lekin yoriq recall i atigi0.778va nuqsonli detallarning0.079qismi o'tkazib yuborildi — hisobotdagi eng muhim raqam aynan shu.Model to'g'ri sababga ko'ra to'g'ri, paket esa o'zini tekshiradi. Grad-CAM energiyasi nuqson niqobida uning yuzasidan
2.4barobar zich chiqdi. Paket sinflar ro'yxati,mean/std, satr versiya va xom nazorat namunasi bilanweights_only=Trueorqali to'liq yuklandi;Bashoratchiesa PNG fayllardan to'g'ridan-to'g'ri bashorat qildi — va zaif joyni ham halol ko'rsatdi: yoriqli namuna "nuqsonsiz" deb tasniflandi.
Bu bilan 22-qism — Kompyuter ko'rish yakunlandi. Endi bizda rasm bilan ishlashning to'liq zanjiri bor: piksellardan konvolyutsiyagacha, klassifikatsiyadan obyekt aniqlash va segmentatsiyagacha, o'rgatishdan tekshiriladigan va topshiriladigan paketgacha. Keyingi qism — NLP: matn bilan ishlash, tokenlash, so'z embeddinglari va ketma-ketlik modellari. Ko'rasizki, bu yerda qurgan skelet u yerda ham deyarli o'zgarmay ishlaydi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!