Haskell'de bir sinir ağı mimarisi uygulamaya ve onu MNIST üzerinde kullanmaya çalışıyorum.
hmatrixPaketi doğrusal cebir için kullanıyorum . Eğitim çerçevem pipespaket 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 NaNhesaplamalarda 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)
lfkayıp fonksiyonudur, nağdır ( her katman için weightmatris ve biasvektör) outve tarağın gerçek çıktısı ve target(istenen) çıktıdır ve dasher katmanın aktivasyon türevleridir.
Toplu modunda, out, tarmatrisler (satırlar çıkış vektörleri), ve dasmatris 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 lfve nyukarıdakiyle aynıdır i, girdidir ve thedef çıktıdır (matrisler olarak her ikisi de toplu halde).
squeezeher satırın toplamını alarak bir matrisi bir vektöre dönüştürür. Yani, dsdelta 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. FCkatman yapıcısıdır ve afbu 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 gradve 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.