שיעור Lisp General Problem Solver בינה מלאכותית יעל נצר

Preview:

Citation preview

Lisp שיעורGeneral 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

XYeqeqlequalequalp

‘x‘xTTTT

‘0‘0?TTT

‘(x)‘(x)nilnilTT

‘”xy”‘”xy”nilnilTT

‘”Xy”‘”xY”nilnilnilT

‘0‘0.0nilnilnilT

‘0‘1nilnilnilnil

Exact same object

Either eq or same number

Either eql or lists/strings

with eql elements

equal and non case/type sensitive

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 terms2. Specify problem in algorithmic terms3. Implement problem in a programming language4. Test the program on representative examples5. 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 goalsachieve - achieve an individual goalappropriate-p - decide if an operator is

appropriate for a goalapply-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 conventionuse: 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

emptystate? NIL is now ambiguous between failure and empty list

ofactions. Solve ambiguity by representing state with lists of

atleast 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

12345

678910

1112131415

1617181920

2122232425

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)