Tôi đang cố gắng triển khai kiến trúc mạng thần kinh trong Haskell và sử dụng nó trên MNIST.
Tôi đang sử dụng hmatrix
gói cho đại số tuyến tính. Khung đào tạo của tôi được xây dựng bằng cách sử dụng pipes
gói.
Mã của tôi biên dịch và không bị lỗi. Nhưng vấn đề là, một số kết hợp nhất định của kích thước lớp (giả sử, 1000), kích thước lô nhỏ và tốc độ học tập sẽ làm tăng NaN
giá trị trong các phép tính. Sau một số kiểm tra, tôi thấy rằng các giá trị cực kỳ nhỏ (thứ tự 1e-100
) cuối cùng xuất hiện trong các kích hoạt. Nhưng, ngay cả khi điều đó không xảy ra, việc đào tạo vẫn không hoạt động. Không có cải thiện về độ mất hoặc độ chính xác của nó.
Tôi đã kiểm tra và kiểm tra lại mã của mình, và tôi không biết gốc rễ của vấn đề có thể là gì.
Đây là đào tạo backpropagation, tính toán các delta cho mỗi lớp:
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
là hàm mất mát, n
là mạng ( weight
ma trận và bias
vectơ cho mỗi lớp), out
và tar
là đầu ra thực tế của mạng và đầu ra target
(mong muốn), và das
là các dẫn xuất kích hoạt của mỗi lớp.
Trong chế độ hàng loạt out
, tar
là ma trận (hàng là vectơ đầu ra) và das
là danh sách các ma trận.
Đây là tính toán gradient thực tế:
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)
Ở đây, lf
và n
cũng giống như trên, i
là đầu vào và t
là đầu ra đích (cả ở dạng lô, dưới dạng ma trận).
squeeze
biến ma trận thành vectơ bằng cách tính tổng trên mỗi hàng. Đó là ds
danh sách các ma trận các delta, trong đó mỗi cột tương ứng với các delta cho một hàng của minibatch. Vì vậy, các gradient cho độ lệch là trung bình của các delta trên tất cả các minibatch. Điều tương tự đối với gs
, tương ứng với độ dốc của các trọng số.
Đây là mã cập nhật thực tế:
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
là tỷ lệ học tập. FC
là hàm tạo lớp, và af
là hàm kích hoạt cho lớp đó.
Thuật toán gradient descent đảm bảo chuyển giá trị âm cho tỷ lệ học tập. Mã thực tế cho sự giảm dần gradient chỉ đơn giản là một vòng lặp xung quanh thành phần của grad
và move
, với điều kiện dừng được tham số hóa.
Cuối cùng, đây là mã cho hàm mất lỗi bình phương trung bình:
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
chỉ gói một hàm mất mát và đạo hàm của nó (để tính toán delta của lớp đầu ra).
Phần còn lại của mã có trên GitHub: NeuralNetwork .
Vì vậy, nếu ai đó có cái nhìn sâu sắc về vấn đề, hoặc thậm chí chỉ cần kiểm tra sự tỉnh táo rằng tôi đang triển khai chính xác thuật toán, tôi sẽ rất biết ơn.