İçeriğe geç
academia.sh

Ders 05 / 24

Geri Yayılım

Zincir kuralının ağ üzerinde elle uygulanması ve elde edilen eğimin sayısal türevle doğrulanması: 113 parametrenin tamamında en büyük mutlak fark ilklendirmede 2,193e-10, yüz tur eğitimden sonra 6,441e-11. Adım büyüklüğü 0,001 seçildiğinde eğitilmiş ağda fark 2,518e-01'e fırlıyor; bu bir hesap hatası değil, doğrultulmuş birimin kırığıdır. Maliyet: geri yayılım 113 eğimin hepsini örnek başına bir ileri ve bir geri geçişle çıkarıyor, sayısal türev aynı iş için 4.520 geçiş istiyor ve oran parametre sayısıyla büyüyor: 58, 226, 898. Üçlü değişmiyor — taban çizgisi 0,7579, ağın sayısı 0,7857, ikinci koşum 0,7698.

İçindekiler

Dört derste eğim hep hazır geldi. Çıkış katmanındaki sayının nasıl bulunduğu 04’te yazıldı, ama o sayının ara katmanın ağırlıklarına nasıl dağıtıldığı hiç açılmadı. Bu ders o dağıtımı yazar ve yazdığı şeyin doğru olduğunu ayrı bir yöntemle sınar.

Dağıtımın kuralı zincir kuralıdır. Bir ağırlığın kayba etkisi iki parçanın çarpımıdır: beslediği birime geriye gelen değer ve çarptığı girdi. Geriye gelen değer de bir çarpımdır — sonraki katmandan gelenlerin ağırlıklarla toplamı, çarpı etkinleştirmenin türevi. Tek bir kural katman katman uygulanınca bütün parametrelerin eğimi çıkar.

  • SA29. Küme, bölme, tohum, on iki sütun ve 300 satıra küçültülmüş eğitim kümesi 0104’teki gibidir; ağ 12–8–1, 113 parametre. Taban çizgisi sınama kümesinde 0,7579.
  • SA30. Kayıp çapraz düzensizliktir. Çıkış katmanına gelen değer aya-y’dir, çünkü sigmoidin türevi kaybın türeviyle sadeleşir; 04’te gösterildi.
  • SA31. Ara katmanın etkinleştirmesi doğrultulmuş doğrusal birimdir. Türevi girdisi pozitifken 1, değilse 0’dır; sıfır noktasında tanımsızdır ve kodda 0 seçilmiştir.
  • SA32. Eğimler örnekler üzerinden toplanır. Sayısal türev ortalama kayıp üzerinden hesaplandığı için sonucu örnek sayısıyla çarpılır ve iki yöntem aynı ölçeğe gelir.
  • SA33. Sayısal türev merkezi farktır: parametre bir yukarı bir aşağı oynatılır ve fark iki adıma bölünür. Tek yanlı farktan daha doğrudur, iki kat geçiş ister.
  • SA34. Doğrulama 20 satırlık bir alt kümede yapılır; gerekçe geçiş sayısıdır.
  • SA35. Geçiş sayısı ileri geçiş cinsindendir. Geri yayılımın geri geçişi bir ileri geçişle aynı mertebededir ve ayrı sayılmaz; oran bu yüzden bir alt sınırdır.

Zincir Kuralı Ağ Üzerinde

Geri yayılım iki koşudan oluşur. İleri geçişte ara değerler saklanır, çünkü ağırlık eğimi onlara ihtiyaç duyar. Geri geçişte çıkıştan başlanır ve her katmanda iki iş yapılır: o katmanın eğimleri yazılır, sonra geriye gelen değer bir önceki katmana taşınır.

# geri_yayilim.py — MODELDIR. Ayni KURGU abone tablosu, ayni tohum ve ayni bolme;
# egitim kumesi 01'deki gibi 300 satira kucultulmustur.
import math
import statistics

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


