14-bo‘lim

Paketli o'qitish

To'liq paket, mini-paket va SGD - shovqin, tezlik va xotira o'rtasidagi almashuv.

🕑 13 daqiqa o‘qish 📄 901 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Uch xil yondashuv
  2. Sinov muhiti
  3. Paketlarga bo'lish
  4. Uch usulni solishtirish
  5. Adolatli taqqoslash: bir xil qadamlar soni
  6. Paket hajmi va o'qitish tezligi bog'liq
  7. Yo'qotishning tebranishi
  8. Xotira
  9. Xulosa

Hozirgacha har qadamda butun ma'lumot ishlatildi. Katta to'plamda bu imkonsiz - va kerak ham emas.

Uch xil yondashuv #

UsulBir qadamdaQadamlar soniShovqin
To'liq paketHamma namunaKamYo'q
Mini-paket32-256 namunaO'rtachaO'rtacha
SGDBitta namunaJuda ko'pKatta
Minimumga uch xil yo'l To'liq paket silliq yo'l har qadam qimmat Mini-paket biroz egri, lekin tez amaliy standart SGD (bittalab) juda shovqinli lekin qadamlar arzon Shovqin har doim ham yomon emas: u mahalliy minimumdan chiqishga yordam beradi. Shu sababli mini-paket ko'pincha to'liq paketdan yaxshiroq yakuniy natija beradi.
Egri yo'l ham maqsadga olib boradi - va tezroq

Sinov muhiti #

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.q = len(olchamlar) - 1
        self.W = [gen.normal(0, np.sqrt(2.0 / 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)
        dZ = (A - y) / len(y)
        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)))

    def aniqlik(self, X, y):
        A, _ = self.oldinga(X)
        return float(((A > 0.5).astype(float) == y).mean())


def malumot(n=1000, urug=0):
    gen = np.random.default_rng(urug)
    X = gen.normal(0, 1, (n, 8))
    qoida = (X[:, 0] * X[:, 1] + X[:, 2] - 0.5 * X[:, 3]) > 0
    y = qoida.astype(float).reshape(-1, 1)
    return X, y


def paketlar(X, y, hajm, gen):
    n = len(X)
    tartib = gen.permutation(n)
    for boshi in range(0, n, hajm):
        idx = tartib[boshi:boshi + hajm]
        yield X[idx], y[idx]


def oqit(hajm, davrlar, tezlik, urug=1):
    X, y = malumot(1000)
    t = Tarmoq([8, 16, 16, 1], urug=urug)
    gen = np.random.default_rng(0)
    qadamlar = 0
    for _ in range(davrlar):
        for xb, yb in paketlar(X, y, hajm, gen):
            t.qadam(xb, yb, tezlik)
            qadamlar += 1
    return t, qadamlar, X, y


def oqit_qadamlar(hajm, jami_qadam, tezlik, urug=1):
    X, y = malumot(1000)
    t = Tarmoq([8, 16, 16, 1], urug=urug)
    gen = np.random.default_rng(0)
    bajarilgan = 0
    while bajarilgan < jami_qadam:
        for xb, yb in paketlar(X, y, hajm, gen):
            t.qadam(xb, yb, tezlik)
            bajarilgan += 1
            if bajarilgan >= jami_qadam:
                break
    return t, X, y
Python
X, y = malumot(1000)
print("X:", X.shape, " y:", y.shape)
print("sinflar muvozanati:", round(float(y.mean()), 4))
Natija
X: (1000, 8)  y: (1000, 1)
sinflar muvozanati: 0.485

Paketlarga bo'lish #

Python
def paketlar(X, y, hajm, gen):
    n = len(X)
    tartib = gen.permutation(n)
    for boshi in range(0, n, hajm):
        idx = tartib[boshi:boshi + hajm]
        yield X[idx], y[idx]


X, y = malumot(1000)
gen = np.random.default_rng(0)
hajmlar = [len(xb) for xb, _ in paketlar(X, y, 32, gen)]

print("paketlar soni:", len(hajmlar))
print("birinchi 5 ta paket hajmi:", hajmlar[:5])
print("oxirgi paket hajmi:", hajmlar[-1])
print("jami namunalar:", sum(hajmlar))
Natija
paketlar soni: 32
birinchi 5 ta paket hajmi: [32, 32, 32, 32, 32]
oxirgi paket hajmi: 8
jami namunalar: 1000
Aralashtirishni unutmang

