我正在尝试在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
各自层的激活衍生物。
在批处理模式中,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)
在这里,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上。
因此,如果有人对问题有深入的了解,或者甚至只是对我是否正确实施了算法进行了健全性检查,我将不胜感激。