誤差逆伝搬法
大学の課題でニューラルネットワークの誤差逆伝搬法*1による学習を作ったので載せてみる。課題自体はC++で作るものだが、適当に書く分にはlispの方がずっと楽である。C++で書いた方はひどいことになってるので載せない。
適当に書いたので、層とユニットの数は任意に入るようにしたが、出力関数がシグモイドに特化していて一般的になっていない。
(defun sigmoid (x) "シグモイド関数" (/ 1 (+ 1 (exp (- x))))) (defun neural-network (layers &optional init) "入力層から出力層まで適当にレイヤ数を入れるとNN用のリストを作る。 initはパラメータ初期化用の関数を入れる。閾値も重みも同じ扱い。" (unless init (setq init (lambda () 0))) (let ((i (pop layers)) j nn) (while (setq j (pop layers)) (push (let (ll) (dotimes (_ j ll) (push (let (l) (dotimes (_ (1+ i) l) ;パラメータ数=閾値1+入力数 (push (funcall init) l))) ll))) nn) (setq i j)) (reverse nn))) (defun nn-output (nn inputs) "NNの応答を得る。入力層のパラメータを入れると,各層の出力をリストで返す。 並びは最終出力から順になり,入力層も付く。" (let ((l (list inputs))) (dolist (layer nn l) (let ((i-lst (car l))) (push (mapcar (lambda (unit-param) (sigmoid (apply '+ (mapcar '* unit-param (append (list 1) i-lst))))) layer) l))))) (defun transverse (lst) "行と列の関係を入れ替える。転置。" (let (l) (apply 'mapcar 'list lst))) (defun bp-sigma (nn outputs model) "BPのσを返す。出力層から順の並び。" (let ((weights (reverse nn)) (l (list (mapcar (lambda (o m) (* (- m o) o (- 1 o))) (car outputs) model)))) ;最終層 (dolist (o-lst (cdr (butlast outputs)) (reverse l)) (let ((weight (transverse (pop weights)))) (push (mapcar (lambda (o w) (* o (- 1 o) (apply '+ (mapcar '* w (car l))))) o-lst weight) ;中間層 l))))) (defun bp-delta (eta sigma outputs) "BPのΔを返す。NNのパラメータと同じ並び。" (pop outputs) (let (l ou su) (while (and (setq ou (pop outputs)) (setq su (pop sigma))) (push (mapcar (lambda (s) (mapcar (lambda (x) (* eta x s)) (append (list 1) ou))) su) l)) l)) (defun back-propagation (nn eta pattern) "BPを適用したNNを返す。" ;pattern {inputs model} (let ((res (copy-tree nn))) (dolist (p pattern res) (let* ((outputs (nn-output nn (car p))) (sigma (bp-sigma nn outputs (cadr p)))) (setq res (mapcar (lambda (ru du) (mapcar (lambda (r d) (mapcar '+ r d)) ru du)) res (bp-delta eta sigma outputs)))))))
neural-network関数でNNを初期化して、手動で指定した学習パターンをback-propagationに突っ込んで学習させる。内部では細かい数値が現れるので、倍精度の数値が必要なようである。
;実行例 (series 1 compile ;非標準(とりあえずコンパイルすべし) 'sigmoid 'neural-network 'nn-output 'bp-sigma 'bp-delta 'back-propagation) ;数値が細かいので結果を見やすく表示する関数を用意 (defun test (nn pattern) (map nil (lambda (x) (format t "~{~A ~}~%" (mapcar (lambda (x) (if (< x 0.1) 'l (if (> x 0.9) 'h 'm))) (car (nn-output nn (car x)))))) pattern)) ;学習パターン。せっかくだから3入力3出力 (let ((l 0.05) (h 0.95)) (setq pattern-3xor (list `((,l ,l ,l) (,l ,l ,l)) `((,l ,l ,h) (,l ,h ,h)) `((,l ,h ,l) (,h ,h ,l)) `((,l ,h ,h) (,h ,l ,h)) `((,h ,l ,l) (,h ,l ,h)) `((,h ,l ,h) (,h ,h ,l)) `((,h ,h ,l) (,l ,h ,h)) `((,h ,h ,h) (,l ,l ,l))))) (setq nn (neural-network '(3 9 3) (lambda () (- 1.0d0 (random 2.0d0))))) ;学習前の出力 (test nn pattern-3xor) | m m m m m m m m m m m m m m m m m m m m m m m m (dotimes (_ 10000) (do-events) (setq nn (back-propagation nn 0.8 pattern-3xor))) ;学習後の出力 (test nn pattern-3xor) | l l l l h h h h l h l h h l h h h l l h h l l l
ちゃんと学習できましたー。
は良いのだが、中間層を多めに取っても確実に学習できるわけではなくて、割と不安定。大方精度に問題がある気がする。
*1:error back propergationだが単純にBPと言われることもある。