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:
- Forward pass:
X’ten tahmin ‘yi hesapla. - Loss: ile
y’yi karşılaştır ve tek bir sayı çıkar. - Backward pass: loss’un her parametreye göre gradyanını hesapla.
- Güncelleme: her parametreyi gradyanının tersine kaydır.
Ağın iki katmanı (layer) var. Gizli katman (hidden layer) kullanır; çıkış nöronu sigmoid kullanır ve ‘yi verir:
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 ‘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: .
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 olur.
Hidden layer’da ‘ın yerel türevi 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 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: . Burada öğ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.
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: sıfırsa hidden layer’a hiç gradyan ulaşmaz. İkincisi: 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 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 yolunu izle:
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:
Weight’ler ve girdiler bağımsız, ortalamaları sıfırdır. Bu durumda terim toplanır ve varyanslar da toplanır. Girdinin varyansını ile göster:
Varyansın bir layer’dan ötekine aynı kalmasını istiyoruz: olsun. Bunun için olmalıdır.
- çok küçükse aktivasyonlar layer’dan layer’a küçülür ve sıfıra iner.
- çok büyükse aktivasyonlar büyür; ile doygunluğa (saturation) ulaşır.
Aşağıda 10 layer’lık bir 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 doygunlukta çalışır: çoğu çıktı ‘e yapışır ve 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: çı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 ister. Aynı argüman geri yönde, gradyanlar için ister. İkisi uyuşmaz; Xavier initialization ortada buluşur:
Xavier, sıfır çevresinde doğrusala yakın davranan 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:
Şu tabloya bak:
| Yöntem | Hangi aktivasyon | |
|---|---|---|
| Xavier | tanh, sigmoid | |
| He | 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 ‘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.
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.
Gradyan ‘dir. yönü, 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 ‘dir. Başlangıç noktası 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
‘de her adımda büyür ve loss patlar. güvenlidir, ama her adımda işaret değiştirir: zikzak çizer.
Yatay yönde ise her adım yalnızca ‘in %18’ini kapatır. En iyi güvenli learning rate ile bile loss’un ‘ü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]
yavaşça ilerler, 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 ile çarpılır ve yeni gradyan eklenir:
Aynı yöne giden gradyanlar birikir, işaret değiştirenler birbirini söndürür. düz gradient descent’tir. Büyük ağlarda tipik değer 0,9’dur.
Bu yazış PyTorch’unkidir. Bazı kaynaklar ve yazar. Learning rate sabitse iki yazış aynı dizisini verir; yalnızca ‘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: , sonra , sonra . İkinci bileşen ise , , ile hızla söner. Zikzak azalır, hızlanır.
Aynı çanakta kaç adımda loss’un ‘ün altına indiğini say. Her yöntem için çalışan bir learning rate seçtik; ‘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
‘yı 0,6 ile 0,9 arasında değiştir ve topun yolunu düz gradient descent’inkiyle karşılaştır:
Kayıp yüzeyi · loss surface · GD η = 0,18 · momentum η = 0,1
adım 0 / 80 · GD kayıp = 30,125 · momentum kayıp = 30,125
- ile loss’un ‘ün altına inmesi 9 adım sürer; düz gradient descent 25 adım sürer.
- ‘a geç. Yol ‘i aşar ve geri döner; 74 adım gerekir.
Momentum yaklaşık 2,8 kat daha az adım ister.
Ama bu çanak için fazladır: velocity gereğinden çok birikir, ‘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 yeter. Momentumlu SGD adımı probleminin istediği fonksiyon momentum_step’tir.
Adam
Adam iki hareketli ortalama tutar: gradyanın kendisi (, birinci moment) ve gradyanın karesi (, ikinci moment). Adımı belirler, ise adımı her parametre için ayrı ölçekler:
Adım oranıyla belirlenir: gradyanı büyük parametrenin ‘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 , ve ‘dir. Learning rate için tipik varsayılan 0,001’dir.
Yanlılık düzeltmesi
ve sıfırdan başlar. İlk adımlarda gerçek değerlerinden küçüktürler: ilk adımda olur.
Bu yanlılığı gidermek için yanlılık düzeltmesi (bias correction) uygulanır. Adım sayısı 1’den başlar:
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 :
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, uzunluğundadır. Düzeltmesiz adım 0,316 çıkar: ilk adımda ve olduğundan oran ‘dir. İlk adım ‘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:
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ı ‘dır, yani 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.
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 ;
- hidden layer’daki nöron sayısı;
- epoch sayısı;
- momentum için , Adam için ve ;
- 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 (0,0001 ve 0,001): 300 epoch’ta yeterince ilerlenmez. Adam’ın varsayılanı 0,001 burada yavaştır.
- Orta (0,01 – 1): iyi çalışır; en düşük validation loss’u ‘dedir (0,019). Adam’ın 0,1’deki düşük training loss’u validation’da da en iyisidir.
- Çok büyük (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:
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.
Ö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 () ve He () 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çe | English |
|---|---|
| ilklendirme | initialization |
| simetri sorunu | symmetry problem |
| momentum | momentum |
| hız | velocity |
| yanlılık düzeltmesi | bias correction |
| hiperparametre | hyperparameter |
| eğitim kümesi | training set |
| doğrulama kümesi | validation set |
| test kümesi | test set |
| aşırı öğrenme | overfitting |
| tohum | seed |
| doygunluk | saturation |
Şimdi sıra sende
Ders tamamlandı. Sıradaki: ilk problem.
- Xavier ve He ilklendirmesiZorluk: OrtaBaşla →
- Momentumlu SGD adımıZorluk: OrtaBaşla →
- Adam adımıZorluk: ZorBaşla →
- İki ay veri setinde MLP eğitZorluk: ZorBaşla →
Notların
Metinden bir bölüm seçip Not al'a basarak da ekleyebilirsin.
Bu derse dair söylemek istediğin bir şey var mı?