ML Temelleri · Modül 4 · 28 dakika

Bir MLP eğitmek

Bir çok katmanlı algılayıcının (multilayer perceptron, MLP)çok katmanlı algılayıcı (multilayer perceptron, MLP): Aralarında aktivasyon fonksiyonu bulunan birkaç yoğun katmanın art arda dizilmesiyle kurulan sinir ağı. ileri geçişini (forward pass) ve geri geçişini (backward pass) yazdın. Eğitmek bu iki geçişi, bir güncelleme adımıyla bir döngüye koymaktır. Döngü çalışır, ama üç seçim sonucu belirler: ağırlıkların (weight) nereden başladığı, adımın nasıl atıldığı ve hangi ayarların sınandığı.

Bu derste şunları yapacaksın:

  • İki ay (two moons) verisinde iki katmanlı bir MLP’yi baştan sona eğiteceksin.
  • İlklendirmenin (initialization)ilklendirme (initialization): Eğitimden önce weight'lere ilk değerlerini verme; sıfır ya da eşit değerler nöronları ayrışmaz bırakır, ölçek yanlışsa aktivasyonlar layer'lar boyunca söner ya da büyür. neden önemli olduğunu göreceksin ve Xavier ile He formüllerini yazacaksın.
  • Momentummomentum: Gradyanların hareketli bir toplamını (hızı) tutan ve adımı bu hızla atan optimizasyon yöntemi; aynı yöne giden gradyanlar birikir, zikzak sönümlenir. ve Adam’ın tek bir güncelleme adımını kodlayacaksın.
  • Hiperparametreleri (hyperparameter)hiperparametre (hyperparameter): Eğitimin öğrenmediği, eğitimden önce seçilen ayar: öğrenme oranı, gizli nöron sayısı, epoch sayısı gibi. bir doğrulama kümesiyle (validation set)doğrulama kümesi (validation set): Eğitimde kullanılmayan, hiperparametreleri seçmek ve aşırı öğrenmeyi görmek için ayrılmış örnek kümesi. seçeceksin.

Bu ders Yoğun katmanın geri geçişi ve gradyan kontrolü dersinin devamıdır. Kod yalnızca NumPy kullanır.

Eğitim döngüsü

Eğitim döngüsü dört adımı tekrarlar: forward pass, kayıp (loss), backward pass, güncelleme. Bu bölümde döngüyü en yalın haliyle kurarsın.

İki ay verisi

Veri, iki yarım daireden oluşur. Her yarım daire bir sınıftır (0 ya da 1). Noktalar biraz gürültüyle dağılır. Tek bir doğru iki sınıfı ayıramaz; MLP’nin işi budur.

Üreteç sabit bir tohumla (seed) çalışır, böylece her çalıştırmada aynı veriyi alırsın. İki ay veri kümesinde MLP eğit problemi de aynı üreteci kullanır.

import numpy as npdef make_moons(n: int, noise: float = 0.2, seed: int = 0):    """Dönüş: X shape (n, 2); y shape (n,), üst ay için 0, alt ay için 1."""    rng = np.random.default_rng(seed)    n_upper = n // 2    n_lower = n - n_upper    t_upper = np.linspace(0, np.pi, n_upper)    t_lower = np.linspace(0, np.pi, n_lower)    upper = np.column_stack([np.cos(t_upper), np.sin(t_upper)])    lower = np.column_stack([1 - np.cos(t_lower), 0.5 - np.sin(t_lower)])    X = np.vstack([upper, lower]) + rng.normal(0.0, noise, (n, 2))    y = np.array([0] * n_upper + [1] * n_lower)    order = rng.permutation(n)    return X[order], y[order]X, y = make_moons(500, 0.15, seed=0)X_train, y_train = X[:300], y[:300]   # ilk 300 örnek eğitim içinX_val, y_val = X[300:], y[300:]       # son 200 örnek ileride doğrulama içinprint(X_train.shape, X_val.shape)     # → (300, 2) (200, 2)print(round(y_train.mean(), 3), y_val.mean())   # → 0.493 0.51

Validation set’i ilerideki bölümde kullanacaksın. Şimdilik yalnızca eğitim kümesiyle (training set) çalış: X_train.

Dört adım

Her döngü turu şu adımlardan oluşur:

  1. Forward pass: X’ten tahmin pp‘yi hesapla.
  2. Loss: pp ile y’yi karşılaştır ve tek bir sayı çıkar.
  3. Backward pass: loss’un her parametreye göre gradyanını hesapla.
  4. Güncelleme: her parametreyi gradyanının tersine kaydır.

Ağın iki katmanı (layer) var. Gizli katman (hidden layer) tanh⁡\tanh kullanır; çıkış nöronu sigmoid kullanır ve pp‘yi verir:

A1=tanh⁡(XW1+b1),p=σ(A1W2+b2)A_1 = \tanh(XW_1 + \mathbf{b}_1), \qquad p = \sigma(A_1 W_2 + b_2)

Loss, Lojistik regresyon dersindeki ikili çapraz entropidir (binary cross-entropy).

Forward pass ve loss

Parametreleri bir listede tutarsın: [W1, b1, W2, b2]. Forward pass, backward pass için A1A_1‘i de döndürür.

İlk deneme için weight’ler küçük rastgele sayılardır. Sapmalar (bias) sıfırdır. Initialization bir sonraki bölümün konusu.