gen.permutation(n) - har davrda ma'lumotni qayta aralashtirish.

Usiz nima bo'ladi: paketlar har davrda bir xil bo'ladi va model ularning tartibini ham "o'rganib" oladi.

Yomonroq holat: ma'lumot saralangan bo'lsa (avval barcha 0, keyin barcha 1), aralashtirmasdan har paket bitta sinfdan iborat bo'ladi. Model navbat bilan "hammasi 0" va "hammasi 1" deyishni o'rganadi va hech qachon yaqinlashmaydi.

Bu klassik xato: ma'lumot fayldan tartib bilan o'qilgan va aralashtirilmagan.

VaziyatAralashtirish
Tasniflash, regressiyaHar davrda
Vaqt qatorlariYo'q - tartib ma'noli
Tekshiruv to'plamiKerak emas
Python
X, y = malumot(200)
# ataylab saralangan ma'lumot
tartib = np.argsort(y.ravel())
X_saralangan, y_saralangan = X[tartib], y[tartib]

gen = np.random.default_rng(0)
print("aralashtirmasdan - har paketdagi 1 lar ulushi:")
for i, boshi in enumerate(range(0, 200, 50)):
    ulush = float(y_saralangan[boshi:boshi + 50].mean())
    print(f"  paket {i}: {ulush:.2f}")

print("aralashtirib:")
for i, (xb, yb) in enumerate(paketlar(X_saralangan, y_saralangan, 50, gen)):
    print(f"  paket {i}: {float(yb.mean()):.2f}")
Natija
aralashtirmasdan - har paketdagi 1 lar ulushi:
  paket 0: 0.00
  paket 1: 0.00
  paket 2: 0.90
  paket 3: 1.00
aralashtirib:
  paket 0: 0.58
  paket 1: 0.48
  paket 2: 0.44
  paket 3: 0.40

Aralashtirmasdan birinchi ikki paketda bitta ham musbat namuna yo'q.

Uch usulni solishtirish #

Python
def oqit(hajm, davrlar, tezlik, urug=1):
    X, y = malumot(1000)
    t = Tarmoq([8, 16, 16, 1], urug=urug)
    gen = np.random.default_rng(0)
    qadamlar = 0
    for _ in range(davrlar):
        for xb, yb in paketlar(X, y, hajm, gen):
            t.qadam(xb, yb, tezlik)
            qadamlar += 1
    return t, qadamlar, X, y


for nom, hajm, davrlar in [("to'liq paket", 1000, 200),
                           ("mini-paket 32", 32, 200),
                           ("SGD (1)", 1, 20)]:
    t, qadamlar, X, y = oqit(hajm, davrlar, 0.1)
    print(f"{nom:<16} qadamlar: {qadamlar:>6}  "
          f"yo'qotish: {t.yoqotish(X, y):.4f}  aniqlik: {t.aniqlik(X, y):.4f}")
Natija
to'liq paket     qadamlar:    200  yo'qotish: 0.2400  aniqlik: 0.9150
mini-paket 32    qadamlar:   6400  yo'qotish: 0.0020  aniqlik: 1.0000
SGD (1)          qadamlar:  20000  yo'qotish: 0.0850  aniqlik: 0.9620
Bir xil davrlar soni - juda boshqa natija

Uchala model ham 200 davr (SGD faqat 20) ishladi, lekin qadamlar soni butunlay boshqa.

To'liq paketda 200 davr = atigi 200 qadam. Bu juda kam: model deyarli hech nimani o'rganmadi.

Mini-paketda esa 200 davr = 6400 qadam, chunki har davrda 32 ta paket bor.

Bu tez-tez chalkashtiriladi:

AtamaMa'nosi
Davr (epoch)Butun ma'lumot bir marta ko'rildi
Qadam (iteration)Og'irliklar bir marta yangilandi
Bog'lanishqadamlar = davrlar × (n / paket_hajmi)

Ya'ni paket hajmini kichraytirish - ko'proq yangilanish degani. Shuning uchun mini-paket to'liq paketdan tezroq o'qiydi, garchi har qadam kamroq ma'lumot ko'rsa ham.

Adolatli taqqoslash: bir xil qadamlar soni #

