 Hi, I'm Dr. Mayfield and you're watching James Madison University Computer Science 101. Our topic for this week is artificial intelligence. Now my goal for the video today is to give you the background information you need to understand section 11.3 of our textbook. We'll talk a little bit about graphs and trees and how algorithms will search through these data structures to find useful information that we can use in AI programs. We'll also talk about heuristics and algorithms and the relationship between these two concepts. Let's get started. One of the most common data structures in computer science is called a graph. Now this is not to be confused with an XY plot or a bar graph like you've learned to draw in different math classes. This is a discrete data structure that allows you to model relationships between different items. In this chapter we're going to talk about the relationship of one state to the next state in a production machine. But let's take a step back here and talk about other uses of graphs so you can understand them at a conceptual level. Let's say you're trying to book a flight and you're trying to get from, I don't know, Washington D.C. to San Francisco. So I'm going to draw some of the airports between here and there. We've got D.C.A. is Washington D.C. Perhaps you can fly through Chicago, which is O.R.D. or Indianapolis, Denver, Salt Lake City, and finally San Francisco. And we know, for example, there are flights between these different airports. So I can go ahead and draw lines between them. There's also a direct flight from San Francisco to Washington D.C. And maybe there's other flights as well, like San Francisco to Denver and so forth. This is called a graph. What we have here at each one of these airports is called a node. Now a node is basically an element in your data set, sorry, in your data structure. So in section 9.3, the nodes are all different states of a production system. And we also have between pairs of nodes and edge. So that's basically what a graph is in terms of a data structure. It's a collection of nodes and edges. Often with graphs, one of the problems to solve is how do I find a path from one node to another node? Now a path basically means a sequence of edges. So for example, there's a path from Washington D.C. directly to San Francisco. There's another path that goes from Indianapolis to Chicago to Denver to Salt Lake to San Francisco. Or you could go from D.C. to Chicago to Salt Lake to San Francisco and so forth. Basically in this particular problem, when you're booking flights or trying to find driving directions, you want to find the path with the shortest length. And it's the same thing when making AI for video games or other puzzle games. You want to find the path that takes you to the goal as quickly as possible. The previous example with the airports is called an undirected graph because there's basically, across D.C. edge, you can go either way. But there's also a version of graphs called directed graphs. So let me draw several people here. I've got Alex, Brian, Fred, Dana, and Casey. Now these are all people in this example of a directed graph. And maybe what we want to model is who is following who on Twitter, for example. So perhaps I have Alex following Brian and Brian is following Dana and Fred. Casey is following Alex and Brian is following Casey and so forth. Now you'll notice the difference between the undirected graph and the directed graph is that I have arrow heads on the directed graph to show the direction of the edge. And the path in a directed graph has to follow the direction of those arrows. So for example, if I want to know the path from Alex to Fred, well, the only way to do that is through Brian and then to Fred. If I want to find the path from Fred to Alex, you'll notice there is no such path because there's no edge coming out of Fred that takes me back to Alex. If I were to add an edge like this and have this direction, then there would be a path through Casey to get to Alex. So again the difference between an undirected graph and a directed graph is merely the presence of these arrow heads. Now notice with both this directed graph and the previous example with the undirected graph is there are multiple paths to get from one place to another. For example, I could have gone this direct route or this indirect route through other vertices. Same thing in this example. I can go from Fred to Casey to Alex to Brian all the way back to Fred. That's called a loop. Now loops make graphs complicated in terms of designing algorithms because if you're trying to take a walk through this graph you have to keep track of where you've been in case you come back there again. There's a special version of graphs called trees and if a graph does not have any loops, we will call that a tree instead. So here's an example of a tree. You might think of this as a single elimination tournament say in basketball or something like that. And notice how in this example I'm writing the names of the nodes outside of the circles. That's perfectly fine. This is all at a conceptual level as long as you have vertices and nodes labeled appropriately. So for example, maybe we have a game between JMU and William & Mary and JMU wins and maybe between Virginia Tech and JMU and Virginia Tech wins and then for the championship, JMU beats Tech again and we have our tree. So trees are very common in athletic tournaments, family trees, anything to do with pedigrees or hierarchy and it's just a nicer data structure than a graph because of this requirement that there is only one path from any node to any other node. And typically the entire set of nodes is connected into one structure. So I know if I'm at this node right here, there is one path to get to any other node in the tree. There's no like two options that I have to consider like I would in the directed graph or undirected graph. So the reason why it's so important to understand data structures like graphs and trees is so that you can solve problems in artificial intelligence. We use these data structures to model the problem space or in other words, every situation you might find yourself when making some decisions about solving a problem or beating a game. Let's take a look at the problem space for the game of Tic Tac Toe. I assume all of you have played this game before and if not, well, you'll have to come to Office Hours and I'll show you how it works. So in yesterday's class, we talked a little bit about production systems which is a part of artificial intelligence that helps control the solution to a problem using a set of states and a set of productions. Let's look at a production system for the game of Tic Tac Toe and I'm going to use a tree data structure to show the problem space of this particular game. So when the game begins, we basically have a grid, this 3 by 3 and if Player X goes first, they can end up playing an X here or they can play an X here or they can play an X here and so forth. Now you can imagine if this were a tree data structure each node of the tree would be a state of the problem and each edge of the tree would be a production. This production right here is put X in slot number 1 let's say if I were to number these slots 1, 2, 3, 4, 5, 6, 7, 8, 9 and each one of these edges corresponds to an action or a production of playing the game. Now furthermore I would have descendants of each one of these nodes for example a proper state after this state here would be X and then the player O could play there or the player O could play here or the player O could play say in the middle and so forth. There's going to be at this level of the tree there's only 8 spots remaining where the O can play their next turn. So the nice thing about the tree is it helps encode possible states of the game you'll never go from this state with an X to some other state with lots of X's and O's you can only produce one more move of the game through this edge of the tree. Now you can imagine me building a tree data structure for the entire game of Tic Tac Toe that represents every possibility of the X's and O's in this game and then it's just a matter of trying to decide which branch should I take that's more likely to lead me to a solution. So speaking of trees I have here the textbook that we use in CS 227 and 228 by Kenneth Rosen it's a discrete math textbook and in Chapter 11 it even has this very Tic Tac Toe example. So here's how you would like to use this data structure in deciding how to play the game. If I look at the bottom of the tree that's what these dot dot dots mean right here I might be in this particular state of the game. Now if X plays in this corner that means there's two possibilities for O to play in. O can either play in this remaining space or this remaining space and you'll see that if O plays there X is going to win if O plays in the other place it will be a draw. Now on this other path of the tree if I decide to play somewhere else O is going to win in either case. So the decision making progress of this production system if I'm in this state right here I know I want to apply this production because that's the only path that will lead to a possible win. If I apply these other two productions then I'm going to lose for sure. And that's basically the idea is if I can somehow build up this tree I can start at the bottom to see how I'm going to win and trace my way back up through the system looking for the decisions I need to make at each branch of the game. Incidentally this is typically done with the stack data structure. If I start at the bottom and know okay I need to play X in that position I push that production onto a stack and then I push the next production onto the stack until I reach the very top of the tree where nothing happened. And as we talked about a couple weeks ago stacks are really useful for reversing a set of instructions. So if I want to play the game I just have to pop those decisions off the stack in the order that I need to play. So tic-tac-toe is a little bit easier than the atile game because every time I make a move I can't go back. I'm moving forward towards the goal of that game or towards the loss of that game. In contrast look at the atile game from chapter 11 of our textbook I could make a dumb decision like this. I could decide let's take the 7 and slide it over that way. And then I could make an even dumber decision by saying oh if I'm in this state let's take the 7 and slide it back that way. This would be a loop. I could be stuck forever if I programmed the control of my production system wrong it could be looping between these two different states of the system rather than applying the rule take the 8 and slide it to the final position. So this would be winning the game this would be forever stuck never winning or losing the game well I guess that would be considered a loss in that case. So the reason why this problem is more complex is that this is now a graph. Remember graphs allow you to have these loops or cycles whereas a tree does not have any loops or cycles. So how do we make sure that we if we have a graph for our problem space of a problem how can we set that up so that we always have a tree so that we know that we're making progress to get towards some solution. So to keep things simpler I'm going to draw a graph that just have letters that alphabetically tell you A is the smallest, I is the greatest and I'm trying to work my way from A over to I if this is my abstract problem I'm solving. Now this is a graph data structure you can see there's multiple paths to get from A to I I can either go through B or not I always have to go through D but then I can decide to go through E or F before I get to G and either I go to I or I go all around the long way and you might imagine that each one of these different paths have a different length this graph is not drawn to scale so it might be more expensive to take one path versus another. Let me show you two different ways of working your way through this graph to convert it to a tree one of them is called breath first search or we can call this BFS for short breath first search basically means at each point in the graph you want to visit all of the next points so for example I'm going to visit from A I'm going to go to B and C but I'm never going to visit the same place twice so if I then go down to B I'm not going to draw an edge to C I've already been to C before there's already an edge between A and C and now from C I'm going to branch out to D from D I'll go to E and F and the way I build this data structure is in alphabetical order so from D I go to E and F the next thing I'm going to visit is E I'm going to go to G and I'm following all of these same edges that I had originally from G I'll go to H and I when I go to H there's already an edge I and so forth so this is an example of a tree generated by breath first search from this graph breath first search is exactly what we were doing to build this kind of tree in the tic-tac-toe game I start at some state and I look at every possible next state before moving on to the next level and building every possible next state it's actually very expensive to build up this tree one level at a time exhausting every possible state and every possible production to get from one to the next and typically what happens with real games that aren't these little tic-tac-toe games is it's absolutely impossible to build a data structure in any reasonable amount of computer memory that could represent the entire collection of every state and every production that I would need to control so I need to come up with a different solution than breath first search if I want to create a strategy for winning games so let's try that graph again this time using a technique called depth first search or DFS for short now depth first search is trying to get from A to I without exploring every possible state that this game can be in so for example if I start at A I might go down to B and then go to C and then go to D and then I need to decide between E and F so I'll pick the one with the least alphabetical value let's go to E and then G and then H and then I so notice how I didn't branch out to every possible neighbor as I was searching through this graph but eventually I found a path focusing on depth from A to I now this isn't necessarily going to be optimal for example I could have gotten to C just by going from A to C in the beginning rather than going through B and let's see I could have gone from G directly to I rather than going through H if I consider the edges in the original graph so it's not to say that depth first search is going to be optimal but let me write that down it's not necessarily optimal but what you do have is a way to turn this graph into a tree without exploring every possible branching of outcomes at every decision point or every node of the graph so now the question is how can I use depth first search in a way that gets rid of these situations I want to go directly from A to C and I want to go directly from G to I without going around the long way well that is where a heuristic comes into play let's take a look at figure 11.12 on page 481 of the textbook where we again have this 8 tile game now if I'm in this situation I've been playing the game for a while and I see the board looks like this there's a couple options I can do I can either move the 2 which would slide this over to there or I can move the 5 which would slide this over to there now I probably also have the move where I move the 6 up but maybe that's where I came from so I don't want to repeat the move that I just previously did I want to move forward in terms of my progress for the game so let's just say I have these 2 alternatives and I try to decide which one of these is more likely to lead to a solution I could use a heuristic which is basically an educated guess now the educated guess I'm going to make is I'm going to take a look at all of these tiles and decide how far away they are from the solution let's say if you are 1 space away you get 1 point and if you're 2 spaces away you get 2 points and whichever one of these solutions have fewer points I'm going to go ahead and pick that solution so looking over here 1 is in the correct position 3 needs to move over 1 to get to this position this 5 needs to move 1, 2 to get to that position so I'll underline that twice this 2 on the other hand needs to move 1, 2 to get up 4 is in the right position 7 and 8 are in the right position 6 needs to move just up 1 to get to the right position so the heuristic value here is 1, 2, 3 4, 5 2 needs to move up and over that should have been a 2 pointer I was just trying to figure out why the book said 6 and I got 5 but it looks like I have 1, 2, 3, 4, 5, 6 total points for that one now let's analyze this one over here 1 is in the correct position 3 needs to move over just 1 space 2 needs to move up just 1 space 5 needs to move to the left 1 space 8 are correct 6 needs to move up 1 space so the heuristic value on the right here is 1, 2, 3, 4 right this is a score of 4 this is a score of 6 and if I want to minimize the number of moves to get to the end I'm going to make the decision to slide the 5 because that puts me in a better situation now just a word about heuristics themselves they basically have 2 desired properties 1 is that a heuristic should be designed to estimate the cost or benefit of applying some kind of production so in this particular example we just had the heuristic was estimating how many moves will be left if I make this production versus this production well this one is 4 so it's going to be least expensive or have a higher benefit than this one that gives you 6 the other requirement is that it should be easy to compute or efficient or fast to compute if you design a heuristic that takes a computer like 3 seconds to decide the answer it might be faster just to go ahead and build up the entire state tree using depth first search or breadth first search rather than this simple heuristic so the idea is that if there's a cheap computation that can be done such as just counting how many tiles a solution is off then that's going to be faster to get an educated guess building my way to the solution across this tree than building the entire graph using breadth first search so in a nutshell that's what heuristics are useful for I would definitely take a look at the algorithm this is figure 11.10 on page 480 and this is where we then use the heuristic to construct an algorithm to solve the game basically the idea is build up the tree but instead of using breadth first search I'm going to use depth first search and at each point rather than just saying I'm going to go to the least alphabetical number I'm going to decide which one of these paths has a better looking heuristic and I'm going to take that path to get all my way to the goal of the problem I'm solving and so now if I combine a heuristic to actually write code that solves this problem using some sort of intelligence and that's basically the way we make decisions on a daily basis well sort of I guess I should end by saying that this is only the beginning of what you'll learn about in an artificial intelligence class how to design an agent that makes intelligent decisions not only based on heuristics but how can it actually learn how to do better sorts of educated guesses than these basic mathematical tricks that we've looked at today but anyway that's a taste of what we'll be doing this week and also you might be thinking about what kind of heuristics are you going to use to guide your finch robot through the obstacle course on Friday's lab so I hope you've had fun watching this little video and we'll see you in class tomorrow