11-bo‘lim

To'liq tarmoq - ixtiyoriy chuqurlik

Formulalarni umumlashtirish, qayta ishlatiladigan tarmoq klassi va gradient tekshiruvi.

🕑 11 daqiqa o‘qish 📄 736 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Umumiy formulalar
  2. Tarmoq klassi
  3. Shakllar mos kelishini tekshirish
  4. Gradient tekshiruvi - to'liq tarmoq uchun
  5. Ataylab buzilgan backprop
  6. O'qitish sikli
  7. Chuqurlikning ta'siri
  8. Xotira va tezlik
  9. Xulosa

Ikki qatlam uchun formulalarni bilamiz. Endi ularni istalgan chuqurlik uchun umumlashtiramiz va qayta ishlatiladigan kod yozamiz.

Umumiy formulalar #

Natija
Oldinga:
    Z[i] = A[i-1] @ W[i] + b[i]
    A[i] = f(Z[i])

Orqaga:
    dZ[oxirgi] = (A[oxirgi] - y) / n
    dW[i]      = A[i-1].T @ dZ[i]
    db[i]      = dZ[i].sum(axis=0)
    dA[i-1]    = dZ[i] @ W[i].T
    dZ[i-1]    = dA[i-1] * f'(Z[i-1])

Beshta satr - istalgan chuqurlikdagi tarmoq uchun.

Tarmoq klassi #

Python
import numpy as np


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


class Tarmoq:
    def __init__(self, olchamlar, urug=42):
        gen = np.random.default_rng(urug)
        self.olchamlar = olchamlar
        self.qatlamlar = len(olchamlar) - 1
        self.W, self.b = [], []
        for i in range(self.qatlamlar):
            chegara = np.sqrt(2.0 / olchamlar[i])       # He boshlash (13-bo'lim)
            self.W.append(gen.normal(0, chegara, (olchamlar[i], olchamlar[i + 1])))
            self.b.append(np.zeros(olchamlar[i + 1]))

    def oldinga(self, X):
        A = X
        kesh = {"A0": X}
        for i in range(self.qatlamlar):
            Z = A @ self.W[i] + self.b[i]
            A = sigmoid(Z) if i == self.qatlamlar - 1 else np.maximum(0.0, Z)
            kesh[f"Z{i+1}"] = Z
            kesh[f"A{i+1}"] = A
        return A, kesh

    def orqaga(self, y, kesh):
        n = len(y)
        dW = [None] * self.qatlamlar
        db = [None] * self.qatlamlar
        dZ = (kesh[f"A{self.qatlamlar}"] - y) / n
        for i in range(self.qatlamlar - 1, -1, -1):
            dW[i] = kesh[f"A{i}"].T @ dZ
            db[i] = dZ.sum(axis=0)
            if i > 0:
                dZ = (dZ @ self.W[i].T) * (kesh[f"Z{i}"] > 0)
        return dW, db

    def yangilash(self, dW, db, tezlik):
        for i in range(self.qatlamlar):
            self.W[i] -= tezlik * dW[i]
            self.b[i] -= tezlik * db[i]

    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)))


def gradient_tekshir(tarmoq, X, y, h=1e-6):
    _, kesh = tarmoq.oldinga(X)
    dW, db = tarmoq.orqaga(y, kesh)

    eng_katta = 0.0
    for i in range(tarmoq.qatlamlar):
        for param, analitik in ((tarmoq.W[i], dW[i]), (tarmoq.b[i], db[i])):
            tekis = param.ravel()
            grad_tekis = analitik.ravel()
            for k in range(tekis.size):
                eski = tekis[k]
                tekis[k] = eski + h
                yuqori = tarmoq.yoqotish(X, y)
                tekis[k] = eski - h
                past = tarmoq.yoqotish(X, y)
                tekis[k] = eski
                sonli = (yuqori - past) / (2 * h)
                eng_katta = max(eng_katta, abs(sonli - grad_tekis[k]))
    return eng_katta
Python
t = Tarmoq([4, 8, 5, 1])
print("qatlamlar soni:", t.qatlamlar)
for i in range(t.qatlamlar):
    print(f"  W{i+1}: {t.W[i].shape}   b{i+1}: {t.b[i].shape}")
Natija
qatlamlar soni: 3
  W1: (4, 8)   b1: (8,)
  W2: (8, 5)   b2: (5,)
  W3: (5, 1)   b3: (1,)

Shakllar mos kelishini tekshirish #

Python
t = Tarmoq([4, 8, 5, 1])
gen = np.random.default_rng(0)
X = gen.normal(0, 1, (6, 4))
y = (gen.uniform(0, 1, (6, 1)) > 0.5).astype(float)

chiqish, kesh = t.oldinga(X)
dW, db = t.orqaga(y, kesh)

print("chiqish shakli:", chiqish.shape)
for i in range(t.qatlamlar):
    print(f"  dW{i+1}: {dW[i].shape} == W{i+1}: {t.W[i].shape} -> "
          f"{dW[i].shape == t.W[i].shape}")
    print(f"  db{i+1}: {db[i].shape} == b{i+1}: {t.b[i].shape} -> "
          f"{db[i].shape == t.b[i].shape}")
Natija
chiqish shakli: (6, 1)
  dW1: (4, 8) == W1: (4, 8) -> True
  db1: (8,) == b1: (8,) -> True
  dW2: (8, 5) == W2: (8, 5) -> True
  db2: (5,) == b2: (5,) -> True
  dW3: (5, 1) == W3: (5, 1) -> True
  db3: (1,) == b3: (1,) -> True

Gradient tekshiruvi - to'liq tarmoq uchun #

Bu backprop kodini isbotlashning yagona ishonchli usuli.

Python
def gradient_tekshir(tarmoq, X, y, h=1e-6):
    _, kesh = tarmoq.oldinga(X)
    dW, db = tarmoq.orqaga(y, kesh)

    eng_katta = 0.0
    for i in range(tarmoq.qatlamlar):
        for param, analitik in ((tarmoq.W[i], dW[i]), (tarmoq.b[i], db[i])):
            tekis = param.ravel()
            grad_tekis = analitik.ravel()
            for k in range(tekis.size):
                eski = tekis[k]
                tekis[k] = eski + h
                yuqori = tarmoq.yoqotish(X, y)
                tekis[k] = eski - h
                past = tarmoq.yoqotish(X, y)
                tekis[k] = eski
                sonli = (yuqori - past) / (2 * h)
                eng_katta = max(eng_katta, abs(sonli - grad_tekis[k]))
    return eng_katta


t = Tarmoq([3, 5, 4, 1], urug=7)
gen = np.random.default_rng(3)
X = gen.normal(0, 1, (5, 3))
y = (gen.uniform(0, 1, (5, 1)) > 0.5).astype(float)

ayirma = gradient_tekshir(t, X, y)
print(f"eng katta ayirma: {ayirma:.3e}")
print("backprop to'g'ri:", ayirma < 1e-6)
Natija
eng katta ayirma: 5.485e-11
backprop to'g'ri: True
Gradient tekshiruvini har doim bir marta bajaring

Backprop kodidagi xato juda hiyla-nayrang: tarmoq ishlaydi, yo'qotish kamayadi, lekin sekinroq va yomonroq natija bilan.

Ya'ni xato hech qanday xabar bermaydi - siz uni faqat "nima uchun natija kutilganidan yomonroq?" degan savol orqali sezasiz, va sababni oylab qidirasiz.

Gradient tekshiruvi buni bir soniyada aniqlaydi.

AyirmaXulosa
< 1e-7To'g'ri
1e-7 ... 1e-5Deyarli aniq to'g'ri
> 1e-3Xato bor

Muhim shartlar:

ShartSabab
Kichik tarmoqda bajaringSekin - har parametr uchun 2 ta o'tish
Dropout ni o'chiringTasodifiylik tekshiruvni buzadi (16-bo'lim)
ReLU da bir-ikki nomuvofiqlikka e'tibor bermangz = 0 da hosila sakraydi
Tekshirgach o'chirib qo'yingO'qitishda u juda sekin

Ataylab buzilgan backprop #

Python
class BuzilganTarmoq(Tarmoq):
    def orqaga(self, y, kesh):
        n = len(y)
        dW = [None] * self.qatlamlar
        db = [None] * self.qatlamlar
        dZ = (kesh[f"A{self.qatlamlar}"] - y) / n
        for i in range(self.qatlamlar - 1, -1, -1):
            dW[i] = kesh[f"A{i}"].T @ dZ
            db[i] = dZ.sum(axis=0)
            if i > 0:
                # XATO: ReLU maskasi unutilgan
                dZ = dZ @ self.W[i].T
        return dW, db


gen = np.random.default_rng(3)
X = gen.normal(0, 1, (5, 3))
y = (gen.uniform(0, 1, (5, 1)) > 0.5).astype(float)

togri = gradient_tekshir(Tarmoq([3, 5, 4, 1], urug=7), X, y)
buzilgan = gradient_tekshir(BuzilganTarmoq([3, 5, 4, 1], urug=7), X, y)