def sigmoid(z):    return 1 / (1 + np.exp(-z))def forward(X, params):    W1, b1, W2, b2 = params    A1 = np.tanh(X @ W1 + b1)    p = sigmoid(A1 @ W2 + b2).ravel()    return A1, pdef bce(p, y, eps=1e-12):    return -np.mean(y * np.log(p + eps) + (1 - y) * np.log(1 - p + eps))rng = np.random.default_rng(0)params = [rng.normal(0, 0.5, (2, 8)), np.zeros(8),          rng.normal(0, 0.5, (8, 1)), np.zeros(1)]A1, p = forward(X_train, params)print(A1.shape, p.shape)           # → (300, 8) (300,)print(round(bce(p, y_train), 4))   # → 0.7136

Ağ henüz hiçbir şey öğrenmedi. Loss, her örneğe 0,5 diyen bir modelin loss’una yakındır: ln⁡2≈0,693\ln 2 \approx 0{,}693.

Backward pass

Backward pass, Yoğun katmanın geri geçişi ve gradyan kontrolü dersindeki formüllerin iki layer’a uygulanmasıdır. Sigmoid ile binary cross-entropy birlikte çok sade bir gradyan verir: çıkış layer’ına gelen gradyan dZ2=(p−y)/NdZ_2 = (p - y)/N olur.

Hidden layer’da tanh⁡\tanh‘ın yerel türevi 1−A121 - A_1^2 devreye girer.

def backward(X, y, params, A1, p):    W1, b1, W2, b2 = params    dZ2 = (p - y).reshape(-1, 1) / len(X)     # sigmoid + BCE    dW2 = A1.T @ dZ2    db2 = dZ2.sum(axis=0)    dZ1 = (dZ2 @ W2.T) * (1 - A1 ** 2)        # tanh'ın yerel türevi    dW1 = X.T @ dZ1    db1 = dZ1.sum(axis=0)    return [dW1, db1, dW2, db2]grads = backward(X_train, y_train, params, A1, p)print([g.shape for g in grads])    # → [(2, 8), (8,), (8, 1), (1,)]

Yanlış yazılmış bir backward pass hata vermez. Bu yüzden bir parametreyi 10−510^{-5} kaydırıp merkezi farkla sına:

h = 1e-5shifted = [w.copy() for w in params]shifted[0][0, 0] += hup = bce(forward(X_train, shifted)[1], y_train)shifted[0][0, 0] -= 2 * hdown = bce(forward(X_train, shifted)[1], y_train)print(round((up - down) / (2 * h), 6), round(grads[0][0, 0], 6))   # → 0.055138 0.055138

Döngü

Her parametre için tek bir kural uygulanır: w←w−η dww \leftarrow w - \eta\,dw. Burada η\eta öğrenme oranıdır (learning rate).

Training set’in tamamı her turda kullanılır; böyle bir tura epoch denir. Mini yığınlar (mini-batch) Ölçekleme, mini-batch ve kapalı form dersindeki gibi araya girebilir.

lr = 0.5for epoch in range(1, 501):    A1, p = forward(X_train, params)    grads = backward(X_train, y_train, params, A1, p)    params = [w - lr * g for w, g in zip(params, grads)]    if epoch in (1, 10, 100, 500):        accuracy = round(np.mean((p > 0.5) == y_train), 3)        print(epoch, round(bce(p, y_train), 4), accuracy)# → 1 0.7136 0.41# → 10 0.3579 0.843# → 100 0.244 0.883# → 500 0.114 0.957

Loss düşer, doğruluk (accuracy) yükselir. Loss önce hızlı, sonra yavaş düşer.

Kısa soruEğitim döngüsünün dört adımı hangi sıradadır?

Cevabı göster

Forward pass, loss, backward pass, güncelleme. Önce tahmin üretilir, sonra loss ölçülür, sonra gradyanlar hesaplanır ve en sonda parametreler güncellenir.

Initialization

Initialization, eğitimin başladığı noktayı seçer ve yanlış seçilirse eğitim hiç ilerlemez. Bu bölümde sıfırın neden olmadığını ve doğru ölçeğin nasıl bulunduğunu göreceksin.

Sıfırdan başlamak olmaz

Bütün weight’leri sıfır yapmak akla yatkın görünür. İki sorun çıkar.

İlk sorun: W2W_2 sıfırsa hidden layer’a hiç gradyan ulaşmaz. İkincisi: W1W_1 sıfırsa gizli nöronların hepsi aynı çıktıyı verir.

zeros = [np.zeros((2, 4)), np.zeros(4), np.zeros((4, 1)), np.zeros(1)]A1, p = forward(X_train, zeros)grads = backward(X_train, y_train, zeros, A1, p)print(np.abs(grads[0]).max(), np.abs(grads[2]).max(), grads[3])   # → 0.0 0.0 [0.00666667]

Yalnızca b2b_2 hareket eder. Ağ, her örnek için aynı sabit tahmini öğrenir.

Sıfır olmayan ama eşit bir değer de işe yaramaz. Aşağıda bütün weight’ler 0,5’tir.

Nöronlar aynı başlarsa aynı gradyanı alır ve hep aynı kalır. Buna simetri sorunu (symmetry problem) denir.