def uretec(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, VERI = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r, v = uretec(TOHUM + 7000 + k["no"]), []
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        v.append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()
    if not v:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"hane": k["hane"], "memnuniyet": k["memnuniyet"], "donem": len(v),
         "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35)}
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, DOG, SIN = K[:756], K[756:1008], K[1008:]
ALAN = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
        "kisi_basi", "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
ORTALAMA = {a: statistics.fmean(x[a] for x in EGT) for a in ALAN}
SAPMA = {a: max(1e-9, statistics.pstdev([x[a] for x in EGT])) for a in ALAN}
TABAN = sum(x["supheli"] == 0 for x in SIN) / len(SIN)


def kume(veri):
    return ([[(x[a] - ORTALAMA[a]) / SAPMA[a] for a in ALAN] for x in veri],
            [float(x["supheli"]) for x in veri])


XE, YE = kume(EGT[:300])                    # KUCULTULMUS egitim kumesi
XD, YD = kume(DOG)
XS, YS = kume(SIN)


def sikistir(z):                            # S bicimli islev, tasma korumali
    if z >= 0:
        return 1.0 / (1.0 + math.exp(-z)) if z < 700 else 1.0
    e = math.exp(z) if z > -700 else 0.0
    return e / (1.0 + e)


def dogrultulmus(z):
    return z if z > 0.0 else 0.0


def ilklendir(tohum, kat):                  # kazanc olcekli ilklendirme, yanma sifir
    r, ag = uretec(tohum), []
    for a, b in zip(kat, kat[1:]):
        o = math.sqrt(2.0 / a)
        ag.append(([[(r() * 2 - 1) * o for _ in range(a)] for _ in range(b)], [0.0] * b))
    return ag


def dizey_carp(W, y, a):                    # DIZEY CARPIMI elle: her satir bir birim
    return [sum(w * v for w, v in zip(satir, a)) + yanma
            for satir, yanma in zip(W, y)]


def ileri(ag, x):                           # ILERI GECIS: katman katman
    a = x
    for k, (W, y) in enumerate(ag):
        z = dizey_carp(W, y, a)
        a = [sikistir(z[0])] if k == len(ag) - 1 else [dogrultulmus(t) for t in z]
    return a


def kayip(a, y):                            # capraz duzensizlik
    return -(math.log(max(a, 1e-12)) if y else math.log(max(1 - a, 1e-12)))


def geri_yayilim(ag, X, Y, iz=False):       # MODELDIR: zincir kurali katman katman
    gW = [[[0.0] * len(W[0]) for _ in W] for W, _ in ag]
    gy = [[0.0] * len(y) for _, y in ag]
    toplam = 0.0
    for i in range(len(X)):
        a, ara = X[i], [X[i]]               # ILERI GECIS: ara degerler saklanir
        for k, (W, y) in enumerate(ag):
            z = dizey_carp(W, y, a)
            a = [sikistir(z[0])] if k == len(ag) - 1 else [dogrultulmus(q) for q in z]
            ara.append(a)
        toplam += kayip(ara[-1][0], Y[i])
        d = [ara[-1][0] - Y[i]]             # cikis katmani: kayip ile sigmoit sadelesir
        for k in range(len(ag) - 1, -1, -1):
            W, a = ag[k][0], ara[k]         # a: bu katmanin GIRDISI
            if iz:
                print(f"  katman {k + 1}: geriye gelen {len(d)} deger, ilk iki "
                      f"{[round(v, 5) for v in d[:2]]}")
            for j, dj in enumerate(d):      # agirlik egimi = geriye gelen x girdi
                s = gW[k][j]
                for p in range(len(a)):
                    s[p] += dj * a[p]
                gy[k][j] += dj              # yanma egimi = geriye gelen
            if k:                           # onceki katmana tasi, etkinlestirme turevi ile
                onceki = [0.0] * len(a)
                for j, dj in enumerate(d):
                    s = W[j]
                    for p in range(len(a)):
                        onceki[p] += dj * s[p]
                d = [onceki[p] if a[p] > 0 else 0.0 for p in range(len(a))]
    return gW, gy, toplam / len(X)


ag = ilklendir(TOHUM + 1, [12, 8, 1])
print(f"tek abone, gercek etiket {int(YE[0])}, agin ciktisi {ileri(ag, XE[0])[0]:.5f}")
gW, gy, k0 = geri_yayilim(ag, XE[:1], YE[:1], iz=True)
print(f"  kayip {k0:.5f}")
print(f"  cikis katmani agirlik egimi, ilk uc "
      f"{[round(v, 5) for v in gW[1][0][:3]]}")
print(f"  ara katman 1. birimin agirlik egimi, ilk uc "
      f"{[round(v, 5) for v in gW[0][0][:3]]}")
print(f"  ara katman yanma egimleri, ilk uc {[round(v, 5) for v in gy[0][:3]]}")
tek abone, gercek etiket 0, agin ciktisi 0.49369
  katman 2: geriye gelen 1 deger, ilk iki [0.49369]
  katman 1: geriye gelen 8 deger, ilk iki [0.0, 0.00155]
  kayip 0.68061
  cikis katmani agirlik egimi, ilk uc [0.0, 0.15433, 0.18243]
  ara katman 1. birimin agirlik egimi, ilk uc [0.0, 0.0, 0.0]
  ara katman yanma egimleri, ilk uc [0.0, 0.00155, -0.03503]

Çıkış katmanına gelen değer 0,49369, yani ağın çıktısı eksi gerçek etiket. Ara katmana sekiz değer iniyor ve birincisi tam olarak 0,0. Nedeni 02’de ölçülmüştü: o birim bu abone için etkin değil, doğrultulmuş işlev girdisini kesmiş, türevi 0 ve zincir orada kopuyor. Sonucu son iki satırda görünüyor — birinci birimin on iki ağırlık eğimi de sıfır ve yanma eğimi de sıfır. Bu abone o birime hiçbir şey öğretemiyor.

Sayısal Türevle Doğrulama

Elle yazılan bir türev sessizce yanlış olabilir: kod çalışır, kayıp düşer, sayı üretilir ve hata yalnız başarımın biraz düşük kalmasıyla kendini belli eder. Bağımsız sınama, kaybı parametrenin küçük bir oynamasıyla iki kez ölçüp farkı almaktır. Yavaştır, ama ağın yapısı hakkında hiçbir şey bilmediği için zincir kuralıyla aynı hatayı yapmaz.

def ortalama_kayip(ag, X, Y):
    return sum(kayip(ileri(ag, x)[0], y) for x, y in zip(X, Y)) / len(X)


def sayisal_turev(ag, X, Y, eps):           # merkezi fark, her parametre icin iki gecis
    gW = [[[0.0] * len(W[0]) for _ in W] for W, _ in ag]
    gy = [[0.0] * len(y) for _, y in ag]
    gecis = 0
    for k, (W, y) in enumerate(ag):
        for j in range(len(W)):
            for p in range(len(W[j]) + 1):
                hedef, dizin = (W[j], p) if p < len(W[j]) else (y, j)
                eski = hedef[dizin]
                hedef[dizin] = eski + eps
                art = ortalama_kayip(ag, X, Y)
                hedef[dizin] = eski - eps
                eksi = ortalama_kayip(ag, X, Y)
                hedef[dizin] = eski
                deger = (art - eksi) / (2 * eps) * len(X)
                gecis += 2 * len(X)
                if p < len(W[j]):
                    gW[k][j][p] = deger
                else:
                    gy[k][j] = deger
    return gW, gy, gecis


def kontrol(ag, X, Y, eps):                 # elle yazilan egim ile sayisal turev
    aW, ay, _ = geri_yayilim(ag, X, Y)
    sW, sy, gecis = sayisal_turev(ag, X, Y, eps)
    f = []
    for k in range(len(ag)):
        for j in range(len(aW[k])):
            for p in range(len(aW[k][j])):
                f.append((abs(aW[k][j][p] - sW[k][j][p]), abs(aW[k][j][p])))
            f.append((abs(ay[k][j] - sy[k][j]), abs(ay[k][j])))
    return len(f), max(a for a, _ in f), max(a / max(b, 1e-12) for a, b in f), gecis


def egit(ag, X, Y, adim, tur):              # MODELDIR: tam toplu egim inisi
    n = len(X)
    for _ in range(tur):
        gW, gy, _ = geri_yayilim(ag, X, Y)
        for k, (W, y) in enumerate(ag):
            for j in range(len(W)):
                s, g = W[j], gW[k][j]
                for p in range(len(s)):
                    s[p] -= adim * g[p] / n
                y[j] -= adim * gy[k][j] / n
    return ag


ALT_X, ALT_Y = XE[:20], YE[:20]             # denetim alt kumesi, 20 satir
EGITILMIS = egit(ilklendir(TOHUM + 1, [12, 8, 1]), XE, YE, 0.3, 100)
print(f"{'ag':<24} {'eps':>7} {'parametre':>10} {'en buyuk mutlak':>16} "
      f"{'en buyuk bagil':>15}")
for etiket, a in (("ilklendirmede", ilklendir(TOHUM + 1, [12, 8, 1])),
                  ("100 tur egitimden sonra", EGITILMIS)):
    for eps in (1e-3, 1e-5, 1e-7):
        n, mutlak, bagil, gecis = kontrol(a, ALT_X, ALT_Y, eps)
        print(f"{etiket:<24} {eps:>7g} {n:>10} {mutlak:>16.3e} {bagil:>15.3e}")
ag                           eps  parametre  en buyuk mutlak  en buyuk bagil
ilklendirmede              0.001        113        2.199e-07       1.583e-07
ilklendirmede              1e-05        113        2.193e-10       4.567e-08
ilklendirmede              1e-07        113        2.591e-08       4.932e-06
100 tur egitimden sonra    0.001        113        2.518e-01       2.881e+00
100 tur egitimden sonra    1e-05        113        6.441e-11       1.227e-09
100 tur egitimden sonra    1e-07        113        5.694e-09       2.697e-07

İlk üç satır sayısal türevin kendi hatasını gösteriyor. Adım büyükken kesme hatası baskındır, küçükken yuvarlama hatası; en iyi uyum ortada, 1e-05’te, 2,193e-10 ile. Yani 113 parametrenin hepsinde iki yöntem yaklaşık on anlamlı basamağa kadar aynı sayıyı veriyor. Bu, elle yazılan zincir kuralının doğru olduğunun kanıtıdır.

Dördüncü satır bir tuzaktır ve ölçülmeden geçilmemelidir. Eğitilmiş ağda, adım 0,001 seçildiğinde en büyük mutlak fark 2,518e-01’e, bağıl fark 2,881’e fırlıyor. Bu bir hesap hatası değildir; aynı ağ aynı adımda 1e-05’te 6,441e-11 veriyor. Neden doğrultulmuş birimin kırığıdır: 0,001’lik bir oynama, ağırlıklı toplamı sıfırın bir yanından öbür yanına geçirmeye yetiyor ve merkezi farkın iki ucu işlevin iki ayrı doğrusal parçasına düşüyor. Doğrulama başarısız olduğunda ilk bakılacak yer kod değil, adım büyüklüğüdür.

Maliyet: Kaç Geçiş

İki yöntem aynı sayıyı üretiyorsa hangisinin kullanılacağı maliyete bakar. Geri yayılım bir ileri ve bir geri geçişte bütün parametrelerin eğimini çıkarır. Sayısal türev her parametre için ayrı iki geçiş ister.

print(f"{'birim':>6} {'parametre':>10} {'geri yayilim':>13} {'sayisal turev':>14} "
      f"{'oran':>7}")
for h in (2, 8, 32):
    kat = [12, h, 1]
    P = sum(a * b + b for a, b in zip(kat, kat[1:]))
    print(f"{h:>6} {P:>10} {len(ALT_X):>13} {2 * P * len(ALT_X):>14} {2 * P:>7}")

print(f"\n{'kosum':>6} {'egt_kayip':>10} {'dogrulama':>10} {'sinama':>8}")
SIN_SAYI = []
for kosum in (1, 2):
    ag = egit(ilklendir(TOHUM + kosum, [12, 8, 1]), XE, YE, 0.3, 100)
    _, _, k = geri_yayilim(ag, XE, YE)
    d = sum((ileri(ag, x)[0] > 0.5) == (y > 0.5) for x, y in zip(XD, YD)) / len(XD)
    s = sum((ileri(ag, x)[0] > 0.5) == (y > 0.5) for x, y in zip(XS, YS)) / len(XS)
    SIN_SAYI.append(s)
    print(f"{kosum:>6} {k:>10.4f} {d:>10.4f} {s:>8.4f}")
print(f"taban cizgisi {TABAN:.4f}, agin sayisi {SIN_SAYI[0]:.4f}, "
      f"ikinci kosum {SIN_SAYI[1]:.4f}, fark "
      f"{round(abs(SIN_SAYI[0] - SIN_SAYI[1]) * len(XS))} abone")
 birim  parametre  geri yayilim  sayisal turev    oran
     2         29            20           1160      58
     8        113            20           4520     226
    32        449            20          17960     898

 kosum  egt_kayip  dogrulama   sinama
     1     0.3597     0.8214   0.7857
     2     0.3646     0.8056   0.7698
taban cizgisi 0.7579, agin sayisi 0.7857, ikinci kosum 0.7698, fark 4 abone

Geri yayılım sütunu üç satırda da 20’dir — örnek sayısı kadar. Sayısal türev sütunu parametre sayısıyla birlikte büyüyor: 1.160, 4.520, 17.960. Oran parametre sayısının iki katıdır ve 32 birimlik ağda 898’e çıkıyor. Geri yayılımın maliyeti parametre sayısından bağımsızdır; işi ileri geçişte saklanan ara değerler yapar. Sayısal türev bu yüzden bir eğitim yöntemi değil, bir denetim aracıdır.

Son satır dört dersin sayısını doğruluyor. Bu bölümde eğitim geri_yayilim ile yürütüldü ve üçlü 01’dekiyle aynı çıktı: 0,7579 — 0,7857 — 0,7698. Bunun nedeni açıktır; 01’den beri koşan kod zaten geri yayılımdı, yalnız adı konmamıştı.

Özet

  • Bir ağırlığın eğimi, beslediği birime geriye gelen değer ile o ağırlığın çarptığı girdinin çarpımıdır; geriye gelen değer sonraki katmandan taşınıp etkinleştirmenin türeviyle çarpılır.
  • Etkin olmayan bir birimin bütün eğimleri sıfırdır: örnek abonede ara katmanın birinci biriminin on iki ağırlık eğimi de yanma eğimi de 0,0 çıkıyor.
  • Merkezi farkla doğrulama 113 parametrenin hepsinde uyuyor: ilklendirmede en büyük mutlak fark 2,193e-10, yüz tur eğitimden sonra 6,441e-11. Adım çok büyük ya da çok küçük seçildiğinde sayısal türevin kendi hatası büyüyor.
  • Eğitilmiş ağda adım 0,001’de fark 2,518e-01’e fırlıyor; bu doğrultulmuş birimin kırığıdır, hesap hatası değil. Aynı ağ 1e-05’te 6,441e-11 veriyor.
  • Geri yayılım 113 eğimin hepsini örnek başına bir ileri ve bir geri geçişle çıkarıyor; sayısal türev aynı iş için 4.520 geçiş istiyor ve oran parametre sayısıyla büyüyor: 58, 226, 898.

Sonraki Adım

Eğim artık doğru hesaplanıyor ve ucuz. Ama onunla ne yapılacağı beş derste hiç sorulmadı: her turda eğim aynı sabit sayıyla çarpılıp ağırlıklardan çıkarıldı, hep aynı büyüklükte, önceki turların ne yaptığına hiç bakılmadan. Aynı eğimi başka türlü kullanmak — önceki adımların yönünü hatırlamak ya da her parametreye kendi adım büyüklüğünü vermek — aynı tur bütçesinde hangi kayba varır ve iki koşum arasında ne kadar oynar, ölçülmedi. Sonraki ders eğimi bir sonuç değil bir girdi olarak ele alır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat