IlmHamroh
Data Science va sun'iy intellekt/Kompyuter korish13/14-dars35 daqiqa
Mundarija (21)

22.13-dars: Segmentatsiya

22-QISM — KOMPYUTER KO'RISH · 13-dars


1. Kirish va motivatsiya

22.12-darsda obyektni quti bilan belgiladik. Quti — qo'pol javob: doira shaklidagi dog' uchun uning to'rt burchagidagi fon ham "obyekt" bo'lib qoladi, qiyshiq tirnalish uchun esa quti deyarli butunlay fondan iborat bo'ladi. Ko'p vazifalarda obyektning aniq shakli kerak: o'smaning chegarasi (jarrohlik rejasi uchun), yo'lning qaysi piksellari bo'sh (avtopilot uchun), sun'iy yo'ldosh suratida qaysi maydon ekin, qaysi biri o'rmon.

Semantik segmentatsiya bu savolga eng to'g'ridan-to'g'ri javob beradi: rasmning har bir pikseliga sinf beradi. 32x32 rasm uchun bu bitta klassifikatsiya emas, 1024 ta klassifikatsiya. Chiqish endi (K,) vektor emas, (K, H, W) xarita — har piksel uchun sinf logitlari.

Buning uchun arxitekturani o'zgartirish kerak. Klassifikatsiya CNN i rasmni pooling bilan asta-sekin kichraytiradi va oxirida Linear bilan bitta javob beradi. Segmentatsiya tarmog'i esa kichraytirgandan keyin yana kattalashtirishi va asl o'lchamdagi niqob qaytarishi kerak. Bu kodlovchi–dekoder (encoder–decoder) tuzilmasiga olib keladi, uning eng mashhur vakili — U-Net.

Segmentatsiyada baholash ham o'z tuzog'iga ega. Odatda fon piksellari juda ko'p: nuqson rasmning 2-3 foizini egallaydi. "Hamma piksel fon" deb javob beradigan model 97% piksel aniqligi oladi va bitta ham nuqsonni topmaydi. Shuning uchun segmentatsiyada IoU va Dice asosiy metrika, loss esa ko'pincha nomutanosiblikka moslab tanlanadi.

Real vaziyat. Metall list sirtidagi korroziyani topish loyihasida birinchi model hisobotda "99.1% aniqlik" ko'rsatdi. Texnolog uni sinab ko'rib, model hech qachon korroziyani belgilamasligini aniqladi — korroziya piksellarning 0.9 foizi edi. Model "hammasi toza" deb o'rgangandi va aynan shu 99.1 foizni olgandi. Metrika IoU ga, loss esa Dice ga almashtirilgach, xuddi shu arxitektura korroziyaning ko'p qismini topa boshladi. Bu darsning 4-misoli xuddi shu hodisani kichik ma'lumotda qayta ko'rsatadi.

Bu darsda har pikselni klassifikatsiya qiladigan tarmoq quramiz va uni halol baholaymiz.

Bu darsda:

  • Semantik segmentatsiya = har piksel klassifikatsiyasi
  • To'liq konvolyutsion tarmoq (FCN)
  • U-Net: kodlovchi, dekoder va skip connection
  • ConvTranspose2d va interpolatsiya bilan kattalashtirish
  • Piksel aniqligi, IoU va Dice
  • Nomutanosiblik: vaznli loss va Dice loss
  • Tuzoqlar
  • Amaliy: kichik U-Net va nuqson niqoblari

ℹ Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).


2. Nazariya — chuqur tushuntirish

2.1. Semantik segmentatsiya — har piksel klassifikatsiyasi

text
KLASSIFIKATSIYA:
  kirish  (B, C, H, W)
  chiqish (B, K)                  bitta rasmga K ta logit
  yorliq  (B,)                    butun son 0..K-1
  loss    cross_entropy(chiqish, yorliq)

SEMANTIK SEGMENTATSIYA:
  kirish  (B, C, H, W)
  chiqish (B, K, H, W)            HAR PIKSELGA K ta logit
  niqob   (B, H, W)               har piksel - butun son 0..K-1 (long!)
  loss    cross_entropy(chiqish, niqob)
          - PyTorch 2-o'lchamni sinf deb oladi va
            barcha piksellar bo'yicha o'rtacha oladi
  bashorat chiqish.argmax(1) -> (B, H, W)

IKKILIK HOLAT (obyekt / fon):
  chiqish (B, 1, H, W) yoki (B, H, W) logit
  niqob   float 0/1
  loss    binary_cross_entropy_with_logits

