ニューラルネットワークのトレーニングで非常に小さい値またはNaN値が表示される


329

Haskellでニューラルネットワークアーキテクチャを実装し、MNISTで使用しようとしています。

hmatrix線形代数のパッケージを使用しています。私のトレーニングフレームワークは、pipesパッケージを使用して構築されています。

私のコードはコンパイルされ、クラッシュしません。ただし、問題は、レイヤーサイズ(たとえば、1000)、ミニバッチサイズ、および学習率の特定の組み合わせによってNaN、計算に値が生じることです。いくつかの検査の後、非常に小さな値(次数1e-100)が最終的にアクティベーションに表示されることがわかります。しかし、それが起こらなくても、トレーニングはまだ機能しません。その損失や精度に改善はありません。

私は自分のコードをチェックして再チェックしましたが、問題の根本が何であるかについて途方に暮れています。

各レイヤーのデルタを計算するバックプロパゲーショントレーニングは次のとおりです。

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損失関数は、あるnネットワーク(れるweightマトリックス及びbias各層のベクトル)、out及びtarネットワークの実際の出力としているtarget(所望の)出力、及びdas各層の活性化誘導体です。

バッチモードではouttar行列(行は出力ベクトルである)であり、das行列のリストです。

実際の勾配計算は次のとおりです。

  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)

ここで、lfおよびnは上記と同じであり、iは入力であり、tはターゲット出力です(両方ともバッチ形式で、行列として)。

squeeze各行を合計することにより、行列をベクトルに変換します。つまり、dsはデルタの行列のリストです。各列は、ミニバッチの行のデルタに対応します。したがって、バイアスの勾配は、すべてのミニバッチにわたるデルタの平均です。の場合も同じですgs。これは、重みの勾配に対応します。

実際の更新コードは次のとおりです。

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学習率です。FCはレイヤーコンストラクターであり、afはそのレイヤーの活性化関数です。

最急降下アルゴリズムは、学習率に負の値を渡すようにします。勾配降下のための実際のコードは、単にの組成物の周りにループであるgradmove、パラメータ化された停止条件付き。

最後に、平均二乗誤差損失関数のコードは次のとおりです。

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 損失関数とその導関数(出力層のデルタを計算するため)をバンドルするだけです。

残りのコードはGitHub:NeuralNetworkにあります。

したがって、誰かが問題についての洞察を持っている場合、または私がアルゴリズムを正しく実装していることをサニティチェックするだけでさえあれば、私は感謝するでしょう。


17
おかげで、私はそれを調べます。しかし、これは正常な動作ではないと思います。私の知る限り、Haskellまたは他の言語のいずれかで、私がやろうとしていることの他の実装(単純なフィードフォワード完全接続ニューラルネットワーク)は、それを行っていないようです。
Charles Langlois 2017年

17
@Charles:他の実装で実際に独自のネットワークとデータセットを試しましたか?私自身の経験では、NNが問題に適していない場合、BPは簡単に混乱します。BPの実装に疑問がある場合は、その出力を単純な勾配計算の出力と比較できます(もちろん、おもちゃサイズのNNを使用)。これは、BPよりも間違いがはるかに困難です。
忍2017年

5
MNISTは通常、分類の問題ではありませんか?なぜMESを使用しているのですか?ソフトマックスクロスエントロピー(ロジットから計算)を使用する必要がありますか?
mdaoust

6
@CharlesLanglois、それはあなたの問題ではないかもしれません(私はコードを読むことができません)が、「平均二乗誤差」は分類問題に対して凸ではありません。「logits」対数オッズを言うためだけの空想の方法です:使用ce = x_j - log(sum_i(exp(x)))計算ここからあなたは(多くの場合、NaNを生成)指数のログを取ることはありませんので、
mdaoust

6
(20年1月現在)最も投票数の多い質問であり、賛成または承認された回答がないことをおめでとうございます。
hongsy

回答:


2

バックプロパゲーションの「消失」と「爆発」の勾配について知っていますか?私はHaskellにあまり詳しくないので、バックプロパゲーションが何をしているのか簡単にはわかりませんが、活性化関数としてロジスティック曲線を使用しているように見えます。

この関数のプロットを見ると、この関数の勾配が両端でほぼ0であることがわかります(入力値が非常に大きくなったり小さくなったりすると、曲線の傾きはほぼ平坦になります)。したがって、乗算または除算します。これにより、バックプロパゲーション中に非常に大きな数または非常に小さな数になります。複数のレイヤーを通過するときにこれを繰り返し実行すると、アクティベーションがゼロまたは無限大に近づきます。backpropはトレーニング中にこれを行うことで重みを更新するため、ネットワークに多くのゼロまたは無限大が発生することになります。

解決策:勾配消失問題を解決するために検索できるメソッドはたくさんありますが、簡単に試すことができるのは、使用している活性化関数のタイプを非飽和関数に変更することです。ReLUは、この特定の問題を軽減するため、一般的な選択肢です(ただし、他の問題が発生する可能性があります)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.