Nöronlar ve aktivasyonlar
Lojistik regresyon girdilerden bir skor hesaplar. Skoru sigmoid’den geçirir ve bir olasılık (probability) verir. Sigmoid, skoru 0 ile 1 arasına sıkıştıran fonksiyondur.
Bu küçük birime nöron (neuron) denir. Sinir ağı, nöronların yoğun katmanlar (dense layer)yoğun katman (dense layer): Her girdinin her nörona bağlandığı katman: girdiler bir ağırlık matrisiyle çarpılır, sapma eklenir ve sonuç bir aktivasyon fonksiyonundan geçer. hâlinde art arda dizilmesiyle kurulur. Böyle katmanlardan oluşan modele çok katmanlı algılayıcı (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ğı. denir.
Sinir ağının işe yaraması için her nöronun sonunda doğrusal olmayan bir aktivasyon fonksiyonu (activation function)aktivasyon fonksiyonu (activation function): Bir nöronun ağırlıklı toplamını çıktıya çeviren ve ağın doğrusal olmayan fonksiyonlar öğrenebilmesini sağlayan fonksiyon; en bilinenleri sigmoid, tanh, ReLU ve GELU'dur. gerekir. Bu seçim eğitimi doğrudan etkiler. Bazı seçimlerde gradyan (gradient)gradyan (gradient): Bir fonksiyonun en hızlı arttığı yönü ve bu artışın dikliğini gösteren türev vektörü. katmanlardan geçerken erir; buna kaybolan gradyan (vanishing gradient)kaybolan gradyan (vanishing gradient): Gradyanın, türevi küçük aktivasyonlardan geçerek girişe yakın katmanlara ulaşana kadar neredeyse sıfıra inmesi; o katmanlar bu yüzden çok yavaş öğrenir ya da hiç öğrenemez. (vanishing gradient) denir.
Bu derste şunları yapacaksın:
- Nöronu kuracaksın.
- Katmanları (layer) aktivasyon olmadan üst üste koymanın neden bir şey kazandırmadığını göreceksin.
- Dört aktivasyon fonksiyonunu ve türevlerini (derivative) karşılaştıracaksın.
- sınıflı bir problem için ham skorları (logits) olasılığa çeviren softmax’ı öğreneceksin.
- Çapraz entropiyi (cross-entropy) öğreneceksin.
Bir nöron
Nöron, ağırlıklı bir toplam ve ardından bir aktivasyon fonksiyonudur.
Ağırlıklı toplam ve aktivasyon
Lojistik regresyonun iki adımı vardı:
- Ağırlıklı toplamı hesapla: girdileri kendi ağırlıklarıyla (weight)ağırlık (weight): Bir girdiyle çarpılarak onun çıktıya ne kadar katkıda bulunacağını belirleyen parametre. Doğrusal modelde her özniteliğin kendi ağırlığı vardır. çarp, topla ve sapmayı (bias)sapma (bias): Girdilerle çarpılmadan çıktıya eklenen parametre. Doğrusal modelde bütün öznitelikler 0 iken modelin verdiği tahmindir. ekle.
- Çıkan skoru sigmoid’e ver.
Aynı iki adım bir nöronun tanımıdır. girdi vektörü, weight’ler ve bias olsun:
Denklemler iki şey tanımlar:
- Birinci denklem ağırlıklı toplamı verir. ‘ye aktivasyon öncesi değer (pre-activation) diyeceğiz.
- İkinci denklem ‘yi bir fonksiyonundan geçirir. Çıktı ‘dır ve aktivasyon sonrası değer (activation) adını taşır.
sigmoid olduğunda ortaya lojistik regresyon çıkar. Başka bir seçersen başka bir nöron elde edersin.
Aşağıda girdileri, weight’leri ve bias’ı kaydır, aktivasyonu değiştir. Başlangıç değerleri örneğimizdir; üçüncü girdi 0’dır.
Nöron · neuron · z = wᵀx + b, a = f(z)
z = w @ x + b# [1.5, 2.0, 1.0] @ [2.0, -1.0, 0.0] + 0.5 → 1.5
a = σ(z) = σ(1,5) = 0,818 0 ile 1 arasında, olasılık gibi okunur: %81,8
Sayılarla kuralım: , ve olsun.
- Ağırlıklı toplamı hesapla: .
- Sigmoid’i uygula: .
Nöron bu girdinin 1 sınıfından olduğuna %81,8 olasılık veriyor.
Aynı hesap kodda iki satırdır. f aktivasyon fonksiyonunu parametre olarak alır:
import numpy as npdef sigmoid(z): return 1 / (1 + np.exp(-z))def neuron(x, w, b, f): z = w @ x + b # ağırlıklı toplam return f(z) # aktivasyonx = np.array([2.0, -1.0])w = np.array([1.5, 2.0])b = 0.5print(w @ x + b) # → 1.5print(neuron(x, w, b, sigmoid)) # → 0.8175744761936437
Aktivasyonsuz nöronlar tek nörona çöker
olmasa nöron yalnızca değerini verirdi. Bu, doğrusal bir modeldir. Asıl sorun, nöronlar art arda dizildiğinde ortaya çıkar.
Tek girdili iki nöronu aktivasyonsuz birbirine bağlayalım. Birincisi , ikincisi olsun.
- Birincinin çıktısını ikincide yerine koy: .
- Parantezi aç: .
Sonuç yine bir doğrudur. Genel olarak:
Sol taraf iki doğrusal adım, sağ taraf tek bir doğrusal adımdır: yeni weight , yeni bias .
Aynı sayılarla kodda iki yolu karşılaştır:
w1, b1 = 2.0, 1.0w2, b2 = 3.0, -4.0x = np.array([-1.0, 0.0, 1.0, 2.0])two_steps = w2 * (w1 * x + b1) + b2one_step = (w2 * w1) * x + (w2 * b1 + b2)two_steps # → array([-7., -1., 5., 11.])one_step # → array([-7., -1., 5., 11.])(w2 * w1, w2 * b1 + b2) # → (6.0, -1.0)
Doğrusal adımları ne kadar üst üste koyarsan koy, sonuç tek bir doğrusal adımdır. Aktivasyonsuz yüz layer’lık bir ağ, tek bir layer’dan daha güçlü değildir.
ReLU ile kırılma
Araya bir aktivasyon koyunca bu değişir. Negatif değerleri 0’a çeken fonksiyonunu seçelim. Adı ReLU’dur; ayrıntısına aşağıda bakacağız.
İlk nöronun çıktısı olsun. İkinci nöron hesaplasın:
| Aktivasyonsuz: | ReLU’lu: | |||
|---|---|---|---|---|
| −1 | −1 | −7 | 0 | −4 |
| 0 | 1 | −1 | 1 | −1 |
| 1 | 3 | 5 | 3 | 5 |
| 2 | 5 | 11 | 5 | 11 |
Tablonun son sütununu ve sütunlardaki farkları kodla bul:
# önceki bloktaki np, w1, b1 ve x ilez1 = w1 * x + b1z2 = 3 * z1 - 4 # aktivasyonsuza1 = np.maximum(0, z1) # ReLU: negatifleri 0 yapz2_relu = 3 * a1 - 4 # ReLU'luz2_relu # → array([-4., -1., 5., 11.])np.diff(z2) # → array([6., 6., 6.])np.diff(z2_relu) # → array([3., 6., 6.])
Komşu satırlar arasındaki farka bak:
- Aktivasyonsuz sütunda fark hep 6’dır. Bu, doğrusunun eğimidir.
- ReLU’lu sütunda farklar 3, 6, 6 çıkar. iken çıktı sabit ‘tür, ondan sonra olur.
Eğim bir noktada 0’dan 6’ya geçiyor ve bir doğru bunu yapamaz.
Evrensel yaklaşım teoremi
Tek bir kırılma küçük görünür. Çok sayıda nöronun kırılmaları birleşince ağ eğri fonksiyonlara yaklaşabilir.
Bu kanıtlanmış bir sonuçtur. Tek bir gizli katmanı (hidden layer) olan bir ağ, sınırlı bir aralıkta sürekli her fonksiyona istenen kadar yaklaşabilir. İki koşul gerekir:
- Aktivasyon fonksiyonu polinom değildir. ReLU, sigmoid ve tanh bu koşulu sağlar.
- Gizli katmanda yeterince nöron vardır.
Teoreme evrensel yaklaşım teoremi (universal approximation theorem) denir. Bu derste kanıtına girmiyoruz.
Küçük bir deneyle görelim. aralığında eğrisine ReLU nöronuyla yaklaşacağız. olsun:
- için bir nöron hesaplar. Weight’i 1, bias’ı ‘dir; ‘de kırılır.
- Nöronların çıktılarını birer weight ile çarpıp topla. Bunlar, yukarıdaki hesabındaki 3 gibi çıkış weight’leridir.
- Her çıkış weight’i, kendi kırılma noktasından sonra eğimi o kadar artırır. ‘yi noktalarında birleştiren kırık çizginin eğimleri olur. Bu yüzden ilk weight , ötekiler ‘dir.
def approx(x, n): h = 1 / n out = 0.0 for k in range(n): w_out = h if k == 0 else 2 * h # eğim artışı: önce h, sonra 2h out = out + w_out * np.maximum(0, x - k * h) # kh'de kırılan ReLU nöronu return outx = np.linspace(0, 1, 4097)for n in (4, 8, 16): print(n, np.abs(approx(x, n) - x ** 2).max())# → 4 0.015625# → 8 0.00390625# → 16 0.0009765625
Bu deneyde nöron sayısı iki katına çıkınca en büyük hata dörtte birine düşer. Weight’leri burada elle seçtik. Teorem böyle weight’lerin var olduğunu söyler; onları veriden bulmak eğitimin işidir.
Dört aktivasyon fonksiyonu
Pratikte dört aktivasyon fonksiyonu sık karşına çıkar: sigmoid, tanh, ReLU ve GELU.
Fonksiyonlar ve türevleri
Eğitimde gradyan bu fonksiyonlardan geçer. Bu yüzden her birinin türevine de ihtiyacımız var. Fonksiyonlar şunlardır:
Türevleri ve özellikleri yan yana koyalım:
| Fonksiyon | Ne yapar | Türev | Not |
|---|---|---|---|
| sigmoid | Girdiyi 0 ile 1 arasına sıkıştırır. | En büyük değeri ‘daki ‘tir. | |
| tanh (hiperbolik tanjant) | Girdiyi ile 1 arasına sıkıştırır; 0 etrafında simetriktir. | ‘da 1’dir. | |
| ReLU | Negatifleri 0’a çeker, pozitifleri olduğu gibi geçirir. | için 1, için 0 | ‘da eğim tanımsızdır; uygulamada 0 alınır ve biz de öyle yapacağız. |
| GELU | ReLU’nun köşesini yumuşatır. | Transformer’larda yaygındır. |
, standart normal dağılımdan çekilen bir sayının değerinden küçük olma olasılığıdır. büyükken 1’e, çok negatifken 0’a yakındır. ise standart normal dağılımın yoğunluğudur.
Üç fonksiyonu ve türevlerini kodla yaz. relu_grad, için 0 verir:
import numpy as npdef sigmoid(z): return 1 / (1 + np.exp(-z))def sigmoid_grad(z): s = sigmoid(z) return s * (1 - s)def tanh_grad(z): return 1 - np.tanh(z) ** 2def relu(z): return np.maximum(0, z)def relu_grad(z): return (z > 0).astype(float) # z = 0 için 0z = np.array([-2.0, 0.0, 3.0])np.round(sigmoid(z), 3) # → array([0.119, 0.5 , 0.953])np.round(sigmoid_grad(z), 3) # → array([0.105, 0.25 , 0.045])np.round(np.tanh(z), 3) # → array([-0.964, 0. , 0.995])np.round(tanh_grad(z), 3) # → array([0.071, 1. , 0.01 ])relu(z) # → array([0., 0., 3.])relu_grad(z) # → array([0., 0., 1.])
GELU için ve gerekir. ‘yi standart kütüphanedeki erf ile yazarız:
from math import erf, sqrt, pidef cdf(z): # Φ(z) = 0.5 * (1 + erf(z / sqrt(2))) return 0.5 * (1 + np.vectorize(erf)(z / sqrt(2)))def pdf(z): # φ(z): standart normal yoğunluk return np.exp(-z ** 2 / 2) / sqrt(2 * pi)def gelu(z): return z * cdf(z)def gelu_grad(z): return cdf(z) + z * pdf(z)z = np.array([-1.0, 1.0])np.round(gelu(z), 2) # → array([-0.16, 0.84])np.round(gelu_grad(z), 2) # → array([-0.08, 1.08])
Grafikte fonksiyon ve türev
Aşağıda bir fonksiyon seç ve imleci ekseni boyunca sürükle. İmlecin altında ile değerini okursun. Türevin sıfıra çok yaklaştığı, yani ile arasında kaldığı bölgeler “gradyan kayboluyor” diye gölgelenir.
Aktivasyon ve türev · activation & derivative · f(z) ve f'(z)
sigmoid · z = 0,00 · f(z) = 0,500 · f'(z) = 0,250
Şunları dene:
- Sigmoid’de imleci ‘a koy: ve . Sonra ‘e sürükle: iken . Fonksiyon hâlâ 1’e varmadı, ama eğrisi neredeyse yatay.
- tanh için ‘da türev 1, ‘de yaklaşık ‘dir. Gölgeli bölge sigmoid’dekinden biraz erken başlar. Sigmoid’de civarında, tanh’ta civarında.
- ReLU’da için ve ; için ikisi de 0’dır. Gölgeli bölge, ‘nin negatif tarafının tamamıdır.
- GELU’da üç şeye bak:
- için ve . Türev 1’i aşabilir.
- için ve . GELU küçük negatif değerler verebilir ve türevi negatif olabilir. ReLU’da böyle bir şey yoktur.
- GELU’nun negatif tarafında gölgesiz dar bir bant kalır. Türev orada ile arasındadır; küçük ama sıfır değildir.
Gölgenin başladığı değerini kodla da bulabilirsin. Türevin ilk kez ‘in altına indiği noktaya bak:
# önceki bloklardaki sigmoid_grad ve tanh_grad ilez = np.linspace(0, 5, 5001)print(z[sigmoid_grad(z) < 0.1][0]) # → 2.064print(z[tanh_grad(z) < 0.1][0]) # → 1.819
Vanishing gradient
Gölgeli bölgelerin önemi geri yayılımdan gelir. Bir sonraki derste göreceğin geri yayılımda (backpropagation)geri yayılım (backpropagation): Zincir kuralıyla, kaybın her parametreye göre gradyanını çıkıştan girişe doğru hesaplama yöntemi., kaybın (loss) gradyanı çıkıştan girişe doğru taşınır. Gradyan her layer’dan geçerken o layer’ın aktivasyon türeviyle çarpılır.
Türev küçükse gradyan küçülür. Bir layer’da küçülmesi önemsizdir, ama küçülmeler çarpıldıkça birikir.
Tipik bir layer’da sigmoid’in türevi yaklaşık ‘dir. Örnek olarak alalım. Orada ve olur.
Weight’leri 1 sayarsak her layer gradyanı türevle çarpar. On sigmoid layer’dan sonra çarpım olur. Girişe ulaşan gradyan, çıkıştakinin on milyonda biri kadardır.
Sigmoid’in türevi en iyi ihtimalle ‘tir (‘da). Gerçek bir ağda genelde 0’da durmaz, bu yüzden çarpan daha da küçüktür.
Kodda ile izle:
# önceki bloktaki sigmoid_grad ve tanh_grad ileprint(sigmoid_grad(1.0)) # → 0.19661193324148185print(tanh_grad(1.0)) # → 0.41997434161402614grad = 1.0for layer in range(1, 11): grad = grad * sigmoid_grad(1.0) # her layer'da türevle çarp if layer in (1, 5, 10): print(layer, grad)# → 1 0.19661193324148185# → 5 0.0002937983603191941# → 10 8.631747652624701e-08
Gradyan weight’lerle de çarpılır. Bu hesap yalnızca aktivasyonların etkisini gösterir. Yine de sigmoid’le gradyan her layer’da erimeye yatkındır.
Aktivasyonu seç ve katman sayısını sürükle.
gradyan = f′(z)ᵏ · her katmanda tipik ön-aktivasyon z = 1, ağırlıklar 1 alındı
sigmoid′(1) = 0,197 · 6 katman geriye: ilk katmana ulaşan gradyan 0,197⁶ = 5,8·10⁻⁵ en iyi durumda bile (z = 0) türev yalnızca 0,25: 0,25⁶ = 2,4·10⁻⁴
Gradyanın giriş tarafına ulaşamadan erimesine vanishing gradientkaybolan gradyan (vanishing gradient): Gradyanın, türevi küçük aktivasyonlardan geçerek girişe yakın katmanlara ulaşana kadar neredeyse sıfıra inmesi; o katmanlar bu yüzden çok yavaş öğrenir ya da hiç öğrenemez. denir. Girişe yakın layer’ların weight’leri neredeyse hiç güncellenmez ve ağ öğrenmez.
Yukarıdaki grafikte sigmoid ve tanh’ın gölgeli bölgelerinin iki uçta olduğuna dikkat et. bu uçlara düştüğünde nöronun çıktısı neredeyse hiç değişmez ve gradyan geçmez.
ReLU ve dying ReLU
ReLU bu yüzden yaygınlaştı. Pozitif tarafta türevi tam 1’dir ve gradyan olduğu gibi geçer. Bedeli negatif taraftır.
Bir nöronun değeri her girdi için negatifse, türevi hep 0’dır. Gradyan o nöronun weight’lerini hiç güncellemez ve nöron öğrenmeyi bırakır. Buna dying ReLU denir (ölen ReLU).
Örnek: olan bir nöron, üç girdinin hepsinde negatif verir:
# önceki bloktaki relu_grad ileX = np.array([[1.0, 2.0], [2.0, 1.0], [3.0, 0.5]])w = np.array([1.0, 1.0])b = -10.0z = X @ w + b # üç girdi için aktivasyon öncesi değerz # → array([-7. , -7. , -6.5])relu_grad(z) # → array([0., 0., 0.])
GELU’nun türevi
GELU’nun türevi de ReLU gibi pozitif tarafta 1’e yaklaşır. Köşesi yoktur. çok negatif olmadıkça sıfırdan farklı, küçük bir türev verir.
# önceki bloktaki gelu_grad ilez = np.array([-6.0, -3.0, -1.0, 0.0, 1.0, 3.0])np.round(gelu_grad(z), 3) # → array([-0. , -0.012, -0.083, 0.5 , 1.083, 1.012])
Layer’lar ve MLP
Bir layer, aynı girdiyi gören nöronların yan yana dizilmesidir.
Dense layer
nöronlu bir layer’da her nöronun kendi weight vektörü ve kendi bias’ı vardır.
Hesabı matrislere şöyle dökeriz:
- Weight vektörlerini matrisinin sütunlarına koy.
- Bias’ları vektörüne koy.
- örnekten (sample) oluşan bir yığını (batch) matrisine koy. ‘in her satırı bir örnektir.
Böylece bütün nöronların ağırlıklı toplamı tek bir matris çarpımı (matrix product) olur:
Formül her örneği her nöronun weight’iyle çarpıp toplar, bias’ı ekler ve aktivasyonu uygular. Şekiller (shape) şöyledir:
- : .
- : .
- : elemanlı.
- ve : .
Matris çarpımında soldakinin sütun sayısı sağdakinin satır sayısına eşit olmalıdır. Bu yüzden iki yanda da aynı kalır.
Layer ‘yi her satıra ekler. NumPy’da buna yayınlama (broadcasting) denir. Layer aktivasyonu matrisinin her elemanına ayrı ayrı, yani eleman bazında (element-wise), uygular.
Her girdinin her nörona bağlandığı bu layer’a dense layeryoğun katman (dense layer): Her girdinin her nörona bağlandığı katman: girdiler bir ağırlık matrisiyle çarpılır, sapma eklenir ve sonuç bir aktivasyon fonksiyonundan geçer. denir.
Bir gizli nörona tıkla ve onun weight’lerinin ‘nin hangi sütunu olduğuna bak.
Dense layer · dense layer · Z = XW + b
W(1) · (d_in, d_out) = (3, 4)
W.shape, b.shape # → ((3, 4), (4,))W[:, 1] # → [-0.8, 0.4, 0.7]
h₂ nöronu girdiyi 3 weight ile görür: W[:, 1] = (−0,8; 0,4; 0,7) · bias b₂ = −0,2 bir nöron: 3 weight + 1 bias = 4 parametre · 4 nöron yan yana: 4 · 4 = 16
Küçük bir örnek: 2 örnek, 3 girdi, 2 nöron.
matrisi , matrisi ‘dir. Ortadaki 3’ler eşleşir ve sonuç olur.
ReLU ise negatif eleman sıfırlanır. ‘nın ilk satırı , ikincisi olur.
Aynı örnek NumPy’da tek satırlık bir matris çarpımıdır:
import numpy as npX = np.array([[1.0, 2.0, 0.0], [-1.0, 1.0, 2.0]])W = np.array([[1.0, -1.0], [1.0, 2.0], [1.0, 1.0]])b = np.array([0.5, -4.0])Z = X @ W + b # b her satıra yayınlanırZ# → array([[ 3.5, -1. ],# [ 2.5, 1. ]])A = np.maximum(0, Z) # ReLU, eleman bazındaA# → array([[3.5, 0. ],# [2.5, 1. ]])(X.shape, W.shape, Z.shape) # → ((2, 3), (3, 2), (2, 2))
Not
PyTorch’un nn.Linear layer’ı weight matrisini bu derstekinin devriği (transpose) olarak, (d_out, d_in) biçiminde saklar. Çarpmayı X @ W.T olarak yapar. Bu derste ve problemlerde hep (d_in, d_out) biçimindedir.
Aktivasyonsuz layer’lar çöker
Aktivasyonsuz nöronların çökmesi, layer’lar için de geçerlidir. Aktivasyon yoksa iki dense layer tek bir layer’a çöker:
Sağ taraftaki tek layer’ın weight matrisi , bias’ı ‘dir. Üst indis hangi layer’ın olduğunu gösterir.
Rastgele sayılarla iki tarafı karşılaştır:
rng = np.random.default_rng(0)X = rng.normal(size=(4, 3))W1, b1 = rng.normal(size=(3, 5)), rng.normal(size=5)W2, b2 = rng.normal(size=(5, 2)), rng.normal(size=2)two_layers = (X @ W1 + b1) @ W2 + b2one_layer = X @ (W1 @ W2) + (b1 @ W2 + b2)np.allclose(two_layers, one_layer) # → True(W1 @ W2).shape # → (3, 2)
MLP
Bu yüzden layer’ların arasına aktivasyon koyarız. Dense layer’lardan ve aralarındaki aktivasyonlardan oluşan ağa 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ğı. denir. Girdi ile çıkış arasında kalan layer’lara gizli katman (hidden layer) denir.
Sınıflandırma (classification) için tipik bir MLP’ye bakalım. 8×8’lik bir rakam görüntüsü 64 öznitelik (feature) verir. Ağın 32 nöronlu bir hidden layer’ı ve 10 sınıfı olsun:
| Shape | |
|---|---|
| , | , |
| , | , |
Son layer’a aktivasyon koymayız: , her örnek için 10 logit’tir.
Bu ağın parametre (parameter) sayısı ‘dur. Kodda her layer’ın W ve b boyutlarını topla:
d_in, d_hidden, d_out = 64, 32, 10n_params = d_in * d_hidden + d_hidden + d_hidden * d_out + d_outprint(n_params) # → 2410
İleri geçiş
Girdiden çıktıya giden bu hesaba ileri geçiş (forward pass) denir. Kodda üç satırdır:
def forward(X, W1, b1, W2, b2): Z1 = X @ W1 + b1 # ilk layer'ın ağırlıklı toplamı, (n, 32) A1 = np.maximum(0, Z1) # aktivasyon, Z1'in her elemanına ayrı ayrı logits = A1 @ W2 + b2 # son layer, aktivasyonsuz, (n, 10) return logitsrng = np.random.default_rng(0)X = rng.normal(size=(5, 64))W1, b1 = rng.normal(size=(64, 32)), np.zeros(32)W2, b2 = rng.normal(size=(32, 10)), np.zeros(10)forward(X, W1, b1, W2, b2).shape # → (5, 10)sum(p.size for p in (W1, b1, W2, b2)) # → 2410
Weight’lerin nasıl öğrenildiğini Geri yayılım dersinde göreceksin. Ardından ileride bir MLP’yi eğitmeye geçeceğiz.
Softmax
Softmax, skoru sınıf üzerinde bir olasılık dağılımına çevirir.
Softmax formülü
Sınıf sayısı olduğunda son layer logit verir. Lojistik regresyonda tek bir skoru sigmoid’le olasılığa çeviriyordun; sınıfta aynı işi softmax yapar.
Adı, “en büyüğünü seç” işlemini yumuşatmasından gelir. En büyük skora en büyük olasılığı verir, ama diğerlerini sıfırlamaz.
Üstel fonksiyon her skoru pozitif yapar. Bölme toplamı 1’e getirir. Sonuç, sınıf üzerinde bir olasılık dağılımıdır.
için hesap şöyledir:
- Skorların üstelini al: , ve .
- Üstelleri topla: .
- Her üsteli toplama böl: .
Olasılıkların oranı yalnızca skorların farkına bağlıdır: . Skorlar arasındaki fark 1 arttığında oran katına çıkar.
Aynı üç adım kodda üç satırdır:
import numpy as npz = np.array([2.0, 1.0, 0.0])exps = np.exp(z)exps # → array([7.3890561 , 2.71828183, 1. ])print(exps.sum()) # → 11.107337927389695probs = exps / exps.sum()probs # → array([0.66524096, 0.24472847, 0.09003057])print(probs[0] / probs[1]) # → 2.7182818284590455
İki sınıfta softmax
olduğunda softmax sigmoid’e iner:
Yani iki sınıfta softmax, skorların farkına uygulanan sigmoid’dir.
İki skor seç ve iki yolla hesapla. Fark 1,5 olduğundan sonuç, nöron örneğindeki ‘dir:
# önceki bloklardaki np ve sigmoid ilez0, z1 = 0.3, 1.8exps = np.exp([z0, z1])print(np.round(exps[1] / exps.sum(), 4)) # → 0.8176print(np.round(sigmoid(z1 - z0), 4)) # → 0.8176
Taşma
Büyük skorlarda üstel fonksiyon taşar. olsun. 64 bitlik bir float’a sığmaz.
ancak ‘e kadar temsil edilir. Taşma (overflow) olur, sonuç inf çıkar ve inf / inf nan verir.
z = np.array([1000.0, 1001.0, 1002.0])# exp taşar; NumPy RuntimeWarning verirnp.exp(z) # → array([inf, inf, inf])np.exp(z) / np.exp(z).sum() # → array([nan, nan, nan])
En büyüğünü çıkarmak
Çıkış yolu softmax’ın bir özelliğindedir. Her skora aynı sayısını eklersen pay ve paydadan sadeleşir ve olasılıklar değişmez.
Burada seçelim, skorların en büyüğü olsun. Örneğimizde olur:
- Skorlardan ‘yi çıkar: .
- Üstelleri al: .
- Üstelleri topla: .
- Her üsteli toplama böl: .
Bu sonuç, örneğinin tersten yazılmış hâlidir. Üstelini almadan önce skorlardan en büyüğünü çıkar.
# önceki bloktaki z = (1000, 1001, 1002) ileshifted = z - z.max()shifted # → array([-2., -1., 0.])exps = np.exp(shifted)exps # → array([0.13533528, 0.36787944, 1. ])print(exps.sum()) # → 1.5032147244080551exps / exps.sum() # → array([0.09003057, 0.24472847, 0.66524096])
En büyüğünü çıkarmanın sebebi üç özelliktir:
- Bütün üsler olur, yani çıkar. Overflow olmaz.
- En büyük skorun üstel değeri tam 1’dir. Toplam en az 1 olur ve sıfıra bölme olmaz.
- Çok küçük üstel değerler 0’a yuvarlanabilir. O sınıfın olasılığı zaten ihmal edilebilir.
Sonuç değişmez, ama hesap sayısal olarak kararlı (numerically stable) olur. Buna sayısal kararlılık (numerical stability) denir.
Üç skoru sürükle, sonra “maksimumu çıkar”ı aç ve olasılıklara bak.
exp: e2,0 = 7,389 · e1,0 = 2,718 · e0,0 = 1,000 · toplam 11,107 p = 0,665 · 0,245 · 0,090
Batch için softmax
Bir batch için fonksiyon, bu adımları her satıra ayrı uygular:
def softmax(logits): shifted = logits - logits.max(axis=1, keepdims=True) exps = np.exp(shifted) return exps / exps.sum(axis=1, keepdims=True)Z = np.array([[2.0, 1.0, 0.0], [1000.0, 1001.0, 1002.0]])softmax(Z)# → array([[0.66524096, 0.24472847, 0.09003057],# [0.09003057, 0.24472847, 0.66524096]])softmax(Z).sum(axis=1) # → array([1., 1.])Z.max(axis=1).shape # → (2,)Z.max(axis=1, keepdims=True).shape # → (2, 1)
Dikkat
Bir batch’te softmax’ı her örneğin kendi skoruna ayrı uygula. Yani sınıfların olduğu eksen (axis) boyunca, satır başına uygula. Sütun boyunca, yani örnekler arasında uygularsan her sütunun toplamı 1 olur ve bir örneğin olasılıkları anlamını yitirir.
En büyük skoru ve üstel toplamı da her satır için ayrı alırsın. Bunlar (n,) shape’inde birer vektördür. Onları ‘den doğrudan çıkaramazsın.
(n,) hizalanınca (1, n) olur. n ile k eşitse sonuç sessizce yanlış çıkar. Vektörü bir sütuna çevir:
reshape(-1, 1)çağır.- Ya da
maxvesumçağrılarındakeepdims=Truever.
İkisi de (n, 1) shape’ini korur.
Çok sınıflı cross-entropy
Cross-entropy, kendinden emin ve yanlış olmanın bedelidir. Lojistik regresyonda ikili çapraz entropi (binary cross-entropy), modelin doğru sınıfa verdiği olasılık küçüldükçe büyüyen bir kayıp fonksiyonuydu (loss function)kayıp fonksiyonu (loss function): Modelin tahminlerinin ne kadar yanlış olduğunu tek bir sayıyla ölçen fonksiyon.. Aynı fikir sınıfa taşınır ve adı cross-entropy olarak kalır.
Doğru sınıfın eksi logaritması
Etiketler (label) arasında tam sayıdır. softmax çıktısı olsun; doğal logaritmadır. . örneğin doğru sınıfı ise loss, modelin o sınıfa verdiği olasılığın eksi logaritmasıdır:
Simgeler şunlardır:
- , ‘nin . satırındaki . sütunudur.
- tek bir örneğin loss’udur.
- batch’in ortalamasıdır.
Doğru sınıfa verilen olasılık 1 ise loss 0’dır. Bu olasılık 0’a giderse loss sonsuza gider.
Yukarıdaki için (yuvarlanmamış olasılıklarla) loss, doğru sınıfa göre değişir:
| Doğru sınıf | Modelin verdiği olasılık | |
|---|---|---|
| 0 | 0,665 | 0,4076 |
| 1 | 0,245 | 1,4076 |
| 2 | 0,090 | 2,4076 |
Modelin en çok güvendiği sınıf doğruysa loss küçük, en az güvendiği sınıf doğruysa büyüktür.
import numpy as npz = np.array([2.0, 1.0, 0.0])probs = np.exp(z) / np.exp(z).sum()-np.log(probs) # → array([0.40760596, 1.40760596, 2.40760596])
İki örnekli bir batch alalım:
- İlk satır , doğru sınıf 0: .
- İkinci satır , doğru sınıf 1: model her sınıfa verir ve olur.
- Ortalamayı al: .
NumPy’da probs[np.arange(n), y], her satırdan doğru sınıfın olasılığını seçer:
logits = np.array([[2.0, 1.0, 0.0], [0.0, 0.0, 0.0]])y = np.array([0, 1])n = len(y)probs = softmax(logits) # önceki bölümdeki softmaxp_true = probs[np.arange(n), y] # her satırdan doğru sınıfp_true # → array([0.66524096, 0.33333333])losses = -np.log(p_true)losses # → array([0.40760596, 1.09861229])print(losses.mean()) # → 0.7531091265562451
Her sınıfa veren, yani hiçbir şey bilmeyen bir modelin loss’u her örnekte olur. için çıkar.
Bundan yüksek bir loss, modelin her sınıfa eşit olasılık veren bir modelden bile kötü olduğunu gösterir. bir kıyas noktasıdır.
print(np.log(3)) # → 1.0986122886681098print(np.log(10)) # → 2.302585092994046
One-hot yazılışı
Kitaplarda aynı şey one-hot label’larla yazılır. One-hot label, doğru sınıfta 1, ötekilerde 0 olan satırdır.
Her satırda tek bir 1 olduğundan toplamda yalnızca doğru sınıfın terimi kalır.
# önceki bloktaki y ve probs ileY = np.eye(3)[y] # her satır bir one-hot labelY# → array([[1., 0., 0.],# [0., 1., 0.]])-(Y * np.log(probs)).sum(axis=1) # → array([0.40760596, 1.09861229])
Logaritmanın tuzağı
Formülü olduğu gibi hesaplamak üç adımdır:
- Skorları hesapla.
- Softmax al.
- Logaritma al.
Son adım tehlikelidir. ve doğru sınıf 0 olsun. En büyüğü çıkarıp softmax alsak bile 0’a yuvarlanır.
Bu durumda olur ve çıkar. Oysa gerçek loss sonludur ve ‘e yakındır.
Bu, binary cross-entropy’de gördüğün tuzağının aynısıdır. Orada olasılığı ile kırpmıştık. Burada kırpma loss’u ‘da keser, oysa doğru değer 800’dür.
logits = np.array([[0.0, 800.0, 0.0]])probs = softmax(logits) # Softmax bölümündeki fonksiyonprobs # → array([[0., 1., 0.]])print(-np.log(probs[0, 0])) # → infprint(-np.log(1e-12)) # → 27.631021115928547
Logit’lerden doğrudan loss
Çıkış yolu, olasılığı hiç hesaplamamaktır. Logaritma bir bölümü farka çevirir:
Böylece tek bir örneğin loss’u doğrudan skorlardan yazılır:
Formül, doğru sınıfın skorunu çıkarır ve bütün skorların üstel toplamının logaritmasını ekler. Doğru sınıfın skoru toplama göre ne kadar geride kalıyorsa loss o kadar büyür.
Örneğin için olur. Doğru sınıf 0 ise çıkar. Bu, yukarıdakiyle aynıdır.
Tehlikeli kısım, skorların üstel toplamının logaritmasıdır. Orada da aynı yöntemi kullanırız. Toplamı, en büyük skor ile şöyle yazarız:
Logaritmayı alırsak şunu elde ederiz:
Logaritmanın içindeki toplamda en büyük terim tam 1’dir. Bu yüzden toplam en az 1’dir. hiç oluşmaz ve üstel değerler taşmaz.
örneğinde şunu yaparız:
- En büyük skoru bul: .
- İçerideki toplamı hesapla: .
- Loss’u yaz: .
Sonuç sonsuz değil, doğru değerdir.
Aynı yöntem iki yerde işe yarar: en büyük skoru çıkar. İki hesap da koda dökülür:
def log_sum_exp(logits): m = logits.max(axis=1, keepdims=True) return m[:, 0] + np.log(np.exp(logits - m).sum(axis=1))def cross_entropy(logits, y): n = logits.shape[0] return float(np.mean(log_sum_exp(logits) - logits[np.arange(n), y]))log_sum_exp(np.array([[2.0, 1.0, 0.0]])) # → array([2.40760596])cross_entropy(np.array([[2.0, 1.0, 0.0]]), np.array([0])) # → 0.4076059644443806cross_entropy(np.array([[0.0, 800.0, 0.0]]), np.array([0])) # → 800.0
Özet
- Bir nöron, ağırlıklı toplamını bir aktivasyon fonksiyonundan geçirir: . sigmoid ise ortaya lojistik regresyon çıkar.
- Aktivasyonsuz layer’lar üst üste konunca sonuç tek bir doğrusal layer’dır. Araya konan doğrusal olmayan bir fonksiyon, ağın eğri fonksiyonları öğrenebilmesini sağlar.
- Bir dense layer hesaplar (:
(d_in, d_out)). Aktivasyon matrisine eleman bazında uygulanır. Dense layer’ları aktivasyonlarla art arda dizersen MLP elde edersin. - Sigmoid ve tanh’ın türevi uçlarda sıfıra yaklaşır. Bu küçük türevler layer’lar boyunca çarpılınca gradyan kaybolur ve girişe yakın layer’lar öğrenemez. ReLU’nun türevi pozitif tarafta 1, negatif tarafta 0’dır; hep negatif kalan bir nöron öğrenmeyi bırakır.
- Softmax skoru olasılığa çevirir. Cross-entropy doğru sınıfın olasılığının eksi logaritmasını alır. Üstelini almadan önce en büyük skoru çıkarmak sonucu değiştirmeden taşmayı önler.
Terimler
| Türkçe | English |
|---|---|
| nöron | neuron |
| aktivasyon fonksiyonu | activation function |
| aktivasyon öncesi değer | pre-activation |
| aktivasyon sonrası değer | activation |
| ağırlık | weight |
| sapma | bias |
| etiket | label |
| evrensel yaklaşım teoremi | universal approximation theorem |
| türev | derivative |
| hiperbolik tanjant | tanh |
| gradyan | gradient |
| kaybolan gradyan | vanishing gradient |
| kayıp | loss |
| geri yayılım | backpropagation |
| katman | layer |
| yoğun katman | dense layer |
| gizli katman | hidden layer |
| çok katmanlı algılayıcı | multilayer perceptron, MLP |
| örnek | sample |
| yığın | batch |
| matris çarpımı | matrix product |
| şekil | shape |
| yayınlama | broadcasting |
| eleman bazında | element-wise |
| olasılık | probability |
| sınıflandırma | classification |
| öznitelik | feature |
| parametre | parameter |
| devrik | transpose |
| ileri geçiş | forward pass |
| ham skorlar | logits |
| eksen | axis |
| taşma | overflow |
| sayısal kararlılık | numerical stability |
| kayıp fonksiyonu | loss function |
| çapraz entropi | cross-entropy |
| ikili çapraz entropi | binary cross-entropy |
| one-hot | one-hot |
| sigmoid | sigmoid |
| ReLU | ReLU |
| GELU | GELU |
| softmax | softmax |
| dying ReLU | dying ReLU |
Şimdi sıra sende
Ders tamamlandı. Sıradaki: ilk problem.
- ReLU ve türeviZorluk: KolayBaşla →
- Kararlı softmaxZorluk: KolayBaşla →
- Yoğun katman ileri geçişiZorluk: OrtaBaşla →
- Çok sınıflı çapraz entropiZorluk: 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ı?