不確定特異点

広く深く、ところどころ超深く

学習とニューラルネットワーク (2)

勾配法

勾配法とは最適解をある関数の極値という形で求める手法です。関数 f(x) というのがあって、これの極小値を探索する場合、ある初期値 x0 から始めて、

{ x_n = x_{n-1} - \epsilon \nabla f(x) }

のように x を次々と更新していきます。これ以上、x が変動しなくなったら、その時の x の値が求める極小値を与えます。{\epsilon} は更新の細かさを制御する定数で、大きくしすぎると収束しない可能性があり、小さくするとなかなか極値に近づかないため、適当な値に調整する必要があります。

実際にプログラムを書いて実験してみました。

;; 勾配法の更新係数
(defparameter *epsilon* 1d-2)

(defun grad (f dfs xs &key (direction :down))
  "勾配法により関数の極値を探索する
f   - 対象の関数
dfs - f の一次微分ベクトル(多変数対応)
xs  - 入力ベクトル(多変数対応)
:direction により勾配の上り・下りを指定する
"
  (let* ((epsilon (if (eq direction :down) *epsilon* (- *epsilon*)))
         (ge (if (eq direction :down) #'>= #'<=))
         (nxs (mapcar #'(lambda (df x) 
                          (- x (* epsilon (apply df xs))))
                      dfs xs)))
    (if (funcall ge (apply f nxs) (apply f xs))
        (values 
         ;; 極値の点
         xs
         ;; 極値
         (apply f xs))
        (grad f dfs nxs :direction direction))))

試しに 2 次関数の極大値を求めてみます。関数 f とその一次微分 f'(x) と x の初期値 0 を grad の引数に渡します。また、極大値を求めるため、:direction には :up を指定して勾配を上るように指定します。

CL-USER> (grad #'(lambda (x) (+ (* x x -2) (* x 5) 2))
               (list #'(lambda (x) (+ (* x -4) 5)))
               (list 0)
               :direction :up)

(1.2499998901346454d0)
5.124999999999977d0

出力は極値を与える x の値と、その時の f(x) の値です。理論的には x=1.25 で極値なので、大体あってます。次はニューロンのモデルを使った関数近似にこの勾配法を適用してみます。

学習とニューラルネットワーク (電子情報通信工学シリーズ)

学習とニューラルネットワーク (電子情報通信工学シリーズ)