# forward, backward, bce, X_train, y_train önceki bloklardandef train_gd(params, steps, lr=0.5):    for _ in range(steps):        A1, p = forward(X_train, params)        grads = backward(X_train, y_train, params, A1, p)        params = [w - lr * g for w, g in zip(params, grads)]    return paramssame = train_gd([np.full((2, 4), 0.5), np.zeros(4), np.full((4, 1), 0.5), np.zeros(1)], 1000)rng = np.random.default_rng(1)rand = train_gd([rng.normal(0, 0.5, (2, 4)), np.zeros(4), rng.normal(0, 0.5, (4, 1)), np.zeros(1)], 1000)print(np.round(same[0], 3))# → [[ 0.179  0.179  0.179  0.179]#    [-0.882 -0.882 -0.882 -0.882]]print(round(bce(forward(X_train, same)[1], y_train), 3), round(bce(forward(X_train, rand)[1], y_train), 3))   # → 0.243 0.069

Başlangıcı sıfır, hepsi 0,5 ya da rastgele seç ve adımları ilerlet; her nöronun (wx,wy)(w_x, w_y) yolunu izle:

init_symmetry
−4−4−2−2002244nöron ağırlıkları (wₓ, w_y)0,00,20,40,60,802505007501000sıfırhepsi 0,5n1n2n3n4lossadım

adım 0 / 1000 · loss 0,741 · nöron 1: w = (0,173; 0,453) · en büyük uzaklık 1,07 her nöron başka bir gradyan alır, ayrışırlar: loss tek nöronun çok altına iner

  • “hepsi 0,5” ile 1000 adıma kadar ilerlet. Dört nöronun yolu üst üste biner ve loss 0,243’te kalır.
  • “rastgele” ile aynı adım sayısına git. Nöronlar ayrılır ve loss 0,069’a iner.
  • “sıfır” ile adım at. Weight’ler hiç hareket etmez.

1000 adım sonra dört nöronun weight’leri hâlâ birbirinin aynısıdır. Dört nöron tek nöron gibi çalışır ve loss 0,24’te kalır; rastgele başlayan aynı ağ 0,07’ye iner. Çözüm: weight’leri rastgele ve doğru ölçekte seçmek.

Varyans argümanı

Ölçeğin önemi bir toplamdan gelir. Bir nöronun girdisi şu toplam olsun:

z=∑i=1dinwixiz = \sum_{i=1}^{d_{\text{in}}} w_i x_i

Weight’ler ve girdiler bağımsız, ortalamaları sıfırdır. Bu durumda dind_{\text{in}} terim toplanır ve varyanslar da toplanır. Girdinin varyansını Var⁡(x)\operatorname{Var}(x) ile göster:

Var⁡(z)=din⋅Var⁡(w)⋅Var⁡(x)\operatorname{Var}(z) = d_{\text{in}} \cdot \operatorname{Var}(w) \cdot \operatorname{Var}(x)

Varyansın bir layer’dan ötekine aynı kalmasını istiyoruz: Var⁡(z)=Var⁡(x)\operatorname{Var}(z) = \operatorname{Var}(x) olsun. Bunun için din⋅Var⁡(w)=1d_{\text{in}} \cdot \operatorname{Var}(w) = 1 olmalıdır.

  • Var⁡(w)\operatorname{Var}(w) çok küçükse aktivasyonlar layer’dan layer’a küçülür ve sıfıra iner.
  • Var⁡(w)\operatorname{Var}(w) çok büyükse aktivasyonlar büyür; tanh⁡\tanh ile doygunluğa (saturation) ulaşır.

Aşağıda 10 layer’lık bir tanh⁡\tanh ağı ve her layer’ın çıktısının kareler ortalamasının karekökü (root mean square, rms) var. Girdinin rms’i 1’dir.

rng = np.random.default_rng(0)x = rng.normal(size=(1000, 256))def layer_rms(std, activation, depth=10):    a, out = x, []    for _ in range(depth):        W = rng.normal(0, std, (256, 256))        a = activation(a @ W)        out.append(np.sqrt((a ** 2).mean()))    return np.round(out, 4)print(layer_rms(0.01, np.tanh)[[0, 4, 9]])        # → [1.569e-01 1.000e-04 0.000e+00]print(layer_rms(1.0, np.tanh)[[0, 4, 9]])         # → [0.9747 0.974  0.9741]print(layer_rms(np.sqrt(1 / 256), np.tanh)[[0, 4, 9]])   # Var(w) = 1/d_in → [0.6275 0.3195 0.2298]

Birinci satırda weight’ler çok küçüktür; aktivasyon 5. layer’da sıfıra yakındır. İkinci satırda rms 1’e yakın kalır, ama bu iyi haber değildir.

Orada tanh⁡\tanh doygunlukta çalışır: çoğu çıktı ±1\pm 1‘e yapışır ve 1−a21 - a^2 türevi sıfıra gider. Bu, Nöronlar ve aktivasyonlar dersindeki vanishing gradient’tir.

Üçüncü satır doğru ölçeğe yaklaşır: tanh⁡\tanh çıktıyı sıkıştırdığı için rms yine de yavaşça küçülür, ama kaybolmaz.

Xavier ve He

Varyans argümanı ileri yönde Var⁡(w)=1/din\operatorname{Var}(w) = 1/d_{\text{in}} ister. Aynı argüman geri yönde, gradyanlar için 1/dout1/d_{\text{out}} ister. İkisi uyuşmaz; Xavier initialization ortada buluşur:

Var⁡(w)=2din+dout\operatorname{Var}(w) = \frac{2}{d_{\text{in}} + d_{\text{out}}}

Xavier, sıfır çevresinde doğrusala yakın davranan tanh⁡\tanh için türetildi; sigmoid ile de sık kullanılır. ReLU girdilerin yarısını sıfırlar; ReLU çıktılarının karelerinin ortalaması yarıya iner. He initialization bunu telafi eder:

Var⁡(w)=2din\operatorname{Var}(w) = \frac{2}{d_{\text{in}}}

Şu tabloya bak:

YöntemVar⁡(w)\operatorname{Var}(w)Hangi aktivasyon
Xavier2/(din+dout)2/(d_{\text{in}} + d_{\text{out}})tanh, sigmoid
He2/din2/d_{\text{in}}ReLU

ReLU ile 10 layer’ın sonunda fark açıkça görünür:

relu = lambda z: np.maximum(0, z)print(layer_rms(np.sqrt(2 / 512), relu)[[0, 4, 9]])   # Xavier → [0.7077 0.1653 0.0325]print(layer_rms(np.sqrt(2 / 256), relu)[[0, 4, 9]])   # He     → [1.0017 1.029  1.0911]

Xavier ReLU’da her layer’da küçülür; He ise rms’i yaklaşık 1’de tutar.

init_weights fonksiyonu

İki formül tek fonksiyona sığar. Xavier ve He ilklendirmesi probleminin istediği fonksiyon budur. Fonksiyon, her layer çifti için normal dağılımdan bir weight matrisi çeker. Bilinmeyen bir yöntem ValueError verir:

def init_weights(layer_sizes, method="he", seed=0):    if method not in ("xavier", "he"):        raise ValueError(f"Bilinmeyen yöntem: {method!r}")    rng = np.random.default_rng(seed)    weights = []    for d_in, d_out in zip(layer_sizes[:-1], layer_sizes[1:]):        if method == "xavier":            std = np.sqrt(2 / (d_in + d_out))        else:            std = np.sqrt(2 / d_in)        weights.append(rng.normal(0.0, std, (d_in, d_out)))    return weightsfor method in ("xavier", "he"):    Ws = init_weights([100, 50, 10], method)    print(method, [W.shape for W in Ws], [round(float(W.std()), 3) for W in Ws])# → xavier [(100, 50), (50, 10)] [0.115, 0.185]# → he [(100, 50), (50, 10)] [0.141, 0.202]

Bias’lar sıfırla başlar; simetriyi weight’ler bozar.

Dikkat

Standart sapma Var⁡(w)\sqrt{\operatorname{Var}(w)}‘dir, varyansın kendisi değil. rng.normal ikinci argüman olarak standart sapma ister; formüldeki değeri karekök almadan verirsen ölçek yanlış çıkar.

Kısa soruBir ağın bütün weight'leri 0,5'e eşitlenip eğitildi. Hidden layer'daki nöronlara ne olur?

Cevabı göster

Hepsi aynı gradyanı alır ve eğitim boyunca birbirinin aynısı kalır. Bu symmetry problem'dir. Weight'ler eşit başladığı için güncellemeler de eşit olur; nöronlar hiç ayrışmaz ve ağ tek nöron gibi çalışır.

Optimizasyon

Güncelleme kuralı, eğitimin ne kadar hızlı ilerlediğini belirler. Düz gradyan inişi (gradient descent) uzun çanaklarda yavaştır. Momentum ve Adam, adımı geçmişteki gradyanlara göre ayarlar.

Uzun çanakta gradient descent

Gradyan inişi dersindeki çanağı hatırla: iki yönde çok farklı eğimi olan bir loss yüzeyi.

L=12w12+5 w22L = \frac{1}{2}w_1^2 + 5\,w_2^2

Gradyan (w1, 10 w2)(w_1,\ 10\,w_2)‘dir. w2w_2 yönü, w1w_1 yönünden 10 kat diktir.

Learning rate’i dik yönün belirlediği sınırın altında tutman gerekir. Sınır yaklaşık 2/10=0,22/10 = 0{,}2‘dir. Başlangıç noktası (−4,5; 2)(-4{,}5;\ 2) olsun.

def bowl_grad(w):    return np.array([w[0], 10 * w[1]])def bowl_loss(w):    return 0.5 * w[0] ** 2 + 5 * w[1] ** 2def run_gd(lr, steps):    w = np.array([-4.5, 2.0])    for _ in range(steps):        w = w - lr * bowl_grad(w)    return wfor lr in (0.18, 0.21):    print(lr, f"{bowl_loss(run_gd(lr, 30)):.3g}")# → 0.18 9.89e-05# → 0.21 6.09e+03

η=0,21\eta = 0{,}21‘de w2w_2 her adımda büyür ve loss patlar. η=0,18\eta = 0{,}18 güvenlidir, ama w2w_2 her adımda işaret değiştirir: zikzak çizer.

Yatay yönde ise her adım yalnızca w1w_1‘in %18’ini kapatır. En iyi güvenli learning rate ile bile loss’un 10−310^{-3}‘ün altına inmesi 25 adım sürer.

w = np.array([-4.5, 2.0])for _ in range(4):    w = w - 0.18 * bowl_grad(w)    print(np.round(w, 3))# → [-3.69 -1.6 ]# → [-3.026  1.28 ]# → [-2.481 -1.024]# → [-2.035  0.819]

