Im Trainings-Neuronalen Netzwerk treten extrem kleine oder NaN-Werte auf

328

Ich versuche, eine neuronale Netzwerkarchitektur in Haskell zu implementieren und auf MNIST zu verwenden.

Ich benutze das hmatrixPaket für die lineare Algebra. Mein Trainingsrahmen wird mit dem pipesPaket erstellt.

Mein Code wird kompiliert und stürzt nicht ab. Das Problem ist jedoch, dass bestimmte Kombinationen von Schichtgröße (z. B. 1000), Minibatch-Größe und Lernrate zu NaNWerten in den Berechnungen führen. Nach einiger Inspektion sehe ich, dass extrem kleine Werte (Reihenfolge 1e-100) schließlich in den Aktivierungen erscheinen. Aber selbst wenn das nicht passiert, funktioniert das Training immer noch nicht. Es gibt keine Verbesserung gegenüber dem Verlust oder der Genauigkeit.

Ich habe meinen Code überprüft und erneut überprüft und bin mir nicht sicher, wo die Ursache des Problems liegen könnte.

Hier ist das Backpropagation-Training, das die Deltas für jede Schicht berechnet:

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)

lfist die Verlustfunktion, nist das Netzwerk ( weightMatrix und biasVektor für jede Schicht) outund tarist die tatsächliche Ausgabe des Netzwerks und die target(gewünschte) Ausgabe und dassind die Aktivierungsableitungen jeder Schicht.

Im Batch - Modus out, tarsind Matrizen (Zeilen Ausgangsvektoren), und daseine Liste der Matrizen.

Hier ist die eigentliche Gradientenberechnung:

  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)

Hier lfund nwie oben iist die Eingabe und tdie Zielausgabe (beide in Stapelform als Matrizen).

squeezetransformiert eine Matrix in einen Vektor durch Summieren über jede Zeile. Das heißt, es dshandelt sich um eine Liste von Matrizen von Deltas, wobei jede Spalte den Deltas für eine Zeile des Minibatch entspricht. Die Gradienten für die Verzerrungen sind also der Durchschnitt der Deltas über das gesamte Minibatch. Das gleiche für gs, was den Verläufen für die Gewichte entspricht.

Hier ist der eigentliche Update-Code:

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))

lrist die Lernrate. FCist der Ebenenkonstruktor und afdie Aktivierungsfunktion für diese Ebene.

Der Gradientenabstiegsalgorithmus stellt sicher, dass ein negativer Wert für die Lernrate übergeben wird. Der eigentliche Code für den Gradientenabstieg ist einfach eine Schleife um eine Zusammensetzung von gradund movemit einer parametrisierten Stoppbedingung.

Schließlich ist hier der Code für eine mittlere quadratische Fehlerverlustfunktion:

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 bündelt nur eine Verlustfunktion und ihre Ableitung (zur Berechnung des Deltas der Ausgangsschicht).

Der Rest des Codes ist auf GitHub: NeuralNetwork verfügbar .

Wenn also jemand einen Einblick in das Problem hat oder nur eine Überprüfung der Gesundheit, ob ich den Algorithmus korrekt implementiere, wäre ich dankbar.

Charles Langlois
quelle
17
Danke, ich werde das untersuchen. Aber ich denke nicht, dass dies normales Verhalten ist. Soweit ich weiß, scheinen andere Implementierungen von dem, was ich versuche (einfaches, vollständig verbundenes neuronales Feedforward-Netzwerk), entweder in Haskell oder in anderen Sprachen, dies nicht zu tun.
Charles Langlois
17
@ Charles: Haben Sie tatsächlich Ihre eigenen Netzwerke und Datensätze mit diesen anderen Implementierungen ausprobiert? Nach meiner eigenen Erfahrung wird BP leicht durcheinander geraten, wenn der NN für das Problem nicht geeignet ist. Wenn Sie Zweifel an Ihrer Implementierung von BP haben, können Sie die Ausgabe mit der einer naiven Gradientenberechnung vergleichen (natürlich über eine NN in Spielzeuggröße) - was viel schwieriger zu verwechseln ist als BP.
Shinobi
5
Ist MNIST nicht normalerweise ein Klassifizierungsproblem? Warum verwenden Sie MES? Sie sollten Softmax-Crossentropie verwenden (berechnet aus den Protokollen). Nein?
11.
6
@CharlesLanglois, es ist möglicherweise nicht Ihr Problem (ich kann den Code nicht lesen), aber "mittlerer quadratischer Fehler" ist für ein Klassifizierungsproblem nicht konvex, was erklären könnte, dass Sie stecken bleiben. "logits" ist nur eine ausgefallene Art, Log-Odds zu sagen: Verwenden Sie die ce = x_j - log(sum_i(exp(x)))Berechnung von hier, damit Sie nicht das Log des Exponentials (das häufig NaNs erzeugt) nehmen
mdaoust
6
Herzlichen Glückwunsch zu der am höchsten bewerteten Frage (Stand: 20. Januar) ohne positive oder akzeptierte Antworten!
Hongsy

Antworten:

2

Kennen Sie "verschwindende" und "explodierende" Gradienten in der Backpropagation? Ich bin mit Haskell nicht allzu vertraut, daher kann ich nicht leicht erkennen, was genau Ihr Backprop tut, aber es sieht so aus, als würden Sie eine Logistikkurve als Aktivierungsfunktion verwenden.

Wenn Sie sich das Diagramm dieser Funktion ansehen, werden Sie feststellen, dass der Gradient dieser Funktion an den Enden nahezu 0 beträgt (da die Eingabewerte sehr groß oder sehr klein werden, ist die Steigung der Kurve fast flach), also multiplizieren oder dividieren Dies führt während der Backpropagation zu einer sehr großen oder sehr kleinen Anzahl. Wenn Sie dies wiederholt tun, während Sie mehrere Ebenen durchlaufen, nähern sich die Aktivierungen Null oder Unendlich. Da Backprop Ihre Gewichte während des Trainings aktualisiert, haben Sie viele Nullen oder Unendlichkeiten in Ihrem Netzwerk.

Lösung: Es gibt eine Vielzahl von Methoden, nach denen Sie suchen können, um das Problem des verschwindenden Gradienten zu lösen. Eine einfache Möglichkeit besteht darin, die Art der von Ihnen verwendeten Aktivierungsfunktion in eine nicht gesättigte zu ändern. ReLU ist eine beliebte Wahl, da es dieses spezielle Problem mildert (aber möglicherweise andere einführt).

jcft2
quelle