IlmHamroh
Data Science va sun'iy intellekt/PyTorch2/12-dars23 daqiqa
Mundarija (21)

21.2-dars: O'z qatlamlaringiz va bloklar

21-QISM — PYTORCH · 2-dars


1. Kirish va motivatsiya

nn.Module ni tushundik. Endi undan foydalanamiz: o'z qatlamlaringiz, takrorlanuvchi bloklar va murakkab tuzilmalar quramiz.

Nima uchun bu kerak? Chunki zamonaviy arxitekturalarning hammasi blok tushunchasiga qurilgan. ResNet — bir xil residual blokning takrori. Transformer — bir xil attention blokining takrori. Siz bitta blokni to'g'ri yozsangiz, 50 qatlamli tarmoq to'rt qator kod bo'lib qoladi.

Bu darsda uchta muhim naqshni ko'ramiz. Birinchisi — residual ulanish (x + f(x)), chuqur tarmoqlarni o'rgatish mumkin qilgan bitta g'oya. Ikkinchisi — parametrlarni baham ko'rish: ikki joyda bir xil og'irliklarni ishlatish. Uchinchisi — konfiguratsiyadan arxitektura qurish, ya'ni modelni lug'at bilan ta'riflash.

Shuningdek forward ni murakkab holatlar uchun yozishni ko'ramiz: bir necha kirish, bir necha chiqish, shart-shariotli yo'llar.

Real vaziyat. Jamoa 12 qatlamli tarmoq yozdi — har qatlam qo'lda, 200 qator kod. Yangi qatlam qo'shish har safar uch joyni o'zgartirishni talab qilardi. Blokka ajratilgandan keyin model 20 qator bo'ldi va chuqurlik bitta son bilan boshqariladigan bo'ldi.

Bu darsda takrorlanuvchi bloklar quramiz.

Bu darsda:

  • Blok naqshi
  • Residual ulanish
  • Parametrlarni baham ko'rish
  • Murakkab forward
  • Konfiguratsiyadan qurish
  • Tuzoqlar
  • Amaliy: o'z arxitekturangiz

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


2. Nazariya — chuqur tushuntirish

2.1. Blok naqshi

text
BLOK = takrorlanadigan eng kichik mantiqiy birlik

class Blok(nn.Module):
    def __init__(self, d, dropout=0.1):
        super().__init__()
        self.chiziqli = nn.Linear(d, d)
        self.norm = nn.LayerNorm(d)
        self.drop = nn.Dropout(dropout)

    def forward(self, x):
        return self.drop(torch.relu(self.norm(self.chiziqli(x))))

TARMOQ = bloklar ketma-ketligi:
  self.bloklar = nn.ModuleList([Blok(d) for _ in range(chuqurlik)])

NIMA BERADI:
  chuqurlik BITTA son bilan boshqariladi
  har blok alohida sinaladi
  state_dict kalitlari tartibli: "bloklar.0.chiziqli.weight"
  bloklarni almashtirish oson

Blokka ajratish — arxitektura kodining asosiy naqshi; chuqurlik bitta parametrga aylanadi.

2.2. Residual ulanish

text
ODDIY:     x -> f(x)
RESIDUAL:  x -> x + f(x)

NIMA UCHUN ISHLAYDI (He va b., 2015):
  1. GRADIENT YO'LI: d(x + f(x))/dx = 1 + df/dx
     -> gradient har doim KAMIDA 1 koeffitsiyent bilan o'tadi
     -> yo'qolayotgan gradient muammosi keskin kamayadi
  2. IDENTIKLIK OSON: f(x) = 0 bo'lsa blok hech narsa qilmaydi
     -> ortiqcha qatlam ZARAR qilmaydi

SHAKL MOS KELISHI SHART:
  x va f(x) bir xil o'lchamda bo'lishi kerak
  bo'lmasa: proyeksiya qo'shiladi -> proj(x) + f(x)

PRE-NORM va POST-NORM:
  post: x + f(norm siz)  -> norm(x + f(x))     eski uslub
  pre:  x + f(norm(x))                         barqarorroq, zamonaviy

x + f(x) gradientga "qisqa yo'l" beradi — chuqur tarmoqlarni o'rgatish shu bilan mumkin bo'ldi.

2.3. Parametrlarni baham ko'rish

text
BIR XIL MODULNI IKKI JOYDA ISHLATISH:
  self.umumiy = nn.Linear(d, d)
  def forward(self, x):
      return self.umumiy(self.umumiy(x))    # BIR XIL og'irliklar

  -> parametrlar BIR MARTA saqlanadi
  -> gradient IKKALA yo'ldan YIG'ILADI

QAYERDA ISHLATILADI:
  siamese tarmoqlar (ikki kirishni bir xil koder bilan)
  RNN (vaqt qadamlari bo'ylab bir xil og'irlik)
  tied embeddings (kirish va chiqish embeddingi - 27-qism)
  autoencoder (koder va dekoder transpozitsiyasi)

DIQQAT: bu NUSXA EMAS
  self.a = nn.Linear(d, d)
  self.b = nn.Linear(d, d)     <- BOSHQA parametrlar
  self.b = self.a              <- BIR XIL parametrlar

Bir modulni ikki marta chaqirish — parametrlarni baham ko'rish, nusxalash emas.

2.4. Murakkab forward

text
BIR NECHA KIRISH:
  def forward(self, x_son, x_kat): ...

BIR NECHA CHIQISH:
  def forward(self, x):
      return bosh1(z), bosh2(z)        # tuple yoki dict

SHART-SHAROITLI YO'L:
  def forward(self, x, rejim="tez"):
      return self.tez(x) if rejim == "tez" else self.aniq(x)

MASKA / UZUNLIK:
  def forward(self, x, maska=None): ...

DICT QAYTARISH ko'proq o'qiladigan:
  return {"logits": ..., "embedding": ..., "attn": ...}

DIQQAT: forward ichida yangi Module YARATMANG
  def forward(self, x):
      return nn.Linear(4, 2)(x)        # ⚠️ har chaqiruvda YANGI

forward ichida Module yaratmang — u ro'yxatga olinmaydi va har chaqiruvda qaytadan boshlanadi.

2.5. Konfiguratsiyadan qurish

