|
14 | 14 | "cell_type": "markdown",
|
15 | 15 | "metadata": {},
|
16 | 16 | "source": [
|
17 |
| - "## Introduction\n", |
| 17 | + "Introduction\n", |
| 18 | + "============\n", |
| 19 | + "\n", |
18 | 20 | "Hello!\n",
|
19 |
| - " In this IPython notebook, we study different kinds of search techniques used in [ search.py ]( https://github.com/aimacode/aima-python/blob/master/search.py ) and try to get an intuition of what search algorithms are best suited for various problems. We first explore uninformed search algorithms and later get our hands on heuristic search strategies.\n", |
| 21 | + "In this IPython notebook, we'll study different kinds of search techniques used in [ search.py ]( https://github.com/aimacode/aima-python/blob/master/search.py ) and try to get an intuition of what search algorithms are best suited for various problems. We first explore uninformed search algorithms and later get our hands on heuristic search strategies.\n", |
| 22 | + "\n", |
| 23 | + "The code in this IPython notebook, and the entire `aima-python` repository is intended to work with Python 3 (specifically, Python 3.4). So if you happen to be working on Python 2, you should switch to Python 3. For more help on how to install python3, or if you are having other problems, you can always have a look the `intro` IPython notebook. \n", |
20 | 24 | "\n",
|
21 |
| - " The code in this IPython notebook, and the entire aima-python repository is intended to work with Python 3 (specifically, Python 3.4). So if you happen to be working on Python 2, you should switch to Python 3. For more help on how to install python3, or if you are having other problems, you can always have a look the 'intro' IPython notebook. Now that you have all that sorted out, lets get started!" |
| 25 | + "Now that you have all that sorted out, let's get started!" |
22 | 26 | ]
|
23 | 27 | },
|
24 | 28 | {
|
|
32 | 36 | "cell_type": "markdown",
|
33 | 37 | "metadata": {},
|
34 | 38 | "source": [
|
35 |
| - "Also called `blind search`, in such search strategies with all the information we have about any state all we can do is generate its successors and check whether it's a `goal state` or not. THAT'S IT. NOTHING MORE(Well ....not really. See the `value` method defined in the following section).\n", |
| 39 | + "Uninformed Search strategies are called `blind search`. In such search strategies, the only information we have about any state is generated by checking if a piece of data, or any of its successors, matches our `goal state` or not. THAT'S IT. NOTHING MORE. (Well ....not really. See the `value` method defined in the following section).\n", |
36 | 40 | "\n",
|
37 | 41 | "First let's formulate the problem we intend to solve. So let's import everything from our module."
|
38 | 42 | ]
|
39 | 43 | },
|
40 | 44 | {
|
41 | 45 | "cell_type": "code",
|
42 |
| - "execution_count": 1, |
| 46 | + "execution_count": 16, |
43 | 47 | "metadata": {
|
44 | 48 | "collapsed": false
|
45 | 49 | },
|
|
55 | 59 | "The first thing we observe is '`from utils import *`'. This means that everything in utils.py is imported for use in this module. Don't worry. You don't need to read utils.py in order to understand search algorithms.\n",
|
56 | 60 | " \n",
|
57 | 61 | "The `Problem` class is an abstract class on which we define our problems(*duh*).\n",
|
58 |
| - "Again, if you are confused about what `abstract class` means have a look at the 'Intro' notebook.\n", |
| 62 | + "Again, if you are confused about what `abstract class` means have a look at the `Intro` notebook.\n", |
59 | 63 | "The `Problem` class has six methods.\n",
|
60 |
| - "* `__init__(self, initial, goal)` : This is what is called a `constructor` and is the first method called when you create an instance of class. In this and all of the below methods `self` refers to the object itself- the object whose method is called. `initial` specifies the initial state of our search problem. It represents the start state from where our agent begins his task of exploration to find the goal state(s) which is given in the `goal` parameter.\n", |
| 64 | + "* `__init__(self, initial, goal)` : This is what is called a `constructor` and is the first method called when you create an instance of class. In this and all of the below methods `self` refers to the object itself--the object whose method is called. `initial` specifies the initial state of our search problem. It represents the start state from where our agent begins his task of exploration to find the goal state(s) which is given in the `goal` parameter.\n", |
61 | 65 | "* `actions(self, state)` : This method returns all the possible actions our agent can make in state `state`.\n",
|
62 | 66 | "* `result(self, state, action)` : This returns the resulting state if action `action` is taken in the state `state`. This `Problem` class only deals with deterministic outcomes. So we know for sure what every action in a state would result to.\n",
|
63 |
| - "* `goal_test(self, state)` : Given a graph state, it checks if it is a terminal state. If the state is indeed a goal state, value of `True` is returned. Else , ofcourse, `False` is returned.\n", |
| 67 | + "* `goal_test(self, state)` : Given a graph state, it checks if it is a terminal state. If the state is indeed a goal state, value of `True` is returned. Else, of course, `False` is returned.\n", |
64 | 68 | "* `path_cost(self, c, state1, action, state2)` : Return the cost of the path that arrives at `state2` as a result of taking `action` from `state1`, assuming total cost of `c` to get up to `state1`.\n",
|
65 | 69 | "* `value(self, state)` : This acts as a bit of extra information in problems where we try to optimize a value when we cannot do a goal test."
|
66 | 70 | ]
|
|
69 | 73 | "cell_type": "markdown",
|
70 | 74 | "metadata": {},
|
71 | 75 | "source": [
|
72 |
| - "Now using the above abstract class as a parent there is another named called `GraphProblem` in our module. It creates a graph problem from an instance of the `Graph` class. To create a graph, simply do `graph = Graph(dict(...))`. The dictionary must contain nodes of the graph as keys, so make sure they are `hashable`. If you don't know what that means just use strings or numbers. Each node in the dictionary should correspond to another dictionary which contain the adjacent nodes as keys and the edge length as its value. The `Graph` class creates a directed(edges allow only one way traffic) by default.If you want to make an undirected graph, use `UndirectedGraph` instead, but make sure to mention any edge in only one of its nodes." |
| 76 | + "Now the above abstract class acts as a parent class, and there is another named called `GraphProblem` in our module. It creates a graph problem from an instance of the `Graph` class. To create a graph, simply type `graph = Graph(dict(...))`. The dictionary must contain nodes of the graph as keys, so make sure they are `hashable`. If you don't know what that means just use strings or numbers. Each node contains the adjacent nodes as keys and the edge length as its value. Each dictionary then should correspond to another dictionary in the graph. The `Graph` class creates a directed(edges allow only one way traffic) by default. If you want to make an undirected graph, use `UndirectedGraph` instead, but make sure to mention any edge in only one of its nodes." |
73 | 77 | ]
|
74 | 78 | },
|
75 | 79 | {
|
|
81 | 85 | },
|
82 | 86 | {
|
83 | 87 | "cell_type": "code",
|
84 |
| - "execution_count": 2, |
| 88 | + "execution_count": 17, |
85 | 89 | "metadata": {
|
86 | 90 | "collapsed": true
|
87 | 91 | },
|
|
105 | 109 | "cell_type": "markdown",
|
106 | 110 | "metadata": {},
|
107 | 111 | "source": [
|
108 |
| - "Suppose we are in a museum showcasing statues of various animals. To navigate through the museum there are paths between some statues and the entrance. We define the entrance and the statues as nodes in our graph with the path connecting them as edges. The cost/weight of an edge specifies its length. So `Start = dict(Dog = 3, Cat = 9, Mouse = 4)` means that there are paths from `Start` to `Dog`, `Cat` and `Mouse` with path costs 3, 9 and 4 respectively. See the image below to better understand the graph." |
| 112 | + "Imagine we are in a museum showcasing statues of various animals. To navigate through the museum there are paths between some statues and the entrance. We define the entrance and the statues as nodes in our graph with the path connecting them as edges. The cost/weight of an edge specifies is its length. So `Start = dict(Dog = 3, Cat = 9, Mouse = 4)` means that there are paths from `Start` to `Dog`, `Cat` and `Mouse` with path costs 3, 9 and 4 respectively. \n", |
| 113 | + "\n", |
| 114 | + "Here's an image below to better understand our graph." |
109 | 115 | ]
|
110 | 116 | },
|
111 | 117 | {
|
|
127 | 133 | "cell_type": "markdown",
|
128 | 134 | "metadata": {},
|
129 | 135 | "source": [
|
130 |
| - "In breadth first search the `shallowest` unexpanded node is chosen for expansion. That means that all nodes of a given depth must be expanded before any node of the next depth level. It accomplishes this by using a `FIFO` meaning 'First In First Out' queue. Any thing thats gets in the queue first also gets out first just like the checkout queue in a supermarket. To use the algorithm, first we need to define our problem. Say we want to find the statue of `Monkey` and we start from the entrance which is the `Start` state. We define our problem using the `GraphProblem` class." |
| 136 | + "In Breadth First Search, the `shallowest` unexpanded node is chosen for expansion. That means that all nodes of a given depth must be expanded before any node of the next depth level. This search strategy accomplishes this by using a `FIFO` meaning 'First In First Out' queue. Anything that gets in the queue first also gets out first just like the checkout queue in a supermarket. To use the algorithm, first we need to define our problem. Say we want to find the statue of `Monkey` and we start from the entrance which is the `Start` state. We'll define our problem using the `GraphProblem` class." |
131 | 137 | ]
|
132 | 138 | },
|
133 | 139 | {
|
134 | 140 | "cell_type": "code",
|
135 |
| - "execution_count": 3, |
| 141 | + "execution_count": 18, |
136 | 142 | "metadata": {
|
137 | 143 | "collapsed": false
|
138 | 144 | },
|
|
150 | 156 | },
|
151 | 157 | {
|
152 | 158 | "cell_type": "code",
|
153 |
| - "execution_count": 4, |
| 159 | + "execution_count": 19, |
154 | 160 | "metadata": {
|
155 | 161 | "collapsed": false
|
156 | 162 | },
|
|
161 | 167 | "['Cat', 'Monkey']"
|
162 | 168 | ]
|
163 | 169 | },
|
164 |
| - "execution_count": 4, |
| 170 | + "execution_count": 19, |
165 | 171 | "metadata": {},
|
166 | 172 | "output_type": "execute_result"
|
167 | 173 | }
|
|
175 | 181 | "cell_type": "markdown",
|
176 | 182 | "metadata": {},
|
177 | 183 | "source": [
|
178 |
| - "We get the output as `['Cat', 'Monkey']`. That is because first the nodes `Dog`, `Cat` and `Mouse` are added to the `FIFO` queue in `some` order when we are explanding the `Start` node. Now when we start expanding nodes in the next level, only the `Cat` node gets us to `Monkey`. Note that in breadth first search the goal test is done when it is being added to the queue." |
| 184 | + "We get the output as `['Cat', 'Monkey']`. That is because first the nodes `Dog`, `Cat` and `Mouse` are added to the `FIFO` queue in `some` order when we are expanding the `Start` node. Now when we start expanding nodes in the next level, only the `Cat` node gets us to `Monkey`. Note that during a breadth first search, the goal test is done when the node is being added to the queue." |
179 | 185 | ]
|
180 | 186 | },
|
181 | 187 | {
|
|
189 | 195 | "cell_type": "markdown",
|
190 | 196 | "metadata": {},
|
191 | 197 | "source": [
|
192 |
| - "In uniform cost search instead of expanding the shallowest node we expand the node with the lowest path cost(cost to reach upto that node from the start). Instead of a `FIFO` queue we use something called a `priority queue` which selects the element with the highest `priority` of all elements in the queue. For our problem lower path cost means higher priority. Whenever we need to enqueue a node already in the queue we update its path cost if the newer path is better. This is a very important step and it means that the path cost to a node may keep getting better until it is selected for expansion. This is the reason that we do a goal check only when a node is selected for expanion." |
| 198 | + "In Uniform-cost Search, we expand the node with the lowest path cost (the cost to reach that node from the start) instead of expanding the shallowest node. Rather than a `FIFO` queue, we use something called a `priority queue` which selects the element with the highest `priority` of all elements in the queue. For our problem, the shortest path between animals has the higher priority; the shortest path has the lowest path cost. Whenever we need to enqueue a node already in the queue, we will update its path cost if the newer path is better. This is a very important step, and it means that the path cost to a node may keep getting better until it is selected for expansion. This is the reason that we do a goal check only when a node is selected for expanion." |
193 | 199 | ]
|
194 | 200 | },
|
195 | 201 | {
|
196 | 202 | "cell_type": "code",
|
197 |
| - "execution_count": 5, |
| 203 | + "execution_count": 20, |
198 | 204 | "metadata": {
|
199 | 205 | "collapsed": false
|
200 | 206 | },
|
|
205 | 211 | "['Dog', 'Bear', 'Monkey']"
|
206 | 212 | ]
|
207 | 213 | },
|
208 |
| - "execution_count": 5, |
| 214 | + "execution_count": 20, |
209 | 215 | "metadata": {},
|
210 | 216 | "output_type": "execute_result"
|
211 | 217 | }
|
|
219 | 225 | "cell_type": "markdown",
|
220 | 226 | "metadata": {},
|
221 | 227 | "source": [
|
222 |
| - "We got `['Dog', 'Bear', 'Monkey']` instead of `['Cat', 'Monkey']` because the path cost is lower! We can also see the path cost with the path_cost attribute. Lets compare the path cost of the Breadth first search solution and Uniform cost search solution" |
| 228 | + "We got the path`['Dog', 'Bear', 'Monkey']` instead of `['Cat', 'Monkey']`. Why? The path cost is lower! We can also see the path cost with the path_cost attribute. Let's compare the path cost of the Breadth first search solution and Uniform cost search solution" |
223 | 229 | ]
|
224 | 230 | },
|
225 | 231 | {
|
226 | 232 | "cell_type": "code",
|
227 |
| - "execution_count": 6, |
| 233 | + "execution_count": 21, |
228 | 234 | "metadata": {
|
229 | 235 | "collapsed": false
|
230 | 236 | },
|
|
235 | 241 | "(18, 17)"
|
236 | 242 | ]
|
237 | 243 | },
|
238 |
| - "execution_count": 6, |
| 244 | + "execution_count": 21, |
239 | 245 | "metadata": {},
|
240 | 246 | "output_type": "execute_result"
|
241 | 247 | }
|
|
248 | 254 | "cell_type": "markdown",
|
249 | 255 | "metadata": {},
|
250 | 256 | "source": [
|
251 |
| - "We were right! The path cost through the `Cat` statue is indeed more than the path cost through `Dog` even though the former has only two roads compared to three roads in `ucs_node`." |
| 257 | + "We were right! \n", |
| 258 | + "\n", |
| 259 | + "The path cost through the `Cat` statue is indeed more than the path cost through `Dog` even though the former passes through two roads compared to the three roads in the `ucs_node` solution." |
252 | 260 | ]
|
| 261 | + }, |
| 262 | + { |
| 263 | + "cell_type": "code", |
| 264 | + "execution_count": null, |
| 265 | + "metadata": { |
| 266 | + "collapsed": true |
| 267 | + }, |
| 268 | + "outputs": [], |
| 269 | + "source": [] |
253 | 270 | }
|
254 | 271 | ],
|
255 | 272 | "metadata": {
|
|
268 | 285 | "name": "python",
|
269 | 286 | "nbconvert_exporter": "python",
|
270 | 287 | "pygments_lexer": "ipython3",
|
271 |
| - "version": "3.5.0" |
| 288 | + "version": "3.4.3" |
272 | 289 | }
|
273 | 290 | },
|
274 | 291 | "nbformat": 4,
|
|
0 commit comments