Python
def oqit_qadamlar(hajm, jami_qadam, tezlik, urug=1):
    X, y = malumot(1000)
    t = Tarmoq([8, 16, 16, 1], urug=urug)
    gen = np.random.default_rng(0)
    bajarilgan = 0
    while bajarilgan < jami_qadam:
        for xb, yb in paketlar(X, y, hajm, gen):
            t.qadam(xb, yb, tezlik)
            bajarilgan += 1
            if bajarilgan >= jami_qadam:
                break
    return t, X, y


for nom, hajm in [("to'liq paket", 1000), ("mini-paket 128", 128),
                  ("mini-paket 32", 32), ("SGD (1)", 1)]:
    t, X, y = oqit_qadamlar(hajm, 3000, 0.1)
    print(f"{nom:<17} 3000 qadam -> yo'qotish: {t.yoqotish(X, y):.4f}  "
          f"aniqlik: {t.aniqlik(X, y):.4f}")
Natija
to'liq paket      3000 qadam -> yo'qotish: 0.0107  aniqlik: 1.0000
mini-paket 128    3000 qadam -> yo'qotish: 0.0105  aniqlik: 1.0000
mini-paket 32     3000 qadam -> yo'qotish: 0.0116  aniqlik: 0.9990
SGD (1)           3000 qadam -> yo'qotish: 0.1488  aniqlik: 0.9370

Endi manzara boshqacha: bir xil qadamlar sonida to'liq paket va mini-paket yaqin natija berdi, SGD esa sezilarli yomonroq.

Nima uchun mini-paket amaliy standart

Ikkala taqqoslash birga to'liq rasmni beradi:

O'lchovTo'liq paketMini-paketSGD
Bir qadamning narxiJuda qimmatO'rtachaArzon
Bir qadamning sifatiEng yaxshiYaxshiShovqinli
Davrdagi qadamlar1n/hajmn
XotiraButun to'plamPaketBitta namuna
Apparat samaradorligiYaxshiEng yaxshiJuda yomon

Oxirgi qator ko'pincha hal qiluvchi: zamonaviy protsessor va videokartalar matritsa amallariga mo'ljallangan. Bitta namunani qayta ishlash ularning quvvatini deyarli ishlatmaydi.

Shu sababli amalda 32 dan 512 gacha paket standart. "Sof SGD" bugun deyarli ishlatilmaydi - lekin uning nomi mini-paket usuliga ham qo'llanadi (chalkashlik shundan).

Paket hajmi va o'qitish tezligi bog'liq #

Python
for hajm, tezlik in [(32, 0.03), (32, 0.1), (32, 0.3),
                     (256, 0.03), (256, 0.1), (256, 0.3)]:
    t, X, y = oqit_qadamlar(hajm, 2000, tezlik)
    print(f"hajm {hajm:>4}, tezlik {tezlik:<5} -> "
          f"yo'qotish: {t.yoqotish(X, y):.4f}  aniqlik: {t.aniqlik(X, y):.4f}")
Natija
hajm   32, tezlik 0.03  -> yo'qotish: 0.0699  aniqlik: 0.9770
hajm   32, tezlik 0.1   -> yo'qotish: 0.0221  aniqlik: 0.9970
hajm   32, tezlik 0.3   -> yo'qotish: 0.0039  aniqlik: 1.0000
hajm  256, tezlik 0.03  -> yo'qotish: 0.0687  aniqlik: 0.9760
hajm  256, tezlik 0.1   -> yo'qotish: 0.0204  aniqlik: 0.9980
hajm  256, tezlik 0.3   -> yo'qotish: 0.0025  aniqlik: 1.0000
Paket kattalashsa - tezlikni ham oshiring

Katta paket aniqroq gradient beradi, ya'ni undan kattaroq qadam tashlash xavfsiz.

Keng tarqalgan qoida (linear scaling rule): paket hajmini k barobar oshirsangiz, tezlikni ham k barobar oshiring.

PaketTezlik
320.01
640.02
1280.04
2560.08

Bu qoida cheksiz ishlamaydi: juda katta paketda (8192+) u buziladi va "warmup" - tezlikni asta oshirish - kerak bo'ladi.

Yuqoridagi jadvalda ham bu ko'rinadi: 256 paket 0.03 tezlikda 32 paketdan yomonroq ishladi, 0.3 tezlikda esa ular tenglashdi.

Yo'qotishning tebranishi #

