ChatGPT解决这个技术问题 Extra ChatGPT

训练神经网络中出现极小或 NaN 值

我正在尝试在 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 向量),outtar 是网络的实际输出,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)

这里,lfn 同上,i 是输入,t 是目标输出(都是批处理形式,作为矩阵)。

squeeze 通过对每一行求和将矩阵转换为向量。也就是说,ds 是一个增量矩阵列表,其中每一列对应于 minibatch 的一行的增量。因此,偏差的梯度是所有小批量的增量的平均值。 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

因此,如果有人对问题有深入的了解,或者只是对我正确实施算法的健全性检查,我将不胜感激。

谢谢,我会调查的。但我不认为这是正常行为。据我所知,我正在尝试做的其他实现(简单的前馈全连接神经网络),无论是用 Haskell 还是其他语言,似乎都没有这样做。
@Charles:您是否真的尝试过您自己的网络和数据集与上述其他实现?根据我自己的经验,当 NN 不适用于该问题时,BP 很容易陷入困境。如果您对 BP 的实现有疑问,您可以将其输出与简单梯度计算的输出进行比较(当然,在玩具大小的 NN 上)——这比 BP 更难出错。
MNIST 通常不是分类问题吗?你为什么使用MES?您应该使用 softmax 交叉熵(从 logits 计算)不是吗?
@CharlesLanglois,这可能不是您的问题(我看不懂代码),但是对于分类问题,“均方误差”不是凸的,这可以解释卡住的原因。 “logits”只是表示对数赔率的一种奇特方式:使用 ce = x_j - log(sum_i(exp(x))) 计算 from here,这样您就不会采用指数的对数(通常会生成 NaN)
恭喜您成为 highest voted 问题(截至 20 年 1 月),没有获得赞成或接受的答案!

j
jcft2

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

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

解决方案:您可以搜索很多方法来解决梯度消失问题,但可以尝试的一件简单的事情是将您正在使用的激活函数类型更改为非饱和激活函数。 ReLU 是一种流行的选择,因为它缓解了这个特定问题(但可能会引入其他问题)。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