IlmHamroh
Data Science va sun'iy intellekt/Neyron tarmoqlar9/12-dars22 daqiqa
Mundarija (21)

20.9-dars: torch tensorlari va autograd

20-QISM — NEYRON TARMOQLAR · 9-dars


1. Kirish va motivatsiya

Shu paytgacha hammasini numpy bilan qildik: oldinga o'tish, loss, orqaga tarqalish, optimizator. Bu kerak edi — endi tarmoq ichida nima bo'layotganini bilamiz.

Lekin amaliyotda hech kim gradientni qo'lda yozmaydi. Buning ikki sababi bor. Birinchisi — xatolik: har yangi qatlam uchun to'rtta formulani qo'lda chiqarish va tekshirish soatlab vaqt oladi. Ikkinchisi — GPU: zamonaviy tarmoqlar numpy da amalda o'rgatilmaydi.

PyTorch ikkala muammoni ham yechadi. Uning asosi — tensor: numpy massiviga juda o'xshash, lekin ikki qo'shimcha qobiliyati bor — GPU da yashay oladi va o'zining tarixini eslaydi. Shu tarix orqali autograd gradientni avtomatik hisoblaydi.

Bu darsda tensorlar bilan tanishamiz va eng muhimi — o'zimiz yozgan backprop ni autograd bilan raqamma-raqam solishtiramiz. Bu solishtirish ikki narsani ko'rsatadi: bizning kodimiz to'g'ri edi, va autograd sehr emas — u aynan o'sha zanjir qoidasini qo'llaydi.

Real vaziyat. Muhandis numpy da tarmoq yozdi va yangi qatlam qo'shmoqchi bo'ldi. Gradientni chiqarishga ikki kun ketdi. torch ga ko'chirgandan keyin yangi qatlam qo'shish besh daqiqa oladigan ishga aylandi.

Bu darsda numpy dan torch ga o'tamiz.

Bu darsda:

  • Tensor va numpy farqi
  • autograd qanday ishlaydi
  • Hisob grafi
  • no_grad va detach
  • Qo'lda yozilgan backprop bilan solishtirish
  • Tuzoqlar
  • Amaliy: torch bilan tarmoq

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


2. Nazariya — chuqur tushuntirish

2.1. Tensor

text
TENSOR = numpy massivi + ikki qo'shimcha xossa

1. QURILMA (device): cpu yoki cuda
2. GRADIENT KUZATUVI: requires_grad=True bo'lsa tarix yoziladi

YARATISH:
  torch.tensor([1.0, 2.0])          ro'yxatdan
  torch.zeros(3, 4) / torch.ones    to'ldirilgan
  torch.randn(3, 4)                 N(0,1)
  torch.from_numpy(massiv)          XOTIRANI BAHAM KO'RADI
  torch.arange, torch.linspace

TURLAR: float32 (SUKUT), float64, int64, bool
  DIQQAT: numpy sukut float64, torch sukut float32

numpy GA QAYTISH:
  t.numpy()            xotira baham, grad bo'lsa XATO
  t.detach().numpy()   to'g'ri usul

torch sukut float32, numpy esa float64 — bu farq aniqlik taqqoslashda darhol seziladi.

2.2. autograd

text
requires_grad=True BO'LGAN TENSOR:
  unga qo'llangan HAR amal grafga yoziladi
  har natijada grad_fn saqlanadi

y.backward() CHAQIRILGANDA:
  graf bo'ylab ORQAGA yuriladi
  har barg tensorning .grad maydoniga gradient QO'SHILADI

MISOL:
  x = torch.tensor([2.0], requires_grad=True)
  y = x ** 3
  y.backward()
  x.grad   ->  3*x^2 = 12

MUHIM 1: .grad YIG'ILADI, almashmaydi
  -> har qadamdan oldin zero_grad() yoki grad = None
MUHIM 2: backward() grafni O'CHIRADI
  -> ikki marta chaqirish uchun retain_graph=True
MUHIM 3: backward() faqat SKALYARDAN
  -> vektor uchun gradient argumenti kerak

.grad yig'iladi — bu torch dagi eng ko'p uchraydigan xato manbai.

2.3. Hisob grafi

text
z = (x * y + 3).sum()

     x       y
      \     /
       \   /
        mul        <- MulBackward
         |
        +3         <- AddBackward
         |
        sum        <- SumBackward
         |
         z