Python
X, y = malumot(1000)
t = Tarmoq([8, 16, 16, 1], urug=1)
gen = np.random.default_rng(0)

paket_yoqotishlari = []
for _ in range(3):
    for xb, yb in paketlar(X, y, 32, gen):
        paket_yoqotishlari.append(t.yoqotish(xb, yb))
        t.qadam(xb, yb, 0.1)

toliq = t.yoqotish(X, y)
p = np.array(paket_yoqotishlari)
print("paket yo'qotishlari - eng kichik:", round(float(p.min()), 4))
print("paket yo'qotishlari - eng katta: ", round(float(p.max()), 4))
print("tarqalish (std):", round(float(p.std()), 4))
print("to'liq to'plamda:", round(toliq, 4))
Natija
paket yo'qotishlari - eng kichik: 0.3075
paket yo'qotishlari - eng katta:  0.7794
tarqalish (std): 0.1111
to'liq to'plamda: 0.3759
Paket yo'qotishi sakraydi - bu normal

Yuqorida paketlar bo'yicha yo'qotish 0.38 dan 0.78 gacha tebrandi. Bu nosozlik emas: har paket boshqa namunalardan iborat va ularning qiyinligi har xil.

Shu sababli o'qitishni kuzatganda:

Nimani chizishIzoh
Har paket yo'qotishiJuda shovqinli - foydasiz
Davr bo'yicha o'rtachaYaxshi
To'liq to'plamda yo'qotishEng aniq, lekin qimmat
Tekshiruv to'plamidaEng muhimi (16-bo'lim)

Amalda: har davr oxirida bir marta o'lchang. Har qadamda o'lchash vaqtni behuda sarflaydi va grafikni o'qib bo'lmaydigan qiladi.

Xotira #

Python
import sys

X, y = malumot(1000)
t = Tarmoq([8, 64, 64, 1], urug=0)

for hajm in [1, 32, 1000]:
    xb = X[:hajm]
    _, kesh = t.oldinga(xb)
    baytlar = sum(v.nbytes for v in kesh.values())
    print(f"paket {hajm:>4}: keshdagi oraliq qiymatlar {baytlar:>8} bayt")
Natija
paket    1: keshdagi oraliq qiymatlar     2128 bayt
paket   32: keshdagi oraliq qiymatlar    68096 bayt
paket 1000: keshdagi oraliq qiymatlar  2128000 bayt

Kesh hajmi paketga to'g'ri proporsional. Kichik tarmoqda bu ahamiyatsiz, lekin katta modelda aynan shu xotira chegarasini belgilaydi.

Amaliy topshiriq
  1. paketlar generatorini yozing va oxirgi paket hajmini tekshiring.
  2. Saralangan ma'lumotda aralashtirmasdan paketlar tarkibini ko'ring.
  3. Aralashtirib, farqni ko'rsating.
  4. Uch usulni bir xil davrlar sonida solishtiring.
  5. Ularni bir xil qadamlar sonida qayta solishtiring.
  6. Davr va qadam farqini o'z so'zlaringiz bilan tushuntiring.
  7. Ikki paket hajmi va uch tezlikda jadval tuzing.
  8. Paket kattalashganda tezlik nima uchun oshishi kerakligini ayting.
  9. Paket yo'qotishlarining tarqalishini hisoblang.
  10. Kesh hajmining paketga bog'liqligini o'lchang.

Xulosa #

  • Davr - ma'lumot bir marta ko'rildi; qadam - og'irliklar bir marta yangilandi.
  • qadamlar = davrlar × (n / paket_hajmi).
  • Har davrda ma'lumotni aralashtiring; vaqt qatorlari - istisno.
  • Aralashtirmaslik saralangan ma'lumotda o'qitishni butunlay buzadi.
  • Bir xil davrda mini-paket to'liq paketdan ancha oldinda.
  • Bir xil qadamda ular yaqinlashadi, SGD esa ortda qoladi.
  • Mini-paket apparat uchun eng samarali - 32 dan 512 gacha.
  • Paket kattalashsa - tezlikni ham oshiring.
  • Paket yo'qotishi sakraydi - davr bo'yicha o'lchang.
  • Kesh hajmi paketga proporsional - xotira chegarasi shundan.

Keyingi bo'limda optimizatorlarni ko'ramiz: momentum, RMSProp va Adam.

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.