13-bo‘lim
Og'irliklarni boshlash
Nima uchun tasodifiy, qanchalik katta va nima uchun Xavier bilan He turlicha.
Ushbu bo‘lim mundarijasi
Og'irliklar tasodifiy boshlanishi kerakligini 5-bo'limda ko'rdik. Endi savol - qanchalik tasodifiy.
Uch xil xato #
import numpy as np
def sigmoid(z):
"""4-bo'limdagi barqaror yozuv - katta |z| da ham toshmaydi."""
musbat = z >= 0
natija = np.empty_like(z, dtype=np.float64)
natija[musbat] = 1.0 / (1.0 + np.exp(-z[musbat]))
eksp = np.exp(z[~musbat])
natija[~musbat] = eksp / (1.0 + eksp)
return natija
def qatlamlar_orqali(X, olchamlar, masshtab_fn, faollashtirish, urug=0):
"""Signal qatlamlar orqali o'tganda uning tarqalishi qanday o'zgaradi."""
gen = np.random.default_rng(urug)
A = X
tarix = []
for i in range(len(olchamlar) - 1):
W = gen.normal(0, masshtab_fn(olchamlar[i]), (olchamlar[i], olchamlar[i + 1]))
A = faollashtirish(A @ W)
tarix.append(float(A.std()))
return tarix
class Tarmoq:
def __init__(self, olchamlar, masshtab_fn, urug=42):
gen = np.random.default_rng(urug)
self.q = len(olchamlar) - 1
self.W = [gen.normal(0, masshtab_fn(olchamlar[i]),
(olchamlar[i], olchamlar[i + 1])) for i in range(self.q)]
self.b = [np.zeros(olchamlar[i + 1]) for i in range(self.q)]
def oldinga(self, X):
A = X
kesh = {"A0": X}
for i in range(self.q):
Z = A @ self.W[i] + self.b[i]
A = sigmoid(Z) if i == self.q - 1 else np.maximum(0.0, Z)
kesh[f"Z{i+1}"] = Z
kesh[f"A{i+1}"] = A
return A, kesh
def qadam(self, X, y, tezlik):
A, kesh = self.oldinga(X)
n = len(y)
dZ = (A - y) / n
for i in range(self.q - 1, -1, -1):
dW = kesh[f"A{i}"].T @ dZ
db = dZ.sum(axis=0)
if i > 0:
dZ = (dZ @ self.W[i].T) * (kesh[f"Z{i}"] > 0)
self.W[i] -= tezlik * dW
self.b[i] -= tezlik * db
def yoqotish(self, X, y, eps=1e-12):
A, _ = self.oldinga(X)
A = np.clip(A, eps, 1 - eps)
return float(-np.mean(y * np.log(A) + (1 - y) * np.log(1 - A)))
gen = np.random.default_rng(42)
X = gen.normal(0, 1, (500, 100))
olchamlar = [100] * 8
print("kirish std:", round(float(X.std()), 4))
for nom, masshtab in [("juda kichik (0.01)", lambda n: 0.01),
("juda katta (1.0)", lambda n: 1.0)]:
tarix = qatlamlar_orqali(X, olchamlar, masshtab, np.tanh)
print(f"{nom:<20}", " ".join(f"{s:.4f}" for s in tarix))
kirish std: 1.0026
juda kichik (0.01) 0.0995 0.0098 0.0010 0.0001 0.0000 0.0000 0.0000
juda katta (1.0) 0.9597 0.9571 0.9570 0.9581 0.9582 0.9573 0.9567
Ikkala holat ham yomon, lekin turli sabablarga ko'ra.
Xavier boshlash #
std = sqrt(1 / n_kirish)
gen = np.random.default_rng(42)
X = gen.normal(0, 1, (500, 100))
olchamlar = [100] * 8
tarix = qatlamlar_orqali(X, olchamlar, lambda n: np.sqrt(1.0 / n), np.tanh)
print("Xavier + tanh:")
print(" ".join(f"{s:.4f}" for s in tarix))
print("birinchi va oxirgi nisbat:", round(tarix[-1] / tarix[0], 4))
Xavier + tanh:
0.6294 0.4828 0.4054 0.3578 0.3177 0.2855 0.2650
birinchi va oxirgi nisbat: 0.4211
Signal hali ham so'nmoqda, lekin ancha sekin: sakkiz
qatlamdan keyin 0.27 - o'qish uchun yetarli.
sqrt(1/n)z = Σ xᵢwᵢ yig'indisi n ta hadda ega. Mustaqil
tasodifiy hadlar yig'indisining dispersiyasi:
Var(z) = n · Var(x) · Var(w)
Var(z) = Var(x) bo'lishini xohlaymiz - ya'ni tarqalish
o'zgarmasin. Bundan:
Var(w) = 1/n => std(w) = sqrt(1/n)
Ba'zi manbalarda sqrt(2 / (n_kirish + n_chiqish)) yoziladi -
bu Glorotning asl formulasi, u orqaga tarqalishni ham
hisobga oladi. Amalda ikkalasi ham ishlaydi.
Muhim nuqta: formulada n bor - ya'ni kengroq qatlam
kichikroq og'irlik talab qiladi. Bu intuitiv: ko'p had
qo'shilsa, har biri kichikroq bo'lishi kerak.
He boshlash - ReLU uchun #
gen = np.random.default_rng(42)
X = gen.normal(0, 1, (500, 100))
olchamlar = [100] * 8
def relu(z):
return np.maximum(0.0, z)
xavier = qatlamlar_orqali(X, olchamlar, lambda n: np.sqrt(1.0 / n), relu)
he = qatlamlar_orqali(X, olchamlar, lambda n: np.sqrt(2.0 / n), relu)
print("Xavier + ReLU:", " ".join(f"{s:.4f}" for s in xavier))
print("He + ReLU:", " ".join(f"{s:.4f}" for s in he))
Xavier + ReLU: 0.5843 0.4038 0.2911 0.2193 0.1823 0.1122 0.0744
He + ReLU: 0.8264 0.8076 0.8234 0.8772 1.0313 0.8976 0.8422
Xavier + ReLU da signal har qatlamda 1.67 barobar
kichrayadi va sakkizinchi qatlamga 0.027 bo'lib yetadi.
He bilan esa 0.31 - o'n barobar kattaroq.
Sakkiz qatlamda farq 0.027 va 0.31. Yigirma qatlamda
farq minglab barobar bo'ladi.
Ma'nosi: Xavier + ReLU bilan chuqur tarmoqning oxirgi qatlamlariga signal deyarli yetib bormaydi - va orqaga gradient ham xuddi shunday so'nadi.
Bu 2010-yillargacha chuqur tarmoqlarni o'qitish nima uchun qiyin bo'lganining asosiy sabablaridan biri edi. He boshlash (2015) shu muammoni hal qildi.
Amaliy qoida juda oddiy:
| Faollashtirish | Boshlash |
|---|---|
| ReLU, Leaky ReLU, ELU | He: sqrt(2/n) |
| sigmoid, tanh | Xavier: sqrt(1/n) |
| Chiqish qatlami (sigmoid) | Xavier yoki kichikroq |
PyTorch va Keras buni avtomatik qiladi - lekin qaysi birini tanlaganini bilish kerak, chunki standart tanlov har doim ham to'g'ri emas.
Nol va bir xil boshlash #
X = np.array([[1.0, 0.5, -1.0], [0.0, 2.0, 1.0]])
for nom, W in [("nol", np.zeros((3, 4))),
("hammasi 0.5", np.full((3, 4), 0.5))]:
A = np.maximum(0.0, X @ W)
bir_xil = bool(np.allclose(A, A[:, :1]))
print(f"{nom:<12} chiqish: {np.round(A[0], 4)} barcha neyronlar bir xilmi: {bir_xil}")
nol chiqish: [0. 0. 0. 0.] barcha neyronlar bir xilmi: True
hammasi 0.5 chiqish: [0.25 0.25 0.25 0.25] barcha neyronlar bir xilmi: True
Ikkala holatda ham qatlamdagi barcha neyronlar aynan bir xil - va gradient ham bir xil bo'lgani uchun ular hech qachon farqlanmaydi.
Siljishlarni boshlash #
gen = np.random.default_rng(0)
W = gen.normal(0, np.sqrt(2.0 / 3), (3, 4))
for nom, b in [("nol siljish", np.zeros(4)),
("tasodifiy siljish", gen.normal(0, 0.5, 4))]:
A = np.maximum(0.0, np.array([[1.0, 0.5, -1.0]]) @ W + b)
print(f"{nom:<20} {np.round(A.ravel(), 4)}")
nol siljish [0.4586 1.073 1.5642 0.4386]
tasodifiy siljish [0. 0.9636 0.9412 0.0724]
Og'irliklar tasodifiy bo'lgani uchun simmetriya allaqachon buzilgan - siljishlarni ham tasodifiy qilish hech nima qo'shmaydi.
Bitta muhim istisno bor: ReLU li qatlamlarda ba'zan
siljish kichik musbat son (0.01) qilib boshlanadi.
Sabab - o'lik neyronlar (4-bo'lim): musbat siljish neyronni boshidanoq "faol" qiladi va u gradient olishini kafolatlaydi.
Amalda bu farq kichik va ko'p kutubxonalar siljishni nol qiladi. Lekin tarmoq o'qimayotgan bo'lsa va ko'p neyron o'lik bo'lsa - sinab ko'rishga arziydi.
Boshlashning o'qitishga ta'siri #
class Tarmoq:
def __init__(self, olchamlar, masshtab_fn, urug=42):
gen = np.random.default_rng(urug)
self.q = len(olchamlar) - 1
self.W = [gen.normal(0, masshtab_fn(olchamlar[i]),
(olchamlar[i], olchamlar[i + 1])) for i in range(self.q)]
self.b = [np.zeros(olchamlar[i + 1]) for i in range(self.q)]
def oldinga(self, X):
A = X
kesh = {"A0": X}
for i in range(self.q):
Z = A @ self.W[i] + self.b[i]
A = sigmoid(Z) if i == self.q - 1 else np.maximum(0.0, Z)
kesh[f"Z{i+1}"] = Z
kesh[f"A{i+1}"] = A
return A, kesh
def qadam(self, X, y, tezlik):
A, kesh = self.oldinga(X)
n = len(y)
dZ = (A - y) / n
for i in range(self.q - 1, -1, -1):
dW = kesh[f"A{i}"].T @ dZ
db = dZ.sum(axis=0)
if i > 0:
dZ = (dZ @ self.W[i].T) * (kesh[f"Z{i}"] > 0)
self.W[i] -= tezlik * dW
self.b[i] -= tezlik * db
def yoqotish(self, X, y, eps=1e-12):
A, _ = self.oldinga(X)
A = np.clip(A, eps, 1 - eps)
return float(-np.mean(y * np.log(A) + (1 - y) * np.log(1 - A)))
gen = np.random.default_rng(7)
X = gen.normal(0, 1, (300, 10))
y = ((X[:, 0] * X[:, 1]) > 0).astype(float).reshape(-1, 1)
olchamlar = [10, 32, 32, 32, 1]
for nom, fn in [("juda kichik", lambda n: 0.01),
("Xavier", lambda n: np.sqrt(1.0 / n)),
("He", lambda n: np.sqrt(2.0 / n)),
("juda katta", lambda n: 1.5)]:
t = Tarmoq(olchamlar, fn, urug=1)
boshlangich = t.yoqotish(X, y)
for _ in range(600):
t.qadam(X, y, 0.1)
print(f"{nom:<14} boshlang'ich = {boshlangich:>8.4f} 600 davrdan keyin = {t.yoqotish(X, y):>8.4f}")
juda kichik boshlang'ich = 0.6931 600 davrdan keyin = 0.6881
Xavier boshlang'ich = 0.7173 600 davrdan keyin = 0.0155
He boshlang'ich = 0.8817 600 davrdan keyin = 0.0128
juda katta boshlang'ich = 15.5809 600 davrdan keyin = 0.4295
Natijalar aniq:
| Boshlash | Nima bo'ldi |
|---|---|
| Juda kichik | 0.6931 → 0.6881 - deyarli qimirlamadi |
| Xavier | 0.7173 → 0.0155 - yaxshi o'qidi |
| He | 0.8817 → 0.0128 - eng yaxshi natija |
| Juda katta | 15.58 → 0.4295 - o'qidi, lekin ancha yomonroq |
"Juda katta" boshlash bilan boshlang'ich yo'qotish 15.58
bo'ldi - ln(2) = 0.693 mo'ljalidan yigirma barobar katta.
Ma'nosi: og'irliklar shunchalik kattaki, model butunlay ishonch bilan noto'g'ri javob berayapti. Sigmoid chetlarga tiqilgan.
600 davrdan keyin u 0.4295 ga tushdi - ya'ni o'qidi,
lekin He (0.0128) dan o'ttiz barobar yomonroq.
Ya'ni yomon boshlash tarmoqni butunlay to'xtatmasligi ham mumkin - u shunchaki ancha ko'p vaqt yo'qotadi va yomonroq yechimga keladi.
Amaliy xulosa: o'qitishni boshlaganda birinchi yo'qotishga qarang.
| Birinchi yo'qotish | Xulosa |
|---|---|
≈ ln(2) (ikkilik) | Boshlash to'g'ri |
| Bir necha barobar katta | Og'irliklar kattaroq - sekinroq o'qiydi |
| O'nlab barobar katta | Boshlashni tuzating |
Aynan ln(2) va qimirlamaydi | Og'irliklar juda kichik |
Oxirgi qator - "juda kichik" holati: yo'qotish aynan
mo'ljalda turibdi va 600 davrda 0.005 ga ham qimirlamadi.
Boshlashni tekshirish #
gen = np.random.default_rng(0)
X = gen.normal(0, 1, (200, 20))
olchamlar = [20, 64, 64, 64, 1]
t = Tarmoq(olchamlar, lambda n: np.sqrt(2.0 / n), urug=5)
_, kesh = t.oldinga(X)
print("qatlam o'rtacha std nolga teng ulush")
for i in range(1, t.q + 1):
A = kesh[f"A{i}"]
print(f" {i} {A.mean():>8.4f} {A.std():>8.4f} {float((A == 0).mean()):>10.3f}")
qatlam o'rtacha std nolga teng ulush
1 0.5552 0.8262 0.498
2 0.6419 0.8927 0.441
3 0.6556 0.9718 0.472
4 0.6870 0.1839 0.000
Uchta yashirin qatlamda std bir xil tartibda qoldi
(0.83 → 0.97) va har qatlamda taxminan yarmi nolga
teng - bu ReLU uchun aynan kutilgan holat.
Tarmoq o'qimayotganda birinchi qiladigan ish - shu jadvalni chop etish.
| Belgi | Sabab |
|---|---|
std har qatlamda kichrayadi | Boshlash juda kichik |
std har qatlamda o'sadi | Boshlash juda katta - portlash xavfi |
Nolga teng ulush > 0.9 | Ko'p neyron o'lik |
Nolga teng ulush ≈ 0.5 | Normal - ReLU uchun kutilgan |
Oxirgi qatlam std ≈ 0 | Chiqish to'yingan |
Oxirgi qatorda bizda std = 0.184 - chiqish 0.69 atrofida
to'plangan va tarqalishi kichik. Bu boshlanish uchun
me'yoriy: model hali hech nimaga qat'iy qaror qilmagan.
O'qitishdan keyin esa u kattalashishi kerak - model ishonch bilan javob bera boshlaganini bildiradi.
0.01masshtab bilan sakkiz qatlamda signal so'nishini ko'ring.1.0masshtab bilan to'yinishni ko'ring.- Xavier bilan tarqalishning saqlanishini tekshiring.
sqrt(1/n)formulasi qayerdan kelib chiqqanini tushuntiring.- Xavier va He ni ReLU bilan solishtiring.
- Nol va bir xil boshlashda simmetriyani ko'rsating.
- Nima uchun siljishlarni nol qilish mumkinligini ayting.
- To'rt xil boshlash bilan tarmoqni o'qiting.
- "Juda katta" holatdagi
0.6931nima anglatishini tushuntiring. - Qatlamlar bo'yicha
stdva nol ulushini chop eting.
Xulosa #
- Juda kichik boshlash - signal so'nadi, gradient nol.
- Juda katta boshlash - faollashtirish to'yinadi, hosila nol.
- Maqsad - tarqalishni qatlamdan qatlamga saqlash.
- Xavier:
sqrt(1/n)- sigmoid va tanh uchun. - He:
sqrt(2/n)- ReLU uchun; ikkilik ReLU yarmini kesgani uchun. - Kengroq qatlam kichikroq og'irlik talab qiladi.
- Nol yoki bir xil boshlash simmetriyani buzmaydi.
- Siljishlarni nol qiling; ReLU da kichik musbat ham mumkin.
- Yo'qotishning kamayishi o'z-o'zidan muvaffaqiyat emas -
ln(2)bilan solishtiring. - Qatlamlar bo'yicha
stdjadvali - eng foydali tashxis vositasi.
Keyingi bo'limda ma'lumotni paketlarga bo'lib o'qitishni ko'ramiz.
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.