440 likes | 666 Views
שיעור Lisp General Problem Solver. בינה מלאכותית יעל נצר. נושאים לפרוייקט. האם מכונות יכולות לחשוב? מבחן טיורינג החדר הסיני מודעות של מכונות Computation and representation סמלים ומערכות סמלים ייצוג ידע ב- AI מודל חישובי של ה- mind האם המוח הוא מכונת חישוב?. Antibugging Tools.
E N D
שיעור LispGeneral Problem Solver בינה מלאכותית יעל נצר
נושאים לפרוייקט • האם מכונות יכולות לחשוב? • מבחן טיורינג • החדר הסיני • מודעות של מכונות • Computation and representation • סמלים ומערכות סמלים • ייצוג ידע ב-AI • מודל חישובי של ה-mind • האם המוח הוא מכונת חישוב?
Antibugging Tools • error / continuable errors (defun average (numbers) (if (null numbers) (error “Average of the empty list is undefined”) (/ (reduce #’+ numbers) (length numbers)))) • check-type: (defun sqr (x) (check-type x number) (* x x)) • assert: (defun sqr (x) (assert (numberp x) (x)) (* x x)) • Timing (defun f (n) (dotimes (i n) nil)) (time (f 100))
Closures • Functions carry their environment with them. (mapcar #'(lambda (x) (+ x x)) '(1 2 3)) --> (2 4 6) (defun adder (c) #'(lambda (x) (+ x c))) (mapcar (adder 3) '(1 2 3)) --> (4 5 6) (mapcar (adder 10) '(1 2 3)) --> (11 12 13)
Special Variables • Note scope is not the same thing as extent: • scope as in other languages (part of code where variable is defined) • Extent – lifetime of a variable: possible to exit from scope and have variable still alive in a closure. • Ex: (defun bank-account (balance) #'(lambda (action amount) (case action (deposit (setf balance (+ balance amount))) (withdraw (setf balance (- balance amount))))))
Special variables cont. • Special variables: indefinite scope (like global variables). • BUT: special variable can be shadowed by a local binding. (defvar *counter* 0) (defun report () (format t "Counter = ~d" *counter*)) (report) ==> Counter = 0 (let ((*counter* 100)) (report)) ==> Counter = 100 (report) ==> Counter = 0
Let did not create a new local variable distinct from the special - • instead it locally shadowed the binding of the same special var. • Name of special variable is available at runtime: (symbol-value '*counter*) • Lexical scoping: variables have lexical scope and indefinite extent. • Dynamic (special) variables: indefinite scope and dynamic extent.
Multiple Values • Functions can return more than 1 value. (round 5.1) ==> 5 .1 (* 2 (round 5.1)) ==> 10 (defun show-both (x) (multiple-value-bind (int rem)(round x) (format t "~f = ~d + ~f" x int rem))) (show-both 5.1) ==> 5.1 = 5 + 0.1 (values 1 2 3) ==> 1 2 3 ;; return 3 values. (values) ==> ;; nothing is returned (procedures)
Equalities equal and non case/type sensitive Exact same object Either eq or same number Either eql or lists/strings with eql elements
Parameters • optional, defaults, rest, by keyword. • Most CLisp functions have interesting keyword arguments (find 6 '(1 2 3 4 5 6.0)) ==> nil (eql as default) (find 6 '(1 2 3 4 5 6.0) :test #'equalp) ==> 6.0 (find 5 '(1 2 3 4 5 6.0) :test #'<) ==> 6.0 (find 5 '((a 1) (b 2) (c 3) (d 4) (e 5)) :key #'second) ==> (e 5) • Common key arguments (on matching and sequence built-in functions): :test, :key, :start, :end, :from-end
Example • Define functions find-all and find-all-if that return all elts from a list matching condition. • Note how similar to remove-if-not (built-in) and to remove. (setf (symbol-function find-all-if) #’remove-if-not)
Not exactly the right thing to do find-all - need to add something: (defun complement (fn) "If fn returns y, then (complement fn) returns (not y)." #'(lambda (&rest args) (not (apply fn args))))
(defun find-all (item sequence &rest keyword-args &key (test #'eql) test-not &allow-other-keys) "Find all those elements of sequence that match item, according to the keywords. Doesn't alter sequence." (if test-not (apply #'remove item sequence :test-not (complement test-not) keyword-args) (apply #'remove item sequence :test (complement test) keyword-args))) • Note: if same keyword appears twice, only first one is taken into account.
GPS: The General Problem Solver • Developed in 1957 by Alan Newell and Herbert Simon. • The "first AI program". General theory of problem solving. • First to separate problem-solving strategy from knowledge (declarative). Methodology: 1. Describe problem in vague terms 2. Specify problem in algorithmic terms 3. Implement problem in a programming language 4. Test the program on representative examples 5. Debug and analyze the resulting program, repeat the process.
Description: Means-end analysis and problem-solving. I want to take my son to nursery school. What's the difference between what I have and what I want? One of distance. What changes distance? My automobile. My automobile won't work. What is needed to make it work? A new battery. What has new batteries? An auto repair shop. I want the repair shop to put in a new battery; but the shop doesn't know I need one. What is the problem? One of communication. What allows communication? A telephone...and so on. [Newell & Simon 72 - Human Problem Solving]
Solve a problem = find a way to eliminate the difference between what I have and what I want. • Other possibility: forward chaining from original situation. • Some actions require solving preconditions as subproblems. • Need description of allowable actions and their preconditions, plus description of situation.
Specification • Description of the world and of the goal: list of conditions. • List of allowable operators. Constant but a parameter. • Description of operators: action, preconditions, effects. • Effects can be simply described as add-list and delete-list (STRIPS). • Calling form: (GPS init-state goal-state list-of-ops) • Output: sequence of operators. • To satisfy a list of goals: satisfy each goal. • To satisfy a single goal: either goal already in current state, or find appropriate operator. • An operator is appropriate for a goal if it has goal in its add-list. • Can apply operator if preconditions hold - that is, precond becomes goal.
Implementation • Toplevel Functions: GPS: toplevel function. • Special Variables: *state*: current state - list of conditions *ops*: list of operators • Data Types: op: operation with preconds, add-list and del-list. • Functions: achieve: achieve an individual goal. appropriate-p: decide if operator is appropriate for a goal. apply-op: apply operator to current state. • Utilities: member, set-difference, union, every, some, find-all.
Code Version 1 (defvar *state* nil "The current state: a list of conditions.") (defvar *ops* nil "A list of available operators.") (defstruct op "An operation" (action nil) (preconds nil) (add-list nil) (del-list nil)) (defun GPS (*state* goals *ops*) "General Problem Solver: achieve all goals using *ops*." (if (every #'achieve goals) 'solved))
(defun achieve (goal) "A goal is achieved if it already holds, or if there is an appropriate op for it that is applicable." (or (member goal *state*) (some #'apply-op (find-all goal *ops* :test #'appropriate-p)))) (defun appropriate-p (goal op) "An op is appropriate to a goal if it is in its add list." (member goal (op-add-list op))) (defun apply-op (op) "Print a message and update *state* if op is applicable." (when (every #'achieve (op-preconds op)) (print (list 'executing (op-action op))) (setf *state* (set-difference *state* (op-del-list op))) (setf *state* (union *state* (op-add-list op))) t))
(make-op :action 'drive-son-to-school :preconds '(son-at-home car-works) :add-list '(son-at-school) :del-list '(son-at-home)) ;;; ==============================
(defparameter *school-ops* (list (make-op :action 'drive-son-to-school :preconds '(son-at-home car-works) :add-list '(son-at-school) :del-list '(son-at-home)) (make-op :action 'shop-installs-battery :preconds '(car-needs-battery shop-knows-problem shop-has-money) :add-list '(car-works)) (make-op :action 'tell-shop-problem :preconds '(in-communication-with-shop) :add-list '(shop-knows-problem)) (make-op :action 'telephone-shop :preconds '(know-phone-number) :add-list '(in-communication-with-shop)) (make-op :action 'look-up-number :preconds '(have-phone-book) :add-list '(know-phone-number)) (make-op :action 'give-shop-money :preconds '(have-money) :add-list '(shop-has-money) :del-list '(have-money))))
(gps '(son-at-home car-needs-battery have-money have-phone-book) '(son-at-school) *school-ops*) (EXECUTING LOOK-UP-NUMBER) (EXECUTING TELEPHONE-SHOP) (EXECUTING TELL-SHOP-PROBLEM) (EXECUTING GIVE-SHOP-MONEY) (EXECUTING SHOP-INSTALLS-BATTERY) (EXECUTING DRIVE-SON-TO-SCHOOL) SOLVED (gps '(son-at-home cat-needs-battery have-money) '(son-at-school) *school-ops*) NIL (gps '(son-at-home car-works) '(son-at-school) *school-ops*) (EXECUTING DRIVE-SON-TO-SCHOOL) SOLVED
Analysis: How General? • First version crucial to understand problem better. • Running around the block problem: limit of KR formalism. • How to represent action "running around the block"? No add or delete. • Clobbered Sibling Goal Problem: (gps '(son-at-home car-needs-battery have-money have-phone-book) '(have-money son-at-school) *school-ops*) (EXECUTING LOOK-UP-NUMBER) (EXECUTING TELEPHONE-SHOP) (EXECUTING TELL-SHOP-PROBLEM) (EXECUTING GIVE-SHOP-MONEY) (EXECUTING SHOP-INSTALLS-BATTERY) (EXECUTING DRIVE-SON-TO-SCHOOL) SOLVED
But not true! Money is spent! (every #'achieve goals) --> each goal have been satisfied in sequence, but not necessarily holds at the end! • Fix: (defun achieve-all (goals) (and (every #'achieve goals) (subsetp goals *state*))) • Checks that goal is reached, but does not force backtracking to find a solution if not.
Leaping before you look • Same example, fixed function. Replies NIL after having spent the money! (gps '() '(jump-off-cliff land-safely) *ops*) --> (EXECUTING JUMP-OFF-CLIFF) nil • Planning and execution are interleaved.
Recursive Subgoal Problem • How to get a phone number? Look up phone book • Add option "Ask someone". (push (make-op :action 'ask-phone-number :preconds '(in-communication-with-shop) :add-list '(know-phone-number)) *school-ops*) --> Infinite loop!
A More General Problem Solver • Solve the problems: "running around the block", "prerequisite clobbers sibling goal", "leaping before you look" and "recursive subgoal" problems.
Implementation • Top-level function: GPS • Special variables: *ops* • Data types: op • Major functions: achieve-all - achieve a list of goals achieve - achieve an individual goal appropriate-p - decide if an operator is appropriate for a goal apply-op- apply operator to current state
Auxiliary functions: executing-p: Is a condition an executing form? starts-with: is the argument a list starting with a given atom? convert-op: convert an operator to use the executing convention use: use a list of operators
Improvement 1: • Instead of just printing a message whenever executing an action, GPS returns the resulting state with a list of "messages" indicating which actions have been taken • This solves the "running around the block" problem: (GPS '((executing running-around-the-block)))
(defun executing-p (x) "Is x of the form (executing ...)?" (starts-with x 'executing)) (defun starts-with (list x) "Is this a list whose first element is x?" (and (consp list) (eql (first list) x))) (defun convert-op (x) "Make op conform to the (EXECUTING op) convention." (unless (some #'executing-p (op-add-list op)) (push (list 'executing (op-action op)) (op-add-list op))) op) (defun op (action &key preconds add-list del-list) "Make a new operator that obeys the (EXECUTING op) convention." (convert-op (make-op :action action :preconds preconds :add-list add-list :del-list del-list)))
Improvement 2 • To solve the "leaping before you look" problem - do not update a global variable *state* before you know you will succeed. (defun GPS (state goals &optional (*ops* *ops*)) "From state, achieve goals using *ops*." (remove-if #'atom (achieve-all (cons '(start) state) goals nil)))
Semi-predicates: why do we add (start) in state? If GPS returns nil, is it because it failed or because it is an empty state? NIL is now ambiguous between failure and empty list of actions. Solve ambiguity by representing state with lists of at least one element. • Why do we remove atoms? The executing convention means that we keep track of the actions in the state as pairs (EXECUTING op), all the other elements are atoms. In the result, we only want the plan of actions, not the state.
Improvement 3 • Use an explicit goal-stack • To solve the "recursive subgoal problem": keep a stack of goals being solved, immediately fail if a goal appears as a subgoal of itself. • To solve the "clobber sibling goal" problem, update achieve-all to test for final satisfaction.
(defun achieve-all (state goals goal-stack) "Achieve each goal, and make sure they still hold at the end" (let ((current-state state)) (if (and (every #'(lambda (g) (setf current-state (achieve current-state g goal-stack))) goals) (subsetp goals current-state :test #'equal)) current-state))) (defun achieve (state goal goal-stack) "A goal is achieved if it already holds, or if there is an appropriate op for it that is applicable." (dbg-indent :gps (length goal-stack) "Goal: ~a" goal) (cond ((member-equal goal state) state) ((member-equal goal goal-stack) nil) (t (some #'(lambda (op) (apply-op state goal op goal-stack)) (find-all goal *ops* :test #'appropriate-p)))))
Why do we use member-equal? • Default test in member is eq. Must test for lists because of (executing op)convention. (defun member-equal (item list) (member item list :test #'equal))
Why can't we use union and set-difference in new apply-op? (defun apply-op (state goal op goal-stack) "Return a new state if op is applicable." (dbg-indent :gps (length goal-stack) "Consider: ~a" (op-action op)) (let ((state2 (achieve-all state (op-preconds op) (cons goal goal-stack)))) (unless (null states) ;; Return updated state (dbg-indent :gps (length goal-stack) "Action: ~a" (op-action op)) (append (remove-if #'(lambda (x) (member-equal x (op-del-list op))) state2) (op-add-list op)))))
State is now an ordered list of actions (to preserve order of (EXECUTING op) elements). • Functions union and set-difference do not preserve order of sets (they use sort). Functions append and remove-if are guaranteed to preserve order.
Improvement 4 • Make program environment a parameter that can be declared easily and switched. • Do NOT depend on global state for any aspect. (defun use (oplist) "Use oplist as the default list of operators." (length (setf *ops* oplist)))
The MAZE searching domain • We don't have variables... so we need many operators. • We still don't need to write them down ourselves... (defun make-maze-ops (pair) "Make maze-op in both directions." (list (make-maze-op (first pair) (second pair)) (make-maze-op (second pair) (first pair)))) (defun make-maze-op (here there) "Make an operator to move between 2 places." (op `(move from ,here to ,there) :preconds `((at ,here)) :add-list `((at ,there)) :del-list `((at ,here)))) (defparameter *maze-ops* (mappend #'make-maze-ops '((1 2) (2 3) (3 4) (4 9) (9 14) (9 8) ...)))
(use *maze-ops*) ==> 48 > (gps '((at 1)) '((at 25))) ((START) (EXECUTING (MOVE FROM 1 TO 2)) ... (EXECUTING (MOVE FROM 20 TO 25)) (AT 25)) Also return the state: (AT 25) when we only wanted the actions! (remove-if #'atom ...) is the culprit: use (find-all-if #'action-p...) (defun action-p (x) (or (equal x '(start)) (executing-p x)))
(defun find-path (start end) "Search a maze for a path from start to end." (let ((result (GPS `((at ,start)) `((at ,end))))) (unless (null result) (cons start (mapcar #'destination (remove '(start) results :test #'equal)))))) (defun destination (action) "Find the Y in (executing (move from X to Y))" (fifth (second action))) (use *maze-ops*) --> 48 (find-path 1 25) --> (1 2 3 4 9 8 7 12 11 16 17 22 23 24 19 20 25) (find-path 1 1) --> (1)