Bir yapay sinir ağının gerçekten nasıl öğrendiğini anlamanın en iyi yolu, hazır makine öğrenmesi kütüphanelerini bir kenara bırakıp bütün parçaları kendimiz yazmaktır. Bu projede yalnızca Python dilinin temel özelliklerini kullanarak MNIST benzeri el yazısı rakamlarını tanıyan, girişten çıkışa ve hatadan geriye doğru öğrenebilen küçük bir sinir ağı geliştireceğiz.
``
Problemi sayılara dönüştürmek
28×28 piksellik gri tonlamalı bir rakam resmi, ağ açısından resim değil, 784 sayıdan oluşan bir vektördür. Her pikseli 255’e bölerek değerleri $[0,1]$ aralığına taşırız:
\[x_i = \frac{piksel_i}{255}\]Çıkış katmanında ise 10 nöron bulunur. Örneğin doğru cevap 3 ise hedef vektörü $[0,0,0,1,0,0,0,0,0,0]$ olur. Buna one-hot kodlama denir.
| Katman | Nöron sayısı | Görevi |
|---|---|---|
| Giriş | 784 | Piksel değerlerini almak |
| Gizli | 64 | Çizgi, kıvrım ve kenar örüntülerini öğrenmek |
| Çıkış | 10 | 0-9 sınıflarını puanlamak |
Bir nöron önce ağırlıklı toplam hesaplar:
\[z_j = \sum_i x_i w_{ij} + b_j\]Ardından doğrusal olmayan sigmoid fonksiyonu uygulanır:
\[\sigma(z)=\frac{1}{1+e^{-z}}\]Doğrusal olmayan aktivasyon kullanılmazsa çok sayıda katman, matematiksel olarak tek bir doğrusal dönüşüme dönüşür. Başka bir deyişle ağ büyür, fakat zekâsı pek büyümez.
Ağı sıfırdan kodlamak
Aşağıdaki Python kodu matris kütüphanesi kullanmaz. Ağırlıkları üretmek için kendi basit sözde rastgele sayı üretecimizi bile yazıyoruz:
seed = 123456
def rnd():
global seed
seed = (1103515245 * seed + 12345) % (2 ** 31)
return seed / (2 ** 31) - 0.5
def sigmoid(x):
return 1.0 / (1.0 + 2.718281828 ** (-max(-30, min(30, x))))
def matrix(rows, cols):
return [[rnd() * 0.1 for _ in range(cols)] for _ in range(rows)]
INPUT, HIDDEN, OUTPUT = 784, 64, 10
w1 = matrix(HIDDEN, INPUT)
b1 = [0.0] * HIDDEN
w2 = matrix(OUTPUT, HIDDEN)
b2 = [0.0] * OUTPUT
def forward(x):
hidden = []
for j in range(HIDDEN):
z = b1[j] + sum(w1[j][i] * x[i] for i in range(INPUT))
hidden.append(sigmoid(z))
output = []
for k in range(OUTPUT):
z = b2[k] + sum(w2[k][j] * hidden[j] for j in range(HIDDEN))
output.append(sigmoid(z))
return hidden, output
forward fonksiyonu tahmin üretir; ancak öğrenme için hatayı ağırlıklara geri dağıtmamız gerekir. Ortalama karesel hata kullanırsak temel hedefimiz şudur:
def train(x, target, rate=0.1):
hidden, output = forward(x)
out_delta = [
(output[k] - target[k]) * output[k] * (1 - output[k])
for k in range(OUTPUT)
]
hid_delta = []
for j in range(HIDDEN):
error = sum(out_delta[k] * w2[k][j] for k in range(OUTPUT))
hid_delta.append(error * hidden[j] * (1 - hidden[j]))
for k in range(OUTPUT):
for j in range(HIDDEN):
w2[k][j] -= rate * out_delta[k] * hidden[j]
b2[k] -= rate * out_delta[k]
for j in range(HIDDEN):
for i in range(INPUT):
w1[j][i] -= rate * hid_delta[j] * x[i]
b1[j] -= rate * hid_delta[j]
Eğitim ve tahmin
Her eğitim resmi için piksel listesini normalize eder, doğru rakamın hedef değerini 1 yapar ve train fonksiyonunu çağırırız. Bu işlemin tüm veri üzerinde bir kez yapılmasına epoch denir. Eğitimden sonra tahmin, en yüksek çıkış değerinin indeksidir:
def predict(pixels):
x = [p / 255.0 for p in pixels]
_, scores = forward(x)
return max(range(10), key=lambda i: scores[i])
| Yaklaşım | Avantaj | Dezavantaj |
|---|---|---|
| Sıfırdan ağ | Matematiği görünür kılar | Yavaş çalışır |
| Hazır kütüphane | Hızlı ve optimize | Ayrıntıları gizleyebilir |
Bu ağ modern modellerle yarışmayacaktır; fakat ileri yayılım, geri yayılım, gradyan inişi ve sınıflandırmanın nasıl birleştiğini açıkça gösterir. Sonraki adım olarak mini-batch eğitimi, ReLU, softmax ve çapraz entropi eklenebilir. Artık sinir ağı gizemli bir kara kutu değil; bolca döngü, türev ve sabırdan oluşan anlaşılır bir makinedir.
Yorumlar