text
G'OYA: arxitekturani LUG'AT bilan ta'riflash

konfig = {"kirish": 20, "yashirin": 128, "chuqurlik": 4,
          "dropout": 0.1, "chiqish": 3, "residual": True}
model = Tarmoq(**konfig)

NIMA BERADI:
  konfigni model bilan birga SAQLASH -> qayta qurish oson
  giperparametr qidiruvi tabiiy
  tajribalarni taqqoslash aniq
  kod bir joyda

SAQLASHDA:
  torch.save({"konfig": konfig, "holat": model.state_dict()}, yol)

YUKLASHDA:
  paket = torch.load(yol, weights_only=False)
  model = Tarmoq(**paket["konfig"])
  model.load_state_dict(paket["holat"])

Konfigni model bilan birga saqlang — usiz state_dict ni qayerga yuklashni bilmaysiz.

2.6. Qatlam tartibi

text
KLASSIK TARTIB:
  Linear -> Norm -> Aktivatsiya -> Dropout

NIMA UCHUN SHU TARTIB:
  Norm aktivatsiyadan OLDIN: chiziqli chiqishni markazlashtiradi
  Dropout oxirida: allaqachon hisoblangan xususiyatlarni o'chiradi

BATCHNORM BILAN:
  Linear(bias=False) -> BatchNorm -> ReLU
  bias KERAK EMAS, chunki BatchNorm uni bekor qiladi

DROPOUT va BATCHNORM birga:
  tartib muammoli (o'rgatish/inference statistikasi farq qiladi)
  amalda: BatchNorm bo'lsa dropout ni kamaytiring yoki olib tashlang

PRE-NORM BLOK (zamonaviy):
  x + Dropout(Linear(Aktivatsiya(Linear(Norm(x)))))

BatchNorm dan oldingi bias bekor bo'ladi — bias=False qo'ying.

2.7. Tuzoqlar

Asosiy tuzoqlar: forward ichida Module yaratish; residual da shakl mos kelmasligi; self.b = self.a ni nusxa deb o'ylash; konfigni saqlamaslik; BatchNorm oldidan bias qoldirish; bloklarni list ga yig'ish; chuqurlikni qo'lda ko'chirib yozish; forward da in-place amallar (x += f(x)).


3. Tez ma'lumotnoma

python
import torch
import torch.nn as nn


class ResidualBlok(nn.Module):
    def __init__(self, d, kengaytirish=4, dropout=0.1):
        super().__init__()
        self.norm = nn.LayerNorm(d)
        self.tarmoq = nn.Sequential(
            nn.Linear(d, d * kengaytirish), nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(d * kengaytirish, d))

    def forward(self, x):
        return x + self.tarmoq(self.norm(x))     # pre-norm


class Model(nn.Module):
    def __init__(self, kirish, d=128, chuqurlik=4, chiqish=3):
        super().__init__()
        self.kirish = nn.Linear(kirish, d)
        self.bloklar = nn.ModuleList(
            [ResidualBlok(d) for _ in range(chuqurlik)])
        self.norm = nn.LayerNorm(d)
        self.bosh = nn.Linear(d, chiqish)

    def forward(self, x):
        x = self.kirish(x)
        for blok in self.bloklar:
            x = blok(x)
        return self.bosh(self.norm(x))

Bloklar xulosasi

blok = takrorlanadigan birlik
residual: x + f(x), shakllar mos bo'lsin
baham ko'rish: bir modulni ikki marta chaqirish
konfig: lug'at, model bilan birga saqlanadi
tartib: Linear -> Norm -> Aktivatsiya -> Dropout

4. Batafsil misollar

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

Misol 1 — Blok va chuqurlik

python
"""Blokka ajratish nima beradi (real torch)."""

import torch
import torch.nn as nn


class Blok(nn.Module):
    def __init__(self, d, dropout=0.1):
        super().__init__()
        self.chiziqli = nn.Linear(d, d)
        self.norm = nn.LayerNorm(d)
        self.aktiv = nn.GELU()
        self.drop = nn.Dropout(dropout)

    def forward(self, x):
        return self.drop(self.aktiv(self.norm(self.chiziqli(x))))


class Tarmoq(nn.Module):
    def __init__(self, kirish, d=64, chuqurlik=3, chiqish=3,
                 dropout=0.1):
        super().__init__()
        self.kirish = nn.Linear(kirish, d)
        self.bloklar = nn.ModuleList(
            [Blok(d, dropout) for _ in range(chuqurlik)])
        self.bosh = nn.Linear(d, chiqish)

    def forward(self, x):
        x = self.kirish(x)
        for blok in self.bloklar:
            x = blok(x)
        return self.bosh(x)


def main() -> None:
    torch.manual_seed(0)

    print("=== 1. Chuqurlik bitta son bilan ===")
    print(f"  {'chuqurlik':>10} {'parametr':>10} {'modullar':>10}")
    for ch in [1, 2, 4, 8]:
        m = Tarmoq(20, chuqurlik=ch)
        print(f"  {ch:>10} {sum(p.numel() for p in m.parameters()):>10} "
              f"{len(list(m.modules())):>10}")

    print("\n=== 2. state_dict kalitlari tartibli ===")
    m = Tarmoq(20, chuqurlik=2)
    for kalit in list(m.state_dict())[:8]:
        print(f"  {kalit}")
    print(f"  ... jami {len(m.state_dict())} ta kalit")

    print("\n=== 3. Blokni alohida sinash ===")
    b = Blok(64)
    x = torch.randn(32, 64)
    b.eval()
    with torch.no_grad():
        chiqish = b(x)
    print(f"  kirish shakli:  {tuple(x.shape)}")
    print(f"  chiqish shakli: {tuple(chiqish.shape)}")
    print(f"  kirish std:  {x.std().item():.4f}")
    print(f"  chiqish std: {chiqish.std().item():.4f}")
    print("  blok mustaqil sinalishi mumkin")

    print("\n=== 4. Qatlamlar bo'ylab signal ===")
    m = Tarmoq(20, d=64, chuqurlik=6)
    m.eval()
    X = torch.randn(256, 20)
    with torch.no_grad():
        h = m.kirish(X)
        print(f"  {'bosqich':<12} {'std':>10} {'o_rtacha':>10}")
        print(f"  {'kirish':<12} {h.std().item():>10.4f} "
              f"{h.mean().item():>10.4f}")
        for i, blok in enumerate(m.bloklar):
            h = blok(h)
            print(f"  {f'blok {i}':<12} {h.std().item():>10.4f} "
                  f"{h.mean().item():>10.4f}")

    print("\n=== 5. Blokni almashtirish ===")
    class BoshqaBlok(nn.Module):
        def __init__(self, d, dropout=0.1):
            super().__init__()
            self.a = nn.Linear(d, d * 2)
            self.b = nn.Linear(d * 2, d)
            self.norm = nn.LayerNorm(d)

        def forward(self, x):
            return self.norm(self.b(torch.relu(self.a(x))))

    m2 = Tarmoq(20, d=64, chuqurlik=3)
    m2.bloklar = nn.ModuleList([BoshqaBlok(64) for _ in range(3)])
    print(f"  {'variant':<16} {'parametr':>10} {'chiqish shakli':>16}")
    for nom, mod in [("Blok", Tarmoq(20, d=64, chuqurlik=3)),
                     ("BoshqaBlok", m2)]:
        mod.eval()
        with torch.no_grad():
            sh = tuple(mod(X).shape)
        print(f"  {nom:<16} "
              f"{sum(p.numel() for p in mod.parameters()):>10} "
              f"{str(sh):>16}")
    print("  ⭐ Bloklar almashtiriladigan bo'lsa tajriba oson")

    print("\n=== 6. Dropout rejimi barcha blokka tarqaladi ===")
    m3 = Tarmoq(20, chuqurlik=3, dropout=0.5)
    m3.train()
    print(f"  train() da bloklar[0].drop.training: "
          f"{m3.bloklar[0].drop.training}")
    m3.eval()
    print(f"  eval() da:  {m3.bloklar[0].drop.training}")
    with torch.no_grad():
        m3.train()
        a1, a2 = m3(X[:4]), m3(X[:4])
        m3.eval()
        b1, b2 = m3(X[:4]), m3(X[:4])
    print(f"  train() da ikki yurish bir xilmi: "
          f"{torch.allclose(a1, a2)}")
    print(f"  eval()  da ikki yurish bir xilmi: "
          f"{torch.allclose(b1, b2)}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Chuqurlik bitta son bilan ===
   chuqurlik   parametr   modullar
           1       5827          9
           2      10115         14
           4      18691         24
           8      35843         44

=== 2. state_dict kalitlari tartibli ===
  kirish.weight
  kirish.bias
  bloklar.0.chiziqli.weight
  bloklar.0.chiziqli.bias
  bloklar.0.norm.weight
  bloklar.0.norm.bias
  bloklar.1.chiziqli.weight
  bloklar.1.chiziqli.bias
  ... jami 12 ta kalit

=== 3. Blokni alohida sinash ===
  kirish shakli:  (32, 64)
  chiqish shakli: (32, 64)
  kirish std:  0.9951
  chiqish std: 0.5913
  blok mustaqil sinalishi mumkin

=== 4. Qatlamlar bo'ylab signal ===
  bosqich             std   o_rtacha
  kirish           0.5771     0.0271
  blok 0           0.5859     0.2855
  blok 1           0.5772     0.2863
  blok 2           0.5826     0.2844
  blok 3           0.5767     0.2860
  blok 4           0.5751     0.2823
  blok 5           0.5733     0.2856

=== 5. Blokni almashtirish ===
  variant            parametr   chiqish shakli
  Blok                  14403         (256, 3)
  BoshqaBlok            51651         (256, 3)
  ⭐ Bloklar almashtiriladigan bo'lsa tajriba oson

=== 6. Dropout rejimi barcha blokka tarqaladi ===
  train() da bloklar[0].drop.training: True
  eval() da:  False
  train() da ikki yurish bir xilmi: False
  eval()  da ikki yurish bir xilmi: True

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

Misol 2 — Residual ulanish

python
"""x + f(x) chuqur tarmoqda nima o'zgartiradi (real torch)."""

import torch
import torch.nn as nn


class OddiyBlok(nn.Module):
    def __init__(self, d):
        super().__init__()
        self.norm = nn.LayerNorm(d)
        self.tarmoq = nn.Sequential(nn.Linear(d, d * 2), nn.GELU(),
                                    nn.Linear(d * 2, d))

    def forward(self, x):
        return self.tarmoq(self.norm(x))


class ResidualBlok(nn.Module):
    def __init__(self, d):
        super().__init__()
        self.norm = nn.LayerNorm(d)
        self.tarmoq = nn.Sequential(nn.Linear(d, d * 2), nn.GELU(),
                                    nn.Linear(d * 2, d))

    def forward(self, x):
        return x + self.tarmoq(self.norm(x))


def yasa(blok_sinfi, kirish=20, d=64, chuqurlik=12, chiqish=3, seed=0):
    torch.manual_seed(seed)
    class M(nn.Module):
        def __init__(self):
            super().__init__()
            self.kirish = nn.Linear(kirish, d)
            self.bloklar = nn.ModuleList(
                [blok_sinfi(d) for _ in range(chuqurlik)])
            self.bosh = nn.Linear(d, chiqish)

        def forward(self, x):
            x = self.kirish(x)
            for b in self.bloklar:
                x = b(x)
            return self.bosh(x)
    return M()


def main() -> None:
    torch.manual_seed(0)
    X = torch.randn(256, 20)
    y = torch.randint(0, 3, (256,))
    kriteriy = nn.CrossEntropyLoss()

    print("=== 1. Gradient normalari qatlamlar bo'ylab ===")
    print(f"  {'blok':>6} {'oddiy':>14} {'residual':>14}")
    normalar = {}
    for nom, sinf in [("oddiy", OddiyBlok), ("residual", ResidualBlok)]:
        m = yasa(sinf)
        m.train()
        kriteriy(m(X), y).backward()
        normalar[nom] = [
            b.tarmoq[0].weight.grad.norm().item() for b in m.bloklar]
    for i in [0, 2, 5, 8, 11]:
        print(f"  {i:>6} {normalar['oddiy'][i]:>14.3e} "
              f"{normalar['residual'][i]:>14.3e}")

    print("\n=== 2. Birinchi va oxirgi blok nisbati ===")
    print(f"  {'variant':<12} {'1-blok':>13} {'oxirgi':>13} "
          f"{'nisbat':>13}")
    for nom in ["oddiy", "residual"]:
        n = normalar[nom]
        print(f"  {nom:<12} {n[0]:>13.3e} {n[-1]:>13.3e} "
              f"{n[0] / n[-1]:>13.3e}")
    print("  residual da gradient pastki bloklarga ham YETADI")

    print("\n=== 3. Signal masshtabi ===")
    print(f"  {'blok':>6} {'oddiy std':>13} {'residual std':>14}")
    oqimlar = {}
    for nom, sinf in [("oddiy", OddiyBlok), ("residual", ResidualBlok)]:
        m = yasa(sinf)
        m.eval()
        with torch.no_grad():
            h = m.kirish(X)
            qator = []
            for b in m.bloklar:
                h = b(h)
                qator.append(h.std().item())
        oqimlar[nom] = qator
    for i in [0, 2, 5, 8, 11]:
        print(f"  {i:>6} {oqimlar['oddiy'][i]:>13.4f} "
              f"{oqimlar['residual'][i]:>14.4f}")

    print("\n=== 4. O'rgatish tezligi ===")
    print(f"  {'variant':<12} {'boshlangich':>12}", end="")
    for d in [20, 60, 150]:
        print(f" {f'{d} qadam':>11}", end="")
    print()
    for nom, sinf in [("oddiy", OddiyBlok), ("residual", ResidualBlok)]:
        m = yasa(sinf)
        opt = torch.optim.AdamW(m.parameters(), lr=0.003)
        m.train()
        boshlangich = kriteriy(m(X), y).item()
        chiqishlar = []
        for qadam in range(1, 151):
            opt.zero_grad()
            loss = kriteriy(m(X), y)
            loss.backward()
            opt.step()
            if qadam in (20, 60, 150):
                chiqishlar.append(loss.item())
        print(f"  {nom:<12} {boshlangich:>12.4f}", end="")
        for c in chiqishlar:
            print(f" {c:>11.4f}", end="")
        print()

    print("\n=== 5. Chuqurlik ortganda ===")
    print(f"  {'chuqurlik':>10} {'oddiy (80 qadam)':>18} "
          f"{'residual (80 qadam)':>21}")
    for ch in [4, 12, 24]:
        qiymatlar = []
        for sinf in [OddiyBlok, ResidualBlok]:
            m = yasa(sinf, chuqurlik=ch)
            opt = torch.optim.AdamW(m.parameters(), lr=0.003)
            m.train()
            for _ in range(80):
                opt.zero_grad()
                loss = kriteriy(m(X), y)
                loss.backward()
                opt.step()
            qiymatlar.append(loss.item())
        print(f"  {ch:>10} {qiymatlar[0]:>18.4f} {qiymatlar[1]:>21.4f}")
    print("  chuqurlik ortganda farq KATTALASHADI")

    print("\n=== 6. Shakl mos kelmasa ===")
    class NotogriResidual(nn.Module):
        def __init__(self, kirish, chiqish):
            super().__init__()
            self.f = nn.Linear(kirish, chiqish)

        def forward(self, x):
            return x + self.f(x)

    try:
        NotogriResidual(8, 16)(torch.randn(4, 8))
        print("  xato chiqmadi (kutilmagan)")
    except RuntimeError as xato:
        print(f"  RuntimeError: {str(xato)[:58]}")

    class ProyeksiyaBilan(nn.Module):
        def __init__(self, kirish, chiqish):
            super().__init__()
            self.f = nn.Linear(kirish, chiqish)
            self.proj = (nn.Identity() if kirish == chiqish
                         else nn.Linear(kirish, chiqish, bias=False))

        def forward(self, x):
            return self.proj(x) + self.f(x)

    p = ProyeksiyaBilan(8, 16)
    print(f"  proyeksiya bilan: {tuple(p(torch.randn(4, 8)).shape)}")
    print(f"  o'lcham teng bo'lsa proj turi: "
          f"{type(ProyeksiyaBilan(8, 8).proj).__name__}")
    print("  ⭐ Shakl mos kelmasa proyeksiya qo'shiladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Gradient normalari qatlamlar bo'ylab ===
    blok          oddiy       residual
       0      8.445e-02      1.773e-01
       2      1.038e-01      1.706e-01
       5      1.260e-01      1.473e-01
       8      1.342e-01      1.388e-01
      11      1.651e-01      1.406e-01

=== 2. Birinchi va oxirgi blok nisbati ===
  variant             1-blok        oxirgi        nisbat
  oddiy            8.445e-02     1.651e-01     5.117e-01
  residual         1.773e-01     1.406e-01     1.261e+00
  residual da gradient pastki bloklarga ham YETADI

=== 3. Signal masshtabi ===
    blok     oddiy std   residual std
       0        0.2125         0.6444
       2        0.2078         0.7085
       5        0.2054         0.7671
       8        0.2025         0.8471
      11        0.2144         0.9394

=== 4. O'rgatish tezligi ===
  variant       boshlangich    20 qadam    60 qadam   150 qadam
  oddiy              1.1069      1.0677      1.0876      1.0872
  residual           1.1709      0.0039      0.0005      0.0000

=== 5. Chuqurlik ortganda ===
   chuqurlik   oddiy (80 qadam)   residual (80 qadam)
           4             0.0000                0.0000
          12             1.0873                0.0001
          24             1.0954                0.0000
  chuqurlik ortganda farq KATTALASHADI

=== 6. Shakl mos kelmasa ===
  RuntimeError: The size of tensor a (8) must match the size of tensor b (
  proyeksiya bilan: (4, 16)
  o'lcham teng bo'lsa proj turi: Identity
  ⭐ Shakl mos kelmasa proyeksiya qo'shiladi

Nima ko'rsatdi: 2.2-bo'lim.

Misol 3 — Parametrlarni baham ko'rish va murakkab forward

python
"""Bir xil og'irlik ikki joyda; bir necha kirish va chiqish."""

import torch
import torch.nn as nn


class Nusxa(nn.Module):
    def __init__(self, d):
        super().__init__()
        self.a = nn.Linear(d, d)
        self.b = nn.Linear(d, d)

    def forward(self, x):
        return self.b(torch.relu(self.a(x)))


class Baham(nn.Module):
    def __init__(self, d):
        super().__init__()
        self.umumiy = nn.Linear(d, d)

    def forward(self, x):
        return self.umumiy(torch.relu(self.umumiy(x)))


class Siamese(nn.Module):
    """Ikki kirishni BIR XIL koder bilan ishlaydi."""

    def __init__(self, kirish, d=32):
        super().__init__()
        self.koder = nn.Sequential(
            nn.Linear(kirish, d), nn.ReLU(), nn.Linear(d, d))

    def forward(self, x1, x2):
        e1, e2 = self.koder(x1), self.koder(x2)
        masofa = (e1 - e2).pow(2).sum(dim=1).sqrt()
        return {"e1": e1, "e2": e2, "masofa": masofa}


class KopBosh(nn.Module):
    """Bir tana, uch bosh (ko'p vazifali)."""

    def __init__(self, kirish, d=64):
        super().__init__()
        self.tana = nn.Sequential(
            nn.Linear(kirish, d), nn.ReLU(), nn.Linear(d, d), nn.ReLU())
        self.sinf = nn.Linear(d, 3)
        self.regressiya = nn.Linear(d, 1)
        self.embedding = nn.Linear(d, 8)

    def forward(self, x, boshlar=("sinf",)):
        z = self.tana(x)
        natija = {}
        if "sinf" in boshlar:
            natija["logits"] = self.sinf(z)
        if "regressiya" in boshlar:
            natija["qiymat"] = self.regressiya(z).squeeze(-1)
        if "embedding" in boshlar:
            natija["embedding"] = self.embedding(z)
        return natija


def main() -> None:
    torch.manual_seed(0)

    print("=== 1. Nusxa va baham ko'rish ===")
    n, b = Nusxa(16), Baham(16)
    print(f"  {'model':<10} {'parametr':>10} {'kalitlar'}")
    print(f"  {'Nusxa':<10} {sum(p.numel() for p in n.parameters()):>10} "
          f"{list(n.state_dict())}")
    print(f"  {'Baham':<10} {sum(p.numel() for p in b.parameters()):>10} "
          f"{list(b.state_dict())}")
    print("  baham ko'rishda parametrlar BIR MARTA saqlanadi")

    print("\n=== 2. Gradient ikki yo'ldan yig'iladi ===")
    x = torch.randn(8, 16)
    y = torch.randn(8, 16)
    b.zero_grad()
    ((b(x) - y) ** 2).mean().backward()
    umumiy_grad = b.umumiy.weight.grad.norm().item()
    bitta = Baham(16)
    bitta.load_state_dict(b.state_dict())
    bitta.zero_grad()
    ((bitta.umumiy(x) - y) ** 2).mean().backward()
    print(f"  ikki marta chaqirilganda grad normasi: "
          f"{umumiy_grad:.6f}")
    print(f"  bir marta chaqirilganda:              "
          f"{bitta.umumiy.weight.grad.norm().item():.6f}")
    print("  gradient IKKALA chaqiruvdan qo'shiladi")

    print("\n=== 3. self.b = self.a ===")
    class Biriktirilgan(nn.Module):
        def __init__(self, d):
            super().__init__()
            self.a = nn.Linear(d, d)
            self.b = self.a

        def forward(self, x):
            return self.b(torch.relu(self.a(x)))

    bi = Biriktirilgan(16)
    print(f"  kalitlar: {list(bi.state_dict())}")
    print(f"  a va b bir obyektmi: {bi.a is bi.b}")
    print(f"  parametr soni: {sum(p.numel() for p in bi.parameters())}")
    print("  torch takrorlanishni o'zi aniqlaydi")

    print("\n=== 4. Siamese: ikki kirish ===")
    s = Siamese(12)
    x1, x2 = torch.randn(6, 12), torch.randn(6, 12)
    natija = s(x1, x2)
    print(f"  {'kalit':<10} {'shakl':>12}")
    for kalit, qiymat in natija.items():
        print(f"  {kalit:<10} {str(tuple(qiymat.shape)):>12}")
    bir_xil = s(x1, x1)
    print(f"  bir xil kirishda masofa: "
          f"{bir_xil['masofa'].abs().max().item():.6f}")
    print(f"  koder parametrlari: "
          f"{sum(p.numel() for p in s.koder.parameters())}")

    print("\n=== 5. Ko'p boshli model ===")
    k = KopBosh(20)
    X = torch.randn(10, 20)
    print(f"  {'so_ralgan boshlar':<32} {'qaytgan kalitlar'}")
    for boshlar in [("sinf",), ("sinf", "regressiya"),
                    ("sinf", "regressiya", "embedding")]:
        chiqish = k(X, boshlar)
        print(f"  {str(boshlar):<32} {sorted(chiqish)}")

    print("\n=== 6. Ko'p vazifali loss ===")
    y_sinf = torch.randint(0, 3, (10,))
    y_qiymat = torch.randn(10)
    chiqish = k(X, ("sinf", "regressiya"))
    l1 = nn.CrossEntropyLoss()(chiqish["logits"], y_sinf)
    l2 = nn.MSELoss()(chiqish["qiymat"], y_qiymat)
    print(f"  {'komponent':<20} {'qiymat':>10}")
    print(f"  {'klassifikatsiya':<20} {l1.item():>10.4f}")
    print(f"  {'regressiya':<20} {l2.item():>10.4f}")
    for vazn in [0.1, 0.5, 1.0]:
        print(f"  jami (vazn={vazn}): {(l1 + vazn * l2).item():.4f}")
    print("  ⭐ Vaznlarni tanlash - ko'p vazifali o'rgatishning kaliti")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Nusxa va baham ko'rish ===
  model        parametr kalitlar
  Nusxa             544 ['a.weight', 'a.bias', 'b.weight', 'b.bias']
  Baham             272 ['umumiy.weight', 'umumiy.bias']
  baham ko'rishda parametrlar BIR MARTA saqlanadi

=== 2. Gradient ikki yo'ldan yig'iladi ===
  ikki marta chaqirilganda grad normasi: 0.466916
  bir marta chaqirilganda:              1.006205
  gradient IKKALA chaqiruvdan qo'shiladi

=== 3. self.b = self.a ===
  kalitlar: ['a.weight', 'a.bias', 'b.weight', 'b.bias']
  a va b bir obyektmi: True
  parametr soni: 272
  torch takrorlanishni o'zi aniqlaydi

=== 4. Siamese: ikki kirish ===
  kalit             shakl
  e1              (6, 32)
  e2              (6, 32)
  masofa             (6,)
  bir xil kirishda masofa: 0.000000
  koder parametrlari: 1472

=== 5. Ko'p boshli model ===
  so_ralgan boshlar                qaytgan kalitlar
  ('sinf',)                        ['logits']
  ('sinf', 'regressiya')           ['logits', 'qiymat']
  ('sinf', 'regressiya', 'embedding') ['embedding', 'logits', 'qiymat']

=== 6. Ko'p vazifali loss ===
  komponent                qiymat
  klassifikatsiya          1.1359
  regressiya               0.5146
  jami (vazn=0.1): 1.1874
  jami (vazn=0.5): 1.3932
  jami (vazn=1.0): 1.6505
  ⭐ Vaznlarni tanlash - ko'p vazifali o'rgatishning kaliti

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

Misol 4 — Konfiguratsiyadan arxitektura

python
"""Model lug'at bilan ta'riflanadi va u bilan saqlanadi."""

import shutil
import tempfile
from pathlib import Path

import torch
import torch.nn as nn


class Blok(nn.Module):
    def __init__(self, d, kengaytirish=2, dropout=0.1, residual=True,
                 aktivatsiya="gelu"):
        super().__init__()
        self.residual = residual
        aktivlar = {"relu": nn.ReLU, "gelu": nn.GELU, "silu": nn.SiLU}
        self.norm = nn.LayerNorm(d)
        self.tarmoq = nn.Sequential(
            nn.Linear(d, d * kengaytirish),
            aktivlar[aktivatsiya](),
            nn.Dropout(dropout),
            nn.Linear(d * kengaytirish, d))

    def forward(self, x):
        chiqish = self.tarmoq(self.norm(x))
        return x + chiqish if self.residual else chiqish


class Tarmoq(nn.Module):
    def __init__(self, kirish, chiqish, d=64, chuqurlik=3,
                 kengaytirish=2, dropout=0.1, residual=True,
                 aktivatsiya="gelu"):
        super().__init__()
        self.konfig = {"kirish": kirish, "chiqish": chiqish, "d": d,
                       "chuqurlik": chuqurlik,
                       "kengaytirish": kengaytirish,
                       "dropout": dropout, "residual": residual,
                       "aktivatsiya": aktivatsiya}
        self.kirish_qatlam = nn.Linear(kirish, d)
        self.bloklar = nn.ModuleList(
            [Blok(d, kengaytirish, dropout, residual, aktivatsiya)
             for _ in range(chuqurlik)])
        self.norm = nn.LayerNorm(d)
        self.bosh = nn.Linear(d, chiqish)
        self.apply(self._boshla)

    @staticmethod
    def _boshla(m):
        if isinstance(m, nn.Linear):
            nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
            if m.bias is not None:
                nn.init.zeros_(m.bias)

    def forward(self, x):
        x = self.kirish_qatlam(x)
        for blok in self.bloklar:
            x = blok(x)
        return self.bosh(self.norm(x))


def main() -> None:
    torch.manual_seed(0)
    papka = Path(tempfile.mkdtemp(prefix="torch_konfig_"))
    try:
        print("=== 1. Konfiguratsiya variantlari ===")
        variantlar = [
            {"d": 32, "chuqurlik": 2},
            {"d": 64, "chuqurlik": 4},
            {"d": 64, "chuqurlik": 4, "kengaytirish": 4},
            {"d": 128, "chuqurlik": 6, "residual": False},
        ]
        X = torch.randn(64, 20)
        print(f"  {'konfig':<48} {'parametr':>10}")
        for v in variantlar:
            m = Tarmoq(20, 3, **v)
            print(f"  {str(v):<48} "
                  f"{sum(p.numel() for p in m.parameters()):>10}")

        print("\n=== 2. Aktivatsiyani konfigdan tanlash ===")
        print(f"  {'aktivatsiya':<14} {'chiqish std':>13} "
              f"{'parametr':>10}")
        for a in ["relu", "gelu", "silu"]:
            m = Tarmoq(20, 3, aktivatsiya=a)
            m.eval()
            with torch.no_grad():
                print(f"  {a:<14} {m(X).std().item():>13.4f} "
                      f"{sum(p.numel() for p in m.parameters()):>10}")

        print("\n=== 3. Konfig model bilan saqlanadi ===")
        model = Tarmoq(20, 3, d=64, chuqurlik=4, dropout=0.15)
        yol = papka / "model.pt"
        torch.save({"konfig": model.konfig,
                    "holat": model.state_dict()}, yol)
        print(f"  saqlangan konfig: {model.konfig}")
        print(f"  fayl hajmi: {yol.stat().st_size / 1024:.1f} KB")

        print("\n=== 4. Qayta qurish ===")
        paket = torch.load(yol, weights_only=False)
        qayta = Tarmoq(**paket["konfig"])
        natija = qayta.load_state_dict(paket["holat"])
        print(f"  missing_keys: {natija.missing_keys}")
        print(f"  unexpected_keys: {natija.unexpected_keys}")
        model.eval()
        qayta.eval()
        with torch.no_grad():
            print(f"  chiqishlar bir xilmi: "
                  f"{torch.allclose(model(X), qayta(X))}")

        print("\n=== 5. Konfigsiz nima bo'ladi ===")
        notogri = Tarmoq(20, 3, d=32, chuqurlik=2)
        try:
            notogri.load_state_dict(paket["holat"])
            print("  xato chiqmadi (kutilmagan)")
        except RuntimeError as xato:
            print(f"  RuntimeError: {str(xato).splitlines()[0][:58]}")
        print("  ⭐ Konfigsiz state_dict ni qayerga yuklashni bilmaysiz")

        print("\n=== 6. Giperparametr qidiruvi tabiiy bo'ladi ===")
        y = torch.randint(0, 3, (64,))
        kriteriy = nn.CrossEntropyLoss()
        print(f"  {'konfig':<34} {'50 qadamdan keyin loss':>24}")
        for v in [{"d": 32, "chuqurlik": 2},
                  {"d": 64, "chuqurlik": 4},
                  {"d": 64, "chuqurlik": 4, "residual": False}]:
            torch.manual_seed(0)
            m = Tarmoq(20, 3, **v)
            opt = torch.optim.AdamW(m.parameters(), lr=0.005)
            m.train()
            for _ in range(50):
                opt.zero_grad()
                loss = kriteriy(m(X), y)
                loss.backward()
                opt.step()
            print(f"  {str(v):<34} {loss.item():>24.4f}")
    finally:
        shutil.rmtree(papka, ignore_errors=True)


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Konfiguratsiya variantlari ===
  konfig                                             parametr
  {'d': 32, 'chuqurlik': 2}                              9347
  {'d': 64, 'chuqurlik': 4}                             68483
  {'d': 64, 'chuqurlik': 4, 'kengaytirish': 4}         134531
  {'d': 128, 'chuqurlik': 6, 'residual': False}        400387

=== 2. Aktivatsiyani konfigdan tanlash ===
  aktivatsiya      chiqish std   parametr
  relu                  1.2676      51779
  gelu                  1.3919      51779
  silu                  1.4129      51779

=== 3. Konfig model bilan saqlanadi ===
  saqlangan konfig: {'kirish': 20, 'chiqish': 3, 'd': 64, 'chuqurlik': 4, 'kengaytirish': 2, 'dropout': 0.15, 'residual': True, 'aktivatsiya': 'gelu'}
  fayl hajmi: 276.3 KB

=== 4. Qayta qurish ===
  missing_keys: []
  unexpected_keys: []
  chiqishlar bir xilmi: True

=== 5. Konfigsiz nima bo'ladi ===
  RuntimeError: Error(s) in loading state_dict for Tarmoq:
  ⭐ Konfigsiz state_dict ni qayerga yuklashni bilmaysiz

=== 6. Giperparametr qidiruvi tabiiy bo'ladi ===
  konfig                               50 qadamdan keyin loss
  {'d': 32, 'chuqurlik': 2}                            0.0035
  {'d': 64, 'chuqurlik': 4}                            0.0011
  {'d': 64, 'chuqurlik': 4, 'residual': False}                   0.0020

Nima ko'rsatdi: 2.5-bo'lim.


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

Noto'g'ri fikr To'g'risi
"Residual — kichik yaxshilanish" Chuqur tarmoqni mumkin qilgan g'oya
"self.b = self.a nusxa" Bir xil parametrlar
"Har blokni qo'lda yozish aniqroq" Blok naqshi xatoni kamaytiradi
"forward da Module yaratsa bo'ladi" Ro'yxatga olinmaydi
"Konfig ortiqcha" Usiz modelni tiklab bo'lmaydi
"BatchNorm oldidan bias zararsiz" Bekor bo'ladi, parametr behuda
"Residual shakl talab qilmaydi" Bir xil o'lcham shart
"Pre-norm va post-norm bir xil" Pre-norm barqarorroq

6. Keng tarqalgan xatolar va yechimlari

1. forward da Module yaratish

python
def forward(self, x): return nn.Linear(4, 2)(x)    # ⚠️
# __init__ da yarating                             # ✅

2. Residual shakli

python
return x + self.f(x)        # f: 8 -> 16           # ⚠️
return self.proj(x) + self.f(x)                    # ✅

3. BatchNorm oldidan bias

python
nn.Linear(d, d), nn.BatchNorm1d(d)                 # ⚠️
nn.Linear(d, d, bias=False), nn.BatchNorm1d(d)     # ✅

4. in-place amal residualda

python
x += self.f(x)                                     # ⚠️ autograd buziladi
x = x + self.f(x)                                  # ✅

5. Konfigni saqlamaslik

python
torch.save(model.state_dict(), yol)                # ⚠️
torch.save({"konfig": k, "holat": ...}, yol)       # ✅

6. Bloklarni list ga

python
self.bloklar = [Blok(d) for _ in range(n)]         # ⚠️
self.bloklar = nn.ModuleList([...])                # ✅

7. Chuqurlikni qo'lda ko'chirish

python
self.b1 = Blok(d); self.b2 = Blok(d); ...          # ⚠️
self.bloklar = nn.ModuleList([Blok(d) for _ in range(n)])  # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 21.1-dars (o'tilgan): nn.Module
  • 21.7-dars: Checkpoint va konfig
  • 24-qism: ResNet bloklari
  • 26-qism: Transformer bloki
  • 27-qism: Tied embeddings

8. Eng yaxshi amaliyotlar

  1. Takrorlanuvchini blokka ajrating.

  2. Chuqur tarmoqda residual ishlating.

  3. Shakl mos kelmasa proyeksiya qo'ying.

  4. Konfigni model bilan saqlang.

  5. BatchNorm oldidan bias=False.

  6. forward da Module yaratmang.

  7. Boshlang'ich qiymatni apply bilan.

  8. Blokni alohida sinang.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # blok nima?
2.  # residual formulasi?
3.  # nega residual ishlaydi?
4.  # shakl mos kelmasa?
5.  # baham ko'rish qanday qilinadi?
6.  # gradient baham ko'rishda qanday?
7.  # forward da Module yaratsa?
8.  # konfig nima uchun?
9.  # BatchNorm oldida bias?
10. # pre-norm nima?
11. # ko'p chiqish qanday qaytariladi?
12. # in-place nega yomon?
Javoblar
  1. Takrorlanadigan mantiqiy birlik
  2. x + f(x)
  3. Gradientga qisqa yo'l
  4. Proyeksiya qo'shiladi
  5. Bir modulni ikki marta chaqirish
  6. Ikkala yo'ldan yig'iladi
  7. Ro'yxatga olinmaydi
  8. Modelni qayta qurish uchun
  9. Kerak emas (bias=False)
  10. x + f(norm(x))
  11. dict yoki tuple
  12. Autograd grafini buzadi

Vazifa 2: Xatolarni tuzating

python
1.  def forward(self, x): return nn.Linear(4, 2)(x)

2.  return x + self.f(x)        # f: 8 -> 16

3.  nn.Linear(d, d), nn.BatchNorm1d(d)

4.  x += self.f(x)

5.  self.bloklar = [Blok(d) for _ in range(n)]
Javoblar
python
1.  # __init__ da yarating

2.  return self.proj(x) + self.f(x)

3.  nn.Linear(d, d, bias=False), nn.BatchNorm1d(d)

4.  x = x + self.f(x)

5.  self.bloklar = nn.ModuleList([Blok(d) for _ in range(n)])

Vazifa 3: Bloklar

Modellang:

  1. Chuqurlik
  2. Kalitlar
  3. Alohida sinash
  4. Signal

Vazifa 4: Residual

Modellang:

  1. Gradientlar
  2. Nisbat
  3. O'rgatish
  4. Shakl

Vazifa 5: Baham ko'rish

Modellang:

  1. Nusxa va baham
  2. Gradient
  3. Siamese
  4. Ko'p bosh

Vazifa 6: Konfig

Modellang:

  1. Variantlar
  2. Saqlash
  3. Qayta qurish
  4. Qidiruv

Vazifa 7: O'ylash

Residual bloklardan 40 qatlamli tarmoq qurdingiz, lekin u 4 qatlamlidan yomon ishlayapti. Residual bor bo'lsa ham nima noto'g'ri bo'lishi mumkin?

Javob

Residual gradient muammosini yechadi, lekin boshqa hamma narsani emas.

1. Ma'lumot yetarli emas

40 qatlamli tarmoqda parametrlar soni 10 barobar ko'p. Tabular vazifada 5000 namuna bilan bu shunchaki yod olish.

python
p = sum(x.numel() for x in model.parameters())
print(p, p / len(X_train))       # > 50 bo'lsa juda katta

Bu eng ehtimolli sabab va uni tekshirish bir qator.

2. Residual to'g'ri joyda emas

python
# noto'g'ri: norm residualdan KEYIN
return self.norm(x + self.f(x))          # post-norm, chuqurda beqaror
# to'g'ri: pre-norm
return x + self.f(self.norm(x))

Post-norm bilan 40 qatlam o'rgatish uchun warmup va ehtiyotkor lr kerak — bu Transformer maqolasidagi klassik muammo.

3. lr chuqurlikka moslanmagan

Chuqur tarmoqda bir xil lr juda katta bo'lishi mumkin. Har blok chiqishga qo'shilgani uchun signal yig'iladi:

python
# 40 ta blok x + f(x) -> chiqish dispersiyasi ~40 barobar
print([h.std().item() for h in oraliq_chiqishlar])

Yechim: blok chiqishini masshtablash yoki lr ni kamaytirish.

python
self.masshtab = nn.Parameter(torch.zeros(1))   # noldan boshlanadi
return x + self.masshtab * self.f(x)           # LayerScale

Bu LayerScale usuli — blok boshida hech narsa qilmaydi va asta faollashadi.

4. Davrlar yetarli emas

40 qatlamli tarmoq 4 qatlamlidan sekinroq yaqinlashadi. 100 davrda 4 qatlamli tugagan bo'lsa, 40 qatlamliga 300 kerak bo'lishi mumkin.

Tekshiruv tartibi:

Qadam Tekshiruv Nimani ko'rsatadi
1 parametr / namuna Yod olish xavfi
2 O'quv va val loss farqi Yodlash yuz berganmi
3 Blok chiqishlari std Signal portlayaptimi
4 Birinchi blok gradienti Gradient yetayaptimi
5 lr ni 3x kamaytirish Beqarorlik sabab bo'lganmi
6 Davrlarni 3x oshirish Shunchaki sekinmi

Eng muhim fikr: chuqurlik bepul emas. U ma'lumot, vaqt va ehtiyotkor sozlash talab qiladi. Tabular vazifada 40 qatlam deyarli hech qachon kerak emas — 2-4 qatlam yetadi (20.3-dars).

Residual ulanish sizga chuqur tarmoqni o'rgatish imkonini beradi, lekin u chuqurlik foydali ekanini kafolatlamaydi.

Nimani mustahkamlaydi: 2.1, 2.2-bo'limlar.


Xulosa

Bu darsda bloklar va arxitektura naqshlarini ko'rdik.

Eng muhim uch fikr:

  1. Blok — arxitektura kodining asosiy birligi. Takrorlanuvchi qismni alohida Module ga ajratsangiz, chuqurlik bitta songa aylanadi, state_dict kalitlari tartibli bo'ladi (bloklar.0.chiziqli.weight), blokni alohida sinash mumkin bo'ladi va uni boshqasiga almashtirish bir qator kodga tushadi.

  2. x + f(x) gradientga qisqa yo'l beradi. Hosila 1 + df/dx bo'lgani uchun gradient har blokda kamida birlik koeffitsiyent bilan o'tadi. 2-misolda 12 qatlamli tarmoqda birinchi va oxirgi blok gradientlari nisbati residualsiz 0.51, residual bilan 1.26 bo'ldi. Farqning asl ko'rinishi esa o'rgatishda chiqdi: residualsiz tarmoq 150 qadamda loss ni 1.107 dan atigi 1.087 ga tushirdi — ya'ni amalda o'rganmadi — residual bilan esa loss 20 qadamdayoq 0.004 ga tushdi. 4 qatlamda ikkalasi ham o'rgandi; farq chuqurlik ortganda paydo bo'ladi.

  3. Konfig modelning bir qismi. state_dict faqat tensorlarni saqlaydi — uni qayerga yuklashni bilmasangiz, u foydasiz. Arxitektura lug'atini model bilan birga saqlash modelni bir qatorda tiklash imkonini beradi va giperparametr qidiruvini tabiiy qiladi.

Keyingi darsda Dataset va DataLoader: ma'lumotni tarmoqqa uzatishning standart yo'li, batch yig'ish, aralashtirish, collate_fn va namuna olish strategiyalari.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
21.2-dars: O'z qatlamlaringiz va bloklar — IlmHamroh