w1w_1 yavaşça ilerler, w2w_2 zıplar. Zikzağı bastırıp ilerleyişi biriktiren bir yöntem lazım.

Momentum

Momentum, gradyanların bir hareketli toplamını tutar. Bu toplama hız (velocity) denir. Her adımda eski velocity β\beta ile çarpılır ve yeni gradyan eklenir:

v←β v+∇L,w←w−η v\mathbf{v} \leftarrow \beta\,\mathbf{v} + \nabla L, \qquad \mathbf{w} \leftarrow \mathbf{w} - \eta\,\mathbf{v}

Aynı yöne giden gradyanlar birikir, işaret değiştirenler birbirini söndürür. β=0\beta = 0 düz gradient descent’tir. Büyük ağlarda tipik değer 0,9’dur.

Bu yazış PyTorch’unkidir. Bazı kaynaklar v←β v−η ∇L\mathbf{v} \leftarrow \beta\,\mathbf{v} - \eta\,\nabla L ve w←w+v\mathbf{w} \leftarrow \mathbf{w} + \mathbf{v} yazar. Learning rate sabitse iki yazış aynı w\mathbf{w} dizisini verir; yalnızca v\mathbf{v}‘nin ölçeği ve işareti farklıdır.

def momentum_step(w, v, grad, lr=0.01, beta=0.9):    v_new = beta * v + grad    w_new = w - lr * v_new    return w_new, v_neww, v = np.array([-4.5, 2.0]), np.zeros(2)for t in range(1, 4):    w, v = momentum_step(w, v, bowl_grad(w), lr=0.1, beta=0.6)    print(t, np.round(w, 3), np.round(v, 2))# → 1 [-4.05  0.  ] [-4.5 20. ]# → 2 [-3.375 -1.2  ] [-6.75 12.  ]# → 3 [-2.632 -0.72 ] [-7.42 -4.8 ]

Velocity’nin ilk bileşeni hep negatif kalır ve büyüklüğü artar: −4,5-4{,}5, sonra −6,75-6{,}75, sonra −7,42-7{,}42. İkinci bileşen ise 2020, 1212, −4,8-4{,}8 ile hızla söner. Zikzak azalır, w1w_1 hızlanır.

Aynı çanakta kaç adımda loss’un 10−310^{-3}‘ün altına indiğini say. Her yöntem için çalışan bir learning rate seçtik; β=0,9\beta = 0{,}9‘u da dene:

def steps_to(tol, lr, beta):    w, v = np.array([-4.5, 2.0]), np.zeros(2)    for t in range(1, 5001):        w, v = momentum_step(w, v, bowl_grad(w), lr, beta)        if bowl_loss(w) < tol:            return tprint(steps_to(1e-3, 0.18, 0.0))   # düz GD (en iyi lr)  → 25print(steps_to(1e-3, 0.1, 0.6))    # momentum            → 9print(steps_to(1e-3, 0.1, 0.9))    # momentum, beta 0,9  → 74

β\beta‘yı 0,6 ile 0,9 arasında değiştir ve topun yolunu düz gradient descent’inkiyle karşılaştır:

loss_landscape

Kayıp yüzeyi · loss surface · GD η = 0,18 · momentum η = 0,1

-5-2,502,55-2-1012w₁w₂minimumbaşlangıç
10²110⁻²10⁻³10⁻⁶020406080adımkayıp (log)
GD · L < 10⁻³ için adım25
momentum (β = 0,6) · L < 10⁻³ için adım9
momentum β

adım 0 / 80 · GD kayıp = 30,125 · momentum kayıp = 30,125

  • β=0,6\beta = 0{,}6 ile loss’un 10−310^{-3}‘ün altına inmesi 9 adım sürer; düz gradient descent 25 adım sürer.
  • β=0,9\beta = 0{,}9‘a geç. Yol w1w_1‘i aşar ve geri döner; 74 adım gerekir.

Momentum yaklaşık 2,8 kat daha az adım ister.

Ama β=0,9\beta = 0{,}9 bu çanak için fazladır: velocity gereğinden çok birikir, w1w_1‘i aşar ve geri döner; 74 adım gerekir. Bu çanak yalnızca 10 kat dik olduğu için küçük bir β\beta yeter. Momentumlu SGD adımı probleminin istediği fonksiyon momentum_step’tir.

Adam

Adam iki hareketli ortalama tutar: gradyanın kendisi (m\mathbf{m}, birinci moment) ve gradyanın karesi (v\mathbf{v}, ikinci moment). Adımı m\mathbf{m} belirler, v\sqrt{\mathbf{v}} ise adımı her parametre için ayrı ölçekler:

m←β1m+(1−β1)∇L,v←β2v+(1−β2)(∇L)2\mathbf{m} \leftarrow \beta_1 \mathbf{m} + (1 - \beta_1)\nabla L, \qquad \mathbf{v} \leftarrow \beta_2 \mathbf{v} + (1 - \beta_2)(\nabla L)^2

Adım m/v\mathbf{m}/\sqrt{\mathbf{v}} oranıyla belirlenir: gradyanı büyük parametrenin v\sqrt{\mathbf{v}}‘si de büyüktür, oran küçülür; gradyanı küçük olanda oran büyür. Adam, gradyanın ölçeğinden bağımsız adım atar.

