tipado - Red neuronal en Haskell
que es haskell en informatica (0)
Estoy tratando de implementar un marco de red neuronal en Haskell, y usarlo en MNIST, como un proyecto personal.
Estoy usando el paquete hmatrix para el álgebra lineal. Mi marco de entrenamiento se construye usando el paquete de tuberías.
Soy nuevo en el aprendizaje automático, así que mi comprensión de la teoría es limitada, pero al usar algunos tutoriales y otros recursos, creo que pude poner las matemáticas en el código de Haskell.
El problema es que mi código no parece funcionar. Se compila y no se cuelga. Pero, por un lado, ciertas combinaciones de tamaño de capa (digamos, 1000), tamaño de minibatch y velocidad de aprendizaje dan lugar a valores de NaN
en los cálculos. Después de algunas inspecciones, veo que los valores extremadamente pequeños (orden de 1e-100
) finalmente aparecen en las activaciones. Pero incluso cuando eso no sucede, la capacitación aún no funciona. No hay mejora sobre la pérdida o la precisión.
Comprobé y volví a verificar mi código, y no sé cuál podría ser la raíz del problema.
Aquí está el código de retropropagación, que calcula los deltas para cada capa:
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
es la función de pérdida, n
es la red (matriz de ponderación y vector de polarización para cada capa), out
y tar
son la salida real de la red y la salida objetivo (deseada), y das
son las derivadas de activación de cada capa. En modo batch, out
, tar
son matrices (las filas son vectores de salida), y das
es una lista de matriz.
Aquí está el cálculo del gradiente real:
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)
Aquí, lf
n
son los mismos que los de arriba, i
es entrada t
es la salida objetivo (ambos en forma de lotes, como matrices). squeeze
transforma una matriz en un vector sumando sobre cada fila. Es decir, ds
es una lista de matrices de deltas, donde cada columna corresponde a los deltas para una fila del minibatch. Entonces, los gradientes para los sesgos son el promedio de los deltas sobre todo el minibatch. Lo mismo para gs
, que corresponde a los gradientes para los pesos.
Aquí está el código de actualización real:
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
es la tasa de aprendizaje. FC
es el constructor de capa, y af
es la función de activación para esa capa. El algoritmo de descenso de gradiente se asegura de pasar un valor negativo para la tasa de aprendizaje. El código real para el descenso del gradiente es simplemente un bucle alrededor de una composición de grad
y move
, con una condición de parada parametrizada.
Finalmente, aquí está el código para una función de pérdida de error cuadrático medio:
mse :: (Floating a) => LossFunction a a
mse = let f (y,y'') = let γ = y''-y in (/ 2) $ γ**2
f'' (y,y'') = (y''-y)
in Evaluator f f''
Evaluator
solo agrupa una función de pérdida y su derivada (para calcular el delta de la capa de salida).
El resto del código está en github: NeuralNetwork
Entonces, si alguien tiene una idea del problema, o incluso un cheque de cordura que estoy implementando correctamente el algoritmo, estaría agradecido.