Sinir ağı eğitiminde son derece küçük veya NaN değerleri görünüyor


328

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.


17
Teşekkürler, bununla ilgileneceğim. Ama bunun normal bir davranış olduğunu düşünmüyorum. Bildiğim kadarıyla, Haskell'de veya diğer dillerde yapmaya çalıştığım şeyin diğer uygulamaları (basit ileri beslemeli, tamamen bağlı sinir ağı), bunu yapıyor gibi görünmüyor.
Charles Langlois

17
@Charles: Kendi ağlarınızı ve veri setlerinizi diğer uygulamalarla gerçekten denediniz mi? Kendi tecrübelerime göre, NN soruna uygun olmadığında BP kolayca karışabilir. BP uygulamanız hakkında şüpheleriniz varsa, bunun çıktısını saf bir gradyan hesaplamasıyla (tabii ki oyuncak boyutlu bir NN üzerinden) karşılaştırabilirsiniz - ki bu BP'den çok daha zor.
shinobi

5
MNIST tipik olarak bir sınıflandırma sorunu değil mi? Neden MES kullanıyorsunuz? Softmax crossentropy kullanmalısınız (günlüklerden hesaplanır) hayır?
mdaoust

6
@CharlesLanglois, Senin sorunun olmayabilir (kodu okuyamıyorum) ama "ortalama kare hatası" bir sınıflandırma problemi için dışbükey değil, bu da takılıp kalmayı açıklayabilir. "logits", log-olasılık demenin süslü bir yoludur: Buradance = x_j - log(sum_i(exp(x))) hesaplamayı kullanın, böylece üstel (genellikle
NaN'ler

6
Olumlu oylanan veya kabul edilen yanıtlar olmadan en yüksek oyu alan soru olduğunuz için tebrikler (Ocak '20 itibarıyla)!
hongsy

Yanıtlar:


2

Geri yayılmadaki "yok olan" ve "patlayan" gradyanları biliyor musunuz? Haskell'e pek aşina değilim, bu yüzden backprop'unuzun tam olarak ne yaptığını kolayca göremiyorum, ancak aktivasyon işleviniz olarak bir lojistik eğri kullanıyormuşsunuz gibi görünüyor.

Bu fonksiyonun grafiğine bakarsanız, bu fonksiyonun gradyanının uçlarda neredeyse 0 olduğunu göreceksiniz (giriş değerleri çok büyük veya çok küçük hale geldikçe, eğrinin eğimi neredeyse düzdür), dolayısıyla çarpma veya bölme bu, geri yayılım sırasında çok büyük veya çok küçük bir sayı ile sonuçlanacaktır. Birden çok katmandan geçerken bunu tekrar tekrar yapmak, etkinleştirmelerin sıfıra veya sonsuza yaklaşmasına neden olur. Backprop, egzersiz sırasında bunu yaparak ağırlıklarınızı güncellediğinden, ağınızda çok sayıda sıfır veya sonsuzluk ile sonuçlanırsınız.

Çözüm: Yok olan gradyan problemini çözmek için arayabileceğiniz birçok yöntem var, ancak denenmesi kolay bir şey, kullandığınız etkinleştirme işlevinin türünü doygun olmayan bir işlevle değiştirmektir. ReLU, bu belirli sorunu hafiflettiği için popüler bir seçimdir (ancak başkalarını da ortaya çıkarabilir).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.