5-bo‘lim
Oldinga tarqalish
Kirishdan chiqishgacha bo'lgan to'liq yo'l - qatlamlar, shakllar va oraliq qiymatlarni saqlash.
Ushbu bo‘lim mundarijasi
Oldinga tarqalish (forward propagation) - kirish ma'lumotini qatlamlar orqali o'tkazib, chiqish olish. Bu tarmoqning "javob berish" rejimi.
Ikki qatlamli tarmoq #
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
p = parametrlar_yasash([3, 4, 2])
for nom, qiymat in p.items():
print(f"{nom}: {qiymat.shape}")
W1: (3, 4)
b1: (4,)
W2: (4, 2)
b2: (2,)
Bir qadam - bir qatlam #
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))
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.
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()))
chiqish: [[0.5003 0.5 ]
[0.5341 0.4319]]
shakl: (2, 2)
har qatordagi qiymatlar 0 va 1 orasidami: True
To'liq funksiya #
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()))
chiqish: [[0.5003 0.5 ]
[0.5341 0.4319]]
keshda saqlanganlar: ['A1', 'A2', 'X', 'Z1', 'Z2']
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 qiymat | Nima uchun |
|---|---|
A1 | W2 gradienti A1 ga bog'liq |
Z1 | ReLU hosilasi Z1 ning ishorasiga bog'liq |
X | W1 gradienti X ga bog'liq |
A2 | Yo'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 #
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)
qatlam kirish og'irliklar chiqish
1 (5, 3) (3, 4) (5, 4)
2 (5, 4) (4, 2) (5, 2)
| Qoida | Izoh |
|---|---|
W shakli | (oldingi_olcham, keyingi_olcham) |
b shakli | (keyingi_olcham,) |
| Namunalar soni | Hech qayerda qatnashmaydi |
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.
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.
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]))
bitta namuna: (1, 2) [[0.5003 0.5 ]]
100 namuna: (100, 2)
birinchi qator bir xilmi: True
Ixtiyoriy chuqurlikdagi tarmoq #
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))
chuqurlik: 4 qatlam
chiqish shakli: (7, 1)
keshdagi kalitlar soni: 9
Yashirin qatlamlarda ReLU, chiqishda esa sigmoid ishlatildi. Bu tasodifiy emas:
| Vazifa turi | Chiqish faollashtirishi | Neyronlar soni |
|---|---|---|
| Ikkilik tasniflash | sigmoid | 1 |
| Ko'p sinfli tasniflash | softmax (18-bo'lim) | sinflar soni |
| Regressiya (son bashorati) | hech qanday | 1 |
| Ko'p yorliqli tasniflash | sigmoid (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 #
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))
yashirin qatlam:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]]
barcha neyronlar bir xilmi: True
chiqish: [[0.5 0.5]
[0.5 0.5]]
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 #
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()))
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.
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))
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.
[3, 4, 2]tarmoq uchun parametrlar yarating va shakllarni chop eting.- Bitta qatlamni qo'lda hisoblang:
Z1, keyinA1. - ReLU dan keyin nechta nol borligini sanang.
- To'liq
oldingafunksiyasini yozing va keshni qaytaring. - Keshda nima uchun
Z1ham kerakligini tushuntiring. - Bitta va 100 namuna uchun natijani solishtiring.
- To'rt qatlamli tarmoq quring va chiqish shaklini tekshiring.
- Regressiya uchun chiqishda nima bo'lishi kerakligini ayting.
- Barcha og'irliklarni nol qilib, simmetriya muammosini ko'rsating.
- Juda katta kirish bering va sigmoid hosilasini hisoblang.
Xulosa #
- Oldinga tarqalish:
Z = A @ W + b, keyinA = f(Z)- qatlamma-qatlam. Wshakli -(oldingi, keyingi),bshakli -(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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.