13-bo‘lim

Og'irliklarni boshlash

Nima uchun tasodifiy, qanchalik katta va nima uchun Xavier bilan He turlicha.

🕑 14 daqiqa o‘qish 📄 1 008 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Uch xil xato
  2. Xavier boshlash
  3. He boshlash - ReLU uchun
  4. Nol va bir xil boshlash
  5. Siljishlarni boshlash
  6. Boshlashning o'qitishga ta'siri
  7. Boshlashni tekshirish
  8. Xulosa

Og'irliklar tasodifiy boshlanishi kerakligini 5-bo'limda ko'rdik. Endi savol - qanchalik tasodifiy.

Uch xil xato #

Python
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)))
Python
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))
Natija
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.

Signal qatlamlar orqali o'tganda Juda kichik Har qatlamda signal 10 barobar kichrayadi. 1.0 → 0.1 → 0.01 → 0.001 Chiqish nolga teng - gradient ham nol. Tarmoq o'qimaydi. Juda katta tanh to'yinadi: barcha qiymatlar ±1 ga tiqiladi. std ≈ 0.98 - lekin bu ±1 larning tarqalishi Hosila ≈ 0 - o'qimaydi. To'g'ri masshtab Signal tarqalishi qatlamdan qatlamga **saqlanadi**. std ≈ 0.6 ... 0.8 Gradient ham saqlanadi - barcha qatlamlar o'qiydi. Ikki formula Xavier (Glorot): std = sqrt(1 / n_kirish) sigmoid, tanh uchun He (Kaiming): std = sqrt(2 / n_kirish) ReLU uchun Farqi ikkilikda: ReLU chiqishning yarmini nolga tushiradi, shuning uchun yo'qolgan quvvatni qoplash uchun 2 marta kattaroq boshlash kerak.
Maqsad - tarqalishni qatlamdan qatlamga saqlash

Xavier boshlash #

Natija
std = sqrt(1 / n_kirish)
Python
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))
Natija
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.

Nima uchun sqrt(1/n)

z = Σ xᵢwᵢ yig'indisi n ta hadda ega. Mustaqil tasodifiy hadlar yig'indisining dispersiyasi:

Natija
Var(z) = n · Var(x) · Var(w)

Var(z) = Var(x) bo'lishini xohlaymiz - ya'ni tarqalish o'zgarmasin. Bundan:

Natija
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 #

Python
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))
Natija
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.

Noto'g'ri boshlash chuqur tarmoqni o'qimaydigan qiladi

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:

FaollashtirishBoshlash
ReLU, Leaky ReLU, ELUHe: sqrt(2/n)
sigmoid, tanhXavier: 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 #

Python
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}")
Natija
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 #

Python
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)}")
Natija
nol siljish          [0.4586 1.073  1.5642 0.4386]
tasodifiy siljish    [0.     0.9636 0.9412 0.0724]
Siljishlarni nol qiling

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 #

Python
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}")
Natija
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:

BoshlashNima bo'ldi
Juda kichik0.69310.6881 - deyarli qimirlamadi
Xavier0.71730.0155 - yaxshi o'qidi
He0.88170.0128 - eng yaxshi natija
Juda katta15.580.4295 - o'qidi, lekin ancha yomonroq
Oxirgi ikki qatorni solishtiring

"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'qotishXulosa
≈ ln(2) (ikkilik)Boshlash to'g'ri
Bir necha barobar kattaOg'irliklar kattaroq - sekinroq o'qiydi
O'nlab barobar kattaBoshlashni tuzating
Aynan ln(2) va qimirlamaydiOg'irliklar juda kichik

Oxirgi qator - "juda kichik" holati: yo'qotish aynan mo'ljalda turibdi va 600 davrda 0.005 ga ham qimirlamadi.

Boshlashni tekshirish #

Python
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}")
Natija
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.830.97) va har qatlamda taxminan yarmi nolga teng - bu ReLU uchun aynan kutilgan holat.

Bu jadval - eng foydali tashxis vositasi

Tarmoq o'qimayotganda birinchi qiladigan ish - shu jadvalni chop etish.

BelgiSabab
std har qatlamda kichrayadiBoshlash juda kichik
std har qatlamda o'sadiBoshlash juda katta - portlash xavfi
Nolga teng ulush > 0.9Ko'p neyron o'lik
Nolga teng ulush ≈ 0.5Normal - ReLU uchun kutilgan
Oxirgi qatlam std ≈ 0Chiqish 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.

Amaliy topshiriq
  1. 0.01 masshtab bilan sakkiz qatlamda signal so'nishini ko'ring.
  2. 1.0 masshtab bilan to'yinishni ko'ring.
  3. Xavier bilan tarqalishning saqlanishini tekshiring.
  4. sqrt(1/n) formulasi qayerdan kelib chiqqanini tushuntiring.
  5. Xavier va He ni ReLU bilan solishtiring.
  6. Nol va bir xil boshlashda simmetriyani ko'rsating.
  7. Nima uchun siljishlarni nol qilish mumkinligini ayting.
  8. To'rt xil boshlash bilan tarmoqni o'qiting.
  9. "Juda katta" holatdagi 0.6931 nima anglatishini tushuntiring.
  10. Qatlamlar bo'yicha std va 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 std jadvali - eng foydali tashxis vositasi.

Keyingi bo'limda ma'lumotni paketlarga bo'lib o'qitishni ko'ramiz.

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.