DINAMIK GRAF: har oldinga o'tishda QAYTA quriladi
  -> if/for/while bemalol ishlatiladi (TensorFlow 1.x da bo'lmagan)
  -> debug oson: oddiy Python

BARG (leaf) TENSOR: foydalanuvchi yaratgan, grad_fn = None
  faqat barglarda .grad saqlanadi
  oraliq tensorda kerak bo'lsa: t.retain_grad()

Graf dinamik: har forward da qayta quriladi, shuning uchun oddiy Python boshqaruvi ishlaydi.

2.4. no_grad va detach

text
torch.no_grad():
  blok ichida graf QURILMAYDI
  -> xotira tejaladi, tezroq
  -> INFERENCE va parametr yangilashda SHART

t.detach():
  grafdan UZILGAN nusxa (xotira baham)
  -> gradient shu nuqtada to'xtaydi

QAYERDA KERAK:
  bashorat:        with torch.no_grad(): model(X)
  metrika:         loss.item() yoki loss.detach()
  qo'lda yangilash: with torch.no_grad(): w -= lr * w.grad
  numpy ga o'tish: t.detach().numpy()

torch.inference_mode(): no_grad dan ham tezroq (yangi API)

no_grad — inference da majburiy; usiz xotira behuda sarflanadi.

2.5. Tensor amallari

text
numpy DAN FARQLAR:
  massiv.reshape     -> t.reshape yoki t.view
  massiv.T           -> t.T (2D), t.transpose(i,j), t.permute
  np.concatenate     -> torch.cat
  np.stack           -> torch.stack
  massiv @ massiv    -> bir xil (@ yoki torch.matmul)
  axis=              -> dim=
  massiv.astype      -> t.to(torch.float64) yoki t.float()

view VA reshape:
  view  - xotira uzluksiz bo'lishi SHART, nusxa olmaydi
  reshape - kerak bo'lsa nusxa oladi (xavfsizroq)

BROADCASTING: numpy bilan bir xil qoidalar

JOYIDA (in-place) AMALLAR: t.add_(1), t.zero_()
  DIQQAT: autograd ni buzishi mumkin

dim= va axis= — ko'chirishda eng ko'p uchraydigan sintaksis farqi.

2.6. Qo'lda va avtomatik

text
BIZNING KOD (20.6-dars):
  dZ = (P - Y) / N
  dW = A.T @ dZ
  db = dZ.sum(0)
  dA = dZ @ W.T

TORCH:
  loss.backward()

NATIJA: AYNAN BIR XIL sonlar (float aniqligi doirasida)

autograd SEHR EMAS:
  u aynan o'sha zanjir qoidasini qo'llaydi
  farq - uni SIZ emas, kutubxona yozgan

SHUNING UCHUN 20.6-dars kerak edi:
  autograd xato bergan joyni faqat MEXANIZMNI bilgan odam topadi

autograd — avtomatlashtirilgan 20.6-dars, boshqa hech narsa emas.

2.7. Tuzoqlar

Asosiy tuzoqlar: zero_grad() ni unutish; parametr yangilashni no_grad siz qilish; float64 va float32 ni aralashtirish; t.numpy() ni detach siz chaqirish; backward() ni ikki marta chaqirish; vektordan backward() qilish; view ni uzluksiz bo'lmagan tensorga qo'llash; item() o'rniga tensorni ro'yxatga yig'ish (xotira sizishi).


3. Tez ma'lumotnoma

python
import torch

x = torch.tensor([2.0, 3.0], requires_grad=True)
y = (x ** 2).sum()
y.backward()
print(x.grad)                       # 2*x = [4., 6.]

x.grad = None                       # yoki opt.zero_grad()

# qo'lda yangilash
with torch.no_grad():
    w -= lr * w.grad
w.grad = None

# numpy bilan
t = torch.from_numpy(massiv)        # xotira baham
m = t.detach().numpy()

# inference
with torch.no_grad():
    bashorat = model(X)

torch.manual_seed(0)                # takrorlanuvchanlik

Tensor xulosasi

tensor = massiv + device + grad tarixi
requires_grad -> graf yoziladi
backward() -> barglarning .grad iga QO'SHADI
no_grad / detach -> grafdan chiqish
sukut dtype: torch float32, numpy float64

4. Batafsil misollar

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

Misol 1 — Tensor asoslari

python
"""Tensor va numpy massivi: o'xshashlik va farqlar (real torch)."""

import numpy as np
import torch


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

    print("=== 1. Yaratish usullari ===")
    tensorlar = {
        "tensor([1.,2.,3.])": torch.tensor([1.0, 2.0, 3.0]),
        "zeros(2,3)": torch.zeros(2, 3),
        "randn(2,3)": torch.randn(2, 3),
        "arange(5)": torch.arange(5),
        "linspace(0,1,5)": torch.linspace(0, 1, 5),
    }
    print(f"  {'ifoda':<22} {'shakl':>10} {'dtype':>16}")
    for nom, t in tensorlar.items():
        print(f"  {nom:<22} {str(tuple(t.shape)):>10} {str(t.dtype):>16}")

    print("\n=== 2. Sukut turlari farqi ===")
    m = np.array([1.0, 2.0])
    t = torch.tensor([1.0, 2.0])
    print(f"  numpy sukut: {m.dtype}")
    print(f"  torch sukut: {t.dtype}")
    print(f"  from_numpy dan keyin: {torch.from_numpy(m).dtype}")
    print("  float64 va float32 aralashsa - xato yoki aniqlik yo'qoladi")

    print("\n=== 3. Xotirani baham ko'rish ===")
    m = np.array([1.0, 2.0, 3.0])
    t = torch.from_numpy(m)
    m[0] = 99.0
    print(f"  numpy ni o'zgartirdik: {m}")
    print(f"  tensor ham o'zgardi:   {t.numpy()}")
    t2 = torch.tensor(m)            # NUSXA oladi
    m[1] = 88.0
    print(f"  torch.tensor() nusxa oladi: {t2.numpy()}")

    print("\n=== 4. Amallar numpy bilan bir xil ===")
    a_np = np.arange(6, dtype=np.float32).reshape(2, 3)
    a_t = torch.arange(6, dtype=torch.float32).reshape(2, 3)
    amallar = [
        ("sum", a_np.sum(), a_t.sum().item()),
        ("mean", a_np.mean(), a_t.mean().item()),
        ("max", a_np.max(), a_t.max().item()),
        ("std (ddof=1)", a_np.std(ddof=1), a_t.std().item()),
        ("sum(axis=0)[0]", a_np.sum(axis=0)[0], a_t.sum(dim=0)[0].item()),
    ]
    print(f"  {'amal':<16} {'numpy':>12} {'torch':>12} {'farq':>10}")
    for nom, v1, v2 in amallar:
        print(f"  {nom:<16} {v1:>12.6f} {v2:>12.6f} "
              f"{abs(v1 - v2):>10.2e}")

    print("\n=== 5. Shakl o'zgartirish ===")
    t = torch.arange(12).reshape(3, 4)
    print(f"  asl: {tuple(t.shape)}")
    print(f"  {'amal':<24} {'natija shakli':>16}")
    for nom, natija in [
            ("reshape(4,3)", t.reshape(4, 3)),
            ("view(2,6)", t.view(2, 6)),
            ("T", t.T),
            ("unsqueeze(0)", t.unsqueeze(0)),
            ("flatten()", t.flatten()),
            ("permute(1,0)", t.permute(1, 0))]:
        print(f"  {nom:<24} {str(tuple(natija.shape)):>16}")

    print("\n=== 6. view va reshape farqi ===")
    t = torch.arange(12).reshape(3, 4)
    tr = t.T
    print(f"  t uzluksizmi:   {t.is_contiguous()}")
    print(f"  t.T uzluksizmi: {tr.is_contiguous()}")
    try:
        tr.view(12)
        print("  t.T.view(12): ishladi")
    except RuntimeError as xato:
        print(f"  t.T.view(12): RuntimeError - {str(xato)[:44]}")
    print(f"  t.T.reshape(12): ishladi, shakl "
          f"{tuple(tr.reshape(12).shape)}")
    print("  ⭐ Shubha bo'lsa reshape ishlating")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Yaratish usullari ===
  ifoda                       shakl            dtype
  tensor([1.,2.,3.])           (3,)    torch.float32
  zeros(2,3)                 (2, 3)    torch.float32
  randn(2,3)                 (2, 3)    torch.float32
  arange(5)                    (5,)      torch.int64
  linspace(0,1,5)              (5,)    torch.float32

=== 2. Sukut turlari farqi ===
  numpy sukut: float64
  torch sukut: torch.float32
  from_numpy dan keyin: torch.float64
  float64 va float32 aralashsa - xato yoki aniqlik yo'qoladi

=== 3. Xotirani baham ko'rish ===
  numpy ni o'zgartirdik: [99.  2.  3.]
  tensor ham o'zgardi:   [99.  2.  3.]
  torch.tensor() nusxa oladi: [99.  2.  3.]

=== 4. Amallar numpy bilan bir xil ===
  amal                    numpy        torch       farq
  sum                 15.000000    15.000000   0.00e+00
  mean                 2.500000     2.500000   0.00e+00
  max                  5.000000     5.000000   0.00e+00
  std (ddof=1)         1.870829     1.870829   0.00e+00
  sum(axis=0)[0]       3.000000     3.000000   0.00e+00

=== 5. Shakl o'zgartirish ===
  asl: (3, 4)
  amal                        natija shakli
  reshape(4,3)                       (4, 3)
  view(2,6)                          (2, 6)
  T                                  (4, 3)
  unsqueeze(0)                    (1, 3, 4)
  flatten()                           (12,)
  permute(1,0)                       (4, 3)

=== 6. view va reshape farqi ===
  t uzluksizmi:   True
  t.T uzluksizmi: False
  t.T.view(12): RuntimeError - view size is not compatible with input tenso
  t.T.reshape(12): ishladi, shakl (12,)
  ⭐ Shubha bo'lsa reshape ishlating

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

Misol 2 — autograd qanday ishlaydi

python
"""Hisob grafi, backward va .grad (real torch)."""

import warnings

import torch


def main() -> None:
    print("=== 1. Eng oddiy holat ===")
    x = torch.tensor([2.0], requires_grad=True)
    y = x ** 3
    y.backward()
    print(f"  y = x^3, x = {x.item()}")
    print(f"  dy/dx = 3x^2 = {3 * 2.0 ** 2}")
    print(f"  x.grad = {x.grad.item()}")

    print("\n=== 2. Graf tuzilishi ===")
    a = torch.tensor([1.0, 2.0], requires_grad=True)
    b = torch.tensor([3.0, 4.0], requires_grad=True)
    c = a * b
    d = c + 3
    e = d.sum()
    print(f"  {'tensor':<8} {'grad_fn':<22} {'barg (leaf)':>12}")
    for nom, t in [("a", a), ("b", b), ("c", c), ("d", d), ("e", e)]:
        fn = type(t.grad_fn).__name__ if t.grad_fn else "None"
        print(f"  {nom:<8} {fn:<22} {str(t.is_leaf):>12}")

    print("\n=== 3. Gradientlar ===")
    e.backward()
    print(f"  e = sum(a*b + 3)")
    print(f"  de/da = b = {b.detach().tolist()}, "
          f"a.grad = {a.grad.tolist()}")
    print(f"  de/db = a = {a.detach().tolist()}, "
          f"b.grad = {b.grad.tolist()}")
    with warnings.catch_warnings(record=True) as ogohlar:
        warnings.simplefilter("always")
        c_grad = c.grad
    print(f"  c.grad (barg emas): {c_grad}")
    print(f"  torch ogohlantirdi: {ogohlar[0].category.__name__}"
          if ogohlar else "  ogohlantirish yo'q")
    print("  oraliq tensorda grad kerak bo'lsa: c.retain_grad()")

    print("\n=== 4. .grad YIG'ILADI ===")
    w = torch.tensor([1.0], requires_grad=True)
    print(f"  {'chaqiruv':>9} {'w.grad':>10}")
    for i in range(1, 4):
        (w * 2).sum().backward()
        print(f"  {i:>9} {w.grad.item():>10.1f}")
    print("  har backward() gradientni QO'SHADI")
    w.grad = None
    (w * 2).sum().backward()
    print(f"  grad = None dan keyin: {w.grad.item()}")

    print("\n=== 5. Vektordan backward ===")
    v = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
    u = v ** 2
    try:
        u.backward()
        print("  ishladi (kutilmagan)")
    except RuntimeError as xato:
        print(f"  RuntimeError: {str(xato)[:52]}")
    u.backward(torch.ones_like(u))
    print(f"  backward(ones) bilan: {v.grad.tolist()}  (= 2*v)")

    print("\n=== 6. no_grad va detach ===")
    p = torch.tensor([1.0, 2.0], requires_grad=True)
    q1 = p * 2
    with torch.no_grad():
        q2 = p * 2
    q3 = p.detach() * 2
    print(f"  {'holat':<22} {'requires_grad':>15} {'grad_fn':>14}")
    for nom, t in [("oddiy", q1), ("no_grad ichida", q2),
                   ("detach() dan keyin", q3)]:
        fn = type(t.grad_fn).__name__ if t.grad_fn else "None"
        print(f"  {nom:<22} {str(t.requires_grad):>15} {fn:>14}")

    print("\n=== 7. Dinamik graf ===")
    def f(x, n):
        natija = x
        for _ in range(n):              # oddiy Python sikli
            natija = natija * x
        return natija

    print(f"  {'n':>3} {'f(2,n)':>10} {'df/dx':>10} {'kutilgan':>10}")
    for n in [1, 2, 3, 4]:
        xx = torch.tensor([2.0], requires_grad=True)
        f(xx, n).backward()
        kutilgan = (n + 1) * 2.0 ** n
        print(f"  {n:>3} {2.0 ** (n + 1):>10.1f} {xx.grad.item():>10.1f} "
              f"{kutilgan:>10.1f}")
    print("  ⭐ Graf har chaqiruvda QAYTA quriladi - Python bemalol")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Eng oddiy holat ===
  y = x^3, x = 2.0
  dy/dx = 3x^2 = 12.0
  x.grad = 12.0

=== 2. Graf tuzilishi ===
  tensor   grad_fn                 barg (leaf)
  a        None                           True
  b        None                           True
  c        MulBackward0                  False
  d        AddBackward0                  False
  e        SumBackward0                  False

=== 3. Gradientlar ===
  e = sum(a*b + 3)
  de/da = b = [3.0, 4.0], a.grad = [3.0, 4.0]
  de/db = a = [1.0, 2.0], b.grad = [1.0, 2.0]
  c.grad (barg emas): None
  torch ogohlantirdi: UserWarning
  oraliq tensorda grad kerak bo'lsa: c.retain_grad()

=== 4. .grad YIG'ILADI ===
   chaqiruv     w.grad
          1        2.0
          2        4.0
          3        6.0
  har backward() gradientni QO'SHADI
  grad = None dan keyin: 2.0

=== 5. Vektordan backward ===
  RuntimeError: grad can be implicitly created only for scalar outpu
  backward(ones) bilan: [2.0, 4.0, 6.0]  (= 2*v)

=== 6. no_grad va detach ===
  holat                    requires_grad        grad_fn
  oddiy                             True   MulBackward0
  no_grad ichida                   False           None
  detach() dan keyin               False           None

=== 7. Dinamik graf ===
    n     f(2,n)      df/dx   kutilgan
    1        4.0        4.0        4.0
    2        8.0       12.0       12.0
    3       16.0       32.0       32.0
    4       32.0       80.0       80.0
  ⭐ Graf har chaqiruvda QAYTA quriladi - Python bemalol

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

Misol 3 — Qo'lda yozilgan backprop va autograd

python
"""20.6-darsdagi formulalar autograd bilan bir xilmi (real torch)."""

import numpy as np
import torch


def softmax_np(Z):
    Z = Z - Z.max(axis=1, keepdims=True)
    E = np.exp(Z)
    return E / E.sum(axis=1, keepdims=True)


def main() -> None:
    rng = np.random.default_rng(0)
    N, d0, d1, K = 16, 8, 12, 4
    X_np = rng.normal(0, 1, (N, d0))
    y_np = rng.integers(0, K, N)
    W1_np = rng.normal(0, np.sqrt(2 / d0), (d0, d1))
    b1_np = np.zeros(d1)
    W2_np = rng.normal(0, np.sqrt(2 / d1), (d1, K))
    b2_np = np.zeros(K)

    print("=== 1. numpy: qo'lda backprop ===")
    Z1 = X_np @ W1_np + b1_np
    A1 = np.maximum(0, Z1)
    Z2 = A1 @ W2_np + b2_np
    P = softmax_np(Z2)
    loss_np = -np.mean(np.log(P[np.arange(N), y_np]))
    Y = np.zeros((N, K))
    Y[np.arange(N), y_np] = 1.0
    dZ2 = (P - Y) / N
    dW2_np = A1.T @ dZ2
    db2_np = dZ2.sum(axis=0)
    dZ1 = (dZ2 @ W2_np.T) * (Z1 > 0)
    dW1_np = X_np.T @ dZ1
    db1_np = dZ1.sum(axis=0)
    print(f"  loss: {loss_np:.10f}")
    print(f"  {'gradient':<8} {'shakl':>10} {'norma':>14}")
    for nom, g in [("dW1", dW1_np), ("db1", db1_np),
                   ("dW2", dW2_np), ("db2", db2_np)]:
        print(f"  {nom:<8} {str(g.shape):>10} "
              f"{np.linalg.norm(g):>14.10f}")

    print("\n=== 2. torch: autograd ===")
    X = torch.tensor(X_np, dtype=torch.float64)
    y = torch.tensor(y_np, dtype=torch.int64)
    W1 = torch.tensor(W1_np, dtype=torch.float64, requires_grad=True)
    b1 = torch.tensor(b1_np, dtype=torch.float64, requires_grad=True)
    W2 = torch.tensor(W2_np, dtype=torch.float64, requires_grad=True)
    b2 = torch.tensor(b2_np, dtype=torch.float64, requires_grad=True)
    z1 = X @ W1 + b1
    a1 = torch.relu(z1)
    z2 = a1 @ W2 + b2
    loss = torch.nn.functional.cross_entropy(z2, y)
    loss.backward()
    print(f"  loss: {loss.item():.10f}")
    print(f"  {'gradient':<8} {'shakl':>10} {'norma':>14}")
    for nom, t in [("dW1", W1.grad), ("db1", b1.grad),
                   ("dW2", W2.grad), ("db2", b2.grad)]:
        print(f"  {nom:<8} {str(tuple(t.shape)):>10} "
              f"{t.norm().item():>14.10f}")

    print("\n=== 3. Taqqoslash ===")
    juftlar = [("loss", np.array(loss_np), np.array(loss.item())),
               ("dW1", dW1_np, W1.grad.numpy()),
               ("db1", db1_np, b1.grad.numpy()),
               ("dW2", dW2_np, W2.grad.numpy()),
               ("db2", db2_np, b2.grad.numpy())]
    print(f"  {'nom':<8} {'maksimal farq':>16} {'bir xilmi':>11}")
    for nom, a, b in juftlar:
        farq = float(np.abs(a - b).max())
        print(f"  {nom:<8} {farq:>16.3e} "
              f"{str(bool(np.allclose(a, b, atol=1e-12))):>11}")
    print("  ⭐ Qo'lda yozganimiz autograd bilan AYNAN bir xil")

    print("\n=== 4. float32 da aniqlik ===")
    W1f = torch.tensor(W1_np, dtype=torch.float32, requires_grad=True)
    b1f = torch.tensor(b1_np, dtype=torch.float32, requires_grad=True)
    W2f = torch.tensor(W2_np, dtype=torch.float32, requires_grad=True)
    b2f = torch.tensor(b2_np, dtype=torch.float32, requires_grad=True)
    Xf = torch.tensor(X_np, dtype=torch.float32)
    z2f = torch.relu(Xf @ W1f + b1f) @ W2f + b2f
    torch.nn.functional.cross_entropy(z2f, y).backward()
    print(f"  {'nom':<8} {'float64 farqi':>16} {'float32 farqi':>16}")
    for nom, a, b64, b32 in [
            ("dW1", dW1_np, W1.grad.numpy(), W1f.grad.numpy()),
            ("dW2", dW2_np, W2.grad.numpy(), W2f.grad.numpy())]:
        print(f"  {nom:<8} {np.abs(a - b64).max():>16.3e} "
              f"{np.abs(a - b32).max():>16.3e}")
    print("  float32 da farq ~1e-7 - bu NORMAL")

    print("\n=== 5. Sonli gradient bilan ham tekshiramiz ===")
    h = 1e-6
    print(f"  {'parametr':<12} {'qo_lda':>13} {'autograd':>13} "
          f"{'sonli':>13}")
    for i, j in [(0, 0), (3, 5), (7, 11)]:
        asl = W1_np[i, j]
        W1_np[i, j] = asl + h
        l1 = -np.mean(np.log(softmax_np(
            np.maximum(0, X_np @ W1_np + b1_np) @ W2_np
            + b2_np)[np.arange(N), y_np]))
        W1_np[i, j] = asl - h
        l2 = -np.mean(np.log(softmax_np(
            np.maximum(0, X_np @ W1_np + b1_np) @ W2_np
            + b2_np)[np.arange(N), y_np]))
        W1_np[i, j] = asl
        print(f"  {f'W1[{i},{j}]':<12} {dW1_np[i, j]:>13.9f} "
              f"{W1.grad.numpy()[i, j]:>13.9f} "
              f"{(l1 - l2) / (2 * h):>13.9f}")
    print("  uchala usul ham bir xil javob beradi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. numpy: qo'lda backprop ===
  loss: 2.3044637610
  gradient      shakl          norma
  dW1         (8, 12)   0.9779808180
  db1           (12,)   0.5037569081
  dW2         (12, 4)   1.2545012438
  db2            (4,)   0.4210806805

=== 2. torch: autograd ===
  loss: 2.3044637610
  gradient      shakl          norma
  dW1         (8, 12)   0.9779808180
  db1           (12,)   0.5037569081
  dW2         (12, 4)   1.2545012438
  db2            (4,)   0.4210806805

=== 3. Taqqoslash ===
  nom         maksimal farq   bir xilmi
  loss            4.441e-16        True
  dW1             2.776e-17        True
  db1             5.551e-17        True
  dW2             8.327e-17        True
  db2             0.000e+00        True
  ⭐ Qo'lda yozganimiz autograd bilan AYNAN bir xil

=== 4. float32 da aniqlik ===
  nom         float64 farqi    float32 farqi
  dW1             2.776e-17        3.075e-08
  dW2             8.327e-17        3.093e-08
  float32 da farq ~1e-7 - bu NORMAL

=== 5. Sonli gradient bilan ham tekshiramiz ===
  parametr            qo_lda      autograd         sonli
  W1[0,0]       -0.138830884  -0.138830884  -0.138830884
  W1[3,5]       -0.093769873  -0.093769873  -0.093769874
  W1[7,11]       0.068387340   0.068387340   0.068387340
  uchala usul ham bir xil javob beradi

Nima ko'rsatdi: 2.6-bo'lim.

Misol 4 — torch bilan to'liq o'rgatish

python
"""Tensorlar va autograd bilan tarmoqni o'rgatish (real torch)."""

import numpy as np
import torch
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler


def main() -> None:
    torch.manual_seed(0)
    X, y = make_classification(n_samples=4000, n_features=20,
                               n_informative=10, n_redundant=4,
                               n_classes=4, flip_y=0.08, class_sep=1.1,
                               random_state=0)
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3,
                                          random_state=0, stratify=y)
    sc = StandardScaler().fit(Xtr)
    Xtr_t = torch.tensor(sc.transform(Xtr), dtype=torch.float32)
    Xte_t = torch.tensor(sc.transform(Xte), dtype=torch.float32)
    ytr_t = torch.tensor(ytr, dtype=torch.int64)
    yte_t = torch.tensor(yte, dtype=torch.int64)

    print("=== 1. Parametrlarni qo'lda yaratish ===")
    olchamlar = [20, 64, 32, 4]
    params = []
    for a, b in zip(olchamlar[:-1], olchamlar[1:]):
        W = (torch.randn(a, b) * np.sqrt(2.0 / a)).requires_grad_(True)
        bb = torch.zeros(b, requires_grad=True)
        params += [W, bb]
    print(f"  arxitektura: {' -> '.join(map(str, olchamlar))}")
    print(f"  {'parametr':<8} {'shakl':>12} {'requires_grad':>15}")
    for i, p in enumerate(params):
        nom = f"{'W' if i % 2 == 0 else 'b'}{i // 2 + 1}"
        print(f"  {nom:<8} {str(tuple(p.shape)):>12} "
              f"{str(p.requires_grad):>15}")

    def oldinga(X):
        A = X
        for i in range(0, len(params), 2):
            Z = A @ params[i] + params[i + 1]
            A = torch.relu(Z) if i < len(params) - 2 else Z
        return A

    def aniqlik(X, y):
        with torch.no_grad():
            return (oldinga(X).argmax(1) == y).float().mean().item()

    print("\n=== 2. Qo'lda SGD ===")
    print(f"  {'davr':>6} {'o_quv loss':>12} {'test loss':>11} "
          f"{'test aniqlik':>13}")
    lr = 0.3
    for davr in range(1, 401):
        loss = torch.nn.functional.cross_entropy(oldinga(Xtr_t), ytr_t)
        for p in params:
            p.grad = None
        loss.backward()
        with torch.no_grad():
            for p in params:
                p -= lr * p.grad
        if davr in (1, 25, 100, 200, 400):
            with torch.no_grad():
                te = torch.nn.functional.cross_entropy(oldinga(Xte_t),
                                                       yte_t)
            print(f"  {davr:>6} {loss.item():>12.4f} {te.item():>11.4f} "
                  f"{aniqlik(Xte_t, yte_t):>13.4f}")

    print("\n=== 3. torch.optim bilan mini-batch ===")
    torch.manual_seed(0)
    params2 = []
    for a, b in zip(olchamlar[:-1], olchamlar[1:]):
        params2 += [(torch.randn(a, b) * np.sqrt(2.0 / a))
                    .requires_grad_(True),
                    torch.zeros(b, requires_grad=True)]

    def oldinga2(X):
        A = X
        for i in range(0, len(params2), 2):
            Z = A @ params2[i] + params2[i + 1]
            A = torch.relu(Z) if i < len(params2) - 2 else Z
        return A

    opt = torch.optim.Adam(params2, lr=0.01)
    g = torch.Generator().manual_seed(0)
    print(f"  {'davr':>6} {'o_quv loss':>12} {'test aniqlik':>13}")
    for davr in range(1, 31):
        tartib = torch.randperm(len(ytr_t), generator=g)
        for boshi in range(0, len(ytr_t), 128):
            idx = tartib[boshi:boshi + 128]
            opt.zero_grad()
            torch.nn.functional.cross_entropy(
                oldinga2(Xtr_t[idx]), ytr_t[idx]).backward()
            opt.step()
        if davr in (1, 5, 10, 20, 30):
            with torch.no_grad():
                l = torch.nn.functional.cross_entropy(oldinga2(Xtr_t),
                                                      ytr_t).item()
                a = (oldinga2(Xte_t).argmax(1)
                     == yte_t).float().mean().item()
            print(f"  {davr:>6} {l:>12.4f} {a:>13.4f}")

    print("\n=== 4. no_grad ning ta'siri ===")
    with torch.no_grad():
        chiqish_yoq = oldinga2(Xte_t)
    chiqish_bor = oldinga2(Xte_t)
    print(f"  {'holat':<20} {'requires_grad':>15} {'grad_fn':>14}")
    for nom, t in [("no_grad ichida", chiqish_yoq),
                   ("no_grad siz", chiqish_bor)]:
        fn = type(t.grad_fn).__name__ if t.grad_fn else "None"
        print(f"  {nom:<20} {str(t.requires_grad):>15} {fn:>14}")
    print("  natija bir xilmi: "
          f"{torch.allclose(chiqish_yoq, chiqish_bor.detach())}")

    print("\n=== 5. Qatlamlar statistikasi ===")
    with torch.no_grad():
        A = Xte_t
        print(f"  {'bosqich':<12} {'shakl':>12} {'std':>9} {'nol %':>8}")
        print(f"  {'kirish':<12} {str(tuple(A.shape)):>12} "
              f"{A.std().item():>9.4f} "
              f"{(A == 0).float().mean().item():>8.1%}")
        for i in range(0, len(params2), 2):
            Z = A @ params2[i] + params2[i + 1]
            A = torch.relu(Z) if i < len(params2) - 2 else Z
            print(f"  {f'qatlam {i // 2 + 1}':<12} "
                  f"{str(tuple(A.shape)):>12} {A.std().item():>9.4f} "
                  f"{(A == 0).float().mean().item():>8.1%}")
    print("  ⭐ 15 qator kod - butun o'rgatish sikli")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Parametrlarni qo'lda yaratish ===
  arxitektura: 20 -> 64 -> 32 -> 4
  parametr        shakl   requires_grad
  W1           (20, 64)            True
  b1              (64,)            True
  W2           (64, 32)            True
  b2              (32,)            True
  W3            (32, 4)            True
  b3               (4,)            True

=== 2. Qo'lda SGD ===
    davr   o_quv loss   test loss  test aniqlik
       1       1.7904      1.5287        0.3075
      25       0.9689      1.0212        0.5958
     100       0.6675      0.8398        0.7100
     200       0.5108      0.8031        0.7425
     400       0.3714      0.8425        0.7542

=== 3. torch.optim bilan mini-batch ===
    davr   o_quv loss  test aniqlik
       1       0.8684        0.6458
       5       0.4918        0.7600
      10       0.3866        0.7742
      20       0.2037        0.7600
      30       0.1044        0.7533

=== 4. no_grad ning ta'siri ===
  holat                  requires_grad        grad_fn
  no_grad ichida                 False           None
  no_grad siz                     True   AddBackward0
  natija bir xilmi: True

=== 5. Qatlamlar statistikasi ===
  bosqich             shakl       std    nol %
  kirish         (1200, 20)    0.9846     0.0%
  qatlam 1       (1200, 64)    1.2708    53.2%
  qatlam 2       (1200, 32)    2.2142    62.4%
  qatlam 3        (1200, 4)    5.6926     0.0%
  ⭐ 15 qator kod - butun o'rgatish sikli

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


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

Noto'g'ri fikr To'g'risi
"autograd sehrli" Aynan 20.6-darsdagi zanjir qoidasi
".grad almashadi" Yig'iladi — tozalash kerak
"backward() ni qayta chaqirsa bo'ladi" Graf o'chadi, retain_graph kerak
"Tensor = numpy massivi" Device va grad tarixi qo'shilgan
"float32 va float64 farqi yo'q" Gradientda ~1e-7 farq
"no_grad ixtiyoriy" Inference da xotira uchun zarur
"view va reshape bir xil" view uzluksizlik talab qiladi
"Graf oldindan quriladi" Har forward da qaytadan

6. Keng tarqalgan xatolar va yechimlari

1. zero_grad ni unutish

python
loss.backward(); opt.step()                   # ⚠️ gradient yig'iladi
opt.zero_grad(); loss.backward(); opt.step()  # ✅

2. no_grad siz yangilash

python
w -= lr * w.grad                              # ⚠️ grafga qo'shiladi
with torch.no_grad(): w -= lr * w.grad        # ✅

3. detach siz numpy

python
t.numpy()                                     # ⚠️ RuntimeError
t.detach().numpy()                            # ✅

4. dtype aralashmasi

python
torch.from_numpy(X) @ W                       # ⚠️ float64 @ float32
torch.tensor(X, dtype=torch.float32) @ W      # ✅

5. Vektordan backward

python
u.backward()          # u vektor                # ⚠️
u.sum().backward()                              # ✅

6. Loss ni ro'yxatga yig'ish

python
tarix.append(loss)                            # ⚠️ butun graf saqlanadi
tarix.append(loss.item())                     # ✅

7. view ni transpozitsiyadan keyin

python
t.T.view(-1)                                  # ⚠️ RuntimeError
t.T.reshape(-1)                               # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 20.4, 20.6-darslar (o'tilgan): Oldinga va orqaga o'tish
  • 20.10-dars: Regularizatsiya
  • 21-qism: nn.Module, DataLoader, GPU
  • 23-qism va keyingilari: Barcha DL modellari

8. Eng yaxshi amaliyotlar

  1. zero_grad ni sikl boshida qiling.

  2. Yangilashni no_grad ichida.

  3. Metrikalarni .item() bilan oling.

  4. dtype ni aniq belgilang.

  5. Inference da no_grad.

  6. Shubhada reshape.

  7. manual_seed qo'ying.

  8. Gradientni sonli tekshiring.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # tensor numpy dan nimasi bilan farq qiladi?
2.  # torch sukut dtype?
3.  # requires_grad nima qiladi?
4.  # .grad yig'iladimi?
5.  # backward() nimadan chaqiriladi?
6.  # grad_fn nima?
7.  # barg tensor nima?
8.  # no_grad nima beradi?
9.  # detach nima qiladi?
10. # view va reshape farqi?
11. # graf qachon quriladi?
12. # numpy ga qanday o'tiladi?
Javoblar
  1. Device va grad tarixi
  2. float32
  3. Amallar grafga yoziladi
  4. Ha
  5. Skalyardan
  6. Gradient funksiyasi
  7. Foydalanuvchi yaratgan
  8. Graf qurilmaydi
  9. Grafdan uzadi
  10. view uzluksizlik talab qiladi
  11. Har forward da
  12. .detach().numpy()

Vazifa 2: Xatolarni tuzating

python
1.  loss.backward(); opt.step()

2.  w -= lr * w.grad

3.  t.numpy()      # requires_grad=True

4.  u.backward()   # u vektor

5.  tarix.append(loss)
Javoblar
python
1.  opt.zero_grad(); loss.backward(); opt.step()

2.  with torch.no_grad(): w -= lr * w.grad

3.  t.detach().numpy()

4.  u.sum().backward()

5.  tarix.append(loss.item())

Vazifa 3: Tensorlar

Modellang:

  1. Yaratish
  2. Turlar
  3. Xotira
  4. Shakllar

Vazifa 4: autograd

Modellang:

  1. Oddiy
  2. Graf
  3. Yig'ilish
  4. Dinamik

Vazifa 5: Taqqoslash

Modellang:

  1. numpy
  2. torch
  3. Farq
  4. Sonli

Vazifa 6: O'rgatish

Modellang:

  1. Parametrlar
  2. Qo'lda SGD
  3. Adam
  4. no_grad

Vazifa 7: O'ylash

O'rgatish sikli ishlayapti, lekin xotira sarfi har davr bilan o'sib boradi va 50-davrda jarayon o'ladi. Sabab nima?

Javob

Eng ehtimolli sabab: graf bilan bog'langan tensorlar saqlanib qolyapti.

Klassik holat:

python
tarix = []
for davr in range(davrlar):
    loss = kriteriy(model(X), y)
    tarix.append(loss)              # ⚠️ TENSOR saqlanmoqda

loss — oddiy son emas, u butun hisob grafiga ulangan tensor. Uni ro'yxatga qo'shish o'sha davrning barcha oraliq aktivatsiyalarini xotirada tirik qoldiradi. 50 davrdan keyin xotirada 50 ta to'liq graf yotadi.

Yechim:

python
tarix.append(loss.item())           # ✅ oddiy float
# yoki
tarix.append(loss.detach())         # ✅ grafdan uzilgan

Boshqa sabablar:

Sabab Kod Yechim
Metrikani tensorda yig'ish jami += loss jami += loss.item()
Bashoratlarni saqlash natijalar.append(chiqish) .detach() yoki .cpu()
no_grad siz baholash model(X_val) with torch.no_grad():
retain_graph=True odat bo'lib qolgan Har backward da Faqat kerak bo'lganda
Yashirin holatni uzmaslik (RNN) h davrdan davrga h = h.detach()

Tashxis:

python
import gc
import torch

def tensor_soni():
    n = 0
    for obj in gc.get_objects():
        try:
            if torch.is_tensor(obj):
                n += 1
        except Exception:
            pass
    return n

for davr in range(davrlar):
    ...
    if davr % 5 == 0:
        print(f"davr {davr}: {tensor_soni()} ta tensor")

Bu son o'sib borsa — sizish tasdiqlandi.

GPU da qo'shimcha:

python
print(torch.cuda.memory_allocated() / 1e9, "GB")
print(torch.cuda.max_memory_allocated() / 1e9, "GB")

Profilaktika — uchta qoida:

  1. Sonni saqlayotgan bo'lsangiz .item() qo'ying
  2. Tensorni saqlayotgan bo'lsangiz .detach() qo'ying
  3. Baholashni har doim with torch.no_grad(): ichida qiling

Bu uchtasi xotira sizishining deyarli barcha holatini yopadi.

Nimani mustahkamlaydi: 2.4-bo'lim.


Xulosa

Bu darsda torch tensorlari va autograd ni ko'rdik.

Eng muhim uch fikr:

  1. Tensor — numpy massivi ustiga ikki qobiliyat. U qurilmani (cpu/cuda) biladi va requires_grad=True bo'lganda o'ziga qo'llangan har amalni grafga yozadi. Sukut turi float32 (numpy da float64) — bu farq gradientlarni taqqoslaganda ~1e-7 ko'rinishida chiqadi va bu normal.

  2. autograd sehr emas. 3-misolda numpy da qo'lda yozilgan dW1, db1, dW2, db2 va float64 dagi autograd natijalari aynan mos keldi, sonli hosila ham o'sha javobni berdi. autograd — avtomatlashtirilgan 20.6-dars; shuning uchun o'sha darsni o'tganimiz bejiz emas.

  3. .grad yig'iladi, graf esa dinamik. Har backward() gradientni qo'shadi, shuning uchun har qadam boshida zero_grad() (yoki p.grad = None) majburiy. Graf esa har forward da qaytadan quriladi — shu sababli if, for, while bemalol ishlatiladi va hisob no_grad bilan istalgan joyda to'xtatiladi.

Keyingi darsda regularizatsiya: dropout, weight decay va erta to'xtash; ular nimani hal qiladi, qachon qaysi biri kerak va ularni bir vaqtda ishlatish qanday natija beradi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!