読者です 読者をやめる 読者になる 読者になる

きくらげ観察日記

好きなことを、適当に。

Gaucheでパーセプトロンを作った

Gaucheの練習。
パーセプトロンは簡単に作れる割に何となく学習してくれた感があって面白いです。

;; perceptron.scm
(use gauche.collection)

;; xがしきい値を超えているかを判定する関数
(define (sig x)
  (if (<= x 0) 0 1))

;; 最終的な結果を出力するためのニューロン
(define-class <neuron> ()
  ((inputs :init-keyword :inputs :accessor inputs) ;; 入力のベクタ
   (weights :init-keyword :weights :accessor weights))) ;; 重みのベクタ

(define-method generate-output ((neuron <neuron>))
  (sig (apply +
            (map (lambda (i w) (* (generate-output i) w))
                        (inputs neuron)
                        (weights neuron)))))

;; ニューロンの重みを関数fを用いて更新する
(define (update-weights-with! f neuron)
  (vector-map-with-index
   (lambda (i input)
     (let ((out (generate-output input))
           (weight (ref (weights neuron) i)))
       (set! (ref (weights neuron) i) (f weight out))))
   (inputs neuron)))

;; ニューロンの重みに入力を足し引きする
(define (increase-weights! neuron) (update-weights-with! + neuron))
(define (decrease-weights! neuron) (update-weights-with! - neuron))

;; 入力のorをとるやつ
(define-class <logical-or> ()
  ((inputs :init-keyword :inputs :accessor inputs)))

(define-method generate-output ((logical-or <logical-or>))
  (fold (lambda (x y) (or x y)) (inputs logical-or)))

;; getterの返り値をそのまま返す
(define-class <constant-input> ()
  ((getter :init-keyword :getter :accessor getter)))

(define-method generate-output ((constant <constant-input>))
  ((getter constant)))

;; パーセプトロンの教師データ
(define-class <supervisor> ()
  ((inputs :init-keyword :inputs :accessor inputs) ; 入力の値
   (expected :init-keyword :expected :accessor expected)))

;; neuronにsupervisorsを用いて学習をさせる。
;; また、入力の変化はupdate-inputsによって行う。
(define (learn supervisors update-inputs neuron)
  (define is-all-correct? #f)
  (while (not is-all-correct?)
    (set! is-all-correct? #t)
    (map
     (lambda (supervisor)
       (let ((ins (inputs supervisor))
             (e (expected supervisor)))
         (update-inputs ins)
         (if (= e (generate-output neuron))
             #f ; 出力結果が教師データと一致した場合、何もしない
             (begin
               (set! is-all-correct? #f)
               (cond
                ((= e 1) ; 1を0と判断
                 (increase-weights! neuron))
                ((= e 0) ; 0を1と判断
                 (decrease-weights! neuron)))))))
     supervisors)))

使用例として、このパーセプトロンに「x1とx2のORとは何か」を学習させてみました。

;; 入力はx1, x2, x3の3つ。
;; x1とx2のORを取る。
;; x3はしきい値を与えるためのダミー

(define input-vector #f)

(define (update-input-vector! vec)
  (set! input-vector vec))

(define x1 (make <constant-input>
             :getter (lambda () (ref input-vector 0))))
(define x2 (make <constant-input>
             :getter (lambda () (ref input-vector 1))))
(define x3 (make <constant-input>
             :getter (lambda () (- 1))))

;; パーセプトロン
(define example-perceptron
  (make <neuron>
    :inputs (vector x1 x2 x3)
    :weights #(0 0 0)))

;; x1, x2の組と、出力が取るべき値の組からなる教師データ
(define example-supervisors
  (list
   (make <supervisor> :inputs #(0 0) :expected 0)
   (make <supervisor> :inputs #(0 1) :expected 1)
   (make <supervisor> :inputs #(1 0) :expected 1)
   (make <supervisor> :inputs #(1 1) :expected 1)))

(define (example-learn)
  (learn example-supervisors update-input-vector! example-perceptron))

(example-learn)

;; 学習した結果を表示
(map
 (lambda (ins)
   (set! input-vector ins)
   (let ((out (generate-output example-perceptron)))
     (display ins)
     (display ": ")
     (display out)
     (newline)))
 (list #(0 0) #(0 1) #(1 0) #(1 1)))

実行結果

$ gosh perceptron.scm
#(0 0): 0
#(0 1): 1
#(1 0): 1
#(1 1): 1