6.034 Artificial Intelligence - Recitations, fall 2004 online slides on learning

Next: Example Previous: Parameters That Can Affect

Perceptron Code

;;;; PERCEPTRON is a simple system for learning from examples which uses the
;;;; one-layer net learning procedure to adjust a set of weights on a single
;;;; linear threshold unit until all of the training examples are correctly
;;;; classified. The perceptron convergence theorem assures that the system
;;;; will halt if the examples are linearly separable but if not the system
;;;; may not halt.  The file BINARY-ENCODER contains the functions needed
;;;; for converting feature vector examples to bit strings
;;;; To run on multi-category problems like SOYBEAN-DATA, after loading the data
;;;; you must encode instances of all categories into bit strings using
;;;; ENCODE-CATEGORY-INSTANCES (e.g. (encode-category-instances soybean-cat
;;;; and then partition encoded examples into training and test sets using
;;;; SEPARATE-INSTANCES and then use PERCEPTRON-CATEGORIES to do the
;;;; learning and TEST-CATEGORIES to test the learned perceptron.

;;;; Uses functions defined in the files: BINARY-ENCODER and TESTER

;;;; Copyright (c) 1988 by Raymond Joseph Mooney. This program may be freely
;;;; copied, used, or modified provided that this copyright notice is included
;;;; in each copy of this code and parts thereof.

(defvar *trace-perceptron* t)       ; produces trace of weight updates if T.
(defvar *perceptron* nil)           ; Stores the final learned perceptron.
(defvar *domains*)                  ; List of domains (possible value list) for
                                    ; each feature
(defvar *eta* 1)                    ; Learning rate

(setf testor '((- (0 0))           ; Logical OR
               (+ (0 1))
	       (+ (1 0))
	       (+ (1 1))))

(setf test1 '((+ (0 1 0))           ; Simple testing example
              (+ (1 0 1))
              (+ (1 1 1))
              (- (0 0 1))))

(setf test2 '((- (0 0))             ; Infamous XOR example
              (+ (0 1))
              (+ (1 0))
              (- (1 1))))

(setf test3 '((- (0 0))           ; Logical AND
              (- (0 1))
	      (- (1 0))
	      (+ (1 1))))

(defmacro trace-print (test-var &rest format-form)
  ;; Print using the format string only is test-var is nonNIL
  `(if ,test-var
       (format t ,@format-form)))

(defun perceptron (examples &optional (threshold 0))
  (let* ((num-features (length (second (first examples))))
         (weights (make-array (list num-features)    ; define weight vector
                              :element-type 'number
                              :initial-element 0))   ; weights initialized to 0
         (all-correct nil) (i 0) (trial-num 0))
    (when *trace-perceptron* (print-perceptron weights threshold))
    (loop (if all-correct (return nil))  ; Loop until all examples are correctly
          (setf all-correct t)           ; classified.
          (dolist (example examples)     ; Each trial look at all examples
            (if (compute-perceptron-output (second example) weights threshold)
                (cond ((eq (first example) `-)  ; If network says + but its -
                       (trace-print *trace-perceptron*
                          "~%~%Classifies ~A wrong" example)
                       (setf all-correct nil)
                       (incf threshold *eta*)   ; Then increase threshold to
                                                ; make + classification harder
                       ;; and decrement weights for features present in example
                       (setf i 0)
                       (dolist (feature-value (second example))
                         (when (eq feature-value 1)
                           (incf (aref weights i) (- *eta*))
                           (trace-print *trace-perceptron*
                                "~%Decrementing weight for feature ~A" (- i 1)))
                         (incf i)))
                      (t (trace-print *trace-perceptron*
                          "~%~%Classifies ~A right" example)))
                (cond ((eq (first example) '+)  ; If network says - but its +
                       (trace-print *trace-perceptron*
                          "~%~%Classifies ~A wrong" example)
                       (setf all-correct nil)
                       (incf threshold (- *eta*)) ; Then decrease threshold to
                                                  ; make + classification easier
                       ;; and increment weights for features present in example
                       (setf i 0)
                       (dolist (feature-value (second example))
                         (when (eq feature-value 1)
                           (incf (aref weights i) *eta*)
                           (trace-print *trace-perceptron*
                                "~%Incrementing weight for feature ~A" (+ i 1))) 
                        (incf i)))
                      (t (trace-print *trace-perceptron*
                            "~%~%Classifies ~A right" example)))))
          (incf trial-num)                  ; Keep track of the number of trials
          (when *trace-perceptron* (print-perceptron weights threshold)))
    (format t "~%Trials: ~A" trial-num)
    (unless *trace-perceptron* (print-perceptron weights threshold))
    (setf *perceptron* (list weights threshold)))) ; Return the final perceptron

(defun compute-perceptron-output (feature-values weights threshold)
  ;;; Determine value of perceptron for the given input. Return T or NIL
  ;;; instead of 0 or 1 to simply tests

  (let ((sum 0) (i 0))
    ;; Simply sum the weight*input for all of the features
    ;; and return T if greater than threshold.
    (dolist (feature-value feature-values)
      (when (eq feature-value 1)
        (incf sum (aref weights i)))
      (incf i))
    (> sum threshold)))

(defun print-perceptron (weights threshold)
  ;; Printout the current weight vector and threshold

  (format t "~%~%Weights:")
  (dotimes (i (length weights))
    (format t " ~A" (aref weights i)))
  (format t "~%Threshold: ~A" threshold))