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

标签 algorithm haskell neural-network backpropagation

我正在尝试在 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是每一层的激活导数。
在批处理模式下,out , tar是矩阵(行是输出向量),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是该层的激活函数。
梯度下降算法确保为学习率传递一个负值。梯度下降的实际代码只是一个围绕 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 .
因此,如果有人对问题有深入的了解,或者只是对我正确实现算法的健全性检查,我将不胜感激。

最佳答案

你知道反向传播中的“消失”和“爆炸”梯度吗?我对 Haskell 不太熟悉,所以我不能轻易看到你的反向传播到底在做什么,但看起来你确实使用逻辑曲线作为你的激活函数。
如果你看一下这个函数的图,你会发现这个函数的梯度在末端几乎为 0(当输入值变得非常大或非常小时,曲线的斜率几乎是平的),所以乘法或除法在反向传播过程中,这将导致一个非常大或非常小的数字。在通过多个层时重复执行此操作会导致激活接近零或无穷大。由于反向传播通过在训练期间这样做来更新你的权重,你最终会在你的网络中得到很多零或无穷大。
解决方案:您可以搜索很多方法来解决梯度消失问题,但可以尝试的一件简单的事情是将您正在使用的激活函数类型更改为非饱和激活函数。 ReLU 是一种流行的选择,因为它缓解了这个特定问题(但可能会引入其他问题)。

关于algorithm - 训练神经网络中出现极小或 NaN 值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44686609/

相关文章:

algorithm - "Log(x,y)"在sql server 2005中怎么办

python - 检查您是否可以将列表分成 2 以及其中的数字的精确总和

algorithm - 计算 2 个整数列表的增量

haskell - 使用 IO.readLn 从 Haskell 中的标准输入读取单行

algorithm - 检查 3 个变量是否都是唯一的熟练方法

haskell - 是否可以有递归求和类型,每个 'level' 都有不同的值?

Haskell euclid算法两个整数的最大公约数

machine-learning - Keras多标签分类 'to_categorical'错误

python - Numpy神经网络成本计算: result changes after first run

train() 中的 ROC 度量,插入符包