训练神经网络中出现极小的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的激活函数。

梯度下降算法可确保为学习率传递一个负值。梯度下降的实际代码只是一个围绕grad和组成的循环move,带有参数化的停止条件。

最后,这是均方误差损失函数的代码:

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

17
@Charles:您是否真的尝试了上述其他实现方式来尝试自己的网络和数据集?以我自己的经验,当NN不适合该问题时,BP会轻易成为麻烦。如果您对BP的实现方式有疑问,可以将其输出与朴素梯度计算(当然是在玩具大小的NN上)的输出进行比较-这比BP更难弄错。
shinobi

5
MNIST通常不是分类问题吗?为什么要使用MES?您应该使用softmax交叉熵(从对数计算)吗?
mdaoust

6
@CharlesLanglois,这可能不是您的问题(我看不懂代码),但是对于分类问题而言,“均方误差”并不是凸面的,这可以解释为什么卡住了。“ logits”只是一种奇数对数的表达方式:从此处开始使用ce = x_j - log(sum_i(exp(x)))计算这样您就不必获取指数对数(通常会生成NaN)
mdaoust

6
恭喜,您的投票最高的问题(截至20年1月),尚无更新或接受的答案!
hongsy

Answers:


2

您知道反向传播中的“消失”和“爆炸”梯度吗?我对Haskell不太熟悉,因此我无法轻易了解您的backprop到底在做什么,但看起来确实像您在使用逻辑曲线作为激活函数。

如果看一下该函数的曲线图,您会发现该函数的斜率在端部几乎为0(由于输入值变得非常大或非常小,曲线的斜率几乎是平坦的),因此可以乘以或除以由此,在反向传播期间将导致非常大或非常小的数量。穿越多层时重复执行此操作会使激活接近零或无穷大。由于backprop在训练过程中通过这样做来更新您的权重,因此您最终在网络中会得到很多零或无穷大。

解决方案:可以找到大量方法来解决梯度消失的问题,但是尝试尝试的一件事是将要使用的激活函数的类型更改为非饱和函数。ReLU是一种流行的选择,因为它可以缓解此特定问题(但可能会引入其他问题)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.