Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Papka tuzilmasi
- 2.2. O'lcham, kanal, normalizatsiya
- 2.3. Stratifikatsiyalangan bo'lish
- 2.4. Sinflar nomutanosibligi
- 2.5. Trainer va eng yaxshi holat
- 2.6. Chalkashlik matritsasi va sinf bo'yicha hisobot
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Papkadan o'qiydigan PapkaDataset
- Misol 2 — Stratifikatsiya, normalizatsiya va nomutanosiblik
- Misol 3 — Trainer, eng yaxshi holat va sinf vaznlari
- Misol 4 — Test bir marta, chalkashlik matritsasi va paket
- 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.10-dars: Rasm klassifikatsiya loyihasi
22-QISM — KOMPYUTER KO'RISH · 10-dars
1. Kirish va motivatsiya
Shu paytgacha rasmlarimiz tayyor tensor bo'lib kelardi: load_digits() chaqirildi — va qo'limizda (N, 8, 8) massiv. Haqiqiy loyihada ma'lumot hech qachon bunday kelmaydi. Sizga papka beriladi: ichida sinf_nomi/ kichik papkalari, ularda turli o'lchamdagi .png va .jpg fayllar, ba'zilari rangli, ba'zilari kulrang, orasida esa kimdir tasodifan qoldirgan izoh.txt yoki Thumbs.db.
Bu darsda rasm klassifikatsiyasini fayllardan boshlab to'liq loyiha sifatida quramiz. torchvision.datasets.ImageFolder aynan shu tuzilmani o'qiydi, lekin u bu muhitda yo'q — shuning uchun uning o'rnini bosuvchi PapkaDataset ni o'zimiz yozamiz va ichida nima bo'layotganini to'liq ko'ramiz: sinflar ro'yxati qanday tuziladi, har xil o'lchamdagi rasm qanday bitta o'lchamga keltiriladi, kulrang rasm uch kanalga qanday aylanadi, normalizatsiya uchun mean/std qayerdan olinadi.
Keyin 21-qismdagi tartib: stratifikatsiyalangan bo'lish, sinflar nomutanosibligi bilan ishlash, Trainer va eng yaxshi holat, test to'plamini bir marta ochish. Oxirida bitta aniqlik raqami bilan cheklanmaymiz — chalkashlik matritsasi va sinf bo'yicha aniqlik modelning qaysi sinflarda qoqilishini ko'rsatadi.
Real vaziyat. Logistika kompaniyasi posilkalardagi yorliq turlarini (4 xil) aniqlaydigan model yaratdi. Umumiy aniqlik 91% edi va hamma xursand bo'ldi. Keyin ma'lum bo'ldiki, eng kam uchraydigan "xavfli yuk" yorlig'ining faqat 55% i to'g'ri topilgan — umumiy raqam uni ko'p sinflar ortiga yashirgan edi. Sinf bo'yicha hisobot va nomutanosiblikni hisobga olgan o'rgatish bu muammoni bir haftada ochib berdi.
Bu darsda papkadagi rasmlardan boshlab to'liq klassifikatsiya loyihasini quramiz.
Bu darsda:
- Papka tuzilmasi va
PapkaDataset - O'lcham, kanal va normalizatsiya
- Stratifikatsiyalangan bo'lish
- Sinflar nomutanosibligi
Trainer, eng yaxshi holat va test- Chalkashlik matritsasi va sinf bo'yicha aniqlik
- Tuzoqlar
ℹ Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
2. Nazariya — chuqur tushuntirish
2.1. Papka tuzilmasi
ildiz/
doira/ rasm_000.png rasm_001.png ...
kvadrat/ rasm_000.png ...
uchburchak/ ...
xoch/ ...
QOIDALAR (ImageFolder bilan bir xil):
sinf nomi = kichik papka nomi
sinflar ALIFBO tartibida saralanadi -> indeks 0, 1, 2, ...
faqat ruxsat etilgan kengaytmalar (.png, .jpg, .jpeg, ...)
boshqa fayllar (izoh.txt, Thumbs.db) tashlab yuboriladi
NEGA SARALASH MUHIM:
os.listdir / iterdir tartibi kafolatlanmagan
saralanmasa, boshqa kompyuterda "doira" = 2 bo'lib qolishi mumkin
-> model boshqa sinf nomini chiqaradi, xato ko'rinmaydiSinf indeksi = saralangan papka nomlaridagi o'rin; bu xarita modelning bir qismi va paketga saqlanadi.
2.2. O'lcham, kanal, normalizatsiya
HAR BIR RASM UCHUN:
Image.open(yol)
.convert("RGB") kulrang (L), RGBA, P -> 3 kanal
.resize((S, S), BILINEAR) har xil o'lcham -> bitta o'lcham
np.asarray / 255 [0, 255] uint8 -> [0, 1] float
(H, W, C) -> (C, H, W) PyTorch tartibi
(x - mean) / std kanal bo'yicha
MEAN/STD QAYERDAN:
FAQAT o'quv to'plamidan, kanal bo'yicha (3 son + 3 son)
val / test / ishlab chiqarish - AYNAN shu sonlar bilan
(pretrained model bo'lsa - uning o'z mean/std si, 22.9)
O'LCHAMNI MOSLASH VARIANTLARI:
resize - tomonlar nisbati buziladi, lekin hamma narsa qoladi
resize + crop - nisbat saqlanadi, chetlari kesiladi
pad + resize - nisbat saqlanadi, bo'sh joy qo'shiladi Har bir rasm bir xil yo'ldan o'tadi: RGB → o'lcham → [0, 1] → (C, H, W) → normalizatsiya; mean/std faqat o'quvdan.
2.3. Stratifikatsiyalangan bo'lish
idx = np.arange(len(ds)); y = ds.yorliqlar
ish, test = train_test_split(idx, test_size=0.2, stratify=y, random_state=0)
oquv, val = train_test_split(ish, test_size=0.25, stratify=y[ish], ...)
Subset(ds, oquv), Subset(ds, val), Subset(ds, test)
NEGA STRATIFY:
kam sinfda (masalan, 30 rasm) tasodifiy bo'lish testga 2 ta
yoki 10 ta tushirishi mumkin -> sinf bo'yicha baho ishonchsiz
HAQIQIY LOYIHADA QO'SHIMCHA:
bir obyektning bir necha surati -> guruh bo'yicha bo'lish (18-qism)
vaqt bo'yicha yig'ilgan -> vaqt bo'yicha bo'lish
dublikat rasmlar -> bo'lishdan OLDIN tozalanadiStratifikatsiya har sinf ulushini uch to'plamda bir xil saqlaydi — kam sinflarda bu ayniqsa muhim.
2.4. Sinflar nomutanosibligi
MUAMMO: doira 120, xoch 30 -> model "ko'p sinf" tomonga og'adi
oddiy aniqlik (accuracy) kam sinf xatolarini yashiradi
YECHIMLAR:
1. SINF VAZNLARI:
w_c = N / (K * n_c)
nn.CrossEntropyLoss(weight=torch.tensor(w))
2. WeightedRandomSampler:
har namunaga 1 / n_c vazn -> batchlarda sinflar ~teng
3. METRIKA:
balanced accuracy = sinf bo'yicha recall larning o'rtachasi
eng kichik sinf recall i
(18-qism: accuracy yetarli emas)
TANLOV: bittasi yetarli (vazn YOKI sampler), ikkalasini birga
qo'llash ko'pincha kam sinfni haddan tashqari kuchaytiradiNomutanosiblikda metrika — balanced accuracy va sinf bo'yicha recall; vaznlar esa o'rgatishni shu metrikaga yo'naltiradi.
2.5. Trainer va eng yaxshi holat
Trainer 21.5-bob:
_davr(dl, orgatish) -> loss, acc, bacc (butun davr bashoratlarida)
EngYaxshisi(monitor="val_bacc", sabr=8):
yaxshilansa - deepcopy(state_dict)
oxirida - load_state_dict(eng yaxshi)
MONITOR TANLASH:
nomutanosib sinflarda val_bacc (val_acc emas)
val_loss - silliq, lekin metrikaga to'g'ridan-to'g'ri mos emas
TEST:
model, giperparametrlar, epoxalar soni - FAQAT val bo'yicha
test oxirida BIR MARTA Monitor metrika — loyihaning asosiy metrikasi bilan bir xil bo'lishi kerak; nomutanosiblikda bu balanced accuracy.
2.6. Chalkashlik matritsasi va sinf bo'yicha hisobot
confusion_matrix(y_haqiqiy, y_bashorat)
satr = haqiqiy sinf
ustun = bashorat qilingan sinf
diagonal = to'g'ri
SINF BO'YICHA:
recall_c = diagonal_c / satr_yig'indisi_c "sinfning qanchasi topildi"
precision_c = diagonal_c / ustun_yig'indisi_c "bashoratning qanchasi to'g'ri"
NIMA IZLANADI:
eng katta diagonaldan tashqari katak -> qaysi juftlik chalkashadi
kam sinf recall i past -> nomutanosiblik ta'siri
bitta ustun katta -> model "yig'uvchi" sinfga og'gan
XATOLARNI KO'ZDAN KECHIRISH:
eng ishonchli xato bashoratlar -> ko'pincha belgilash xatosiChalkashlik matritsasi "qancha xato" emas, "qanday xato" ni ko'rsatadi.
2.7. Tuzoqlar
Asosiy tuzoqlar: sinflarni saralamasdan indekslash; kulrang yoki RGBA rasmni convert("RGB") siz yuklash (kanal soni har xil bo'lib, batch yig'ilmaydi); mean/std ni butun to'plamda yoki testda hisoblash; stratifikatsiyasiz bo'lish; nomutanosib sinflarda faqat accuracy ga qarash; vazn va samplerni birga qo'llab kam sinfni haddan oshirish; testni epoxa tanlashda ishlatish; paketga sinf nomlari va mean/std ni qo'shmaslik; papkadagi begona fayllarni filtrlamaslik.
3. Tez ma'lumotnoma
from pathlib import Path
import numpy as np
import torch
from PIL import Image
from torch.utils.data import Dataset
KENGAYTMALAR = {".png", ".jpg", ".jpeg", ".bmp"}
class PapkaDataset(Dataset):
def __init__(self, ildiz, olcham=24):
ildiz = Path(ildiz)
self.sinflar = sorted(p.name for p in ildiz.iterdir() if p.is_dir())
self.namunalar = [(f, i) for i, s in enumerate(self.sinflar)
for f in sorted((ildiz / s).iterdir())
if f.suffix.lower() in KENGAYTMALAR]
self.olcham, self.mean, self.std = olcham, None, None
def __len__(self):
return len(self.namunalar)
def __getitem__(self, i):
yol, y = self.namunalar[i]
with Image.open(yol) as im:
im = im.convert("RGB").resize((self.olcham, self.olcham),
Image.BILINEAR)
x = torch.from_numpy(np.asarray(im, np.float32) / 255).permute(2, 0, 1)
if self.mean is not None:
x = (x - self.mean[:, None, None]) / self.std[:, None, None]
return x, y
# sinf vaznlari: w_c = N / (K * n_c)
soni = np.bincount(y_oquv, minlength=K)
kriteriy = torch.nn.CrossEntropyLoss(
weight=torch.tensor(len(y_oquv) / (K * soni), dtype=torch.float32))Loyiha xulosasi
papka -> sinflar (saralangan) -> PapkaDataset (RGB, resize, /255, CHW)
stratify bo'lish -> mean/std faqat o'quvda
nomutanosiblik: sinf vaznlari yoki sampler; metrika - balanced accuracy
Trainer + EngYaxshisi(val_bacc) -> test BIR MARTA
chalkashlik matritsasi + sinf bo'yicha recall/precision
paket: holat + sinflar + mean/std + olcham4. Batafsil misollar
Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
Barcha misollar vaqtincha papkada PIL bilan sintetik rasmlar yaratadi: to'rt sinf — doira, kvadrat, uchburchak, xoch. Rasmlar turli o'lchamda (tomonlari 20–48 piksel, nisbati 0,7–1,4 gacha), fon qorong'iroq, shakl yorqinroq, ranglar tasodifiy, ustiga kuchli shovqin qo'shilgan, har yettinchi rasm atrofida kulrang (L rejim). Sinflar nomutanosib: 120, 90, 60 va 30 rasm. doira papkasida esa begona izoh.txt fayli ham bor.
Misol 1 — Papkadan o'qiydigan PapkaDataset
"""Papka tuzilmasi va ImageFolder ga o'xshash o'z Dataset imiz."""
import shutil
import tempfile
from collections import Counter
from pathlib import Path
import numpy as np
import torch
from PIL import Image, ImageDraw
from torch.utils.data import DataLoader, Dataset
SINFLAR = {"doira": 120, "kvadrat": 90, "uchburchak": 60, "xoch": 30}
KENGAYTMALAR = {".png", ".jpg", ".jpeg", ".bmp"}
def rasm_chiz(sinf, rng):
w = int(rng.integers(20, 49))
h = int(np.clip(w * rng.uniform(0.7, 1.4), 20, 48))
fon = rng.integers(0, 130, 3)
rang = rng.integers(110, 256, 3)
im = Image.new("RGB", (w, h), tuple(int(v) for v in fon))
d = ImageDraw.Draw(im)
r = rng.uniform(0.22, 0.42) * min(w, h)
cx, cy = rng.uniform(r, w - r), rng.uniform(r, h - r)
f = tuple(int(v) for v in rang)
if sinf == "doira":
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=f)
elif sinf == "kvadrat":
k = 0.8 * r
d.rectangle([cx - k, cy - k, cx + k, cy + k], fill=f)
elif sinf == "uchburchak":
d.polygon([(cx, cy - r), (cx - r, cy + 0.8 * r),
(cx + r, cy + 0.8 * r)], fill=f)
else:
t = 0.3 * r
d.rectangle([cx - r, cy - t, cx + r, cy + t], fill=f)
d.rectangle([cx - t, cy - r, cx + t, cy + r], fill=f)
arr = np.asarray(im, np.float32) + rng.normal(0, 30, (h, w, 3))
im = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
return im.convert("L") if rng.random() < 0.15 else im
def papka_yarat(ildiz, seed=0):
rng = np.random.default_rng(seed)
for sinf, soni in SINFLAR.items():
(ildiz / sinf).mkdir(parents=True)
for i in range(soni):
rasm_chiz(sinf, rng).save(ildiz / sinf / f"rasm_{i:03d}.png")
(ildiz / "doira" / "izoh.txt").write_text("begona fayl", encoding="utf-8")
class PapkaDataset(Dataset):
def __init__(self, ildiz, olcham=24):
ildiz = Path(ildiz)
self.sinflar = sorted(p.name for p in ildiz.iterdir() if p.is_dir())
self.sinf_idx = {s: i for i, s in enumerate(self.sinflar)}
self.namunalar, self.tashlangan = [], []
for s in self.sinflar:
for f in sorted((ildiz / s).iterdir()):
if f.suffix.lower() in KENGAYTMALAR:
self.namunalar.append((f, self.sinf_idx[s]))
else:
self.tashlangan.append(f)
self.yorliqlar = np.array([y for _, y in self.namunalar])
self.olcham, self.mean, self.std = olcham, None, None
def __len__(self):
return len(self.namunalar)
def __getitem__(self, i):
yol, y = self.namunalar[i]
with Image.open(yol) as im:
im = im.convert("RGB").resize((self.olcham, self.olcham),
Image.BILINEAR)
x = torch.from_numpy(np.asarray(im, np.float32) / 255)
x = x.permute(2, 0, 1).contiguous()
if self.mean is not None:
x = (x - self.mean[:, None, None]) / self.std[:, None, None]
return x, y
def main() -> None:
ildiz = Path(tempfile.mkdtemp(prefix="rasmlar_"))
try:
papka_yarat(ildiz)
print("=== 1. Papka tuzilmasi ===")
for p in sorted(ildiz.iterdir()):
fayllar = sorted(p.iterdir())
turlar = Counter(f.suffix for f in fayllar)
print(f" {p.relative_to(ildiz).as_posix() + '/':<12} "
f"{len(fayllar):>4} fayl {dict(sorted(turlar.items()))}")
print("\n=== 2. Rasmlar bir xil emas ===")
olchamlar, rejimlar = [], Counter()
for p in sorted(ildiz.glob("*/*.png")):
with Image.open(p) as im:
olchamlar.append(im.size)
rejimlar[im.mode] += 1
w = np.array([s[0] for s in olchamlar])
h = np.array([s[1] for s in olchamlar])
print(f" kenglik {w.min()}..{w.max()}, balandlik {h.min()}..{h.max()}")
print(f" har xil o'lchamlar soni: {len(set(olchamlar))}")
print(f" rejimlar: {dict(sorted(rejimlar.items()))}")
print("\n=== 3. PapkaDataset ===")
ds = PapkaDataset(ildiz, olcham=24)
print(f" sinflar (saralangan): {ds.sinf_idx}")
print(f" namunalar: {len(ds)}")
print(f" tashlangan: "
f"{[f.relative_to(ildiz).as_posix() for f in ds.tashlangan]}")
print(f" sinf bo'yicha: {np.bincount(ds.yorliqlar).tolist()}")
x, y = ds[0]
print(f" ds[0]: x {tuple(x.shape)} {x.dtype}, "
f"oraliq [{x.min():.3f}, {x.max():.3f}], y = {y} "
f"({ds.sinflar[y]})")
print("\n=== 4. Kulrang rasm -> 3 kanal ===")
kul = next(i for i, (f, _) in enumerate(ds.namunalar)
if Image.open(f).mode == "L")
xk, _ = ds[kul]
print(f" {ds.namunalar[kul][0].relative_to(ildiz).as_posix()}: "
f"shakl {tuple(xk.shape)}")
print(f" uch kanal teng: {torch.equal(xk[0], xk[1]) and torch.equal(xk[1], xk[2])}")
with Image.open(ds.namunalar[kul][0]) as im:
xom = np.asarray(im)
print(f" convert siz shakl bo'lardi: {xom.shape} - batch yig'ilmaydi")
print("\n=== 5. DataLoader ===")
dl = DataLoader(ds, batch_size=32, shuffle=True,
generator=torch.Generator().manual_seed(0))
xb, yb = next(iter(dl))
print(f" batch: x {tuple(xb.shape)}, y {tuple(yb.shape)}")
print(f" batchdagi sinflar: {np.bincount(yb.numpy(), minlength=4).tolist()}")
print(" ⭐ Har xil fayllar -> bir xil (3, 24, 24) tensor")
finally:
shutil.rmtree(ildiz, ignore_errors=True)
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Papka tuzilmasi ===
doira/ 121 fayl {'.png': 120, '.txt': 1}
kvadrat/ 90 fayl {'.png': 90}
uchburchak/ 60 fayl {'.png': 60}
xoch/ 30 fayl {'.png': 30}
=== 2. Rasmlar bir xil emas ===
kenglik 20..48, balandlik 20..48
har xil o'lchamlar soni: 201
rejimlar: {'L': 42, 'RGB': 258}
=== 3. PapkaDataset ===
sinflar (saralangan): {'doira': 0, 'kvadrat': 1, 'uchburchak': 2, 'xoch': 3}
namunalar: 300
tashlangan: ['doira/izoh.txt']
sinf bo'yicha: [120, 90, 60, 30]
ds[0]: x (3, 24, 24) torch.float32, oraliq [0.000, 0.675], y = 0 (doira)
=== 4. Kulrang rasm -> 3 kanal ===
doira/rasm_001.png: shakl (3, 24, 24)
uch kanal teng: True
convert siz shakl bo'lardi: (34, 43) - batch yig'ilmaydi
=== 5. DataLoader ===
batch: x (32, 3, 24, 24), y (32,)
batchdagi sinflar: [6, 13, 8, 5]
⭐ Har xil fayllar -> bir xil (3, 24, 24) tensorNima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — Stratifikatsiya, normalizatsiya va nomutanosiblik
"""Bo'lish, o'quvdan mean/std va sinflar nomutanosibligi."""
import shutil
import tempfile
from pathlib import Path
import numpy as np
import torch
from PIL import Image, ImageDraw
from sklearn.model_selection import train_test_split
from torch.utils.data import (DataLoader, Dataset, Subset,
WeightedRandomSampler)
SINFLAR = {"doira": 120, "kvadrat": 90, "uchburchak": 60, "xoch": 30}
KENGAYTMALAR = {".png", ".jpg", ".jpeg", ".bmp"}
def rasm_chiz(sinf, rng):
w = int(rng.integers(20, 49))
h = int(np.clip(w * rng.uniform(0.7, 1.4), 20, 48))
fon = rng.integers(0, 130, 3)
rang = rng.integers(110, 256, 3)
im = Image.new("RGB", (w, h), tuple(int(v) for v in fon))
d = ImageDraw.Draw(im)
r = rng.uniform(0.22, 0.42) * min(w, h)
cx, cy = rng.uniform(r, w - r), rng.uniform(r, h - r)
f = tuple(int(v) for v in rang)
if sinf == "doira":
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=f)
elif sinf == "kvadrat":
k = 0.8 * r
d.rectangle([cx - k, cy - k, cx + k, cy + k], fill=f)
elif sinf == "uchburchak":
d.polygon([(cx, cy - r), (cx - r, cy + 0.8 * r),
(cx + r, cy + 0.8 * r)], fill=f)
else:
t = 0.3 * r
d.rectangle([cx - r, cy - t, cx + r, cy + t], fill=f)
d.rectangle([cx - t, cy - r, cx + t, cy + r], fill=f)
arr = np.asarray(im, np.float32) + rng.normal(0, 30, (h, w, 3))
im = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
return im.convert("L") if rng.random() < 0.15 else im
def papka_yarat(ildiz, seed=0):
rng = np.random.default_rng(seed)
for sinf, soni in SINFLAR.items():
(ildiz / sinf).mkdir(parents=True)
for i in range(soni):
rasm_chiz(sinf, rng).save(ildiz / sinf / f"rasm_{i:03d}.png")
(ildiz / "doira" / "izoh.txt").write_text("begona fayl", encoding="utf-8")
class PapkaDataset(Dataset):
def __init__(self, ildiz, olcham=24):
ildiz = Path(ildiz)
self.sinflar = sorted(p.name for p in ildiz.iterdir() if p.is_dir())
self.namunalar = [(f, i) for i, s in enumerate(self.sinflar)
for f in sorted((ildiz / s).iterdir())
if f.suffix.lower() in KENGAYTMALAR]
self.yorliqlar = np.array([y for _, y in self.namunalar])
self.olcham, self.mean, self.std = olcham, None, None
def __len__(self):
return len(self.namunalar)
def __getitem__(self, i):
yol, y = self.namunalar[i]
with Image.open(yol) as im:
im = im.convert("RGB").resize((self.olcham, self.olcham),
Image.BILINEAR)
x = torch.from_numpy(np.asarray(im, np.float32) / 255)
x = x.permute(2, 0, 1).contiguous()
if self.mean is not None:
x = (x - self.mean[:, None, None]) / self.std[:, None, None]
return x, y
def kanal_statistika(ds, indekslar):
x = torch.stack([ds[i][0] for i in indekslar])
return x.mean((0, 2, 3)), x.std((0, 2, 3))
def main() -> None:
ildiz = Path(tempfile.mkdtemp(prefix="rasmlar_"))
try:
papka_yarat(ildiz)
ds = PapkaDataset(ildiz)
y = ds.yorliqlar
K = len(ds.sinflar)
print("=== 1. Stratifikatsiyalangan bo'lish 60/20/20 ===")
idx = np.arange(len(ds))
ish, te = train_test_split(idx, test_size=0.2, stratify=y,
random_state=0)
tr, va = train_test_split(ish, test_size=0.25, stratify=y[ish],
random_state=0)
print(f" {'sinf':<11} {'jami':>5} {'o_quv':>6} {'val':>5} "
f"{'test':>5} {'test ulushi':>12}")
for c, s in enumerate(ds.sinflar):
print(f" {s:<11} {(y == c).sum():>5} {(y[tr] == c).sum():>6} "
f"{(y[va] == c).sum():>5} {(y[te] == c).sum():>5} "
f"{(y[te] == c).mean():>12.3f}")
rng = np.random.default_rng(3)
tasodif_xoch = [int((y[rng.permutation(len(y))[:84]] == 3).sum())
for _ in range(200)]
print(f" stratifysiz: testdagi xoch soni 200 urinishda "
f"{min(tasodif_xoch)}..{max(tasodif_xoch)} (stratify: "
f"{(y[te] == 3).sum()})")
print("\n=== 2. Mean/std faqat o'quvdan ===")
m_tr, s_tr = kanal_statistika(ds, tr)
m_all, s_all = kanal_statistika(ds, idx)
print(f" o'quv: mean {m_tr.numpy().round(4)}, "
f"std {s_tr.numpy().round(4)}")
print(f" hammasi: mean {m_all.numpy().round(4)} (ishlatilmaydi)")
ds.mean, ds.std = m_tr, s_tr
m2, s2 = kanal_statistika(ds, tr)
m3, s3 = kanal_statistika(ds, va)
print(f" normallashgan o'quv: mean {m2.numpy().round(3)}, "
f"std {s2.numpy().round(3)}")
print(f" normallashgan val: mean {m3.numpy().round(3)}, "
f"std {s3.numpy().round(3)}")
print(" val ~0/1 ga yaqin, lekin aynan emas - bu normal")
print("\n=== 3. Nomutanosiblik ===")
soni = np.bincount(y[tr], minlength=K)
print(f" o'quvdagi soni: {dict(zip(ds.sinflar, soni.tolist()))}")
eng_kop = soni.argmax()
print(f" hammasini '{ds.sinflar[eng_kop]}' deyish: accuracy "
f"{(y[te] == eng_kop).mean():.3f}, balanced accuracy "
f"{1 / K:.3f}")
w = len(y[tr]) / (K * soni)
print(f" sinf vaznlari N/(K*n_c): "
f"{dict(zip(ds.sinflar, w.round(3).tolist()))}")
print(f" vaznli yig'indi har sinfga: {(w * soni).round(1).tolist()}")
print("\n=== 4. WeightedRandomSampler ===")
namuna_vazn = torch.tensor(1.0 / soni[y[tr]], dtype=torch.double)
g = torch.Generator().manual_seed(0)
sampler = WeightedRandomSampler(namuna_vazn, len(tr),
replacement=True, generator=g)
dl_s = DataLoader(Subset(ds, tr), batch_size=32, sampler=sampler)
dl_o = DataLoader(Subset(ds, tr), batch_size=32, shuffle=True,
generator=torch.Generator().manual_seed(0))
for nom, dl in [("oddiy shuffle", dl_o), ("sampler", dl_s)]:
yy = torch.cat([b for _, b in dl]).numpy()
ulush = np.bincount(yy, minlength=K) / len(yy)
print(f" {nom:<14} bir davrdagi ulushlar: {ulush.round(3).tolist()}")
takror = len(tr) - len(set(sampler))
print(" sampler: kam sinf takrorlanadi, ko'p sinfning bir qismi "
"ko'rilmaydi")
print(f" (bir davrda noyob bo'lmagan tanlovlar: {takror > 0})")
print(" ⭐ Vazn YOKI sampler - ikkalasi birga emas")
finally:
shutil.rmtree(ildiz, ignore_errors=True)
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Stratifikatsiyalangan bo'lish 60/20/20 ===
sinf jami o_quv val test test ulushi
doira 120 72 24 24 0.400
kvadrat 90 54 18 18 0.300
uchburchak 60 36 12 12 0.200
xoch 30 18 6 6 0.100
stratifysiz: testdagi xoch soni 200 urinishda 3..14 (stratify: 6)
=== 2. Mean/std faqat o'quvdan ===
o'quv: mean [0.3746 0.3752 0.3871], std [0.2299 0.2421 0.2286]
hammasi: mean [0.3816 0.3792 0.3777] (ishlatilmaydi)
normallashgan o'quv: mean [-0. 0. 0.], std [1. 1. 1.]
normallashgan val: mean [ 0.11 -0.004 -0.079], std [1.067 0.943 1.05 ]
val ~0/1 ga yaqin, lekin aynan emas - bu normal
=== 3. Nomutanosiblik ===
o'quvdagi soni: {'doira': 72, 'kvadrat': 54, 'uchburchak': 36, 'xoch': 18}
hammasini 'doira' deyish: accuracy 0.400, balanced accuracy 0.250
sinf vaznlari N/(K*n_c): {'doira': 0.625, 'kvadrat': 0.833, 'uchburchak': 1.25, 'xoch': 2.5}
vaznli yig'indi har sinfga: [45.0, 45.0, 45.0, 45.0]
=== 4. WeightedRandomSampler ===
oddiy shuffle bir davrdagi ulushlar: [0.4, 0.3, 0.2, 0.1]
sampler bir davrdagi ulushlar: [0.222, 0.211, 0.278, 0.289]
sampler: kam sinf takrorlanadi, ko'p sinfning bir qismi ko'rilmaydi
(bir davrda noyob bo'lmagan tanlovlar: True)
⭐ Vazn YOKI sampler - ikkalasi birga emasNima ko'rsatdi: 2.2, 2.3, 2.4-bo'limlar.
Misol 3 — Trainer, eng yaxshi holat va sinf vaznlari
"""Trainer + EngYaxshisi(val_bacc); vaznli va vaznsiz loss taqqoslash."""
import copy
import shutil
import tempfile
from pathlib import Path
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from PIL import Image, ImageDraw
from sklearn.metrics import balanced_accuracy_score, recall_score
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, Dataset, TensorDataset
SINFLAR = {"doira": 120, "kvadrat": 90, "uchburchak": 60, "xoch": 30}
KENGAYTMALAR = {".png", ".jpg", ".jpeg", ".bmp"}
def rasm_chiz(sinf, rng):
w = int(rng.integers(20, 49))
h = int(np.clip(w * rng.uniform(0.7, 1.4), 20, 48))
fon = rng.integers(0, 130, 3)
rang = rng.integers(110, 256, 3)
im = Image.new("RGB", (w, h), tuple(int(v) for v in fon))
d = ImageDraw.Draw(im)
r = rng.uniform(0.22, 0.42) * min(w, h)
cx, cy = rng.uniform(r, w - r), rng.uniform(r, h - r)
f = tuple(int(v) for v in rang)
if sinf == "doira":
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=f)
elif sinf == "kvadrat":
k = 0.8 * r
d.rectangle([cx - k, cy - k, cx + k, cy + k], fill=f)
elif sinf == "uchburchak":
d.polygon([(cx, cy - r), (cx - r, cy + 0.8 * r),
(cx + r, cy + 0.8 * r)], fill=f)
else:
t = 0.3 * r
d.rectangle([cx - r, cy - t, cx + r, cy + t], fill=f)
d.rectangle([cx - t, cy - r, cx + t, cy + r], fill=f)
arr = np.asarray(im, np.float32) + rng.normal(0, 30, (h, w, 3))
im = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
return im.convert("L") if rng.random() < 0.15 else im
def papka_yarat(ildiz, seed=0):
rng = np.random.default_rng(seed)
for sinf, soni in SINFLAR.items():
(ildiz / sinf).mkdir(parents=True)
for i in range(soni):
rasm_chiz(sinf, rng).save(ildiz / sinf / f"rasm_{i:03d}.png")
class PapkaDataset(Dataset):
def __init__(self, ildiz, olcham=24):
ildiz = Path(ildiz)
self.sinflar = sorted(p.name for p in ildiz.iterdir() if p.is_dir())
self.namunalar = [(f, i) for i, s in enumerate(self.sinflar)
for f in sorted((ildiz / s).iterdir())
if f.suffix.lower() in KENGAYTMALAR]
self.yorliqlar = np.array([y for _, y in self.namunalar])
self.olcham = olcham
def __len__(self):
return len(self.namunalar)
def __getitem__(self, i):
yol, y = self.namunalar[i]
with Image.open(yol) as im:
im = im.convert("RGB").resize((self.olcham, self.olcham),
Image.BILINEAR)
x = torch.from_numpy(np.asarray(im, np.float32) / 255)
return x.permute(2, 0, 1).contiguous(), y
def tayyor_tensorlar(ds, tr, va):
"""Kichik to'plam: hamma rasm bir marta o'qiladi (kesh), mean/std o'quvdan."""
x = torch.stack([ds[i][0] for i in range(len(ds))])
y = torch.tensor(ds.yorliqlar)
m = x[tr].mean((0, 2, 3))[:, None, None]
s = x[tr].std((0, 2, 3))[:, None, None]
return (x - m) / s, y
class CNN(nn.Module):
def __init__(self, k):
super().__init__()
def blok(a, b):
return [nn.Conv2d(a, b, 3, padding=1), nn.BatchNorm2d(b),
nn.ReLU()]
self.q = nn.Sequential(*blok(3, 16), nn.MaxPool2d(2),
*blok(16, 32), nn.MaxPool2d(2),
*blok(32, 64), nn.AdaptiveAvgPool2d(1),
nn.Flatten(), nn.Linear(64, k))
def forward(self, x):
return self.q(x)
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, kriteriy, callbacklar, jadval=None):
self.model, self.opt, self.kriteriy = model, opt, kriteriy
self.jadval = jadval
self.callbacklar, self.tarix, self.toxtash = callbacklar, [], False
def _davr(self, dl, orgatish):
self.model.train(orgatish)
jami, n, p, t = 0.0, 0, [], []
with torch.set_grad_enabled(orgatish):
for xb, yb in dl:
ch = self.model(xb)
loss = self.kriteriy(ch, yb)
if orgatish:
self.opt.zero_grad()
loss.backward()
self.opt.step()
jami += loss.item() * len(yb)
n += len(yb)
p.append(ch.argmax(1))
t.append(yb)
p, t = torch.cat(p).numpy(), torch.cat(t).numpy()
return {"loss": jami / n, "acc": (p == t).mean(),
"bacc": balanced_accuracy_score(t, 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):
a, b = self._davr(dl_tr, True), self._davr(dl_va, False)
if self.jadval is not None:
self.jadval.step()
log = {"train_loss": a["loss"], "val_loss": b["loss"],
"val_acc": b["acc"], "val_bacc": b["bacc"]}
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 pd.DataFrame(self.tarix)
def yurish(x, y, tr, va, vaznli, seed, davrlar=30):
torch.manual_seed(seed)
K = int(y.max()) + 1
soni = np.bincount(y[tr].numpy(), minlength=K)
w = torch.tensor(len(tr) / (K * soni), dtype=torch.float32)
kriteriy = nn.CrossEntropyLoss(weight=w if vaznli else None)
model = CNN(K)
opt = torch.optim.AdamW(model.parameters(), lr=3e-3, weight_decay=1e-4)
jadval = torch.optim.lr_scheduler.CosineAnnealingLR(opt, davrlar)
dl_tr = DataLoader(TensorDataset(x[tr], y[tr]), batch_size=32,
shuffle=True,
generator=torch.Generator().manual_seed(seed))
dl_va = DataLoader(TensorDataset(x[va], y[va]), batch_size=256)
eyx = EngYaxshisi("val_bacc", sabr=8)
tarix = Trainer(model, opt, kriteriy, [eyx], jadval).fit(dl_tr, dl_va,
davrlar)
model.eval()
with torch.no_grad():
p = model(x[va]).argmax(1).numpy()
rec = recall_score(y[va].numpy(), p, average=None)
return model, tarix, eyx, rec
def main() -> None:
ildiz = Path(tempfile.mkdtemp(prefix="rasmlar_"))
try:
papka_yarat(ildiz)
ds = PapkaDataset(ildiz)
y_np = ds.yorliqlar
idx = np.arange(len(ds))
ish, _ = train_test_split(idx, test_size=0.2, stratify=y_np,
random_state=0)
tr, va = train_test_split(ish, test_size=0.25, stratify=y_np[ish],
random_state=0)
x, y = tayyor_tensorlar(ds, tr, va)
finally:
shutil.rmtree(ildiz, ignore_errors=True)
print(f" o'quv {len(tr)}, val {len(va)}; test bu misolda OCHILMAYDI")
print("\n=== 1. Bitta yurish: vaznli loss, monitor val_bacc ===")
model, tarix, eyx, rec = yurish(x, y, tr, va, vaznli=True, seed=0)
print(f" {len(tarix)} davr, eng yaxshi val_bacc {eyx.eng:.4f} "
f"({eyx.davr}-davr)")
tanlov = sorted({1, 5, 10, eyx.davr, len(tarix)})
print(tarix[tarix["davr"].isin(tanlov)].round(4).to_string(index=False))
print("\n=== 2. Tiklangan holat ===")
with torch.no_grad():
p = model(x[va]).argmax(1).numpy()
qayta = balanced_accuracy_score(y[va].numpy(), p)
print(f" qayta hisoblangan val_bacc: {qayta:.4f}, callback: "
f"{eyx.eng:.4f}, mos: {abs(qayta - eyx.eng) < 1e-9}")
print("\n=== 3. Vaznli va vaznsiz loss, 3 seed ===")
print(f" {'seed':>4} {'vaznsiz bacc':>13} {'vaznli bacc':>12} "
f"{'vaznsiz xoch':>13} {'vaznli xoch':>12}")
b0, b1, r0, r1 = [], [], [], []
for s in range(3):
_, _, e0, q0 = yurish(x, y, tr, va, vaznli=False, seed=s)
_, _, e1, q1 = yurish(x, y, tr, va, vaznli=True, seed=s)
b0.append(e0.eng)
b1.append(e1.eng)
r0.append(q0[3])
r1.append(q1[3])
print(f" {s:>4} {e0.eng:>13.4f} {e1.eng:>12.4f} {q0[3]:>13.3f} "
f"{q1[3]:>12.3f}")
for nom, a, b in [("bacc", b0, b1), ("xoch recall", r0, r1)]:
d = np.array(b) - np.array(a)
se = d.std(ddof=1) / np.sqrt(len(d))
belgi = "sezilarli" if abs(d.mean()) > 2 * se else "2*SE ichida"
print(f" vaznli - vaznsiz, {nom:<12}: {d.mean():+.4f} "
f"(SE {se:.4f}) {belgi}")
if se < 1e-9:
print(f" SE = 0: uch farq aynan bir xil - val da atigi "
f"{int((y[va] == 3).sum())} ta xoch, metrika pog'onali")
if np.mean(b1) > np.mean(b0) and np.mean(r1) > np.mean(r0):
print(" yo'nalish vaznli loss foydasiga, lekin kichik val da buni")
print(" kattaroq to'plam yoki ko'proq seed bilan tasdiqlash kerak")
print(" ⭐ Qaror juftlashgan farq bilan; test hali yopiq")
if __name__ == "__main__":
main()Natijaning muhim qismi:
o'quv 180, val 60; test bu misolda OCHILMAYDI
=== 1. Bitta yurish: vaznli loss, monitor val_bacc ===
29 davr, eng yaxshi val_bacc 0.9167 (21-davr)
davr train_loss val_loss val_acc val_bacc
1 1.4023 1.3624 0.2667 0.2986
5 1.0460 1.3839 0.5000 0.3715
10 0.4066 0.7241 0.8833 0.8021
21 0.0638 0.3073 0.9667 0.9167
29 0.0663 0.3283 0.9333 0.8333
=== 2. Tiklangan holat ===
qayta hisoblangan val_bacc: 0.9167, callback: 0.9167, mos: True
=== 3. Vaznli va vaznsiz loss, 3 seed ===
seed vaznsiz bacc vaznli bacc vaznsiz xoch vaznli xoch
0 0.8750 0.9167 0.500 0.667
1 0.9167 0.9583 0.667 0.833
2 0.9236 0.9653 0.833 1.000
vaznli - vaznsiz, bacc : +0.0417 (SE 0.0000) sezilarli
SE = 0: uch farq aynan bir xil - val da atigi 6 ta xoch, metrika pog'onali
vaznli - vaznsiz, xoch recall : +0.1667 (SE 0.0000) sezilarli
SE = 0: uch farq aynan bir xil - val da atigi 6 ta xoch, metrika pog'onali
yo'nalish vaznli loss foydasiga, lekin kichik val da buni
kattaroq to'plam yoki ko'proq seed bilan tasdiqlash kerak
⭐ Qaror juftlashgan farq bilan; test hali yopiqNima ko'rsatdi: 2.4, 2.5-bo'limlar.
Misol 4 — Test bir marta, chalkashlik matritsasi va paket
"""Yakuniy model: test bir marta, sinf bo'yicha hisobot, paket."""
import copy
import shutil
import tempfile
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
from PIL import Image, ImageDraw
from sklearn.metrics import (balanced_accuracy_score, confusion_matrix,
precision_score, recall_score)
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, Dataset, TensorDataset
SINFLAR = {"doira": 120, "kvadrat": 90, "uchburchak": 60, "xoch": 30}
KENGAYTMALAR = {".png", ".jpg", ".jpeg", ".bmp"}
def rasm_chiz(sinf, rng):
w = int(rng.integers(20, 49))
h = int(np.clip(w * rng.uniform(0.7, 1.4), 20, 48))
fon = rng.integers(0, 130, 3)
rang = rng.integers(110, 256, 3)
im = Image.new("RGB", (w, h), tuple(int(v) for v in fon))
d = ImageDraw.Draw(im)
r = rng.uniform(0.22, 0.42) * min(w, h)
cx, cy = rng.uniform(r, w - r), rng.uniform(r, h - r)
f = tuple(int(v) for v in rang)
if sinf == "doira":
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=f)
elif sinf == "kvadrat":
k = 0.8 * r
d.rectangle([cx - k, cy - k, cx + k, cy + k], fill=f)
elif sinf == "uchburchak":
d.polygon([(cx, cy - r), (cx - r, cy + 0.8 * r),
(cx + r, cy + 0.8 * r)], fill=f)
else:
t = 0.3 * r
d.rectangle([cx - r, cy - t, cx + r, cy + t], fill=f)
d.rectangle([cx - t, cy - r, cx + t, cy + r], fill=f)
arr = np.asarray(im, np.float32) + rng.normal(0, 30, (h, w, 3))
im = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
return im.convert("L") if rng.random() < 0.15 else im
def papka_yarat(ildiz, seed=0):
rng = np.random.default_rng(seed)
for sinf, soni in SINFLAR.items():
(ildiz / sinf).mkdir(parents=True)
for i in range(soni):
rasm_chiz(sinf, rng).save(ildiz / sinf / f"rasm_{i:03d}.png")
def rasm_tensor(yol, olcham):
with Image.open(yol) as im:
im = im.convert("RGB").resize((olcham, olcham), Image.BILINEAR)
return torch.from_numpy(np.asarray(im, np.float32) / 255).permute(2, 0, 1)
class PapkaDataset(Dataset):
def __init__(self, ildiz, olcham=24):
ildiz = Path(ildiz)
self.sinflar = sorted(p.name for p in ildiz.iterdir() if p.is_dir())
self.namunalar = [(f, i) for i, s in enumerate(self.sinflar)
for f in sorted((ildiz / s).iterdir())
if f.suffix.lower() in KENGAYTMALAR]
self.yorliqlar = np.array([y for _, y in self.namunalar])
self.olcham = olcham
def __len__(self):
return len(self.namunalar)
def __getitem__(self, i):
yol, y = self.namunalar[i]
return rasm_tensor(yol, self.olcham), y
class CNN(nn.Module):
def __init__(self, k):
super().__init__()
def blok(a, b):
return [nn.Conv2d(a, b, 3, padding=1), nn.BatchNorm2d(b),
nn.ReLU()]
self.q = nn.Sequential(*blok(3, 16), nn.MaxPool2d(2),
*blok(16, 32), nn.MaxPool2d(2),
*blok(32, 64), nn.AdaptiveAvgPool2d(1),
nn.Flatten(), nn.Linear(64, k))
def forward(self, x):
return self.q(x)
def orgat(x, y, tr, va, seed=0, davrlar=30, sabr=8):
"""Vaznli loss, eng yaxshi holat val_bacc bo'yicha (3-misol)."""
torch.manual_seed(seed)
K = int(y.max()) + 1
soni = np.bincount(y[tr].numpy(), minlength=K)
kriteriy = nn.CrossEntropyLoss(
weight=torch.tensor(len(tr) / (K * soni), dtype=torch.float32))
model = CNN(K)
opt = torch.optim.AdamW(model.parameters(), lr=3e-3, weight_decay=1e-4)
jadval = torch.optim.lr_scheduler.CosineAnnealingLR(opt, davrlar)
dl = DataLoader(TensorDataset(x[tr], y[tr]), batch_size=32, shuffle=True,
generator=torch.Generator().manual_seed(seed))
eng, holat, hisob = -1.0, None, 0
for _ in range(davrlar):
model.train()
for xb, yb in dl:
opt.zero_grad()
kriteriy(model(xb), yb).backward()
opt.step()
jadval.step()
model.eval()
with torch.no_grad():
b = balanced_accuracy_score(y[va].numpy(),
model(x[va]).argmax(1).numpy())
if b > eng:
eng, hisob, holat = b, 0, copy.deepcopy(model.state_dict())
else:
hisob += 1
if hisob >= sabr:
break
model.load_state_dict(holat)
return model.eval(), eng
class Bashoratchi:
def __init__(self, yol):
p = torch.load(yol, weights_only=True)
self.sinflar, self.olcham = p["sinflar"], p["olcham"]
self.mean = torch.tensor(p["mean"])[:, None, None]
self.std = torch.tensor(p["std"])[:, None, None]
self.model = CNN(len(self.sinflar))
self.model.load_state_dict(p["holat"])
self.model.eval()
def __call__(self, yollar):
x = torch.stack([rasm_tensor(f, self.olcham) for f in yollar])
with torch.no_grad():
pr = torch.softmax(self.model((x - self.mean) / self.std), 1)
return [(self.sinflar[i], q.item()) for q, i in zip(*pr.max(1))]
def main() -> None:
ildiz = Path(tempfile.mkdtemp(prefix="rasmlar_"))
try:
papka_yarat(ildiz)
ds = PapkaDataset(ildiz)
y_np = ds.yorliqlar
idx = np.arange(len(ds))
ish, te = train_test_split(idx, test_size=0.2, stratify=y_np,
random_state=0)
tr, va = train_test_split(ish, test_size=0.25, stratify=y_np[ish],
random_state=0)
xs = torch.stack([ds[i][0] for i in range(len(ds))])
mean = xs[tr].mean((0, 2, 3))
std = xs[tr].std((0, 2, 3))
x = (xs - mean[:, None, None]) / std[:, None, None]
y = torch.tensor(y_np)
print("=== 1. Yakuniy model (val bo'yicha tanlangan) ===")
model, val_bacc = orgat(x, y, tr, va)
print(f" val balanced accuracy: {val_bacc:.4f}")
print("\n=== 2. Test BIR MARTA ===")
with torch.no_grad():
p = model(x[te]).argmax(1).numpy()
t = y_np[te]
print(f" test: {len(te)} rasm, accuracy {(p == t).mean():.4f}, "
f"balanced {balanced_accuracy_score(t, p):.4f}")
print("\n=== 3. Chalkashlik matritsasi (satr = haqiqiy) ===")
cm = confusion_matrix(t, p)
qisqa = [s[:6] for s in ds.sinflar]
print(" " + " " * 11 + "".join(f"{s:>8}" for s in qisqa))
for s, satr in zip(ds.sinflar, cm):
print(f" {s:<11}" + "".join(f"{v:>8}" for v in satr))
tash = cm.copy()
np.fill_diagonal(tash, 0)
i, j = np.unravel_index(tash.argmax(), tash.shape)
print(f" eng ko'p chalkashlik: {ds.sinflar[i]} -> "
f"{ds.sinflar[j]} ({tash[i, j]} ta)")
print("\n=== 4. Sinf bo'yicha hisobot ===")
rec = recall_score(t, p, average=None)
pre = precision_score(t, p, average=None, zero_division=0)
print(f" {'sinf':<11} {'soni':>5} {'recall':>7} {'precision':>10}")
for c, s in enumerate(ds.sinflar):
print(f" {s:<11} {(t == c).sum():>5} {rec[c]:>7.3f} "
f"{pre[c]:>10.3f}")
eng_past = int(rec.argmin())
print(f" eng past recall: {ds.sinflar[eng_past]} "
f"({rec[eng_past]:.3f})")
if rec.min() < (p == t).mean() - 0.05:
print(" umumiy accuracy bu sinfning muammosini yashiradi")
print("\n=== 5. Paket va yangi fayllar ===")
yol = ildiz / "model.pt"
torch.save({"holat": model.state_dict(), "sinflar": ds.sinflar,
"olcham": ds.olcham, "mean": mean.tolist(),
"std": std.tolist(),
"metrika": {"val_bacc": round(float(val_bacc), 4)},
"torch": str(torch.__version__)}, yol)
b = Bashoratchi(yol)
print(f" yuklandi (weights_only=True), sinflar: {b.sinflar}")
yangi = ildiz / "yangi"
yangi.mkdir()
rng = np.random.default_rng(99)
haqiqiy, yollar = [], []
for k, s in enumerate(["xoch", "doira", "uchburchak", "kvadrat"] * 10):
f = yangi / f"surat_{k:02d}.png"
rasm_chiz(s, rng).save(f)
haqiqiy.append(s)
yollar.append(f)
natija = b(yollar)
for f, h, (s, q) in list(zip(yollar, haqiqiy, natija))[:4]:
print(f" {f.relative_to(ildiz).as_posix()}: {s:<10} "
f"({q:.2f}) haqiqiy {h}")
tog = np.mean([h == s for h, (s, _) in zip(haqiqiy, natija)])
print(f" 40 ta yangi faylda aniqlik: {tog:.3f}")
print(" ⭐ Paketda sinflar, olcham va mean/std - aks holda bashorat "
"ma'nosiz")
finally:
shutil.rmtree(ildiz, ignore_errors=True)
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Yakuniy model (val bo'yicha tanlangan) ===
val balanced accuracy: 0.9167
=== 2. Test BIR MARTA ===
test: 60 rasm, accuracy 0.9667, balanced 0.9479
=== 3. Chalkashlik matritsasi (satr = haqiqiy) ===
doira kvadra uchbur xoch
doira 23 1 0 0
kvadrat 0 18 0 0
uchburchak 0 0 12 0
xoch 1 0 0 5
eng ko'p chalkashlik: doira -> kvadrat (1 ta)
=== 4. Sinf bo'yicha hisobot ===
sinf soni recall precision
doira 24 0.958 0.958
kvadrat 18 1.000 0.947
uchburchak 12 1.000 1.000
xoch 6 0.833 1.000
eng past recall: xoch 0.833-bob
umumiy accuracy bu sinfning muammosini yashiradi
=== 5. Paket va yangi fayllar ===
yuklandi (weights_only=True), sinflar: ['doira', 'kvadrat', 'uchburchak', 'xoch']
yangi/surat_00.png: xoch 1.00-bob haqiqiy xoch
yangi/surat_01.png: doira 0.88-bob haqiqiy doira
yangi/surat_02.png: uchburchak 0.69-bob haqiqiy uchburchak
yangi/surat_03.png: kvadrat 0.76-bob haqiqiy kvadrat
40 ta yangi faylda aniqlik: 1.000
⭐ Paketda sinflar, olcham va mean/std - aks holda bashorat ma'nosizNima ko'rsatdi: 2.5, 2.6-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Papka tartibi doim bir xil" | iterdir tartibi kafolatlanmagan — saralash shart |
| "Hamma rasm RGB" | Kulrang, RGBA, palitrali rasmlar uchraydi — convert("RGB") |
| "Rasmlar bir xil o'lchamda" | Batch uchun resize majburiy |
| "Mean/std ni butun to'plamda hisoblash mumkin" | Faqat o'quvda — aks holda sizish (leakage) |
| "Accuracy 90% — yaxshi model" | Kam sinf recall i past bo'lishi mumkin |
| "Vazn va sampler birga — ikki barobar yaxshi" | Kam sinfni haddan oshiradi — bittasini tanlang |
| "Test natijasi bo'yicha epoxani tanlash mumkin" | Faqat val; test bir marta |
"Paketga faqat state_dict kifoya" |
Sinflar, o'lcham, mean/std ham kerak |
6. Keng tarqalgan xatolar va yechimlari
1. Saralanmagan sinflar
sinflar = [p.name for p in ildiz.iterdir()] # ⚠️
sinflar = sorted(p.name for p in ildiz.iterdir() if p.is_dir()) # ✅2. Kanal soni har xil
x = np.asarray(Image.open(yol)) # ⚠️ (H, W) yoki (H, W, 4)
x = np.asarray(Image.open(yol).convert("RGB")) # ✅ doim (H, W, 3)3. Begona fayllar
fayllar = list((ildiz / s).iterdir()) # ⚠️ izoh.txt ham
fayllar = [f for f in ... if f.suffix.lower() in KENGAYTMALAR] # ✅4. Mean/std hamma to'plamda
mean = x.mean((0, 2, 3)) # ⚠️ test ham kirdi
mean = x[oquv].mean((0, 2, 3)) # ✅5. Stratifikatsiyasiz bo'lish
train_test_split(idx, test_size=0.2) # ⚠️
train_test_split(idx, test_size=0.2, stratify=y) # ✅6. Faqat accuracy
print((p == t).mean()) # ⚠️
print(balanced_accuracy_score(t, p), recall_score(t, p, average=None)) # ✅7. To'liq bo'lmagan paket
torch.save(model.state_dict(), yol) # ⚠️
torch.save({"holat": ..., "sinflar": ..., "mean": ..., "std": ...,
"olcham": ...}, yol) # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 21.3-dars (o'tilgan):
DatasetvaDataLoader - 21.5-dars (o'tilgan):
Trainerva callback lar - 21.7-dars (o'tilgan): Checkpoint va
weights_only=True - 18-qism (o'tilgan): Balanced accuracy, chalkashlik matritsasi, stratifikatsiya
- 22.8-, 22.9-darslar (o'tilgan): Augmentatsiya va transfer learning — shu loyihaga qo'shiladi
- Keyingi darslar: model nimani ko'rishini tahlil qilish, obyekt aniqlash — bir xil papka va
Datasetg'oyasi, faqat yorliq murakkabroq
8. Eng yaxshi amaliyotlar
Sinflarni saralab indekslang va xaritani saqlang.
Har rasmni
convert("RGB")varesizedan o'tkazing.Begona fayllarni kengaytma bo'yicha filtrlang.
mean/stdni faqat o'quvda hisoblang.Stratifikatsiyalangan bo'lishdan foydalaning.
Nomutanosiblikda balanced accuracy va sinf bo'yicha recall ni kuzating.
Test to'plamini oxirida bir marta oching.
Paketga sinflar, o'lcham va
mean/stdni qo'shing.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # ImageFolder da sinf indeksi qanday aniqlanadi?
2. # nega sinflarni saralash kerak?
3. # kulrang rasmni qanday 3 kanalga aylantirish?
4. # PIL rasm -> tensor: o'q tartibi?
5. # mean/std qaysi to'plamdan?
6. # stratify nima beradi?
7. # sinf vazni formulasi?
8. # WeightedRandomSampler da namuna vazni?
9. # nomutanosiblikda qaysi metrika?
10. # chalkashlik matritsasida satr nima?
11. # recall va precision farqi?
12. # paketga nima kiradi?Javoblar
- Saralangan papka nomlaridagi o'rin
iterdirtartibi kafolatlanmagan — indekslar boshqa mashinada almashishi mumkinim.convert("RGB")(H, W, C)→permute(2, 0, 1)→(C, H, W)- Faqat o'quv
- Har sinf ulushi o'quv, val va testda bir xil
w_c = N / (K * n_c)1 / n_c(namuna sinfining soni)- Balanced accuracy va sinf bo'yicha recall
- Haqiqiy sinf
- Recall — sinfning qanchasi topildi; precision — bashoratning qanchasi to'g'ri
state_dict, sinflar, o'lcham,mean/std, metrika, versiya
Vazifa 2: Xatolarni tuzating
1. sinflar = [p.name for p in ildiz.iterdir()]
2. x = torch.from_numpy(np.asarray(Image.open(yol)) / 255)
3. mean = x_hammasi.mean((0, 2, 3))
4. ish, te = train_test_split(idx, test_size=0.2)
5. eyx = EngYaxshisi("val_acc", sabr=8) # sinflar 120/90/60/30Javoblar
1. sinflar = sorted(p.name for p in ildiz.iterdir() if p.is_dir())
2. im = Image.open(yol).convert("RGB").resize((S, S))
x = torch.from_numpy(np.asarray(im, np.float32) / 255).permute(2, 0, 1)
3. mean = x_hammasi[oquv].mean((0, 2, 3))
4. ish, te = train_test_split(idx, test_size=0.2, stratify=y)
5. eyx = EngYaxshisi("val_bacc", sabr=8)Vazifa 3: Dataset
Modellang:
- Papka tuzilmasi
- Har xil o'lcham va rejim
- Begona fayl
- Kulrang rasm
Vazifa 4: Tayyorlash
Modellang:
- Stratify
- Mean/std
- Sinf vaznlari
- Sampler
Vazifa 5: O'rgatish
Modellang:
- Trainer
- Eng yaxshi holat
- Vaznli loss
- Juftlashgan farq
Vazifa 6: Hisobot
Modellang:
- Test bir marta
- Chalkashlik matritsasi
- Sinf bo'yicha recall
- Paket va Bashoratchi
Vazifa 7: O'ylash
Model ishlab chiqarishga chiqdi. Bir oydan keyin operator shikoyat qildi: "Telefon kamerasidan kelgan suratlarda model deyarli hamma narsani doira deyapti". O'quv rasmlari skanerdan olingan edi. Nima bo'lgan bo'lishi mumkin va qanday tekshirasiz?
Javob
Qisqa javob: bu ma'lumot siljishi (distribution shift). Yangi rasmlar o'quvdagilardan statistik jihatdan farq qiladi, model esa o'quvda ko'rmagan narsaga ishonchli, lekin noto'g'ri javob beradi.
Ehtimoliy sabablar:
- Kanal va rang: telefon rasmi
RGBAyokiEXIFbo'yicha burilgan bo'lishi mumkin.convert("RGB")bo'lmasa, kod umuman ishlamasdi — demak kanal emas, lekin burilish (ImageOps.exif_transpose) hisobga olinmagan bo'lishi mumkin. - Yorug'lik va kontrast: kanal o'rtachalari o'quvdagi
mean/stddan juda uzoq — normalizatsiyadan keyin qiymatlar model ko'rmagan oraliqqa tushadi. - O'lcham va nisbat: telefon surati 4000×3000, obyekt kichik va chetda;
resizedan keyin u bir necha pikselga aylanadi. - Yig'uvchi sinf: model noaniq kirishda ko'p sinf (
doira— 120 ta) tomonga og'adi.
Qanday tekshiriladi:
# 1. Kirish statistikasi: o'quv va yangi rasmlar
m_yangi = x_yangi.mean((0, 2, 3)); s_yangi = x_yangi.std((0, 2, 3))
print(m_yangi - mean_oquv, s_yangi / std_oquv)
# 2. Bashorat taqsimoti: o'quvdagi sinf ulushlari bilan
print(np.bincount(bashoratlar, minlength=K) / len(bashoratlar))
# 3. Ishonch: o'rtacha max softmax pasaydimi?
# 4. 50 ta yangi suratni qo'lda belgilab, chalkashlik matritsasiYechimlar:
exif_transpose, markaziy kesish yoki obyektni topib kesish — quvurni yangi manbaga moslash- Augmentatsiya 22.8-bob: yorug'lik, kontrast, masshtab, burilish — model bunga chidamli bo'lsin
- Telefon suratlaridan kichik belgilangan to'plam yig'ib, fine-tune (22.9)
- Monitoring: bashorat taqsimoti va o'rtacha ishonchni har kuni kuzatish — siljish shikoyatdan oldin ko'rinadi
Xulosa: test to'plami faqat o'quv bilan bir xil manbadagi ma'lumotda sifatni kafolatlaydi. Yangi manba — yangi test.
Nimani mustahkamlaydi: 2.2, 2.4, 2.6-bo'limlar.
Xulosa
Bu darsda papkadagi rasmlardan boshlab to'liq klassifikatsiya loyihasini qurdik.
Eng muhim uch fikr:
Fayldan tensorgacha bo'lgan yo'l — loyihaning birinchi qismi. 1-misolda 300 ta rasm 201 xil o'lchamda edi, 42 tasi kulrang (
L) rejimda, papkada esa begonaizoh.txtbor edi.PapkaDatasetsinflarni saralab indeksladi, begona faylni tashlab yubordi va har bir rasmniconvert("RGB")→resize→/255→(C, H, W)yo'lidan o'tkazib, bir xil(3, 24, 24)tensorga aylantirdi.convertsiz kulrang rasm(H, W)shaklida qolib, batchni buzardi.Bo'lish va normalizatsiya o'quvdan boshlanadi. Stratifikatsiya bilan testga har doim aynan 6 ta
xochtushdi; stratifikatsiyasiz tasodifiy bo'lish 200 urinishda 3 tadan 14 tagacha berdi — bunday testda kam sinf bo'yicha xulosa qilib bo'lmaydi.mean/stdfaqat o'quvda hisoblandi: o'quv to'plami aniq 0/1 ga keldi, val esa unga yaqin, lekin aynan emas — shunday bo'lishi kerak.Nomutanosiblikda umumiy aniqlik yetarli emas. 3-misolda vaznli loss uch seedning har birida val balanced accuracy ni +0.042 ga,
xochrecall ini +0.167 ga oshirdi; lekin val da atigi 6 taxochbo'lgani uchun metrika pog'onali vaSE = 0ishonch emas, kichik to'plam belgisi. Test bir marta ochilganda accuracy 0.967, balanced accuracy 0.948 chiqdi, eng kam sinfxochning recall i esa 0.833 — chalkashlik matritsasi va sinf bo'yicha hisobot shuni ko'rsatdi. Paketga sinflar, o'lcham vamean/stdqo'shilgani uchunBashoratchiyangi fayllarni to'g'ridan-to'g'ri yo'l bo'yicha tasnifladi.
Keyingi darsda model nimani ko'radi: birinchi qatlam filtrlari, aktivatsiya xaritalari, gradient bo'yicha saliency, Grad-CAM va okklyuziya testi — va ular haqiqatan signal joyini topadimi, raqam bilan o'lchaymiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!