Varsayılan değerler β1=0,9\beta_1 = 0{,}9, β2=0,999\beta_2 = 0{,}999 ve ϵ=10−8\epsilon = 10^{-8}‘dir. Learning rate için tipik varsayılan 0,001’dir.

Yanlılık düzeltmesi

m\mathbf{m} ve v\mathbf{v} sıfırdan başlar. İlk adımlarda gerçek değerlerinden küçüktürler: ilk adımda m=0,1 ∇L\mathbf{m} = 0{,}1\,\nabla L olur.

Bu yanlılığı gidermek için yanlılık düzeltmesi (bias correction) uygulanır. Adım sayısı tt 1’den başlar:

m^=m1−β1t,v^=v1−β2t,w←w−η m^v^+ϵ\hat{\mathbf{m}} = \frac{\mathbf{m}}{1 - \beta_1^t}, \qquad \hat{\mathbf{v}} = \frac{\mathbf{v}}{1 - \beta_2^t}, \qquad \mathbf{w} \leftarrow \mathbf{w} - \eta\,\frac{\hat{\mathbf{m}}}{\sqrt{\hat{\mathbf{v}}} + \epsilon}

Bu formüller tek bir fonksiyondur. Adam adımı probleminin istediği fonksiyon budur:

def adam_step(w, m, v, grad, t, lr=0.001, beta1=0.9, beta2=0.999, eps=1e-8):    m_new = beta1 * m + (1 - beta1) * grad    v_new = beta2 * v + (1 - beta2) * grad ** 2    m_hat = m_new / (1 - beta1 ** t)    v_hat = v_new / (1 - beta2 ** t)    w_new = w - lr * m_hat / (np.sqrt(v_hat) + eps)    return w_new, m_new, v_new

İlk adımı iki gradyanla dene: biri 2, öteki −0,5-0{,}5:

g = np.array([2.0, -0.5])lr = 0.1w, m, v = np.zeros(2), np.zeros(2), np.zeros(2)w1, m, v = adam_step(w, m, v, g, t=1, lr=lr)print(w1)                                  # → [-0.1  0.1]print(m, v)                                # → [ 0.2  -0.05] [0.004   0.00025]print(lr * m / (np.sqrt(v) + 1e-8))        # düzeltmesiz → [ 0.31622772 -0.31622757]

Düzeltmeyle ilk adım, gradyanın işaretinin tersine, η=0,1\eta = 0{,}1 uzunluğundadır. Düzeltmesiz adım 0,316 çıkar: ilk adımda m=0,1 g\mathbf{m} = 0{,}1\,g ve v=0,001 ∣g∣≈0,0316 ∣g∣\sqrt{\mathbf{v}} = \sqrt{0{,}001}\,\lvert g\rvert \approx 0{,}0316\,\lvert g\rvert olduğundan oran 0,1/0,0316≈3,20{,}1/0{,}0316 \approx 3{,}2‘dir. İlk adım η\eta‘nın 3,2 katı olurdu.

Gradyanlardan biri 100 kat büyürse bile ilk adım aynı kalır:

g = np.array([200.0, -0.5])w, m, v = np.zeros(2), np.zeros(2), np.zeros(2)print(adam_step(w, m, v, g, t=1, lr=0.1)[0])   # → [-0.1  0.1]

Optimizasyon yöntemi seçimi

Üç yöntemi aynı ağda, aynı başlangıç weight’leriyle 100 epoch eğit. Karşılaştırma adil olsun diye her yöntem için üç learning rate denenir ve en iyisine bakılır.

def fit(optimizer, lr, epochs=100):    W1, W2 = init_weights([2, 16, 1], "xavier", 0)    params = [W1, np.zeros(16), W2, np.zeros(1)]    m = [np.zeros_like(q) for q in params]    v = [np.zeros_like(q) for q in params]    for t in range(1, epochs + 1):        A1, p = forward(X_train, params)        grads = backward(X_train, y_train, params, A1, p)        for i in range(4):            if optimizer == "sgd":                params[i] = params[i] - lr * grads[i]            elif optimizer == "momentum":                params[i], v[i] = momentum_step(params[i], v[i], grads[i], lr)            else:                params[i], m[i], v[i] = adam_step(params[i], m[i], v[i], grads[i], t, lr)    return bce(forward(X_train, params)[1], y_train)for optimizer, lrs in [("sgd", (0.5, 2, 8)), ("momentum", (0.05, 0.2, 0.8)), ("adam", (0.01, 0.03, 0.1))]:    print(optimizer, [round(float(fit(optimizer, lr)), 4) for lr in lrs])# → sgd [0.2428, 0.172, 0.0243]# → momentum [0.2435, 0.231, 0.0287]# → adam [0.2419, 0.1163, 0.0388]

Yöntemi, learning rate’i ve gizli nöron sayısını seç, sonra eğit:

net_playground
optimizer
learning rate
gizli nöron
00,51050100karar sınırıloss · epoch

epoch 0 / 100 · loss 0,6756 · doğruluk %51 Adam · η = 0,03 · 16 nöron Adam, ilk adımda en büyük parametre değişimi 0,030 = 1,00·η (düzeltmeyle tam η) önceki koşu yok

  • Her yöntemin en büyük learning rate’ini dene: SGD 8’de loss 0,0243, momentum 0,8’de 0,0287, Adam 0,1’de 0,0388 olur.
  • Önceki koşu kesikli çizgi olarak kalır. Learning rate’i değiştirip eğrileri karşılaştır.