SEMANTIK va INSTANCE:
  semantik: ikki mushuk -> ikkalasi ham "mushuk" sinfi, bitta niqob
  instance: mushuk-1 va mushuk-2 - alohida niqoblar (Mask R-CNN)
  panoptik: ikkalasi birga (osmon, yo'l - semantik; mashinalar - instance)

Formal jihatdan segmentatsiya — oddiy klassifikatsiya, faqat har piksel uchun. F.cross_entropy buni to'g'ridan-to'g'ri qo'llab-quvvatlaydi: (B, K, H, W) logit va (B, H, W) butun son niqob beriladi. Hech qanday view yoki flatten kerak emas.

Lekin piksellar mustaqil emas. Bitta pikselning sinfini uning o'zi emas, atrofi belgilaydi: kulrang piksel yo'lning ham, osmonning ham, mashinaning ham bir qismi bo'lishi mumkin. Tarmoq bir vaqtning o'zida ikki narsani bilishi kerak: keng kontekst ("bu hudud — yo'l") va aniq joylashuv ("chegara aynan shu pikseldan o'tadi"). Bu ikki talab bir-biriga zid: kontekst uchun rasmni kichraytirish (pooling) kerak, aniq joylashuv uchun esa to'liq o'lcham. U-Net aynan shu ziddiyatni hal qiladi 2.3-bob.

Segmentatsiya = har piksel uchun klassifikatsiya; chiqish (B, K, H, W), niqob (B, H, W) long.

2.2. To'liq konvolyutsion tarmoq (FCN)

text
ODDIY CNN:
  conv -> pool -> conv -> pool -> flatten -> Linear -> (K,)
  Linear kirish o'lchamini QOTIRADI -> faqat bitta rasm o'lchami

FCN (Long va boshq., 2015):
  Linear O'RNIGA 1x1 conv:
    1x1 conv = har pikselda alohida Linear (kanal bo'yicha)
  -> istalgan H, W ni qabul qiladi
  -> chiqish - xarita, vektor emas

  conv -> pool -> conv -> pool -> 1x1 conv (K kanal) -> kattalashtirish
  (1, 32, 32)   ...   (16, 8, 8)   ->   (K, 8, 8)   ->   (K, 32, 32)

O'LCHAM SHARTI:
  2 marta pool (stride 2) -> jami qadam 4
  H va W 4 ga BO'LINISHI kerak, aks holda chiqish qisqaradi
  30 -> 15 -> 7 -> kattalashtirish 28   (30 emas!)
  yechim: padding bilan 32 ga to'ldirish yoki F.interpolate(size=...)

FCN ning g'oyasi oddiy: klassifikatsiya tarmog'idagi Linear qatlamni 1x1 konvolyutsiya bilan almashtirish. 1x1 konvolyutsiya har pikselda kanallar bo'yicha bir xil chiziqli akslantirishni qo'llaydi — ya'ni bu Linear, lekin har piksel uchun alohida va barcha piksellar uchun umumiy og'irlik bilan. Natijada tarmoq istalgan o'lchamdagi rasmni qabul qiladi va o'lchamga mos xarita qaytaradi.

2-misolda kichik FCN 32x32, 48x64 va 16x24 rasmlarni o'zgarishsiz qabul qildi. Lekin 30x30 rasmda chiqish 28x28 bo'lib qoldi — 30 soni umumiy qadam 4 ga bo'linmaydi. Bu xato jim o'tadi va niqob bilan loss hisoblashda shakl xatosi beradi yoki, undan yomoni, kesilgan niqob bilan noto'g'ri solishtiriladi.

FCN da Linear yo'q — shuning uchun istalgan o'lcham ishlaydi, lekin o'lcham kodlovchining umumiy qadamiga karrali bo'lishi kerak.

2.3. U-Net: kodlovchi, dekoder va skip connection

text
            KODLOVCHI (kontekst)          DEKODER (joylashuv)

  (1,24,24) -> e1 --------------- skip ---------------> concat -> d1 -> 1x1 -> (K,24,24)
  (k,24,24)     | pool                                    ^ up (x2)
                v                                         |
  (k,12,12) -> e2 ----------- skip -----------> concat -> d2
  (2k,12,12)    | pool                          ^ up (x2)
                v                               |
  (2k,6,6)  -> O'RTA (bottleneck) (4k,6,6) -----+

HAR BLOK: conv3x3 -> BN -> ReLU -> conv3x3 -> BN -> ReLU 22.7-bob

SKIP CONNECTION:
  kodlovchining shu o'lchamdagi xaritasi dekoderga KANAL bo'yicha qo'shiladi
  torch.cat([up(h), s], dim=1)  -> kanal soni 2 barobar
  (ResNet da skip - QO'SHISH, U-Net da - CONCAT)

NEGA KERAK:
  pooling "qayerda" ma'lumotini yo'qotadi: 6x6 xaritada har katak 4x4 piksel
  dekoder faqat 6x6 dan kattalashtirsa - chegaralar xira
  skip esa 24x24 dagi aniq tafsilotni qaytaradi
  kodlovchi: "bu - obyekt", skip: "chegarasi aynan shu yerda"

U-Net (Ronneberger va boshq., 2015) tibbiy tasvirlar uchun yaratilgan va hozir ham segmentatsiyaning standart boshlang'ich nuqtasi. Uning chap yarmi — oddiy klassifikatsiya CNN i: har pog'onada o'lcham ikki marta kichrayadi, kanallar ikki marta ko'payadi. O'ng yarmi — ko'zgu aksi: o'lcham kattalashadi, kanallar kamayadi.

Skip connection bo'lmasa, dekoder faqat eng kichik (6x6) xaritadan ish boshlaydi. Undagi har katak asl rasmning 4x4 bo'lagini ifodalaydi — chegaraning aniq qaysi pikseldan o'tishi u yerda saqlanmagan. Dekoder uni taxmin qilishga majbur bo'ladi, natijada niqob chetlari yumaloqlashadi. Skip connection kodlovchining yuqori aniqlikdagi xaritasini dekoderga to'g'ridan-to'g'ri uzatadi.

3-misolda bir xil tuzilmali ikki modelni solishtirdik — farq faqat torch.cat da. Ikkala seedda U-Net mIoU ni oshirdi (0.837-0.847 dan 0.908-0.912 ga, farq 0.071 va 0.065), eng katta farq esa chegara piksellarida bo'ldi: chet aniqligi 0.793-0.816 dan 0.889-0.898 ga ko'tarildi. Piksel aniqligidagi farq esa atigi ~2 foiz punkt (0.955-0.960 ga qarshi 0.976-0.978). Parametrlar soni esa atigi ~11% ga ko'paydi (26779 dan 29659 ga).

Kodlovchi kontekstni, skip connection aniq joylashuvni beradi; U-Net ikkalasini birlashtiradi.

2.4. Kattalashtirish: ConvTranspose2d va interpolatsiya

text
INTERPOLATSIYA (o'rganilmaydi):
  F.interpolate(x, scale_factor=2, mode="nearest")    - har qiymat 2x2 ga
  F.interpolate(x, scale_factor=2, mode="bilinear",
                align_corners=False)                  - silliq o'tish
  parametr yo'q; ko'pincha keyin conv3x3 qo'yiladi ("resize-conv")

TRANSPOZITSIYALANGAN KONVOLYUTSIYA (o'rganiladi):
  nn.ConvTranspose2d(k_in, k_out, kernel, stride, padding)
  chiqish = (H - 1) * stride - 2 * padding + kernel + output_padding
    kernel=2, stride=2, padding=0  ->  2H     (U-Net da odatiy)
    kernel=4, stride=2, padding=1  ->  2H
    kernel=3, stride=2, padding=1  ->  2H - 1 (!)

  kernel=2, stride=2, og'irliklar 1 -> nearest bilan AYNAN teng
  parametrlar: k_in * k_out * kernel^2 + k_out

SHAXMAT ARTEFAKTI (checkerboard):
  kernel stride ga bo'linmasa (3 va 2) - chiqish piksellari
  turli sonli kirishdan hissa oladi -> 1, 2, 1, 2 naqsh
  yechim: kernel = stride ning karralisi (2/2, 4/2) yoki interpolatsiya + conv

Kattalashtirishning ikki yo'li bor. Interpolatsiya — belgilangan qoida: nearest qiymatni takrorlaydi, bilinear qo'shni qiymatlar orasida chiziqli o'tish qiladi. Unda o'rganiladigan hech narsa yo'q, u tez va artefaktsiz.

ConvTranspose2d — o'rganiladigan kattalashtirish. Uni "teskari konvolyutsiya" deyishadi, lekin matematik jihatdan u konvolyutsiyaning teskarisi emas, balki uning gradient operatsiyasi: har kirish qiymati yadro bilan ko'paytirilib chiqishga "yoyiladi" va qoplangan joylar qo'shiladi. 2-misolda kernel=2, stride=2 va birlik og'irliklar bilan u nearest interpolatsiyaga aynan teng chiqdi — ya'ni nearest uning xususiy holati, o'rgatish esa yaxshiroq og'irlik topishga imkon beradi.

Yoyilgan bo'laklar qoplanmasa yoki notekis qoplansa, shaxmat artefakti paydo bo'ladi. 2-misolda kernel=3, stride=2 bir xil kirishda 1, 2, 1, 2 naqshli chiqish berdi, kernel=4, stride=2 esa tekis 4, 4, 4 chiqardi. Shuning uchun U-Net da kernel=2, stride=2 yoki "interpolatsiya + conv3x3" ishlatiladi.

Kernel stride ga karrali bo'lsin — aks holda ConvTranspose2d shaxmat naqshini chizadi.

2.5. Metrikalar: piksel aniqligi, IoU va Dice

text
Ikkilik niqob uchun (obyekt = 1):
  TP - obyekt, obyekt deb topildi
  FP - fon, obyekt deb belgilandi
  FN - obyekt, fon deb qoldirildi
  TN - fon, fon

PIKSEL ANIQLIGI = (TP + TN) / hammasi
  - TN (fon) ko'p bo'lsa, u hukmronlik qiladi

IoU (Jaccard) = TP / (TP + FP + FN)
  - TN QATNASHMAYDI -> fon ko'pligi yashirinmaydi

DICE (F1)    = 2 TP / (2 TP + FP + FN)
  - piksel darajasidagi F1 (18-qism)

BOG'LIQLIK:
  Dice = 2 IoU / (1 + IoU)       IoU = Dice / (2 - Dice)
  Dice >= IoU har doim; tartib bir xil (monoton)

KO'P SINF:
  har sinf uchun IoU (o'sha sinf = 1, qolgani = 0)
  mIoU = sinflar bo'yicha o'rtacha (fon ham sinf!)
  kichik sinfning past IoU si mIoU da ko'rinadi, piksel aniqligida - yo'q

BO'SH NIQOB: TP + FP + FN = 0 -> 0/0
  kelishuv: 1 (to'g'ri "hech narsa yo'q") yoki eps bilan silliqlash

Uch metrika bir xil to'rtta sondan (TP, FP, FN, TN) hisoblanadi, lekin turli savolga javob beradi. Piksel aniqligi "piksellarning qancha qismi to'g'ri" deydi — va fon piksellari ko'p bo'lsa, bu savol ma'nosiz. IoU va Dice TN ni umuman ishlatmaydi: ular faqat obyekt atrofida nima bo'lganiga qaraydi.

1-misoldagi raqamlar farqni yaqqol ko'rsatadi. Obyekt 64x64 rasmning 2.8 foizini egallagan holda "hammasi fon" javobi 0.972 aniqlik oldi, IoU va Dice esa 0. Obyektni 3 pikselga siljitib chizgan bashorat deyarli bir xil aniqlik (0.975) oldi, IoU esa 0.378 — aniqlik ikki mutlaqo boshqa sifatdagi javobni ajrata olmadi.

Dice va IoU bir-biriga monoton bog'liq (Dice = 2 IoU / (1 + IoU)), shuning uchun modellarni tartiblashda bir xil natija beradi. Farq faqat shkalada: Dice doim kattaroq ko'rinadi (1-misolda IoU 0.286 bo'lganda Dice 0.444). Hisobotda qaysi biri ishlatilganini aniq yozing.

IoU va Dice TN ni ishlatmaydi — shuning uchun fon ko'pligi ularni aldamaydi, piksel aniqligini esa aldaydi.

2.6. Nomutanosiblik: vaznli loss va Dice loss

text
MUAMMO:
  nuqson piksellari 2-4%, fon 96-98%
  oddiy BCE / CE - har piksel TENG vaznda
  -> "hammasi fon" yechimi loss ni tez tushiradi va shu yerda qotib qoladi

1. VAZNLI BCE:
  F.binary_cross_entropy_with_logits(logit, y, pos_weight=w)
  w ~ fon / obyekt (masalan 25)
  ko'p sinf: F.cross_entropy(logit, y, weight=torch.tensor([...]))
  + recall keskin oshadi
  - precision tushadi (obyekt ortiqcha "chiziladi"),
    w juda katta bo'lsa chegara qalinlashadi

2. DICE LOSS (yumshoq Dice):
  p = sigmoid(logit)
  dice = (2 * sum(p * y) + eps) / (sum(p) + sum(y) + eps)
  loss = 1 - dice
  + to'g'ridan-to'g'ri IoU/Dice ga o'xshash maqsadni optimallaydi
  + fon piksellarining SONI ahamiyatsiz
  - kichik batchda shovqinli; bo'sh niqoblarda eps muhim

3. BIRGALIKDA:  BCE + Dice   (amalda eng keng tarqalgan)
   FOCAL LOSS: oson (fon) piksellar vaznini kamaytiradi

BAHOLASH:
  asosiy: IoU / Dice, sinf bo'yicha recall
  piksel aniqligi - faqat qo'shimcha, bazaviy bilan birga
  bazaviy: "hammasi fon" modeli - uning aniqligini doim yozing

Nomutanosiblik segmentatsiyada klassifikatsiyadagidan ham kuchliroq: rasmlarning o'zi muvozanatli bo'lsa ham (har rasmda nuqson bor), piksellar darajasida fon baribir ustun. Oddiy BCE har pikselga teng vazn beradi va "hammasi fon" deyish loss ni darhol past qiymatga tushiradi. Bu yechimdan chiqish uchun gradient signali juda zaif.

4-misolda aynan shu ro'y berdi: oddiy BCE bilan o'rgatilgan U-Net 6 davr davomida validatsiyada IoU 0.000 da qoldi va aniqligi (0.961) "hammasi fon" bazaviysi bilan bir xil chiqdi. Vaznli BCE (pos_weight = 25.6) recall ni 0.986 ga chiqardi, lekin precision 0.699 bo'ldi — model nuqsonni ortiqcha qalin chizdi. Dice loss esa birinchi davrdayoq IoU 0.529 berdi va 6-davrda IoU 0.861 ga yetdi, recall va precision muvozanatli bo'ldi (0.923 va 0.928).

Bu bitta seed va qisqa o'rgatish natijasi: ko'proq davr bilan oddiy BCE ham oxir-oqibat bu yechimdan chiqishi mumkin. Lekin xulosa o'zgarmaydi — nomutanosib niqobda loss tanlovi o'rgatish tezligi va natija sifatini keskin o'zgartiradi, piksel aniqligi esa buni ko'rsatmaydi (hamma variantlar 0.961-0.994 orasida).

Fon ko'p bo'lsa — Dice loss yoki vaznli loss, baholash esa IoU/Dice va recall bilan.

2.7. Tuzoqlar

Asosiy tuzoqlar: niqobni float (ko'p sinfda) yoki (B, 1, H, W) shaklda berish (cross_entropy (B, H, W) long kutadi); piksel aniqligini asosiy metrika qilish; "hammasi fon" bazaviysini yozmaslik; mIoU da fonni tashlab yuborib, buni aytmaslik; rasm o'lchamini kodlovchi qadamiga karrali qilmaslik; kernel stride ga bo'linmaydigan ConvTranspose2d (shaxmat artefakti); niqobni bilinear bilan o'lchamini o'zgartirish (sinf raqamlari aralashadi — faqat nearest); augmentatsiyada rasmni akslantirib, niqobni akslantirmaslik; bo'sh niqoblarda Dice ni eps siz hisoblash (0/0); vaznli loss da pos_weight ni juda katta qo'yib, chegaralarni qalinlashtirish; bitta rasmda metrikani o'rtachalash va butun to'plamda hisoblashni aralashtirish.


3. Tez ma'lumotnoma

python
import torch
import torch.nn as nn
import torch.nn.functional as F

# ko'p sinfli segmentatsiya
logit = model(x)                                   # (B, K, H, W)
loss = F.cross_entropy(logit, niqob)               # niqob (B, H, W) long
bashorat = logit.argmax(1)                         # (B, H, W)

# ikkilik + nomutanosiblik
loss = F.binary_cross_entropy_with_logits(logit, y, pos_weight=torch.tensor(40.0))

def dice_loss(logit, y, eps=1.0):
    p = torch.sigmoid(logit)
    kes = (p * y).sum((1, 2))
    return (1 - (2 * kes + eps) / (p.sum((1, 2)) + y.sum((1, 2)) + eps)).mean()

# metrikalar
tp = ((p == 1) & (y == 1)).sum(); fp = ((p == 1) & (y == 0)).sum()
fn = ((p == 0) & (y == 1)).sum()
iou, dice = tp / (tp + fp + fn), 2 * tp / (2 * tp + fp + fn)

# kattalashtirish
nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2)          # 2x, o'rganiladi
F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=False)
F.interpolate(niqob[:, None].float(), size=(H, W), mode="nearest")  # niqob uchun

# U-Net skip
h = self.dekoder(torch.cat([self.up(h), skip], dim=1))

Segmentatsiya xulosasi

segmentatsiya = har piksel klassifikatsiyasi: (B, K, H, W) vs (B, H, W) long
FCN: Linear o'rniga 1x1 conv -> istalgan o'lcham (qadamga karrali)
U-Net: kodlovchi (kontekst) + dekoder + skip concat (aniq chegara)
kattalashtirish: ConvTranspose2d(k=2, s=2) yoki interpolatsiya + conv
metrika: IoU = TP/(TP+FP+FN), Dice = 2TP/(2TP+FP+FN); aniqlik aldaydi
nomutanosiblik: Dice loss, vaznli BCE, BCE + Dice; bazaviy "hammasi fon"

4. Batafsil misollar

Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).

Misol 1 — Piksel aniqligi, IoU va Dice ni qo'lda

python
"""Segmentatsiya metrikalari qo'lda: piksel aniqligi, IoU, Dice."""

import numpy as np


def metrikalar(bashorat, haqiqiy):
    """Ikkilik niqoblar (0/1) uchun asosiy metrikalar."""
    tp = int(np.sum((bashorat == 1) & (haqiqiy == 1)))
    fp = int(np.sum((bashorat == 1) & (haqiqiy == 0)))
    fn = int(np.sum((bashorat == 0) & (haqiqiy == 1)))
    tn = int(np.sum((bashorat == 0) & (haqiqiy == 0)))
    aniqlik = (tp + tn) / (tp + tn + fp + fn)
    iou = tp / (tp + fp + fn) if tp + fp + fn > 0 else 1.0
    dice = 2 * tp / (2 * tp + fp + fn) if tp + fp + fn > 0 else 1.0
    return {"tp": tp, "fp": fp, "fn": fn, "tn": tn,
            "aniqlik": aniqlik, "iou": iou, "dice": dice}


def doira(r_markaz, c_markaz, radius, n=64):
    yy, xx = np.mgrid[0:n, 0:n]
    return ((yy - r_markaz) ** 2 + (xx - c_markaz) ** 2
            <= radius ** 2).astype(np.int64)


def main() -> None:
    print("=== 1. Kichik misol: 6x6 niqob ===")
    haqiqiy = np.zeros((6, 6), dtype=np.int64)
    haqiqiy[1:4, 1:4] = 1                                  # 9 piksel obyekt
    bashorat = np.zeros((6, 6), dtype=np.int64)
    bashorat[2:5, 2:5] = 1                                 # 1 pikselga siljigan
    for nom, m in [("haqiqiy", haqiqiy), ("bashorat", bashorat)]:
        print(f"  {nom}:")
        for q in m:
            print("    " + " ".join("#" if v else "." for v in q))
    m = metrikalar(bashorat, haqiqiy)
    print(f"  TP={m['tp']} FP={m['fp']} FN={m['fn']} TN={m['tn']}")
    print(f"  piksel aniqligi = (TP+TN)/hammasi = {m['aniqlik']:.3f}")
    print(f"  IoU  = TP/(TP+FP+FN)     = {m['tp']}/{m['tp'] + m['fp'] + m['fn']}"
          f" = {m['iou']:.3f}")
    print(f"  Dice = 2TP/(2TP+FP+FN)   = {2 * m['tp']}/"
          f"{2 * m['tp'] + m['fp'] + m['fn']} = {m['dice']:.3f}")
    print(f"  tekshiruv: 2*IoU/(1+IoU) = {2 * m['iou'] / (1 + m['iou']):.3f}")

    print("\n=== 2. Nomutanosiblik: 64x64, kichik obyekt ===")
    haqiqiy = doira(20, 40, 6)
    ulush = haqiqiy.mean()
    print(f"  obyekt piksellari: {haqiqiy.sum()} / {haqiqiy.size} "
          f"({100 * ulush:.1f}%)")
    variantlar = {
        "hammasi fon": np.zeros_like(haqiqiy),
        "yarim radius": doira(20, 40, 3),
        "3 px siljigan": doira(23, 43, 6),
        "deyarli to'g'ri": doira(20, 41, 6),
        "hammasi obyekt": np.ones_like(haqiqiy),
    }
    print(f"  {'bashorat':<17} {'aniqlik':>8} {'IoU':>6} {'Dice':>6}")
    natijalar = {}
    for nom, b in variantlar.items():
        r = metrikalar(b, haqiqiy)
        natijalar[nom] = r
        print(f"  {nom:<17} {r['aniqlik']:>8.3f} {r['iou']:>6.3f} "
              f"{r['dice']:>6.3f}")
    fon = natijalar["hammasi fon"]
    if fon["aniqlik"] > 0.95 and fon["iou"] == 0:
        print(f"  'hammasi fon' {fon['aniqlik']:.1%} aniqlik oldi, lekin "
              "obyektning bitta pikselini ham topmadi")

    print("\n=== 3. Ko'p sinfli: sinf bo'yicha IoU va mIoU ===")
    rng = np.random.default_rng(0)
    h = np.zeros((64, 64), dtype=np.int64)
    h[doira(20, 20, 12) == 1] = 1                          # katta obyekt
    h[doira(48, 48, 4) == 1] = 2                           # kichik obyekt
    b = h.copy()
    b[doira(48, 50, 4) == 1] = 2                           # kichikni biroz sur
    b[(h == 2) & (doira(48, 50, 4) == 0)] = 0
    shovqin = rng.random(h.shape) < 0.02
    b[shovqin & (h == 1)] = 0                              # katta obyekt ichida
    print(f"  {'sinf':<8} {'piksel':>7} {'IoU':>6}")
    ioular = []
    for k, nom in enumerate(["fon", "katta", "kichik"]):
        r = metrikalar((b == k).astype(int), (h == k).astype(int))
        ioular.append(r["iou"])
        print(f"  {nom:<8} {int((h == k).sum()):>7} {r['iou']:>6.3f}")
    print(f"  mIoU (sinflar o'rtachasi) = {np.mean(ioular):.3f}")
    print(f"  piksel aniqligi           = {(b == h).mean():.3f}")
    print("  ⭐ Piksel aniqligi fon ko'pligini yashiradi; IoU/Dice yashirmaydi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Kichik misol: 6x6 niqob ===
  haqiqiy:
    . . . . . .
    . # # # . .
    . # # # . .
    . # # # . .
    . . . . . .
    . . . . . .
  bashorat:
    . . . . . .
    . . . . . .
    . . # # # .
    . . # # # .
    . . # # # .
    . . . . . .
  TP=4 FP=5 FN=5 TN=22
  piksel aniqligi = (TP+TN)/hammasi = 0.722
  IoU  = TP/(TP+FP+FN)     = 4/14 = 0.286
  Dice = 2TP/(2TP+FP+FN)   = 8/18 = 0.444
  tekshiruv: 2*IoU/(1+IoU) = 0.444

=== 2. Nomutanosiblik: 64x64, kichik obyekt ===
  obyekt piksellari: 113 / 4096 (2.8%)
  bashorat           aniqlik    IoU   Dice
  hammasi fon          0.972  0.000  0.000
  yarim radius         0.979  0.257  0.408
  3 px siljigan        0.975  0.378  0.549
  deyarli to'g'ri      0.994  0.794  0.885
  hammasi obyekt       0.028  0.028  0.054
  'hammasi fon' 97.2% aniqlik oldi, lekin obyektning bitta pikselini ham topmadi

=== 3. Ko'p sinfli: sinf bo'yicha IoU va mIoU ===
  sinf      piksel    IoU
  fon         3606  0.988
  katta        441  0.971
  kichik        49  0.508
  mIoU (sinflar o'rtachasi) = 0.822
  piksel aniqligi           = 0.989
  ⭐ Piksel aniqligi fon ko'pligini yashiradi; IoU/Dice yashirmaydi

Nima ko'rsatdi: 2.5, 2.6-bo'limlar.

Misol 2 — Kattalashtirish va to'liq konvolyutsion tarmoq

python
"""Kattalashtirish: ConvTranspose2d, interpolatsiya va to'liq konvolyutsion tarmoq."""

import torch
import torch.nn as nn
import torch.nn.functional as F


def jadval(t):
    return "\n".join("    " + " ".join(f"{v:5.2f}" for v in q)
                     for q in t.tolist())


class KichikFCN(nn.Module):
    """To'liq konvolyutsion: Linear yo'q -> istalgan o'lchamdagi rasm."""

    def __init__(self, sinflar=3):
        super().__init__()
        self.enc = nn.Sequential(
            nn.Conv2d(1, 8, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(8, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2))
        self.bosh = nn.Conv2d(16, sinflar, 1)            # 1x1 conv = piksel
        self.up = nn.ConvTranspose2d(sinflar, sinflar, 4, stride=4)

    def forward(self, x):
        return self.up(self.bosh(self.enc(x)))


def main() -> None:
    torch.manual_seed(0)
    x = torch.tensor([[1.0, 2.0], [3.0, 4.0]]).view(1, 1, 2, 2)

    print("=== 1. Interpolatsiya: 2x2 -> 4x4 ===")
    print("  nearest:")
    print(jadval(F.interpolate(x, scale_factor=2, mode="nearest")[0, 0]))
    print("  bilinear (align_corners=False):")
    print(jadval(F.interpolate(x, scale_factor=2, mode="bilinear",
                               align_corners=False)[0, 0]))
    print("  interpolatsiyada o'rganiladigan parametr yo'q")

    print("\n=== 2. ConvTranspose2d chiqish o'lchami ===")
    print("  formula: (H - 1) * stride - 2 * padding + kernel "
          "+ output_padding")
    print(f"  {'kernel':>6} {'stride':>6} {'padding':>7} {'8x8 ->':>8} "
          f"{'formula':>8}")
    for k, s, p in [(2, 2, 0), (4, 2, 1), (3, 2, 1), (3, 1, 1), (4, 4, 0)]:
        ct = nn.ConvTranspose2d(1, 1, k, stride=s, padding=p)
        h = ct(torch.zeros(1, 1, 8, 8)).shape[-1]
        f = (8 - 1) * s - 2 * p + k
        print(f"  {k:>6} {s:>6} {p:>7} {h:>8} {f:>8}")

    print("\n=== 3. kernel=2, stride=2, og'irlik 1 -> nearest bilan bir xil ===")
    ct = nn.ConvTranspose2d(1, 1, 2, stride=2, bias=False)
    with torch.no_grad():
        ct.weight.fill_(1.0)
    y = ct(x)
    print(jadval(y[0, 0].detach()))
    teng = torch.equal(y.detach(), F.interpolate(x, scale_factor=2))
    print(f"  nearest bilan teng: {teng}")
    print(f"  o'rganiladigan parametrlar (1->1 kanal): "
          f"{sum(p.numel() for p in ct.parameters())}")
    ct64 = nn.ConvTranspose2d(64, 32, 2, stride=2)
    print(f"  64->32 kanal, kernel 2: "
          f"{sum(p.numel() for p in ct64.parameters())} parametr")

    print("\n=== 4. Shaxmat artefakti: kernel stride ga bo'linmasa ===")
    for k, s, p in [(3, 2, 1), (4, 2, 1)]:
        ct = nn.ConvTranspose2d(1, 1, k, stride=s, padding=p, bias=False)
        with torch.no_grad():
            ct.weight.fill_(1.0)
        q = ct(torch.ones(1, 1, 6, 6))[0, 0, 4, 2:9].detach()
        print(f"  kernel={k}, stride={s}: markaziy qator "
              f"{[round(v, 1) for v in q.tolist()]}")
    print("  bir xil kirish -> notekis chiqish (3/2 da 1,2,1,2 naqsh)")

    print("\n=== 5. FCN istalgan o'lchamni qabul qiladi ===")
    fcn = KichikFCN()
    for h, w in [(32, 32), (48, 64), (16, 24)]:
        chiqish = fcn(torch.randn(2, 1, h, w))
        print(f"  kirish (2, 1, {h}, {w}) -> chiqish {tuple(chiqish.shape)}")
    print(f"  parametrlar: {sum(p.numel() for p in fcn.parameters())}")
    ch = fcn(torch.randn(1, 1, 30, 30)).shape
    print(f"  kirish (1, 1, 30, 30) -> chiqish {tuple(ch)}")
    if ch[-1] != 30:
        print("  30 soni 4 ga bo'linmaydi -> niqob kirishdan KICHIK chiqdi")
    print("  ⭐ O'lchamni kodlovchining umumiy qadamiga karrali qiling")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Interpolatsiya: 2x2 -> 4x4 ===
  nearest:
     1.00  1.00  2.00  2.00
     1.00  1.00  2.00  2.00
     3.00  3.00  4.00  4.00
     3.00  3.00  4.00  4.00
  bilinear (align_corners=False):
     1.00  1.25  1.75  2.00
     1.50  1.75  2.25  2.50
     2.50  2.75  3.25  3.50
     3.00  3.25  3.75  4.00
  interpolatsiyada o'rganiladigan parametr yo'q

=== 2. ConvTranspose2d chiqish o'lchami ===
  formula: (H - 1) * stride - 2 * padding + kernel + output_padding
  kernel stride padding   8x8 ->  formula
       2      2       0       16       16
       4      2       1       16       16
       3      2       1       15       15
       3      1       1        8        8
       4      4       0       32       32

=== 3. kernel=2, stride=2, og'irlik 1 -> nearest bilan bir xil ===
     1.00  1.00  2.00  2.00
     1.00  1.00  2.00  2.00
     3.00  3.00  4.00  4.00
     3.00  3.00  4.00  4.00
  nearest bilan teng: True
  o'rganiladigan parametrlar (1->1 kanal): 4
  64->32 kanal, kernel 2: 8224 parametr

=== 4. Shaxmat artefakti: kernel stride ga bo'linmasa ===
  kernel=3, stride=2: markaziy qator [1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0]
  kernel=4, stride=2: markaziy qator [4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0]
  bir xil kirish -> notekis chiqish (3/2 da 1,2,1,2 naqsh)

=== 5. FCN istalgan o'lchamni qabul qiladi ===
  kirish (2, 1, 32, 32) -> chiqish (2, 3, 32, 32)
  kirish (2, 1, 48, 64) -> chiqish (2, 3, 48, 64)
  kirish (2, 1, 16, 24) -> chiqish (2, 3, 16, 24)
  parametrlar: 1446
  kirish (1, 1, 30, 30) -> chiqish (1, 3, 28, 28)
  30 soni 4 ga bo'linmaydi -> niqob kirishdan KICHIK chiqdi
  ⭐ O'lchamni kodlovchining umumiy qadamiga karrali qiling

Nima ko'rsatdi: 2.2, 2.4-bo'limlar.

Misol 3 — Kichik U-Net: skip bilan va skipsiz

python
"""Kichik U-Net: skip connection bilan va skipsiz taqqoslash."""

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

R = 24
SINFLAR = ["fon", "yorug'", "qorong'i"]


def sahnalar(n, seed):
    """Har rasmda 2-4 obyekt (doira yoki kvadrat shaklida, yorug' yoki
    qorong'i) + shovqin; niqob (H, W): 0 fon, 1 yorug', 2 qorong'i."""
    rng = np.random.default_rng(seed)
    yy, xx = np.mgrid[0:R, 0:R]
    X = rng.normal(0.0, 0.5, (n, 1, R, R)).astype(np.float32)
    Y = np.zeros((n, R, R), dtype=np.int64)
    for i in range(n):
        for _ in range(rng.integers(2, 5)):
            s = int(rng.integers(0, 2)) + 1
            r = rng.uniform(2.0, 5.0)
            cy, cx = rng.uniform(r, R - r, 2)
            if rng.random() < 0.5:
                m = (yy - cy) ** 2 + (xx - cx) ** 2 <= r ** 2
            else:
                m = ((np.abs(yy - cy) <= r * 0.85)
                     & (np.abs(xx - cx) <= r * 0.85))
            Y[i][m] = s
        X[i, 0] += (Y[i] == 1) * 1.0 - (Y[i] == 2) * 1.0
    return torch.tensor(X), torch.tensor(Y)


def blok(k_in, k_out):
    return nn.Sequential(
        nn.Conv2d(k_in, k_out, 3, padding=1), nn.BatchNorm2d(k_out),
        nn.ReLU(),
        nn.Conv2d(k_out, k_out, 3, padding=1), nn.BatchNorm2d(k_out),
        nn.ReLU())


class KichikUNet(nn.Module):
    """Kodlovchi 24 -> 12 -> 6, dekoder 6 -> 12 -> 24; skip - concat."""

    def __init__(self, skip=True, k=8, sinflar=3):
        super().__init__()
        self.skip = skip
        self.e1, self.e2 = blok(1, k), blok(k, 2 * k)
        self.orta = blok(2 * k, 4 * k)
        self.up2 = nn.ConvTranspose2d(4 * k, 2 * k, 2, stride=2)
        self.d2 = blok(4 * k if skip else 2 * k, 2 * k)
        self.up1 = nn.ConvTranspose2d(2 * k, k, 2, stride=2)
        self.d1 = blok(2 * k if skip else k, k)
        self.bosh = nn.Conv2d(k, sinflar, 1)

    def forward(self, x):
        s1 = self.e1(x)                               # (k, 24, 24)
        s2 = self.e2(F.max_pool2d(s1, 2))             # (2k, 12, 12)
        h = self.orta(F.max_pool2d(s2, 2))            # (4k, 6, 6)
        h = self.up2(h)
        h = self.d2(torch.cat([h, s2], 1) if self.skip else h)
        h = self.up1(h)
        h = self.d1(torch.cat([h, s1], 1) if self.skip else h)
        return self.bosh(h)                           # (sinflar, 24, 24)


def orgat(model, X, Y, davrlar, seed):
    g = torch.Generator().manual_seed(seed)
    opt = torch.optim.Adam(model.parameters(), lr=3e-3)
    for _ in range(davrlar):
        model.train()
        tartib = torch.randperm(len(Y), generator=g)
        for i in range(0, len(Y), 32):
            b = tartib[i:i + 32]
            loss = F.cross_entropy(model(X[b]), Y[b])
            opt.zero_grad()
            loss.backward()
            opt.step()
    return model


def baho(model, X, Y):
    model.eval()
    with torch.no_grad():
        p = model(X).argmax(1)
    ioular = []
    for k in range(len(SINFLAR)):
        kes = ((p == k) & (Y == k)).sum().item()
        bir = ((p == k) | (Y == k)).sum().item()
        ioular.append(kes / bir)
    # chegara piksellari: 3x3 qo'shnisida boshqa sinf bor
    chet = (F.max_pool2d(Y[:, None].float(), 3, 1, 1)
            != -F.max_pool2d(-Y[:, None].float(), 3, 1, 1))[:, 0]
    return {"aniqlik": (p == Y).float().mean().item(),
            "iou": ioular, "miou": float(np.mean(ioular)),
            "chet_aniqlik": (p == Y)[chet].float().mean().item()}


def main() -> None:
    X_tr, Y_tr = sahnalar(600, seed=1)
    X_te, Y_te = sahnalar(300, seed=2)
    print("=== 1. Ma'lumot ===")
    print(f"  rasmlar {tuple(X_tr.shape)}, niqoblar {tuple(Y_tr.shape)}")
    ulush = torch.bincount(Y_tr.flatten(), minlength=3).float()
    ulush = ulush / ulush.sum()
    print(f"  piksel ulushlari: "
          f"{dict(zip(SINFLAR, [round(v, 3) for v in ulush.tolist()]))}")

    print("\n=== 2. Ikki model ===")
    for skip in [True, False]:
        m = KichikUNet(skip=skip)
        print(f"  skip={str(skip):<5} parametrlar "
              f"{sum(p.numel() for p in m.parameters())}")

    print("\n=== 3. O'rgatish va test (2 seed) ===")
    print(f"  {'model':<10} {'seed':>4} {'aniqlik':>8} {'mIoU':>6} "
          f"{'yorug':>6} {'qorongi':>8} {'chet':>6}")
    natija = {True: [], False: []}
    for seed in [0, 1]:
        for skip in [True, False]:
            torch.manual_seed(seed)
            m = orgat(KichikUNet(skip=skip), X_tr, Y_tr, 6, seed)
            r = baho(m, X_te, Y_te)
            natija[skip].append(r)
            nom = "U-Net" if skip else "skipsiz"
            print(f"  {nom:<10} {seed:>4} {r['aniqlik']:>8.3f} "
                  f"{r['miou']:>6.3f} {r['iou'][1]:>6.3f} "
                  f"{r['iou'][2]:>8.3f} {r['chet_aniqlik']:>6.3f}")

    print("\n=== 4. Xulosa ===")
    farq_miou = [a["miou"] - b["miou"]
                 for a, b in zip(natija[True], natija[False])]
    farq_chet = [a["chet_aniqlik"] - b["chet_aniqlik"]
                 for a, b in zip(natija[True], natija[False])]
    print(f"  mIoU farqi (U-Net - skipsiz) seedlar bo'yicha: "
          f"{[round(v, 3) for v in farq_miou]}")
    print(f"  chet aniqligi farqi: {[round(v, 3) for v in farq_chet]}")
    if min(farq_miou) > 0:
        print("  har ikki seedda skip connection mIoU ni oshirdi")
    else:
        print("  skip connection foydasi seedlarda izchil emas")
    print("  ⭐ Skip dekoderga pooling da yo'qolgan chegara tafsilotini "
          "qaytaradi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot ===
  rasmlar (600, 1, 24, 24), niqoblar (600, 24, 24)
  piksel ulushlari: {'fon': 0.816, "yorug'": 0.087, "qorong'i": 0.096}

=== 2. Ikki model ===
  skip=True  parametrlar 29659
  skip=False parametrlar 26779

=== 3. O'rgatish va test (2 seed) ===
  model      seed  aniqlik   mIoU  yorug  qorongi   chet
  U-Net         0    0.976  0.908  0.863    0.890  0.889
  skipsiz       0    0.955  0.837  0.792    0.772  0.793
  U-Net         1    0.978  0.912  0.875    0.886  0.898
  skipsiz       1    0.960  0.847  0.795    0.791  0.816

=== 4. Xulosa ===
  mIoU farqi (U-Net - skipsiz) seedlar bo'yicha: [0.071, 0.065]
  chet aniqligi farqi: [0.096, 0.082]
  har ikki seedda skip connection mIoU ni oshirdi
  ⭐ Skip dekoderga pooling da yo'qolgan chegara tafsilotini qaytaradi

Nima ko'rsatdi: 2.1, 2.3-bo'limlar.

Misol 4 — Nomutanosiblik: BCE, vaznli BCE va Dice loss

python
"""Nomutanosib segmentatsiya: BCE, vaznli BCE va Dice loss taqqoslash."""

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

R = 24


def rasmlar(n, seed):
    """Teksturali fon, ustida mayda dog'lar va ingichka tirnalishlar."""
    rng = np.random.default_rng(seed)
    yy, xx = np.mgrid[0:R, 0:R]
    X = rng.normal(0.0, 0.5, (n, 1, R, R)).astype(np.float32)
    Y = np.zeros((n, R, R), dtype=np.float32)
    for i in range(n):
        for _ in range(rng.integers(1, 3)):                     # dog'lar
            r = rng.uniform(1.0, 2.2)
            cy, cx = rng.uniform(3, R - 3, 2)
            Y[i][(yy - cy) ** 2 + (xx - cx) ** 2 <= r ** 2] = 1
        if rng.random() < 0.6:                                  # tirnalish
            y0, x0 = rng.uniform(4, R - 4, 2)
            burchak = rng.uniform(0, np.pi)
            for t in np.linspace(-7, 7, 36):
                py = int(round(y0 + t * np.sin(burchak)))
                px = int(round(x0 + t * np.cos(burchak)))
                if 0 <= py < R and 0 <= px < R:
                    Y[i, py, px] = 1
        X[i, 0] += Y[i] * 1.5
    return torch.tensor(X), torch.tensor(Y)


def blok(k_in, k_out):
    return nn.Sequential(
        nn.Conv2d(k_in, k_out, 3, padding=1), nn.BatchNorm2d(k_out),
        nn.ReLU(),
        nn.Conv2d(k_out, k_out, 3, padding=1), nn.BatchNorm2d(k_out),
        nn.ReLU())


class KichikUNet(nn.Module):
    def __init__(self, k=8):
        super().__init__()
        self.e1, self.e2 = blok(1, k), blok(k, 2 * k)
        self.orta = blok(2 * k, 4 * k)
        self.up2 = nn.ConvTranspose2d(4 * k, 2 * k, 2, stride=2)
        self.d2 = blok(4 * k, 2 * k)
        self.up1 = nn.ConvTranspose2d(2 * k, k, 2, stride=2)
        self.d1 = blok(2 * k, k)
        self.bosh = nn.Conv2d(k, 1, 1)

    def forward(self, x):
        s1 = self.e1(x)
        s2 = self.e2(F.max_pool2d(s1, 2))
        h = self.orta(F.max_pool2d(s2, 2))
        h = self.d2(torch.cat([self.up2(h), s2], 1))
        h = self.d1(torch.cat([self.up1(h), s1], 1))
        return self.bosh(h)[:, 0]                  # logit (B, H, W)


def dice_loss(logit, y, eps=1.0):
    p = torch.sigmoid(logit)
    kes = (p * y).sum((1, 2))
    maxraj = p.sum((1, 2)) + y.sum((1, 2))
    return (1 - (2 * kes + eps) / (maxraj + eps)).mean()


def orgat(X, Y, X_va, Y_va, loss_turi, pos_weight, seed=0, davrlar=6):
    """Har davr oxirida validatsiyadagi metrikalarni yozib boradi."""
    torch.manual_seed(seed)
    model = KichikUNet()
    g = torch.Generator().manual_seed(seed)
    opt = torch.optim.Adam(model.parameters(), lr=3e-3)
    pw = torch.tensor(pos_weight)
    tarix = []
    for _ in range(davrlar):
        model.train()
        tartib = torch.randperm(len(Y), generator=g)
        for i in range(0, len(Y), 16):
            b = tartib[i:i + 16]
            logit = model(X[b])
            if loss_turi == "BCE":
                loss = F.binary_cross_entropy_with_logits(logit, Y[b])
            elif loss_turi == "vaznli BCE":
                loss = F.binary_cross_entropy_with_logits(
                    logit, Y[b], pos_weight=pw)
            else:
                loss = dice_loss(logit, Y[b])
            opt.zero_grad()
            loss.backward()
            opt.step()
        model.eval()
        with torch.no_grad():
            p = (torch.sigmoid(model(X_va)) > 0.5).float()
        tarix.append(metrikalar(p, Y_va))
    return tarix


def metrikalar(p, y):
    tp = ((p == 1) & (y == 1)).sum().item()
    fp = ((p == 1) & (y == 0)).sum().item()
    fn = ((p == 0) & (y == 1)).sum().item()
    return {"aniqlik": (p == y).float().mean().item(),
            "iou": tp / max(tp + fp + fn, 1),
            "dice": 2 * tp / max(2 * tp + fp + fn, 1),
            "recall": tp / max(tp + fn, 1),
            "precision": tp / max(tp + fp, 1)}


def main() -> None:
    X_tr, Y_tr = rasmlar(400, seed=1)
    X_va, Y_va = rasmlar(200, seed=2)
    ulush = Y_tr.mean().item()
    print("=== 1. Ma'lumot ===")
    print(f"  nuqson piksellari ulushi: {ulush:.3f} "
          f"(fon {1 - ulush:.3f})")
    pos_w = (1 - ulush) / ulush
    print(f"  pos_weight = fon / nuqson = {pos_w:.1f}")

    print("\n=== 2. Bazaviy: hammasini fon deymiz ===")
    r0 = metrikalar(torch.zeros_like(Y_va), Y_va)
    print(f"  aniqlik {r0['aniqlik']:.3f}, IoU {r0['iou']:.3f}, "
          f"Dice {r0['dice']:.3f}, recall {r0['recall']:.3f}")

    print("\n=== 3. Davrlar bo'yicha validatsiya IoU si ===")
    tarixlar = {t: orgat(X_tr, Y_tr, X_va, Y_va, t, pos_w)
                for t in ["BCE", "vaznli BCE", "Dice"]}
    print(f"  {'loss':<11}" + "".join(f"{d:>7}" for d in range(1, 7)))
    for t, tarix in tarixlar.items():
        print(f"  {t:<11}" + "".join(f"{r['iou']:>7.3f}" for r in tarix))

    print("\n=== 4. Oxirgi davr: to'liq metrikalar ===")
    print(f"  {'loss':<12} {'aniqlik':>8} {'IoU':>6} {'Dice':>6} "
          f"{'recall':>7} {'precision':>10}")
    natija = {"hammasi fon": r0}
    natija.update({t: tarix[-1] for t, tarix in tarixlar.items()})
    for t, r in natija.items():
        print(f"  {t:<12} {r['aniqlik']:>8.3f} {r['iou']:>6.3f} "
              f"{r['dice']:>6.3f} {r['recall']:>7.3f} "
              f"{r['precision']:>10.3f}")

    print("\n=== 5. Xulosa (natijadan) ===")
    aniq = [v["aniqlik"] for v in natija.values()]
    print(f"  aniqlik diapazoni: {min(aniq):.3f} - {max(aniq):.3f} "
          f"(farq {max(aniq) - min(aniq):.3f})")
    iou = {k: v["iou"] for k, v in natija.items()}
    print(f"  IoU diapazoni:     {min(iou.values()):.3f} - "
          f"{max(iou.values()):.3f}")
    print(f"  IoU bo'yicha eng yaxshi: {max(iou, key=iou.get)}")
    if natija["BCE"]["iou"] == 0 and natija["BCE"]["aniqlik"] >= r0["aniqlik"]:
        print("  oddiy BCE 'hammasi fon' yechimida qolib ketdi - "
              "aniqligi bazaviy bilan bir xil")
    vb = natija["vaznli BCE"]
    if vb["recall"] > vb["precision"] + 0.2:
        print(f"  vaznli BCE: recall {vb['recall']:.3f}, precision "
              f"{vb['precision']:.3f} - vazn nuqsonni ortiqcha chizdiradi")
    print("  ⭐ Nomutanosib niqobda IoU/Dice va recall ga qarang")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot ===
  nuqson piksellari ulushi: 0.038 (fon 0.962)
  pos_weight = fon / nuqson = 25.6

=== 2. Bazaviy: hammasini fon deymiz ===
  aniqlik 0.961, IoU 0.000, Dice 0.000, recall 0.000

=== 3. Davrlar bo'yicha validatsiya IoU si ===
  loss             1      2      3      4      5      6
  BCE          0.000  0.000  0.000  0.000  0.000  0.000
  vaznli BCE   0.345  0.419  0.567  0.588  0.630  0.692
  Dice         0.529  0.623  0.783  0.836  0.844  0.861

=== 4. Oxirgi davr: to'liq metrikalar ===
  loss          aniqlik    IoU   Dice  recall  precision
  hammasi fon     0.961  0.000  0.000   0.000      0.000
  BCE             0.961  0.000  0.000   0.000      0.000
  vaznli BCE      0.983  0.692  0.818   0.986      0.699
  Dice            0.994  0.861  0.926   0.923      0.928

=== 5. Xulosa (natijadan) ===
  aniqlik diapazoni: 0.961 - 0.994 (farq 0.033)
  IoU diapazoni:     0.000 - 0.861
  IoU bo'yicha eng yaxshi: Dice
  oddiy BCE 'hammasi fon' yechimida qolib ketdi - aniqligi bazaviy bilan bir xil
  vaznli BCE: recall 0.986, precision 0.699 - vazn nuqsonni ortiqcha chizdiradi
  ⭐ Nomutanosib niqobda IoU/Dice va recall ga qarang

Nima ko'rsatdi: 2.6-bo'lim.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"97% piksel aniqligi — yaxshi model" "Hammasi fon" ham shuncha oladi
"Segmentatsiya uchun maxsus loss kerak" Oddiy cross_entropy (B, K, H, W) ni qabul qiladi
"Dice va IoU turli modelni tanlaydi" Monoton bog'liq, tartib bir xil
"Skip connection — faqat chuqur tarmoq uchun" Chegaralar aniqligini beradi
"ConvTranspose2d — konvolyutsiyaning teskarisi" Gradient operatsiyasi, yoyish va qo'shish
"Har qanday kernel/stride ishlaydi" Bo'linmasa — shaxmat artefakti
"Niqobni ham bilinear bilan kichraytirish mumkin" Faqat nearest
"Vaznli loss — hamma muammoning yechimi" Recall oshadi, precision tushadi

6. Keng tarqalgan xatolar va yechimlari

1. Niqob shakli va turi

python
F.cross_entropy(logit, niqob[:, None].float())     # ⚠️ (B, 1, H, W) float
F.cross_entropy(logit, niqob.long())               # ✅ (B, H, W) long

2. Piksel aniqligi bilan hisobot

python
print((p == y).float().mean())                     # ⚠️ 0.961 = hammasi fon
print(iou, dice, recall, bazaviy_aniqlik)          # ✅

3. Niqobni bilinear bilan o'lchash

python
F.interpolate(niqob.float(), size=s, mode="bilinear")  # ⚠️ 0.5 sinf yo'q
F.interpolate(niqob.float(), size=s, mode="nearest")   # ✅

4. Shaxmat artefakti

python
nn.ConvTranspose2d(c, c, kernel_size=3, stride=2)  # ⚠️ 1,2,1,2 naqsh
nn.ConvTranspose2d(c, c, kernel_size=2, stride=2)  # ✅

5. Qadamga karrali bo'lmagan o'lcham

python
model(torch.randn(1, 1, 30, 30))                   # ⚠️ chiqish 28x28
model(F.pad(x, (1, 1, 1, 1)))                      # ✅ 32x32

6. Augmentatsiyada niqob unutilgan

python
x = x.flip(-1)                                     # ⚠️ niqob eski holatda
x, niqob = x.flip(-1), niqob.flip(-1)              # ✅ birga

7. Dice da eps yo'q

python
2 * kes / (p.sum() + y.sum())                      # ⚠️ bo'sh niqobda 0/0
(2 * kes + 1) / (p.sum() + y.sum() + 1)            # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 18-qism (o'tilgan): Precision, recall, F1 — Dice aslida piksel darajasidagi F1
  • 22.3-22.4-darslar (o'tilgan): Konvolyutsiya va pooling o'lcham formulalari
  • 22.6-22.7-darslar (o'tilgan): Skip connection (ResNet) va BatchNorm2d — U-Net bloklari
  • 22.12-dars (o'tilgan): IoU — endi qutilar emas, piksellar to'plami uchun
  • 22.14-dars: Nuqson loyihasi — nomutanosiblik va sinf bo'yicha recall
  • Generativ AI qismida: Diffuziya modellarining ichida ham U-Net ishlaydi
  • MLOps qismida: Segmentatsiya modelini ishlab chiqarishda kuzatish

8. Eng yaxshi amaliyotlar

  1. Har doim "hammasi fon" bazaviysining aniqligini yozing.

  2. Asosiy metrika — IoU yoki Dice, sinf bo'yicha va o'rtacha.

  3. Fon ko'p bo'lsa, Dice loss yoki BCE + Dice bilan boshlang.

  4. Rasm o'lchamini kodlovchi qadamiga karrali qiling.

  5. ConvTranspose2d da kernel = stride (yoki interpolatsiya + conv).

  6. Niqobni faqat nearest bilan o'lchang va rasm bilan birga augmentatsiya qiling.

  7. Chegara piksellaridagi sifatni alohida tekshiring — skip connection foydasi aynan shu yerda.

  8. Bashorat niqoblarini rasm ustiga chizib ko'ring — raqam yashirgan xatolar ko'rinadi.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # segmentatsiya chiqishi shakli?
2.  # cross_entropy uchun niqob shakli va turi?
3.  # 1x1 conv nimaga teng?
4.  # FCN nega istalgan o'lchamni qabul qiladi?
5.  # 2 ta pool dan keyin 30x30 kirish -> chiqish?
6.  # ConvTranspose2d(k=2, s=2) 8x8 -> ?
7.  # ConvTranspose2d(k=3, s=2, p=1) 8x8 -> ?
8.  # U-Net skip - qo'shishmi yoki concat?
9.  # IoU = 0.5 bo'lsa Dice = ?
10. # 2.8% obyektda "hammasi fon" aniqligi?
11. # pos_weight qanday tanlanadi?
12. # niqobni qaysi interpolatsiya bilan o'lchash kerak?
Javoblar
  1. (B, K, H, W)
  2. (B, H, W), long
  3. Har pikselda alohida Linear (kanallar bo'yicha)
  4. Linear yo'q — barcha qatlamlar lokal
  5. 28x28 (30 soni 4 ga bo'linmaydi)
  6. 16x16
  7. 15x15
  8. Concat (kanal bo'yicha)
  9. 2 * 0.5 / 1.5 = 0.667
  10. 0.972
  11. Taxminan fon / obyekt, keyin validatsiyada sozlanadi
  12. nearest

Vazifa 2: Xatolarni tuzating

python
1.  loss = F.cross_entropy(logit, niqob.float()[:, None])

2.  niqob_kichik = F.interpolate(niqob, scale_factor=0.5, mode="bilinear")

3.  self.up = nn.ConvTranspose2d(64, 32, kernel_size=3, stride=2)

4.  dice = 2 * (p * y).sum() / (p.sum() + y.sum())

5.  print(f"aniqlik {(p == y).float().mean():.3f}")   # yagona metrika
Javoblar
python
1.  loss = F.cross_entropy(logit, niqob.long())

2.  niqob_kichik = F.interpolate(niqob[:, None].float(), scale_factor=0.5,
                                 mode="nearest")[:, 0].long()

3.  self.up = nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2)

4.  dice = (2 * (p * y).sum() + 1) / (p.sum() + y.sum() + 1)

5.  print(f"IoU {iou:.3f}, Dice {dice:.3f}, recall {rec:.3f}, "
          f"bazaviy aniqlik {1 - y.float().mean():.3f}")

Vazifa 3: Metrikalar

Modellang:

  1. TP, FP, FN, TN dan uchta metrika
  2. Dice = 2 IoU / (1 + IoU) ni tekshiring
  3. Obyekt ulushi 1%, 5%, 20% da "hammasi fon" aniqligi
  4. Ko'p sinf uchun sinf bo'yicha IoU va mIoU

Vazifa 4: Kattalashtirish

Modellang:

  1. nearest va bilinear jadvali
  2. ConvTranspose2d o'lcham formulasi — 5 xil kernel/stride
  3. Shaxmat artefaktini ko'rsating
  4. Interpolatsiya + conv3x3 bilan shaxmat yo'qligini tekshiring

Vazifa 5: U-Net

Modellang:

  1. Kichik U-Net va skipsiz varianti
  2. Ikkalasi 2 seed bilan
  3. mIoU va chegara piksellari aniqligi
  4. Skip ni concat o'rniga qo'shish bilan almashtiring — natija?

Vazifa 6: Nomutanosiblik

Modellang:

  1. Obyekt ulushi 2-3% bo'lgan niqoblar
  2. BCE, vaznli BCE, Dice, BCE + Dice
  3. Davrlar bo'yicha IoU jadvali
  4. pos_weight = 5, 12, 25 — precision va recall qanday o'zgaradi?

Vazifa 7: O'ylash

Qishloq xo'jaligi startapi sun'iy yo'ldosh suratlarida kasallangan ekin maydonlarini segmentatsiya qiladi. Kasal hudud suratlarning o'rtacha 4 foizini egallaydi. Jamoa ikki modelni solishtirdi: A modeli — piksel aniqligi 97.1%, Dice 0.41; B modeli — piksel aniqligi 96.2%, Dice 0.63. Direktor "A aniqroq ekan, shuni olamiz" dedi. Nima deysiz va yana nimani so'raysiz?

Javob

Qisqa javob: B modelini tanlash kerak — asosiy metrika Dice, va unda B ancha yaxshi. A ning aniqlikdagi ustunligi deyarli to'liq fon piksellari hisobidan.

Nima uchun:

1. Bazaviyni hisoblang. Kasal hudud 4% bo'lsa, "hammasi sog'lom" modeli 96% aniqlik oladi. A ning 97.1 foizi bazaviydan atigi 1.1 foiz punkt yuqori, B ning 96.2 foizi esa deyarli bazaviy darajasida. Ya'ni aniqlik bu ikki modelni amalda ajrata olmaydi — 1-misolda ham "hammasi fon" (0.972) va 3 pikselga siljigan bashorat (0.975) deyarli bir xil aniqlik oldi.

2. Dice aniqlikning yashirgan qismini ko'rsatadi. Dice TN ni ishlatmaydi — u faqat kasal hudud atrofida nima bo'lganiga qaraydi. 0.41 va 0.63 farqi katta: IoU ga o'girsak (IoU = Dice / (2 - Dice)), bu taxminan 0.26 va 0.46.

3. Nega A aniqligi yuqoriroq bo'lishi mumkin? A ehtiyotkor — u kasal deb kam belgilaydi, shuning uchun fonda kam xato qiladi (yuqori TN), lekin kasal hududning ko'p qismini o'tkazib yuboradi (katta FN). Bu 4-misoldagi oddiy BCE ning xulqiga o'xshaydi: uning aniqligi bazaviy bilan bir xil edi, IoU esa 0.

Nimani so'raysiz:

  • Recall va precision alohida qancha? Biznes uchun qaysi xato qimmatroq — kasal maydonni o'tkazib yuborishmi (hosil yo'qoladi) yoki sog'lom maydonga ortiqcha dori sepishmi?
  • Dice butun to'plam bo'yicha hisoblanganmi yoki har surat bo'yicha o'rtachalanganmi? Kasal hudud yo'q suratlar qanday hisobga olingan?
  • Natijalar bir necha seed yoki fold da barqarormi?
  • Kichik kasal o'choqlar (bir necha piksel) uchun sifat qanday — ular erta ogohlantirish uchun eng muhimi bo'lishi mumkin.

Muhim nuans: B modeli ham mukammal emas — Dice 0.63 hali ancha yaxshilanishi mumkin. Lekin qaror "qaysi biri yaxshiroq" haqida, va to'g'ri metrika bo'yicha javob aniq: B. Direktorga aniqlikning bazaviy bilan birga ko'rsatilishi odatda eng tez tushuntiradi.

Nimani mustahkamlaydi: 2.5, 2.6-bo'limlar.


Xulosa

Bu darsda har pikselni klassifikatsiya qiladigan tarmoq qurdik va uni halol baholashni o'rgandik.

Eng muhim uch fikr:

  1. Piksel aniqligi nomutanosib niqobda aldaydi, IoU va Dice aldamaydi. Obyekt rasmning 2.8 foizi bo'lganda "hammasi fon" javobi 0.972 aniqlik oldi, IoU esa 0. 4-misolda oddiy BCE bilan o'rgatilgan U-Net 6 davr davomida aynan shu yechimda qolib ketdi — aniqlik 0.961, IoU 0.000. Barcha variantlarning aniqligi 0.961-0.994 oralig'ida bo'ldi, IoU esa 0 dan 0.861 gacha tarqaldi.

  2. Skip connection chegaralarni tiklaydi. Bir xil tuzilmali ikki modelda faqat torch.cat farq qildi: U-Net ikkala seedda ham mIoU ni 0.065-0.071 ga oshirdi, chegara piksellaridagi aniqlik esa 0.08-0.10 ga ko'tarildi. Parametrlar atigi ~11% ko'paydi — bu arzon va samarali qo'shimcha.

  3. Loss tanlovi nomutanosiblikda hal qiluvchi. Vaznli BCE (pos_weight = 25.6) recall ni 0.986 ga chiqardi, lekin precision 0.699 bo'lib qoldi. Dice loss birinchi davrdanoq o'rgana boshladi va IoU 0.861 ga, recall va precision esa 0.923 va 0.928 ga yetdi. Kattalashtirish qatlamlarida esa kernel = stride qoidasi shaxmat artefaktidan saqlaydi.

Keyingi darsda 22-qismning hamma bilimini bitta loyihaga yig'amiz: sanoat nuqsonlarini klassifikatsiya qiluvchi to'liq quvur, bazaviy bilan juftlashgan taqqoslash, Grad-CAM tekshiruvi va topshirish paketi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
22.13-dars: Segmentatsiya — IlmHamroh