print(f"to'g'ri kod:   {togri:.3e}")
print(f"buzilgan kod:  {buzilgan:.3e}")
print("tekshiruv xatoni tutdi:", buzilgan > 1e-3)
Natija
to'g'ri kod:   5.485e-11
buzilgan kod:  2.838e-01
tekshiruv xatoni tutdi: True

ReLU maskasini unutish - eng ko'p uchraydigan backprop xatosi. Gradient tekshiruvi uni darhol tutdi.

O'qitish sikli - to'rtta funksiya oldinga() bashorat + kesh yoqotish() xatoni o'lchash orqaga() gradientlar yangilash() W -= lr * dW takrorlanadi Har kutubxonada shu to'rtlik bor PyTorch: model(x) · loss_fn(...) · loss.backward() · optimizer.step() Keras: predict() · compile(loss=...) · avtomatik · optimizer Farqi faqat shundaki, ular orqaga() ni o'zi hisoblaydi - qolgani bir xil. Shu sababli noldan yozgan odam istalgan kutubxonani tez o'zlashtiradi.
To'rtta funksiya - butun chuqur o'qitish shu

O'qitish sikli #

Python
def oqit(tarmoq, X, y, tezlik=0.5, davrlar=2000, har=500):
    tarix = []
    for davr in range(davrlar + 1):
        _, kesh = tarmoq.oldinga(X)
        dW, db = tarmoq.orqaga(y, kesh)
        tarmoq.yangilash(dW, db, tezlik)
        if davr % har == 0:
            L = tarmoq.yoqotish(X, y)
            tarix.append((davr, L))
            print(f"davr {davr:>5}: yo'qotish = {L:.6f}")
    return tarix


gen = np.random.default_rng(5)
X = gen.normal(0, 1, (200, 3))
# oddiy qoida: birinchi ikki belgining yig'indisi musbatmi
y = ((X[:, 0] + X[:, 1]) > 0).astype(float).reshape(-1, 1)

t = Tarmoq([3, 8, 1], urug=1)
oqit(t, X, y, tezlik=0.5, davrlar=2000, har=500)

bashorat = (t.oldinga(X)[0] > 0.5).astype(float)
print("aniqlik:", round(float((bashorat == y).mean()), 4))
Natija
davr     0: yo'qotish = 0.933291
davr   500: yo'qotish = 0.018882
davr  1000: yo'qotish = 0.011583
davr  1500: yo'qotish = 0.008585
davr  2000: yo'qotish = 0.006877
aniqlik: 1.0
Birinchi yo'qotish 0.93 - mo'ljaldan yuqori

6-bo'limda ko'rganimizdek, tasodifiy model uchun mo'ljal ln(2) = 0.6931. Bizning tarmoq esa 0.9333 dan boshladi - ya'ni mo'ljaldan yuqori.

Bu nimani anglatadi? Boshlang'ich og'irliklar shunchalik kattaki, sigmoid 0.5 atrofida emas, chetlarga siljigan - va model ishonch bilan noto'g'ri javob berayapti.

Bu halokat emas: tarmoq baribir o'qidi va 2000 davrda yo'qotish 0.0069 ga tushdi. Lekin bu belgi e'tiborga loyiq:

Birinchi yo'qotishXulosa
≈ ln(2)Boshlash muvozanatli
Sezilarli yuqoriOg'irliklar kattaroq - o'qitish sekinroq boshlanadi
Juda yuqori (> 3)Boshlashni tekshiring (13-bo'lim)

Sabab bu yerda ma'lum: Tarmoq klassi He boshlashni (sqrt(2/n)) ishlatadi. U ReLU li yashirin qatlamlar uchun mo'ljallangan, chiqish qatlamidagi sigmoid uchun esa biroz katta.

13-bo'limda chiqish qatlamiga alohida, kichikroq boshlash berish yaxshiroq ekanini ko'ramiz.

Xulosa: o'qitishni boshlaganda birinchi yo'qotishga qarang va uni mo'ljal bilan solishtiring. Bu bir soniyalik tekshiruv juda ko'p narsani aytadi.

Chuqurlikning ta'siri #

Python
gen = np.random.default_rng(5)
X = gen.normal(0, 1, (200, 3))
y = ((X[:, 0] + X[:, 1]) > 0).astype(float).reshape(-1, 1)

for olchamlar in [[3, 1], [3, 8, 1], [3, 8, 8, 1], [3, 8, 8, 8, 1]]:
    t = Tarmoq(olchamlar, urug=1)
    for _ in range(1500):
        _, kesh = t.oldinga(X)
        dW, db = t.orqaga(y, kesh)
        t.yangilash(dW, db, 0.5)
    bashorat = (t.oldinga(X)[0] > 0.5).astype(float)
    aniqlik = float((bashorat == y).mean())
    print(f"{str(olchamlar):<20} yo'qotish = {t.yoqotish(X, y):.4f}  "
          f"aniqlik = {aniqlik:.4f}")
Natija
[3, 1]               yo'qotish = 0.0550  aniqlik = 0.9950
[3, 8, 1]            yo'qotish = 0.0086  aniqlik = 1.0000
[3, 8, 8, 1]         yo'qotish = 0.0013  aniqlik = 1.0000
[3, 8, 8, 8, 1]      yo'qotish = 0.0003  aniqlik = 1.0000
Chuqurlik har doim ham yordam bermaydi

Bu masalada [3, 1] - ya'ni yashirin qatlamsiz model - allaqachon 99.5% aniqlik berdi.

Sabab: masala chiziqli ajraladigan. x₀ + x₁ > 0 - bu to'g'ri chiziq, va 3-bo'limda ko'rganimizdek bitta neyron uni yecha oladi.

Uch va to'rt qatlamli tarmoqlar deyarli bir xil natija berdi - ortiqcha chuqurlik hech nima qo'shmadi.

Masala turiKerakli chuqurlik
Chiziqli ajraladiganYashirin qatlam kerak emas
XOR kabi oddiy chiziqsizlikBitta yashirin qatlam
Rasm, matn, ovozKo'p qatlam

Amaliy qoida: kichikdan boshlang. Model yetarli bo'lmasa, kattalashtiring. Teskarisi - katta modeldan boshlab kichraytirish - ancha qiyin, chunki katta model har doim "ishlagandek" ko'rinadi (16-bo'lim).

Xotira va tezlik #

Python
import time

gen = np.random.default_rng(0)
X = gen.normal(0, 1, (1000, 20))
y = (gen.uniform(0, 1, (1000, 1)) > 0.5).astype(float)

for olchamlar in [[20, 32, 1], [20, 128, 128, 1]]:
    t = Tarmoq(olchamlar, urug=0)
    parametrlar = sum(w.size for w in t.W) + sum(b.size for b in t.b)
    boshlandi = time.perf_counter()
    for _ in range(100):
        _, kesh = t.oldinga(X)
        dW, db = t.orqaga(y, kesh)
        t.yangilash(dW, db, 0.1)
    vaqt = time.perf_counter() - boshlandi
    print(f"{str(olchamlar):<22} parametrlar: {parametrlar:>7}  "
          f"100 davr bajarildi: {vaqt > 0}")
Natija
[20, 32, 1]            parametrlar:     705  100 davr bajarildi: True
[20, 128, 128, 1]      parametrlar:   19329  100 davr bajarildi: True

Ikkinchi tarmoqda parametrlar 27 barobar ko'p - va o'qitish ham shunga mos sekinroq kechadi.

Amaliy topshiriq
  1. Tarmoq klassini yozing va shakllarni tekshiring.
  2. oldinga va orqaga ni ishga tushirib, gradient shakllarini tasdiqlang.
  3. gradient_tekshir funksiyasini yozing.
  4. Ayirmani chop eting - 1e-7 dan kichikmi?
  5. ReLU maskasini ataylab olib tashlang va tekshiruvni qayta bajaring.
  6. Tekshiruv xatoni tutganini ko'rsating.
  7. O'qitish siklini yozib, yo'qotish tarixini chop eting.
  8. Birinchi yo'qotish ln(2) ga yaqinligini tasdiqlang.
  9. To'rt xil chuqurlikda natijani solishtiring.
  10. Parametrlar sonini hisoblang va chuqurlik bilan bog'lang.

Xulosa #

  • Beshta formula istalgan chuqurlik uchun yetarli.
  • Klass: oldinga, orqaga, yangilash, yoqotish.
  • Gradient shakli doim parametr shakliga teng.
  • Gradient tekshiruvi - backpropni isbotlashning yagona usuli.
  • Buzilgan backprop jimgina ishlaydi, lekin yomonroq.
  • ReLU maskasini unutish - eng ko'p uchraydigan xato.
  • Tekshiruvni bir marta bajaring, keyin o'chirib qo'ying.
  • Birinchi yo'qotish ln(2) ga yaqin bo'lishi kerak.
  • Chiziqli masalada chuqurlik foyda bermaydi.
  • Kichikdan boshlang - katta model har doim ishlagandek ko'rinadi.

Keyingi bo'limda XOR ni yechamiz - 3-bo'limda imkonsiz bo'lgan masalani.

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.