İyi bir learning rate ile üçü de benzer bir loss’a iner; bu küçük problemde düz gradient descent de yeter. Fark, iyi learning rate’in nerede olduğundadır. Momentum’un etkili adımı η/(1−β)\eta/(1-\beta)‘dır, yani β=0,9\beta = 0{,}9 ile 10 kat büyür. Adam’ın adımı ise gradyanın ölçeğine bağlı değildir.

Pratikte Adam iyi bir ilk seçimdir; momentumlu SGD de sıkça kullanılır. Her ikisinde de learning rate’i sen seçersin.

train_mlp fonksiyonu

Parçalar yerine oturdu: Xavier initialization, Adam ve döngü. Hepsi tek fonksiyonda toplanır. Fonksiyon eğitilmiş parametreleri döndürür:

def evaluate(params, X, y):    p = forward(X, params)[1]    return bce(p, y), np.mean((p > 0.5) == y)def train_mlp(X, y, hidden=16, lr=0.01, epochs=300, seed=0):    W1, W2 = init_weights([2, hidden, 1], "xavier", seed)    params = [W1, np.zeros(hidden), W2, np.zeros(1)]    m = [np.zeros_like(q) for q in params]    v = [np.zeros_like(q) for q in params]    for t in range(1, epochs + 1):        A1, p = forward(X, params)        grads = backward(X, y, params, A1, p)        for i in range(4):            params[i], m[i], v[i] = adam_step(params[i], m[i], v[i], grads[i], t, lr)    return paramsparams = train_mlp(X_train, y_train)for name, (Xs, ys) in {"train": (X_train, y_train), "val": (X_val, y_val)}.items():    loss, acc = evaluate(params, Xs, ys)    print(name, round(loss, 4), acc)# → train 0.1091 0.96# → val 0.1432 0.935

Training set’te %96, validation set’te %93,5 accuracy çıktı. Validation loss’u training loss’undan biraz yüksektir; model eğitim örneklerini az da olsa daha iyi bilir.

İki ay veri kümesinde MLP eğit problemi aynı döngüyü başka bir arayüzle ister: training set’i ve test noktalarını alır, test noktalarının sınıflarını döndürür. Arayüz, train_mlp’nin üstüne ince bir sarmalayıcıdır:

def train(X_train, y_train, X_test):    params = train_mlp(X_train, y_train, hidden=16, lr=0.1, epochs=300)   # 0,1'i bir sonraki bölümdeki tarama seçer    p = forward(X_test, params)[1]    return (p > 0.5).astype(int)          # 0 ya da 1pred = train(X_train, y_train, X_val)     # problemdeki bölme: 300 eğitim, 200 testprint(pred.shape, np.mean(pred == y_val))   # → (200,) 0.995

Problemin Çözüm bölümü ReLU, He ve düz gradient descent kullanır. Önemli olan %95 accuracy’dir.

Kısa soruAdam'da bias correction neden yapılır?

Cevabı göster

m ve v sıfırdan başladığı için ilk adımlarda gerçek değerlerinden küçüktür; düzeltme bunu giderir. İlk adımda m yalnızca 0,1 kat gradyandır. 1 - beta1 üzeri t'ye bölmek m'yi gerçek ölçeğine getirir; v için aynısı yapılır.

Hyperparameter’ler ve validation

Eğitimin kendisinin öğrenmediği ayarlara hyperparameter denir; onları validation set’e bakarak seçersin. Training set bu işe yaramaz.

Hyperparameter nedir

Parametreler eğitimde öğrenilir: weight’ler ve bias’lar. Hyperparameter’leri eğitimden önce sen seçersin. Bu derste şunlar var:

  • learning rate η\eta;
  • hidden layer’daki nöron sayısı;
  • epoch sayısı;
  • momentum için β\beta, Adam için β1\beta_1 ve β2\beta_2;
  • initialization yöntemi.

Gradient descent bu ayarları değiştirmez. Seçim senin işindir.

Üç küme

Hyperparameter’i training loss’una bakarak seçersen en çok ezberleyen ayarı seçersin. Bunun için veriyi üçe böl:

  • eğitim kümesi (training set): parametreler bunun gradyanıyla öğrenilir;
  • doğrulama kümesi (validation set): hiperparametreleri seçmek için kullanılır;
  • test kümesi (test set): en sonda, seçimin bittiğinde bir kez bakılır.

Bu derste test kümesi yok; validation set yeterli. Seçim yaptıktan sonra validation skoru biraz iyimser olur; test kümesi bu yüzden vardır. Bir hyperparameter seçmek için her adayı training set’te eğit, validation set’te ölç.

Learning rate taraması

Learning rate’i 10 kat arayla tara. Her değer için aynı ağı 300 epoch eğit ve validation loss’unu ölç:

# train_mlp, evaluate ve X_val önceki bloklardanfor lr in (0.0001, 0.001, 0.01, 0.1, 1.0, 10.0):    params = train_mlp(X_train, y_train, lr=lr)    loss, acc = evaluate(params, X_val, y_val)    print(lr, round(loss, 3), acc)# → 0.0001 0.604 0.785# → 0.001 0.311 0.855# → 0.01 0.143 0.935# → 0.1 0.019 0.995# → 1.0 0.037 0.99# → 10.0 2.834 0.875   son satır kararsız: sayılar NumPy sürümüne göre değişir

