A very large number of AI problems can be formulated as search problems. So, an analysis of search techniques is a good starting point for understanding AI methods.
Representation of a search problem involves representing
One of the most important representations of search is the problem space representation:
The objective of search is find a sequence of actions that transforms the initial state of the world into the goal state.
Possibilities:
Set of all states which can be reached by applying some combination of operators to the initial state.
(A measure of the size or the complexity of the problem.)
S A V E
+ M O R E
---------
M O N E Y
Each state represents a partial assignment of numbers to letters.
{no assignments}
/ | | \
/ | | \
/ | | \
/ | | \
{S=1} {A=1} {V=1} {E=1}
/ | \
/ | \
/ | \
{S=1, {S=1, ...
A=2} A=3}
Goal State: A leaf at which the assignment is consistent and is a solution to the puzzle.
Both resolution and chaining (backward as well as forward) can be viewed as a kind of search. At each step, you need to select the next clause to be resolved or the next rule to be applied. The objective is to find a sequence of steps (e.g., a sequence of rule applications) that will result in the desired theorem.
State of the world represents current position of robot. Operators represent available next moves.
Search techniques can be classified along two dimensions:
Two basic types of processing are forward chaining and backward chaining.
Note: We want to avoid having to generate the entire search tree. Instead, we use available operators to generate daughter nodes "on the fly."
Three basic types are
We will focus on blind and heuristic search.
Types:
Types:
A search method may be evaluated based on three criteria.
Follow one branch and pursue as deeply as possible before trying alternatives. If you run up against a dead end, back up to previous choice point. Prune repeated states (don't bother to check them again).
Put start node on list.
If start node is the goal, done.
--> If list empty, no solution.
| Otherwise: Select first node from list.
| If selected node is a goal, done.
| Otherwise: Expand selected node and add all successors to front of list.
--- Repeat.
Check all the daughter nodes at one level first before expanding the tree one level deeper, i.e., search by levels. Prune repeated states (don't bother to check them again).
Same as depth first, except that successors are added to end of list. In effect, depth first uses a stack (FIFO) whereas breadth first uses a queue (LIFO).
Put start node on list.
If start node is the goal, done.
--> If list empty, no solution.
| Otherwise: Select first node from list.
| If selected node is a goal, done.
| Otherwise: Expand selected node and add all successors to end of list.
--- Repeat.
Both of these are exhaustive or "brute force" search techniques. In the worst case, you may have to search the entire tree before finding a solution.
Depth first tends to be a little more efficient. On the average, depth first looks at half the nodes in the tree, whereas breadth first looks at all the nodes except for half on the last level. But if the tree is infinite, depth first search may never find the solution, whereas breadth first search will always find a solution if it exists.
EVALUATION --- DEPTH-FIRST vs. BREADTH-FIRST
DEPTH FIRST BREADTH FIRST
Guiding principle go as deep as you can completely explore
along a path before shallowest unvisited node
backing up first before going deeper
Algorithm start node on queue start node on queue
if goal, done if goal, done
remove node from queue remove node from queue
expand node, put sons expand node, put sons
on front of queue on back of queue
repeat till goal found repeat till goal found
or queue empty or queue empty
Guaranteed solution yes yes
(unless infinite tree) (if one exists)
No halt conditions infinite tree infinite tree AND no solution
Optimal solution no yes (minimum # of operators
required to transform initial
state to goal state)
Efficiency bad very bad, explores all avenues
When bad deep trees (can place always
a bound on search
depth for efficiency)
Knowledge requirements must be able to must be able to
recognize goal state recognize goal state
When useful if a solution is if an optimal solution is
wanted, any will do, required, and no search
and many are available control heuristics exist
Start node on list
If goal, done, else set cost of start node g*(start) = 0
--> If list empty, no solution.
| Select node from list with g*(node) minimum
| If goal node, done
| Otherwise: Expand selected node
| Cost of each successor g*(successor) = g*(parent) + cost(operator)
| Replace each successor on list
--- Repeat till goal found (done) or list empty (no solution)
Useful if start and goal are both known (e.g., 8-puzzle)
Similar to uniform cost, except start from both initial and goal states and expand in both directions.
Use some method to control or guide the search.
Continue working from the best node on the active list so far (as long as its successors are still improving.
Requires an evaluation function h* which will determine how "good" a given node is. (The lower the h* value, the better the node.)
Will find a solution since all successors of a node are added (no pruning). However, does not guarantee an optimal answer unless the heuristic function is set up just right.
Put start node on list.
If start node is the goal, done.
--> If list empty, no solution.
| Otherwise: Select node from list such that h*(node) is minimum.
| If selected node is a goal, done.
| Otherwise: Expand selected node and add all successors to list such
| h*(successor) > h*(node).
--- Repeat.
For the 8 puzzle, h*(n) could be the 1 / (number of tiles in the correct position). The more tiles are correct in some state, the better the node representing that state.
Compute h* of successor at time of expansion, and store with node. Thus h* is computed only once per node.
Sort list to add successors in appropriate position. Thus don't have to select minimum each time.
Variation of best first.
Best N successors of each expanded node added to list, for some N.
Cuts down on number of alternatives.
May miss solution if h* is poorly chosen.
Follow the path that looks like it's improving the fastest. Keep going as long as the path is improving.
Need a locally evaluable "goodness" function, known as the evaluation function (similar to h* in best first search).
Never backs up.
Fast, but not guaranteed to find a solution; solution found not necessarily optimal. Will work if the solution path is monotonic.
Similar to best first, but successors added differently.
Example: HILL BEST
CLIMBING FIRST
0 0 (0)
/ \ (4 2)
4 2 4 (7 5 2)
/ \ / | \ (8 5 2)
7 5 1 9 6 7 (8 10 2)
| / \ . | / \ (8 10 9 6)
8 10 3 . 12 34 17 8 (8 10 12 6)
HC . BF (8 10 12 34 17)
.
50 (optimal)
Solution: 8 (marked HC) 34 (marked BF)
follows fastest pursues best nodes,
improving path but will not find
to local minima path that looks bad
at first
more efficient better solutions
Breadth First, Depth First -- each node is or isn't a solution
Best First, Hill Climbing -- each node has a value ("goodness")
Branch and Bound -- each node is or isn't a solution, arcs have costs
HILL CLIMBING BEST FIRST BRANCH AND BOUND
Guaranteed No Almost Yes
solution?
Always Yes Yes Yes
halts?
Optimal? No Often Yes (cheapest cost
getting to node,
not necessarily
goodness of node)
When fails May find local If foothills
minima instead of surrounding the
global peak highest peak
Efficiency Very Good Good Good
Knowledge
requirements Evaluation Evaluation Need to know
function function arc costs
Like best first search, with:
f*(n) = g*(n) + h*(n)
where: g*(n) = minimum cost of path from start node to node n
h*(n) = estimated minimum cost from node n to goal node
/\ /\
start \ current / goal
\/\ /\ /
\/ \/\ /
\/
|<-- g --->|<---- h ---->|
|<-------- f --------->| f = g + h for best path
f*, g*, h* are guesses at the true values of f, g, h, respectively
Requires knowing the cost of each move (as in branch and bound) to calculate g*.
Requires a heuristic evaluation function to judge how hard it is to get from the current state to the end (h*).
Initial list of paths to a 0 length, 0 step path.
If list empty, no solution.
--> If first path on list reaches the goal, done.
| Otherwise: remove first path from list.
| Expand terminal node n of path, creating more paths.
| Add all new paths to list.
| Sort list by f*(n) = g*(n) + h*(n), putting least cost in front.
| If two or more paths have a common node, delete all paths containing
| that node except for the least cost path (handles graph cycles).
--- Repeat.
Improve performance without loss of optimality.
To prune search tree for other search algorithms.
Such PRUNING HEURISTICS may lose the solution, but can make a feasible solution possible by reduction of the search space.
AND-OR Graphs (Goal Trees) are are useful when it is possible to decompose the original problem into subproblems.
Generalization of search tree idea:
OR nodes: C C is solved if either A or B is solved.
/ \
/ \
/ \
A B
AND nodes: C C is solved if both A and B are solved.
/ \
/---\
/ \
A B
P
/ \
/ \
A B
/ \ /| \
/---\ /-|--\
C D G H I
| /|\ |\
E /-|-\ M N
J K L
The problem-solving procedure uses operators to generate successors as it is traversing down the graph, and then propagates success or failure back up the graph. It can use any of the search techniques discussed earlier to search the graph.
In adversarial situations such as game-playing (e.g., chess), a search tree consists of alternate moves made by each opponent, each of whom is trying to move towards a position best for himself. Each move leads to a completely predictable, finite set of states. The search is tricky because for every move a player can make, he must consider possible responses made by his opponent, who will trying to foil him.
A tree of alternative courses of play of a game of this kind. Each pair of moves is known as a PLY. A game tree is just a goal tree or an AND/OR tree:
Initial position MAXIMIZING LEVEL
/ | \
/ | \
Position Position Position MINIMIZING LEVEL
after my after my after my
move a move b move c
/ | \ / | \ / | \
/----|----\ /---|---\ /----|----\
Position
after your ... ... ...
move a1
When it's my turn to move, I'm looking at an OR node (representing alternative strategies). I have to choose my move so as to MAXIMIZE my chances of winning, i.e., to MAXIMIZE the goodness of the board position that I achieve. But for each move I consider, I have to consider every possible move of my opponent (an AND node), and I have to assume that my opponent will try to MINIMIZE my chances of winning, i.e., to MINIMIZE the goodness of the board position for me.
Levels in a game tree alternate in this fashion.
Clearly it is impossible to generate the entire game tree for non-trivial games. The LOOKAHEAD DEPTH is a threshold that controls the depth of search.
MINIMAX is a lookahead search procedure that is used to select the next move. Assume that we have an evaluation function that will return the goodness of a board position as a single number. This function is known as the STATIC BOARD EVALUATOR.
A Suppose D, E, F and G have values 2, 7, 1, 8.
/ \ B's value is 2 (because if I move to B, my opponent
B C will move to D (the best move for him).
/-\ /-\ C's value, similarly, is 1.
D E F G Therefore A's value is 2 (B=2 is a better move for
2 7 1 8 me than C=1).
People are type B. Most good game playing programs are currently type A. (Why? What's the problem?)
In any heuristic involving cutoff of lookahead search, there is always the danger that you missed seeing something that you would have seen if you had only looked another move ahead. E.g., you may be in a chess situation where a move looks good (you take the opponent's pawn, for example), but then the next move leaves your queen open for your opponent to take. If your lookahead stops at the point where you take the opponent's pawn, you'll make a bad move. This is known as the horizon effect.
A way to make minimax more efficient.
E.g., in the above example, if we minimax'd B, then we began to minimax C, we did F and found its value to be 1, there is no need to find the value of G since whatever it is, C's value can at most be 1, which is worse than the current maximum of 2. (If G is better than we expected, it doesn't matter since my opponent will not play there anyway; he'll play F if I play C. If G is worse than we expected, my opponent will play G if I play C, which will be even worse. In either case, C will be at most 1 and maybe worse. So I should play B regardless of the value of G.)
This can be used to prune away the entire search tree under G.
A B = 2
/ \ C <= 1
B C A = 2
/-\ /-\
D E F G
2 7 1 (pruned)
Similarly, I can prune successors of nodes at maximizing levels which have successors whose values are more than the value found so far, because my opponent will never play there (since then I would play to the successor with the larger value).