11-bo‘lim
To'liq tarmoq - ixtiyoriy chuqurlik
Formulalarni umumlashtirish, qayta ishlatiladigan tarmoq klassi va gradient tekshiruvi.
Ushbu bo‘lim mundarijasi
Ikki qatlam uchun formulalarni bilamiz. Endi ularni istalgan chuqurlik uchun umumlashtiramiz va qayta ishlatiladigan kod yozamiz.
Umumiy formulalar #
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 #
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
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}")
qatlamlar soni: 3
W1: (4, 8) b1: (8,)
W2: (8, 5) b2: (5,)
W3: (5, 1) b3: (1,)
Shakllar mos kelishini tekshirish #
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}")
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.
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)
eng katta ayirma: 5.485e-11
backprop to'g'ri: True
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.
| Ayirma | Xulosa |
|---|---|
< 1e-7 | To'g'ri |
1e-7 ... 1e-5 | Deyarli aniq to'g'ri |
> 1e-3 | Xato bor |
Muhim shartlar:
| Shart | Sabab |
|---|---|
| Kichik tarmoqda bajaring | Sekin - har parametr uchun 2 ta o'tish |
| Dropout ni o'chiring | Tasodifiylik tekshiruvni buzadi (16-bo'lim) |
| ReLU da bir-ikki nomuvofiqlikka e'tibor bermang | z = 0 da hosila sakraydi |
| Tekshirgach o'chirib qo'ying | O'qitishda u juda sekin |
Ataylab buzilgan backprop #
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)
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 #
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))
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
0.93 - mo'ljaldan yuqori6-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'qotish | Xulosa |
|---|---|
≈ ln(2) | Boshlash muvozanatli |
| Sezilarli yuqori | Og'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 #
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}")
[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
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 turi | Kerakli chuqurlik |
|---|---|
| Chiziqli ajraladigan | Yashirin qatlam kerak emas |
| XOR kabi oddiy chiziqsizlik | Bitta yashirin qatlam |
| Rasm, matn, ovoz | Ko'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 #
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}")
[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.
Tarmoqklassini yozing va shakllarni tekshiring.oldingavaorqagani ishga tushirib, gradient shakllarini tasdiqlang.gradient_tekshirfunksiyasini yozing.- Ayirmani chop eting -
1e-7dan kichikmi? - ReLU maskasini ataylab olib tashlang va tekshiruvni qayta bajaring.
- Tekshiruv xatoni tutganini ko'rsating.
- O'qitish siklini yozib, yo'qotish tarixini chop eting.
- Birinchi yo'qotish
ln(2)ga yaqinligini tasdiqlang. - To'rt xil chuqurlikda natijani solishtiring.
- 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.
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.