heuristic search cis 479/579 bruce r. maxim um-dearborn
TRANSCRIPT
Heuristic Search
CIS 479/579
Bruce R. Maxim
UM-Dearborn
State Space Search
• Many AI problems can be conceptualized as searching a well defined set of related problem states– Puzzle solving– Game playing– Generate and test– Genetic algorithms– Scheduling algorithms
Water Jug Problem
• Problem– You have a four gallon jug and a three
gallon jug and the goal is to come up with exactly two gallons of water.
• Operations– Dump bucket contents on the ground– Dump bucket contents in other bucket– Fill bucket to top with water
Tree Representation
(0,0)
(4,0) (0,3)
(0,3) (0,0) (4,3) (1,3) (3,0) (4,3) (0,0)
Digraph Representation
(0,0)
(4,0) (0,3)
(1,3) (4,3) (3,0)
Adjacency List
• (0 0) ((4 0) (0 3))
• (4 0) (( 4 3) (0 0) (1 3) (0 3))
• (0 3) ((4 3) (3 0) (0 0))
• (1 3) ((1 0) (4 3) (4 0) (0 3))
Good Knowledge Representations
• Important things made clear /explicit• Expose natural constraints• Must be complete• Are concise• Transparent (easily understood)• Information can be retrieved & stored quickly• Detail suppressed (can be found as needed)• Computable using existing procedures
River Puzzle
• Problem– There are four items a farmer, wolf, goose,
and corn. The farmer can only take one item across the river at a time.
• Constraints– Wolf will eat the goose if left alone with it – Goose will eat the corn if left alone with it
Problem Analysis
• Each of the 4 problem elements can be considered a Boolean variable based on its bank location
• There are 24 or 16 possible states• 6 of these states fail the “no eat” test• That leaves 10 states to consider which could
be linked n C(10,2) = 45 ways• However only 10 of these links can really be
used to connect states
F=Farmer W=Wolf G=Goose C=Corn ~=River
W F
~ W
F G
F W F G ~ G F ~
W C W C C ~ G F
G ~ C F ~ W
C F ~ C F W W G
~ G G ~ G C C C
F C
W ~
G W
Solution
• Once graph is constructed finding solution is easy (simply find a path)
• AI programs would rarely construct the entire graph explicitly before searching
• AI programs would generate nodes as needed and restrict the path search to the nodes generated
• May use forward reasoning (initial to goal) or backward reasoning (goal to initial)
Rules
• Many time these state transitions are written as rules:
If ((Wolf Corn) (Farmer Goose))
Then ((Wolf Corn Farmer) (Goose))
• Depending on which direction you are reasoning you match either the left (if) or right (then) part
Potential Problems
• If you have more than one state to move to then you need a procedure to choose one
• Exact matches often do not occur in the real world so you may need to measure how “close” you are to an exact match
Control Strategies
• A good control strategy causes motion (ideally toward the goal state)
• The control strategy should be systematic and not allow repeated use of the same rule sequences (to avoid infinite loops)
Heuristics
• AI programming often relies on the use of heuristics
• Heuristics are techniques that improves efficiency by trading “speed” for “completeness”
Search Considerations
• Can the search algorithm work for the problem space?
• Is the search algorithm efficient?
• Is it easy to implement?
• Is search the best approach or is more knowledge better?
Example Graph
S
A
B C
F
Adjacency List
(setf (get 's 'children) '(a b)) (setf (get 'a 'children) '(s b f)) (setf (get 'b 'children) '(s a c)) (setf (get 'c 'children) '(b f)) (setf (get 'f 'children) '(a c))
“Any Path” Search Algorithms
• Depth First Search
• Hill Climbing
• Best First Search
• Breadth First Search
• Beam Search
Depth First Search
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementadd its children to the front of the queue of the
partial pathsIf goal is attained then
announce success
Depth First Output> (depth 's 'f)
((s))
((a s) (b s))
((b a s) (f a s) (b s))
((c b a s) (f a s) (b s))
((f c b a s) (f a s) (b s))
(s a b c f)
Depth First Weakness
• Depth first is not good for “tall” trees when it is possible to over commit to a “bad” path early
• In our example depth first missed a complete solution because it focused on checking the first partial path and did not test the others in the queue
Depth First Search
(defun depth (start finish)
(depth1 (list (list start)) finish))
(defun depth1 (queue finish)
(cond ((null queue) nil) ((equal finish (caar queue))
(print queue) (reverse (car queue))) (t (print queue) (depth1 (append (expand (car queue))
(cdr queue)) finish))))
expand(defun expand (path) ;kills cycles (remove-if #’(lambda (path) (member (car path) (cdr path)) ) (mapcar #'(lambda (child)(cons child path)) (get (car path) 'children) ) ))> (expand '(a s))((b a s) (f a s))
Recursive Depth First
(defun simple-depth (tree goal)
(cond ((= (car tree) goal) tree)
((atom tree) nil)
(t (cons (car tree)
(or (simple-depth (cadr tree) goal)
(simple-depth (caadr tree) goal)
)
)
)
)
)
Breadth First Search
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementadd its children to the rear of the queue of the
partial pathsIf goal is attained then
announce success
Breadth First Output
> (breadth 's 'f)
((s))
((a s) (b s))
((b s) (b a s) (f a s))
((b a s) (f a s) (a b s) (c b s))
((f a s) (a b s) (c b s) (c b a s))
(s a f)
Breadth First Weakness
• Breadth first is not good for “fat” trees where the nodes have high branching factors
• Can also be bad choice when several partial paths lead to the same node several levels down (bottleneck)
• Not always fast but it will never miss a complete solution
Breadth First Search(defun breadth (start finish) (breadth1 (list (list start)) finish))
(defun breadth1 (queue finish) (cond ((null queue) nil)
((equal finish (caar queue)) (print queue) (reverse (car queue)))
(t (print queue) (breadth1 (append (cdr queue)
(expand (car queue))) finish))))
Improving Search
• We will need to add knowledge to our algorithms to make them perform better
• We will need to add some distance estimates, even using “best guesses” would help
• We will need to begin sorting part of the the queue of partial paths
Insertion Sort(defun insertion-sort (s predicate)
;insertion sort for lists (cond ((null s) nil) (t (splice-in (car s)
(insertion-sort (cdr s) predicate) predicate)))
Insertion Sort Helper(defun splice-in (element s predicate) ;insert in order
(cond ((null s) (list element))
;end of s? ((funcall predicate element (car s)) (cons element s))
;add here? (t (cons (car s)
(splice-in element (cdr s) predicate)))
;try further down
))
sort
• Common Lisp has a built in “sort” function that allows you to sort any list using a predicate capable of comparing two list elements
• Examples> (sort '(1 2 3 4 5) '>)
(5 4 3 2 1)
> (sort '(5 4 3 2 1) '<)
(1 2 3 4 5)
closerp
• When could define a predicate function that either retrieves or computes the distance to the goal for the nodes
(defun closerp (x y)
(< (get (car x) 'distance)
(get (car y) 'distance)
)
)
Example Graph
S
A
B C
F2
1
1
0
2
Using closerp
> (setf (get 's 'distance) 2)
> (setf (get 'a 'distance) 1)
> (setf (get 'b 'distance) 2)
> (setf (get 'c 'distance) 1)
> (setf (get 'f 'distance) 0)
> (sort '((b s) (b a s) (f a s)) 'closerp)
((f a s) (b s) (b a s))
Euclidean Distance
• Distance could be computed based on properties of the node instead
• Another possibility
(defun distance (n1 n2) (sqrt (+ (expt (- (get n1 ‘x) (get n2 ‘x)) 2) (expt (- (get n1 ‘y) (get n2 ‘y)) 2) ) ) )
Hill Climbing
• An improved depth first search
• Requires the ability to guess the distance to the goal using an ordinal scale (does not require exact distances)
• Children are sorted before being added to the front of the queue of partial paths
Uses for Hill Climbing
• You must have adjustable (and reversible) operations to control progress– Adjusting thermostat without numbers– Adjusting fuzzy TV signal– Mountain climbing in the fog using an
altimeter
Hill Climbing
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementsort its children based on distance to goaladd children to the front of the queue of the
partial pathsIf goal is attained then
announce success
Hill Climbing Output> (hill 's 'f)
((s))
((a s) (b s))
((f a s) (b a s) (b s))
(s a f)
Hill Climbing(defun hill (start finish) (hill1 (list (list start)) finish))
(defun hill1 (queue finish) (cond ((null queue) nil) ((equal finish (caar queue)) (print queue) (reverse (car queue))) (t (print queue)
(hill1 (append (sort (expand (car queue)) 'closerp)
(cdr queue) )
finish))))
Hill Climbing Weaknesses
• Foothill problem– Local peaks attract procedure’s attention away
from trying to reach the “top”
• Plateau problem– Area flat so there is very little to attract procedure
to one path over another
• Ridge problem– Every step is down though not at a local minimum
(e.g. at the Northpole every step is south)
Best First Search
• An improved hill climbing or depth first search
• Requires the ability to guess the distance to the goal using an ordinal scale (does not require exact distances)
• Entire queue of partial path is sorted after it is modified
Best First Search
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementadd its children to the front of the queue of the
partial pathssort the queue of partial paths
If goal is attained thenannounce success
Best First Output> (best 's 'f)
((s))
((a s) (b s))
((f a s) (b a s) (b s))
(s a f)
Best First Search(defun best (start finish) (best1 (list (list start)) finish))
(defun best1 (queue finish) (cond ((null queue) nil) ((equal finish (caar queue)) (print queue) (reverse (car queue))) (t (print queue)
(best1 (sort (append (expand (car queue)) (cdr queue))
'closerp) finish))))
Best First Weaknesses
• Still requires the use of a “natural” distance measure
• Sorting takes time
• Tends to produce shorter paths than other depth first search algorithms, but is not guaranteed to produce the shortest path
Beam First Search
• Improved version of breadth first search
• Good for “fat” trees
• Does not guarantee it will find shortest path
• Trades speed for completeness
Beam First Search Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else If length (queue) > then then take first queue elements as new queue
remove the first queue element add its sorted children to the rear of the queue
of the partial paths
If goal is attained then announce success
Beam First Output> (beam 's 'f 3)
((s))
((a s) (b s))
((f a s) (a b s) (c b s) (b a s))
(s a f)
Beam First Search(defun beam (start finish width) (beam1 (list (list start)) finish width))
(defun beam1 (queue finish width) (cond ((null queue) nil) ((equal finish (caar queue)) (print queue)
(reverse (car queue))) (t (print queue) (beam1
(sort (apply 'nconc
(mapcar 'expand (if (< (length queue) width) queue (first-n queue width)))) 'closerp) finish width)))
first-n
(defun first-n (l n) (cond ((zerop n) nil) (t (cons (car l)
(first-n (cdr l) (1- n))))))
Improving Things
• We would still like to find optimal paths faster then breadth first search does
• More knowledge should mean less search
• Don’t try to “tune” the search efficiency, improve understanding and try to remove the need for search
British Museum Search
• If enough monkeys had enough type writers and enough time then they could recreate all the knowledge housed in the British Museum
• So we could compute every path by trial and error then pick the shortest
Branch and Bound
• An optimal depth first search
• Requires the ability to measure the distance traveled “so far” using an ordinal scale (does not require exact distances)
• Entire queue of partial path is sorted after it is modified
Branch and Bound
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementadd its children to the front of the queue of the
partial pathssort the queue of partial paths by distance
traveledIf goal is attained then announce success
Branch and Bound Output> (branch 's 'f)
((s))
((a s) (b s))
((b s) (b a s) (f a s))
((a b s) (c b s) (b a s) (f a s))
((c b s) (b a s) (f a s) (f a b s))
((b a s) (f a s) (f c b s) (f a b s))
((f a s) (c b a s) (f c b s) (f a b s))
(s a f)
Branch and Bound(defun branch (start finish) (branch1 (list (list start)) finish))
(defun branch1 (queue finish) (cond ((null queue) nil) ((equal finish (caar queue)) (print queue) (reverse (car queue))) (t (print queue)
(branch1 (sort ;new predicate used (append (expand (car queue)) (cdr queue)) 'shorterp)
finish))))
shorterp
• These distances should be computable for most problem spaces
• For this example using path “length” as the cost of traveling so far
(defun shorterp (p1 p2)
;check path length
(< (length p1) (length p2)))
Branch and Bound Weaknesses
• Still requires the use of a “natural” distance measure
• Sorting takes time
• Produces shorter paths, but like many optimal path algorithms it requires more work
Branch and Bound with Underestimator
• If we can get a good estimate of the remaining distance to the goal we could get a better estimate of the real cost of traversing each evolving path
• It is essential that our estimate of the remaining distance never exceed the actual distance to the goal (to ensure the promise that the algorithm return the shortest path)
Branch and Bound with Underestimator
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementadd its children to the front of the queue of the
partial pathssort the queue of partial paths by distance
traveled plus the estimate of the distance to the goal
If goal is attained then announce success
Branch and Bound with Underestimator Output
> (under 's 'f)
((s))
((a s) (b s))
((f a s) (b s) (b a s))
(s a f)
Branch and Bound with Underestimator
(defun under (start finish) (under1 (list (list start)) finish))
(defun under1 (queue finish) (cond ((null queue) nil) ((equal finish (caar queue)) (print queue) (reverse (car queue))) (t (print queue) (under1 (sort (append (expand (car queue)) (cdr queue)) 'betterp) finish))))
betterp
• For this example using path “length” as the cost of traveling so far
• We are pretending the the distances are underestimators of the distance to the goal
(defun betterp (p1 p2) (if (< (+ (length p1) (get (car p1) 'distance)) (+ (length p2) (get (car p2) 'distance))
)
t nil))
Branch and Bound with
Underestimator Weaknesses
• Still requires the use of a “natural” distance measure
• Underestimator is a guess as best• Sorting takes time• Allows redundant paths to evolve (like
ordinary branch and bound)
Branch and Bound with Dynamic Programming
• If we can have tried to improve Branch and Bound by eliminating redundant paths
• We will not use an underestimator in this version of the algorithm
Branch and Bound with Dynamic Programming
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementadd its children to the front of the queue of the
partial pathssort the queue of partial paths by distance
traveledremove redundant paths
If goal is attained then announce success
Branch and Bound with Dynamic Programming Output
> (dynamic 's 'f)
((s))
((a s) (b s))
((b s) (f a s))
((a b s) (c b s) (f a s))
((c b s) (f a s))
((f a s))
(s a f)
Branch and Bound with Dynamic Programming
(defun dynamic (start finish)(dynamic1 (list (list start)) finish))
(defun dynamic1 (queue finish) (cond ((null queue) nil) ((equal finish (caar queue)) (print queue) (reverse (car queue))) (t (print queue) (dynamic1 (remove-dups (sort (append (expand (car queue)) (cdr queue)) 'shorterp)) finish))))
remove-dups(defun remove-dups (queue) (do* ((path (car queue) (cadr (member path queue))) (remd (cdr queue) (cdr (member path queue))) )
((null remd) queue)
;drop any path reaching same node as first path (dolist (path2 remd)
(if (equal (car path2) (car path)) (setq queue (remove path2 queue))
) ) ))
Branch and Bound with Dynamic Programming Weaknesses
• Still requires the use of a “natural” distance measure
• I have seen some evidence of thrashing when underestimator if not used
• Sorting takes time
A*
• Makes use of both a cost and underestimator
• Removes redundant paths from the queue of partial paths
A*
Add root to queue of partial pathsUntil queue is empty or goal is attained
If first queue element equals the goal then do nothing
Else remove the first queue elementadd its children to the front of the queue of the
partial pathssort the queue of partial paths by distance
traveled plus the estimate of distance to goalremove redundant paths
If goal is attained then announce success
A*
> (a* 's 'f)
((s))
((a s) (b s))
((f a s) (b s))
(s a f)
A*
(defun a* (start finish) (a*1 (list (list start)) finish))
(defun a*1 (queue finish) (cond ((null queue) nil) ((equal finish (caar queue)) (print queue) (reverse (car queue))) (t (print queue) (a*1 (remove-dups (sort(append (expand (car queue)) (cdr queue)) 'betterp)) finish))))
A* Weaknesses
• Still requires the use of a “natural” distance measure
• Underestimator is a guess as best
• Sorting takes time
• Removing paths takes time
Summary
• British Museum – only works for small search spaces
• Branch and Bound– good for large search spaces if bad paths look bad early
• B & B with Underestimator– good if distance estimate is reliable
• B & B with Dynamic Programming– good when several paths reach the same state on the way
to a solution
• A*– good whenever B & B is good with underestimator and
dynamic programming