Haskell'de bir sinir ağı mimarisi uygulamaya ve onu MNIST üzerinde kullanmaya çalışıyorum.
hmatrix
Paketi doğrusal cebir için kullanıyorum . Eğitim çerçevem pipes
paket kullanılarak oluşturuldu .
Kodum derleniyor ve çökmüyor. Ancak sorun şu ki, belirli katman boyutu (örneğin 1000), mini parti boyutu ve öğrenme oranı kombinasyonlarının NaN
hesaplamalarda değerlere yol açması . Biraz incelemeden sonra, son derece küçük değerlerin (sıra 1e-100
) sonunda aktivasyonlarda göründüğünü görüyorum . Ancak bu olmasa bile eğitim hala işe yaramıyor. Kaybı veya doğruluğu konusunda hiçbir gelişme yok.
Kodumu kontrol ettim ve yeniden kontrol ettim ve sorunun kökeninin ne olabileceği konusunda bir kaygım var.
İşte her katman için deltaları hesaplayan geri yayılım eğitimi:
backward lf n (out,tar) das = do
let δout = tr (derivate lf (tar, out)) -- dE/dy
deltas = scanr (\(l, a') δ ->
let w = weights l
in (tr a') * (w <> δ)) δout (zip (tail $ toList n) das)
return (deltas)
lf
kayıp fonksiyonudur, n
ağdır ( her katman için weight
matris ve bias
vektör) out
ve tar
ağın gerçek çıktısı ve target
(istenen) çıktıdır ve das
her katmanın aktivasyon türevleridir.
Toplu modunda, out
, tar
matrisler (satırlar çıkış vektörleri), ve das
matris bir listesidir.
İşte gerçek gradyan hesaplaması:
grad lf (n, (i,t)) = do
-- Forward propagation: compute layers outputs and activation derivatives
let (as, as') = unzip $ runLayers n i
(out) = last as
(ds) <- backward lf n (out, t) (init as') -- Compute deltas with backpropagation
let r = fromIntegral $ rows i -- Size of minibatch
let gs = zipWith (\δ a -> tr (δ <> a)) ds (i:init as) -- Gradients for weights
return $ GradBatch ((recip r .*) <$> gs, (recip r .*) <$> squeeze <$> ds)
Burada lf
ve n
yukarıdakiyle aynıdır i
, girdidir ve t
hedef çıktıdır (matrisler olarak her ikisi de toplu halde).
squeeze
her satırın toplamını alarak bir matrisi bir vektöre dönüştürür. Yani, ds
delta matrislerinin bir listesidir, burada her sütun, mini partinin bir satırı için deltalara karşılık gelir. Öyleyse, önyargılar için gradyanlar, tüm mini parti üzerindeki deltaların ortalamasıdır. Aynı şey gs
, ağırlıkların gradyanlarına karşılık gelir.
İşte gerçek güncelleme kodu:
move lr (n, (i,t)) (GradBatch (gs, ds)) = do
-- Update function
let update = (\(FC w b af) g δ -> FC (w + (lr).*g) (b + (lr).*δ) af)
n' = Network.fromList $ zipWith3 update (Network.toList n) gs ds
return (n', (i,t))
lr
öğrenme oranıdır. FC
katman yapıcısıdır ve af
bu katman için etkinleştirme işlevidir.
Gradyan iniş algoritması, öğrenme oranı için negatif bir değerin geçmesini sağlar. Gradyan inişinin gerçek kodu , parametreleştirilmiş bir durdurma koşuluyla grad
ve bileşiminin etrafındaki bir döngüdür move
.
Son olarak, ortalama kare hata kaybı fonksiyonunun kodu:
mse :: (Floating a) => LossFunction a a
mse = let f (y,y') = let gamma = y'-y in gamma**2 / 2
f' (y,y') = (y'-y)
in Evaluator f f'
Evaluator
sadece bir kayıp fonksiyonunu ve türevini bir araya getirir (çıktı katmanının deltasını hesaplamak için).
Kodun geri kalanı GitHub'da: NeuralNetwork .
Bu nedenle, herhangi biri problemle ilgili bir kavrayışa sahipse veya hatta algoritmayı doğru uyguladığıma dair bir akıl sağlığı kontrolü varsa, minnettar olurum.