Üç bölge görünür:

  • Çok küçük η\eta (0,0001 ve 0,001): 300 epoch’ta yeterince ilerlenmez. Adam’ın varsayılanı 0,001 burada yavaştır.
  • Orta η\eta (0,01 – 1): iyi çalışır; en düşük validation loss’u η=0,1\eta = 0{,}1‘dedir (0,019). Adam’ın 0,1’deki düşük training loss’u validation’da da en iyisidir.
  • Çok büyük η\eta (10): adımlar aşırı büyüktür ve loss yeniden yükselir.

Learning rate genellikle en etkili hyperparameter’dir. Doğru değer problemden probleme değişir; 10 kat arayla taramak iyi bir başlangıçtır.

Ezberlemeye ilk bakış

Training set küçük ve ağ büyükse ağ training set’i ezberler. Bunu görmek için yalnızca 40 örnekle ve 64 gizli nöronla eğit; validation için yine X_val’i kullan:

X_small, y_small = X[:40], y[:40]     # yalnızca 40 eğitim örneğifor epochs in (30, 100, 300, 3000):    params = train_mlp(X_small, y_small, hidden=64, lr=0.05, epochs=epochs)    tl, ta = evaluate(params, X_small, y_small)    vl, va = evaluate(params, X_val, y_val)    print(epochs, round(tl, 3), ta, round(vl, 3), va)# → 30 0.179 0.9 0.624 0.845# → 100 0.004 1.0 0.217 0.93# → 300 0.001 1.0 0.244 0.935# → 3000 0.0 1.0 0.362 0.94

Kaydırıcıyı epoch’lar boyunca sür ve iki loss eğrisinin ayrıldığı yeri bul:

train_vs_val
dolu: eğitim · boş: doğrulamaeğitimdoğrulama00,51,01101001000en iyi · 67lossepoch · log ekseni
epoch3000
eğitim loss0,000
doğrulama loss0,362
en iyi · epoch 670,183

epoch 3000 / 3000 · eğitim loss 0,000 · doğrulama loss 0,362 · en iyi epoch 67 (0,183) eğitim loss düşmeye devam ediyor, doğrulama loss artıyor: ağ gürültüyü ezberliyor

  • 30, 100, 300 ve 3000. epoch’a git. Validation loss’u 0,624, 0,217, 0,244 ve 0,362 olur.
  • “En iyi epoch”a bas. Validation loss’u 67. epoch’ta en düşüktür (0,183).

Training loss’u 0,179’dan 0’a iner ve training accuracy %100 olur. Validation loss’u ise 100. epoch’ta 0,217’dir ve sonra 0,362’ye yükselir. Training loss’u düşerken validation loss’u yükseliyorsa model ezberliyordur. Buna aşırı öğrenme (overfitting)aşırı öğrenme (overfitting): Modelin eğitim verisini gürültüsüyle birlikte ezberleyip yeni veride kötü çalışması; eğitim loss'u düşerken validation loss'u yükselir. denir.

Validation accuracy düşmez (%93’ten %94’e), ama loss kötüleşir: model yanlış tahminlerinden artık çok emindir. Bu yüzden validation set’te accuracy ile birlikte loss’a da bakmak gerekir.

Çare, eğitimi validation loss’unun en düşük olduğu yerde durdurmak ya da modeli kısıtlamaktır. Bu yöntemler Normalizasyon ve düzenlileştirme dersinin konusu.

Kısa soruTraining loss'u düşmeye devam ediyor, validation loss'u yükseliyor. Ne olmuştur?

Cevabı göster

Model training set'i ezberliyor; overfitting başlamıştır. Training set'te başarı artıp validation set'te azalıyorsa model genel örüntüyü değil eğitim örneklerini öğreniyordur.

Özet

  • Eğitim döngüsü forward pass, loss, backward pass ve güncelleme adımlarını tekrarlar.
  • Weight’ler sıfırdan ya da eşit başlarsa nöronlar ayrışmaz; Xavier (2/(din+dout)2/(d_{\text{in}}+d_{\text{out}})) ve He (2/din2/d_{\text{in}}) varyansı layer’lar boyunca korur.
  • Momentum gradyanları biriktirerek zikzağı söndürür; Adam, bias correction ile her parametrenin adımını gradyanın ölçeğinden bağımsız ayarlar.
  • Hyperparameter’leri training set’e bakarak değil, validation set’e bakarak seç.
  • Training loss’u düşerken validation loss’u yükseliyorsa model overfitting yapmıştır.

Terimler

TürkçeEnglish
ilklendirmeinitialization
simetri sorunusymmetry problem
momentummomentum
hızvelocity
yanlılık düzeltmesibias correction
hiperparametrehyperparameter
eğitim kümesitraining set
doğrulama kümesivalidation set
test kümesitest set
aşırı öğrenmeoverfitting
tohumseed
doygunluksaturation

Şimdi sıra sende

  1. Xavier ve He ilklendirmesiZorluk: OrtaBaşla →
  2. Momentumlu SGD adımıZorluk: OrtaBaşla →
  3. Adam adımıZorluk: ZorBaşla →
  4. İki ay veri setinde MLP eğitZorluk: ZorBaşla →
İlk problem: Xavier ve He ilklendirmesi →

Notların

Metinden bir bölüm seçip Not al'a basarak da ekleyebilirsin.

Geri bildirim

Tür