5-bo‘lim

Oldinga tarqalish

Kirishdan chiqishgacha bo'lgan to'liq yo'l - qatlamlar, shakllar va oraliq qiymatlarni saqlash.

🕑 14 daqiqa o‘qish 📄 758 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Ikki qatlamli tarmoq
  2. Bir qadam - bir qatlam
  3. To'liq funksiya
  4. Shakllar jadvali
  5. Ixtiyoriy chuqurlikdagi tarmoq
  6. Nol bilan boshlash - nima uchun ishlamaydi
  7. Sonli barqarorlik
  8. Xulosa

Oldinga tarqalish (forward propagation) - kirish ma'lumotini qatlamlar orqali o'tkazib, chiqish olish. Bu tarmoqning "javob berish" rejimi.

Ikki qatlamli tarmoq #

Ikki qatlamli tarmoq: 3 → 4 → 2 kirish yashirin (ReLU) chiqish (sigmoid) W1: (3, 4) b1: (4,) W2: (4, 2) b2: (2,) Og'irliklar matritsasining shakli: (oldingi qatlam, keyingi qatlam)
Har og'irlik matritsasi ikki qatlamni bog'laydi
Python
import numpy as np


def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))


def relu(z):
    return np.maximum(0.0, z)


def parametrlar_yasash(olchamlar, urug=42):
    gen = np.random.default_rng(urug)
    p = {}
    for i in range(len(olchamlar) - 1):
        p[f"W{i+1}"] = gen.normal(0, 0.5, (olchamlar[i], olchamlar[i+1]))
        p[f"b{i+1}"] = np.zeros(olchamlar[i+1])
    return p


def oldinga(X, p):
    Z1 = X @ p["W1"] + p["b1"]
    A1 = relu(Z1)
    Z2 = A1 @ p["W2"] + p["b2"]
    A2 = sigmoid(Z2)
    kesh = {"X": X, "Z1": Z1, "A1": A1, "Z2": Z2, "A2": A2}
    return A2, kesh
Python
p = parametrlar_yasash([3, 4, 2])
for nom, qiymat in p.items():
    print(f"{nom}: {qiymat.shape}")
Natija
W1: (3, 4)
b1: (4,)
W2: (4, 2)
b2: (2,)

Bir qadam - bir qatlam #

Python
p = parametrlar_yasash([3, 4, 2])
X = np.array([[1.0, 0.5, -1.0],
              [0.0, 2.0, 1.0]])

Z1 = X @ p["W1"] + p["b1"]
A1 = relu(Z1)

print("X :", X.shape)
print("Z1:", Z1.shape)
print("A1:", A1.shape)
print(np.round(A1, 4))
Natija
X : (2, 3)
Z1: (2, 4)
A1: (2, 4)
[[0.     0.     0.     0.0023]
 [0.     0.     0.5675 0.0727]]

A1 da ko'p nol bor - ReLU manfiy qiymatlarni kesib tashladi.

Python
p = parametrlar_yasash([3, 4, 2])
X = np.array([[1.0, 0.5, -1.0], [0.0, 2.0, 1.0]])

Z1 = X @ p["W1"] + p["b1"]
A1 = relu(Z1)
Z2 = A1 @ p["W2"] + p["b2"]
A2 = sigmoid(Z2)

print("chiqish:", np.round(A2, 4))
print("shakl:", A2.shape)
print("har qatordagi qiymatlar 0 va 1 orasidami:",
      bool(((A2 > 0) & (A2 < 1)).all()))
Natija
chiqish: [[0.5003 0.5   ]
 [0.5341 0.4319]]
shakl: (2, 2)
har qatordagi qiymatlar 0 va 1 orasidami: True

To'liq funksiya #

Python
def oldinga(X, p):
    Z1 = X @ p["W1"] + p["b1"]
    A1 = relu(Z1)
    Z2 = A1 @ p["W2"] + p["b2"]
    A2 = sigmoid(Z2)
    kesh = {"X": X, "Z1": Z1, "A1": A1, "Z2": Z2, "A2": A2}
    return A2, kesh


p = parametrlar_yasash([3, 4, 2])
X = np.array([[1.0, 0.5, -1.0], [0.0, 2.0, 1.0]])
chiqish, kesh = oldinga(X, p)

print("chiqish:", np.round(chiqish, 4))
print("keshda saqlanganlar:", sorted(kesh.keys()))
Natija
chiqish: [[0.5003 0.5   ]
 [0.5341 0.4319]]
keshda saqlanganlar: ['A1', 'A2', 'X', 'Z1', 'Z2']
Kesh - orqaga tarqalishning sharti

oldinga funksiyasi faqat javobni emas, barcha oraliq qiymatlarni ham qaytaradi. Bu ortiqcha ish emas - u 9-11 bo'limlar uchun majburiy.

Sabab: gradientni hisoblash uchun har qatlamda nima bo'lganini bilish kerak:

Kerakli qiymatNima uchun
A1W2 gradienti A1 ga bog'liq
Z1ReLU hosilasi Z1 ning ishorasiga bog'liq
XW1 gradienti X ga bog'liq
A2Yo'qotish hosilasi shundan boshlanadi

Ularni saqlamasangiz, orqaga tarqalishda hammasini qayta hisoblash kerak bo'ladi - ya'ni ish ikki barobar ortadi.

PyTorch da bu avtomatik: har tenzor o'zining "qanday paydo bo'lgani" ni eslab qoladi. Bu hisoblash grafi deb ataladi va aynan shu keshning umumlashtirilgan shakli.

Xotira narxi ham shundan: chuqur tarmoqni o'qitish xotirasining katta qismi - saqlangan oraliq qiymatlar.

Shakllar jadvali #

Python
def shakllarni_korsatish(olchamlar, namunalar_soni):
    p = parametrlar_yasash(olchamlar)
    X = np.zeros((namunalar_soni, olchamlar[0]))
    joriy = X
    sarlavha = ["qatlam", "kirish", "og'irliklar", "chiqish"]
    print(f"{sarlavha[0]:<12}{sarlavha[1]:>12}{sarlavha[2]:>14}{sarlavha[3]:>12}")
    for i in range(len(olchamlar) - 1):
        W = p[f"W{i+1}"]
        keyingi = joriy @ W + p[f"b{i+1}"]
        print(f"{i+1:<12}{str(joriy.shape):>12}{str(W.shape):>14}{str(keyingi.shape):>12}")
        joriy = keyingi


shakllarni_korsatish([3, 4, 2], 5)
Natija
qatlam            kirish   og'irliklar     chiqish
1                 (5, 3)        (3, 4)      (5, 4)
2                 (5, 4)        (4, 2)      (5, 2)
QoidaIzoh
W shakli(oldingi_olcham, keyingi_olcham)
b shakli(keyingi_olcham,)
Namunalar soniHech qayerda qatnashmaydi
Namunalar soni og'irliklarga ta'sir qilmaydi

Bu muhim va ko'pincha e'tibordan chetda qoladigan fakt: tarmoqni bitta namuna bilan ham, ming namuna bilan ham bir xil og'irliklar bilan ishlatish mumkin.

Python
oldinga(np.zeros((1, 3)), p)      # bitta namuna
oldinga(np.zeros((1000, 3)), p)   # ming namuna

Ikkalasi ham ishlaydi - chunki namunalar @ amalining birinchi o'lchamida yotadi va u shunchaki takrorlanadi.

Bu 14-bo'limdagi mini-paketli o'qitishning asosi: paket hajmini xohlagancha o'zgartirsangiz ham, model o'zgarmaydi.

Shu sababli "paket hajmi" - modelning emas, o'qitishning parametri.

Python
p = parametrlar_yasash([3, 4, 2])
bitta, _ = oldinga(np.array([[1.0, 0.5, -1.0]]), p)
kop, _ = oldinga(np.array([[1.0, 0.5, -1.0]] * 100), p)

print("bitta namuna:", bitta.shape, np.round(bitta, 4))
print("100 namuna: ", kop.shape)
print("birinchi qator bir xilmi:", np.allclose(bitta[0], kop[0]))
Natija
bitta namuna: (1, 2) [[0.5003 0.5   ]]
100 namuna:  (100, 2)
birinchi qator bir xilmi: True

Ixtiyoriy chuqurlikdagi tarmoq #

Python
def oldinga_chuqur(X, p, qatlamlar_soni):
    kesh = {"A0": X}
    A = X
    for i in range(1, qatlamlar_soni + 1):
        Z = A @ p[f"W{i}"] + p[f"b{i}"]
        A = sigmoid(Z) if i == qatlamlar_soni else relu(Z)
        kesh[f"Z{i}"] = Z
        kesh[f"A{i}"] = A
    return A, kesh


olchamlar = [4, 8, 6, 3, 1]
p = parametrlar_yasash(olchamlar)
X = np.zeros((7, 4))
chiqish, kesh = oldinga_chuqur(X, p, len(olchamlar) - 1)

print("chuqurlik:", len(olchamlar) - 1, "qatlam")
print("chiqish shakli:", chiqish.shape)
print("keshdagi kalitlar soni:", len(kesh))
Natija
chuqurlik: 4 qatlam
chiqish shakli: (7, 1)
keshdagi kalitlar soni: 9
Nima uchun oxirgi qatlam boshqacha

Yashirin qatlamlarda ReLU, chiqishda esa sigmoid ishlatildi. Bu tasodifiy emas:

Vazifa turiChiqish faollashtirishiNeyronlar soni
Ikkilik tasniflashsigmoid1
Ko'p sinfli tasniflashsoftmax (18-bo'lim)sinflar soni
Regressiya (son bashorati)hech qanday1
Ko'p yorliqli tasniflashsigmoid (har biriga)yorliqlar soni

Uchinchi qator muhim: uy narxini bashorat qilayotgan tarmoq chiqishida sigmoid bo'lmasligi kerak - aks holda javob 0 va 1 orasida qamalib qoladi.

Bu klassik xato: "hamma joyda faollashtirish qo'yaman" degan odat regressiya modelini butunlay ishlamaydigan qiladi.

Nol bilan boshlash - nima uchun ishlamaydi #

Python
X = np.array([[1.0, 0.5, -1.0], [0.0, 2.0, 1.0]])

nol_p = {
    "W1": np.zeros((3, 4)),
    "b1": np.zeros(4),
    "W2": np.zeros((4, 2)),
    "b2": np.zeros(2),
}
chiqish, kesh = oldinga(X, nol_p)

print("yashirin qatlam:")
print(kesh["A1"])
print("barcha neyronlar bir xilmi:",
      bool(np.allclose(kesh["A1"], kesh["A1"][:, :1])))
print("chiqish:", np.round(chiqish, 4))
Natija
yashirin qatlam:
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]]
barcha neyronlar bir xilmi: True
chiqish: [[0.5 0.5]
 [0.5 0.5]]
Simmetriya muammosi

Barcha og'irliklar nol (yoki bir xil son) bo'lsa, qatlamdagi har neyron aynan bir xil qiymat hisoblaydi.

Va bu o'qitishda ham davom etadi: bir xil chiqish → bir xil gradient → bir xil yangilanish. Neyronlar hech qachon farqlanmaydi.

Natijada to'rt neyronli qatlam bitta neyron kabi ishlaydi - tarmoqning butun kengligi behuda.

Shuning uchun og'irliklar tasodifiy boshlanadi. Lekin qanchalik tasodifiy? Juda kichik bo'lsa - signal so'nadi, juda katta bo'lsa - to'yinadi.

Bu savolga 13-bo'lim javob beradi: Xavier va He boshlash usullari.

Diqqat: siljishlarni nol qilib boshlash esa to'g'ri - ular simmetriyani buzmaydi, chunki og'irliklar allaqachon har xil.

Sonli barqarorlik #

Python
X = np.array([[100.0, 100.0, 100.0]])
katta_p = {
    "W1": np.full((3, 4), 10.0),
    "b1": np.zeros(4),
    "W2": np.full((4, 2), 10.0),
    "b2": np.zeros(2),
}
chiqish, kesh = oldinga(X, katta_p)

print("Z1 birinchi qiymat:", kesh["Z1"][0, 0])
print("Z2 birinchi qiymat:", kesh["Z2"][0, 0])
print("chiqish:", chiqish[0])
print("nan yoki inf bormi:", bool(~np.isfinite(chiqish).all()))
Natija
Z1 birinchi qiymat: 3000.0
Z2 birinchi qiymat: 120000.0
chiqish: [1. 1.]
nan yoki inf bormi: False

Chiqish aynan 1.0 bo'lib to'yindi. Formal xato yo'q, lekin tarmoq bu holatda o'qiy olmaydi: sigmoid hosilasi 1 · (1 - 1) = 0.

Python
def sigmoid_hosila_qiymati(z):
    s = 1.0 / (1.0 + np.exp(-z))
    return s * (1.0 - s)

print("hosila z=120000 da:", sigmoid_hosila_qiymati(120000.0))
print("hosila z=0 da:     ", sigmoid_hosila_qiymati(0.0))
Natija
hosila z=120000 da: 0.0
hosila z=0 da:      0.25

Gradient aniq nol - og'irliklar hech qachon yangilanmaydi. 17-bo'limdagi normalizatsiya aynan shuning oldini oladi.

Amaliy topshiriq
  1. [3, 4, 2] tarmoq uchun parametrlar yarating va shakllarni chop eting.
  2. Bitta qatlamni qo'lda hisoblang: Z1, keyin A1.
  3. ReLU dan keyin nechta nol borligini sanang.
  4. To'liq oldinga funksiyasini yozing va keshni qaytaring.
  5. Keshda nima uchun Z1 ham kerakligini tushuntiring.
  6. Bitta va 100 namuna uchun natijani solishtiring.
  7. To'rt qatlamli tarmoq quring va chiqish shaklini tekshiring.
  8. Regressiya uchun chiqishda nima bo'lishi kerakligini ayting.
  9. Barcha og'irliklarni nol qilib, simmetriya muammosini ko'rsating.
  10. Juda katta kirish bering va sigmoid hosilasini hisoblang.

Xulosa #

  • Oldinga tarqalish: Z = A @ W + b, keyin A = f(Z) - qatlamma-qatlam.
  • W shakli - (oldingi, keyingi), b shakli - (keyingi,).
  • Namunalar soni og'irliklarga umuman ta'sir qilmaydi.
  • Oraliq qiymatlarni keshda saqlang - orqaga tarqalish uchun shart.
  • Kesh = PyTorch dagi hisoblash grafining oddiy shakli.
  • Yashirin qatlamlarda ReLU, chiqishda vazifaga mos funksiya.
  • Regressiyada chiqishda faollashtirish bo'lmasligi kerak.
  • Nol boshlash simmetriyani buzmaydi - neyronlar bir xil qoladi.
  • Siljishlarni nol qilish esa to'g'ri.
  • To'yingan sigmoid gradienti nol - tarmoq o'qishni to'xtatadi.

Keyingi bo'limda yo'qotish funksiyasini ko'ramiz: tarmoq qanchalik xato qilayotganini son bilan o'lchaymiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.