└── simple_demo.py /simple_demo.py: -------------------------------------------------------------------------------- 1 | class JointProbabilityTable: 2 | def __init__(self, columns, data): 3 | self._columns = columns 4 | self._probability_index = len(columns) 5 | self._data = self._normalize(data) 6 | def _normalize(self, data): 7 | probability_sum = 0 8 | for row in data: 9 | probability_sum += row[-1] 10 | for row in data: 11 | if probability_sum != 0: 12 | row[-1] = row[-1]/probability_sum 13 | else: 14 | row[-1] = 0 15 | return data 16 | def given(self, event_name, event_happened_value): 17 | contextual_columns = [entry for entry in self._columns] 18 | contextual_data = [] 19 | event_column_index = self._columns.index(event_name) 20 | probability_sum = 0 21 | for row in self._data: 22 | if row[event_column_index] == event_happened_value: 23 | new_row = [entry for i, entry in enumerate(row)] 24 | probability_sum += new_row[-1] 25 | contextual_data.append(new_row) 26 | else: 27 | new_row = [entry for i, entry in enumerate(row)] 28 | new_row[-1] = 0 29 | contextual_data.append(new_row) 30 | for row in contextual_data: 31 | if probability_sum != 0: 32 | row[-1] = row[-1]/probability_sum 33 | else: 34 | row[-1] = 0 35 | return JointProbabilityTable(contextual_columns, contextual_data) 36 | def _get_matching_probability(self, new_beliefs, event_value): 37 | for new_belief in new_beliefs: 38 | if new_belief[0] == event_value: 39 | return new_belief[1] 40 | def _clone_data(self): 41 | return [list(row) for row in self._data] 42 | def _add_to_current_beliefs(self, current_beliefs, event_value, probability): 43 | if not event_value in current_beliefs: 44 | current_beliefs[event_value] = 0 45 | current_beliefs[event_value] += probability 46 | def _get_current_beliefs_for_event(self, event_name): 47 | current_beliefs = {} 48 | event_column_index = self._columns.index(event_name) 49 | for row in self._data: 50 | row_event_name = row[event_column_index] 51 | row_event_probability = row[self._probability_index] 52 | self._add_to_current_beliefs(current_beliefs, row_event_name, row_event_probability) 53 | return current_beliefs 54 | def _get_belief_shifts(self, current_beliefs, new_beliefs): 55 | belief_shifts = {} 56 | for event_value in new_beliefs: 57 | updated_probability = new_beliefs[event_value] 58 | current_probability = current_beliefs[event_value] 59 | if current_probability != 0: 60 | probability_shift = updated_probability / current_probability 61 | else: 62 | probability_shift = 0 63 | belief_shifts[event_value] = probability_shift 64 | return belief_shifts 65 | def update_belief(self, event_name, new_beliefs): 66 | current_beliefs = self._get_current_beliefs_for_event(event_name) 67 | belief_shifts = self._get_belief_shifts(current_beliefs, new_beliefs) 68 | event_column_index = self._columns.index(event_name) 69 | new_table = self._clone_data() 70 | for row in new_table: 71 | row[-1] = row[-1] * belief_shifts[row[event_column_index]] 72 | return JointProbabilityTable(self._columns, new_table) 73 | def probability(self, event_name): 74 | beliefs = {} 75 | event_column_index = self._columns.index(event_name) 76 | for row in self._data: 77 | event_value = row[event_column_index] 78 | if not (event_value in beliefs): 79 | beliefs[event_value] = 0 80 | beliefs[event_value] += row[-1] 81 | return beliefs 82 | def update_applicable_beliefs(self, node_name, joint_probability_table): 83 | for event_name in joint_probability_table._columns: 84 | if event_name in self._columns: 85 | event_beliefs = joint_probability_table.probability(event_name) 86 | self._data = self.update_belief(event_name, event_beliefs)._data 87 | def clone(self): 88 | return JointProbabilityTable(self._columns, self._clone_data()) 89 | def __str__(self): 90 | return str([self._columns, self._data]) 91 | 92 | class BayesianNode: 93 | def __init__(self, name, joint_probability_table): 94 | self._name = name 95 | self._original_joint_probability_table = joint_probability_table 96 | self._joint_probability_table = joint_probability_table 97 | self._affects_nodes = [] 98 | self._affected_by = [] 99 | self._known = False 100 | def affected_by(self, other_node): 101 | self._affected_by.append(other_node) 102 | def affects(self, node): 103 | self._affects_nodes.append(node) 104 | node.affected_by(self) 105 | def _forward_propagate(self, joint_probability_table): 106 | self._joint_probability_table.update_applicable_beliefs(self._name, joint_probability_table) 107 | for affected_node in self._affects_nodes: 108 | affected_node._forward_propagate(self._joint_probability_table) 109 | def _backward_propagate(self, joint_probability_table): 110 | self._joint_probability_table.update_applicable_beliefs(self._name, joint_probability_table) 111 | for affected_node in self._affected_by: 112 | affected_node._backward_propagate(self._joint_probability_table) 113 | def given(self, value): 114 | if not self._known: 115 | self._joint_probability_table = self._joint_probability_table.given(self._name, value) 116 | self._known = True 117 | jpt = self._joint_probability_table.clone() 118 | for affected_node in self._affects_nodes: 119 | affected_node._forward_propagate(jpt) 120 | for affected_node in self._affected_by: 121 | affected_node._backward_propagate(jpt) 122 | for affected_node in self._affects_nodes: 123 | affected_node._backward_propagate(jpt) 124 | for affected_node in self._affected_by: 125 | affected_node._forward_propagate(jpt) 126 | def probability(self): 127 | return self._joint_probability_table.probability(self._name) 128 | def __str__(self): 129 | return str(self._joint_probability_table) 130 | 131 | door_picked_table = JointProbabilityTable( 132 | columns=['door_picked'], 133 | data = [ 134 | ['red', 0.3333], 135 | ['blue', 0.3333], 136 | ['green', 0.3334], 137 | 138 | ]) 139 | 140 | prize_behind_door_table = JointProbabilityTable( 141 | columns=['prize_behind'], 142 | data = [ 143 | ['red', 0.3333], 144 | ['blue', 0.3333], 145 | ['green', 0.3334], 146 | ]) 147 | 148 | shown_empty_table = JointProbabilityTable( 149 | columns=['door_picked', 'prize_behind', 'shown_empty'], 150 | data = [ 151 | ['red', 'red', 'red', 0.0], 152 | ['red', 'red', 'green', 0.5], 153 | ['red', 'red', 'blue', 0.5], 154 | ['red', 'green', 'red', 0.0], 155 | ['red', 'green', 'green', 0.0], 156 | ['red', 'green', 'blue', 1.0], 157 | ['red', 'blue', 'red', 0.0], 158 | ['red', 'blue', 'green', 1.0], 159 | ['red', 'blue', 'blue', 0.0], 160 | 161 | ['green', 'red', 'red', 0.0], 162 | ['green', 'red', 'green', 0.0], 163 | ['green', 'red', 'blue', 1.0], 164 | ['green', 'green', 'red', 0.5], 165 | ['green', 'green', 'green', 0.0], 166 | ['green', 'green', 'blue', 0.5], 167 | ['green', 'blue', 'red', 1.0], 168 | ['green', 'blue', 'green', 0.0], 169 | ['green', 'blue', 'blue', 0.0], 170 | 171 | ['blue', 'red', 'red', 0.0], 172 | ['blue', 'red', 'green', 1.0], 173 | ['blue', 'red', 'blue', 0.0], 174 | ['blue', 'green', 'red', 1.0], 175 | ['blue', 'green', 'green', 0.0], 176 | ['blue', 'green', 'blue', 0.0], 177 | ['blue', 'blue', 'red', 0.5], 178 | ['blue', 'blue', 'green', 0.5], 179 | ['blue', 'blue', 'blue', 0.0], 180 | ]) 181 | 182 | switch_table = JointProbabilityTable( 183 | columns=['switch_or_stay'], 184 | data=[ 185 | ['switch', 0.5], 186 | ['stay', 0.5], 187 | ]) 188 | 189 | door_after_choice_table = JointProbabilityTable( 190 | columns=['door_picked', 'shown_empty', 'switch_or_stay', 'door_after_choice'], 191 | data=[ 192 | ['red', 'red', 'switch', 'red', 0.0], 193 | ['red', 'red', 'switch', 'green', 0.0], 194 | ['red', 'red', 'switch', 'blue', 0.0], 195 | ['red', 'red', 'stay', 'red', 0.0], 196 | ['red', 'red', 'stay', 'green', 0.0], 197 | ['red', 'red', 'stay', 'blue', 0.0], 198 | 199 | ['red', 'blue', 'switch', 'red', 0.0], 200 | ['red', 'blue', 'switch', 'blue', 0.0], 201 | ['red', 'blue', 'switch', 'green', 1.0], 202 | ['red', 'blue', 'stay', 'red', 1.0], 203 | ['red', 'blue', 'stay', 'blue', 0.0], 204 | ['red', 'blue', 'stay', 'green', 0.0], 205 | 206 | ['red', 'green', 'switch', 'red', 0.0], 207 | ['red', 'green', 'switch', 'blue', 1.0], 208 | ['red', 'green', 'switch', 'green', 0.0], 209 | ['red', 'green', 'stay', 'red', 1.0], 210 | ['red', 'green', 'stay', 'blue', 0.0], 211 | ['red', 'green', 'stay', 'green', 0.0], 212 | 213 | #~~~~~~~~ 214 | 215 | ['blue', 'red', 'switch', 'red', 0.0], 216 | ['blue', 'red', 'switch', 'green', 1.0], 217 | ['blue', 'red', 'switch', 'blue', 0.0], 218 | ['blue', 'red', 'stay', 'red', 0.0], 219 | ['blue', 'red', 'stay', 'green', 0.0], 220 | ['blue', 'red', 'stay', 'blue', 1.0], 221 | 222 | ['blue', 'blue', 'switch', 'red', 0.0], 223 | ['blue', 'blue', 'switch', 'blue', 0.0], 224 | ['blue', 'blue', 'switch', 'green', 0.0], 225 | ['blue', 'blue', 'stay', 'red', 0.0], 226 | ['blue', 'blue', 'stay', 'blue', 0.0], 227 | ['blue', 'blue', 'stay', 'green', 0.0], 228 | 229 | ['blue', 'green', 'switch', 'red', 1.0], 230 | ['blue', 'green', 'switch', 'blue', 0.0], 231 | ['blue', 'green', 'switch', 'green', 0.0], 232 | ['blue', 'green', 'stay', 'red', 0.0], 233 | ['blue', 'green', 'stay', 'blue', 0.0], 234 | ['blue', 'green', 'stay', 'green', 1.0], 235 | 236 | #~~~~~~~~ 237 | 238 | ['green', 'red', 'switch', 'red', 0.0], 239 | ['green', 'red', 'switch', 'green', 0.0], 240 | ['green', 'red', 'switch', 'blue', 1.0], 241 | ['green', 'red', 'stay', 'red', 0.0], 242 | ['green', 'red', 'stay', 'green', 1.0], 243 | ['green', 'red', 'stay', 'blue', 0.0], 244 | 245 | ['green', 'blue', 'switch', 'red', 1.0], 246 | ['green', 'blue', 'switch', 'blue', 0.0], 247 | ['green', 'blue', 'switch', 'green', 0.0], 248 | ['green', 'blue', 'stay', 'red', 0.0], 249 | ['green', 'blue', 'stay', 'blue', 1.0], 250 | ['green', 'blue', 'stay', 'green', 0.0], 251 | 252 | ['green', 'green', 'switch', 'red', 0.0], 253 | ['green', 'green', 'switch', 'blue', 0.0], 254 | ['green', 'green', 'switch', 'green', 0.0], 255 | ['green', 'green', 'stay', 'red', 0.0], 256 | ['green', 'green', 'stay', 'blue', 0.0], 257 | ['green', 'green', 'stay', 'green', 0.0], 258 | 259 | ]) 260 | 261 | win_prize_table = JointProbabilityTable( 262 | columns=['prize_behind', 'door_after_choice', 'win_prize'], 263 | data = [ 264 | ['red', 'red', True, 1.0], 265 | ['red', 'red', False, 0.0], 266 | ['red', 'green', True, 0.0], 267 | ['red', 'green', False, 1.0], 268 | ['red', 'blue', True, 0.0], 269 | ['red', 'blue', False, 1.0], 270 | 271 | ['green', 'red', True, 0.0], 272 | ['green', 'red', False, 1.0], 273 | ['green', 'green',True, 1.0], 274 | ['green', 'green',False, 0.0], 275 | ['green', 'blue', True, 0.0], 276 | ['green', 'blue', False, 1.0], 277 | 278 | ['blue', 'red', True, 0.0], 279 | ['blue', 'red', False, 1.0], 280 | ['blue', 'green', True, 0.0], 281 | ['blue', 'green', False, 1.0], 282 | ['blue', 'blue', True, 1.0], 283 | ['blue', 'blue', False, 0.0], 284 | ]) 285 | 286 | informed_agent_table = JointProbabilityTable( 287 | columns=['informed_agent'], 288 | data=[ 289 | ['informed', 0.2], 290 | ['uninformed', 0.8], 291 | ]) 292 | 293 | door_picked_node = BayesianNode('door_picked', door_picked_table) 294 | prize_behind_node = BayesianNode('prize_behind', prize_behind_door_table) 295 | shown_empty_node = BayesianNode('shown_empty', shown_empty_table) 296 | win_prize_node = BayesianNode('win_prize', win_prize_table) 297 | door_after_choice_node = BayesianNode('door_after_choice', door_after_choice_table) 298 | switch_node = BayesianNode('switch_or_stay', switch_table) 299 | informed_agent_node = BayesianNode('informed_agent', informed_agent_table) 300 | 301 | print "Win prize original: " + str(win_prize_table.probability('win_prize')) 302 | 303 | # Original 304 | door_picked_node.affects(shown_empty_node) 305 | prize_behind_node.affects(shown_empty_node) 306 | 307 | # New 308 | shown_empty_node.affects(door_after_choice_node) 309 | door_picked_node.affects(door_after_choice_node) 310 | door_after_choice_node.affects(win_prize_node) 311 | 312 | # For fun (need to update joint probability tables as well) 313 | # informed_agent_node.affects(switch_node) 314 | # informed_agent_node.affects(win_prize_node) 315 | 316 | 317 | prize_behind_node.affects(win_prize_node) 318 | switch_node.affects(door_after_choice_node) 319 | 320 | def print_all_nodes(): 321 | print "" 322 | print "Door picked: " + str(door_picked_node.probability()) 323 | print "Prize behind door: " + str(prize_behind_node.probability()) 324 | print "Door shown empty: " + str(shown_empty_node.probability()) 325 | print "Win prize: " + str(win_prize_node.probability()) 326 | print "Updated door choice: " + str(door_after_choice_node.probability()) 327 | print "Switch or stay: " + str(switch_node.probability()) 328 | print "~~~~~" 329 | 330 | print "Before doing anything..." 331 | print_all_nodes() 332 | 333 | door_picked_node.given('red') 334 | print "After initially picking the red door..." 335 | print_all_nodes() 336 | 337 | shown_empty_node.given('green') 338 | print "After being shown the green door..." 339 | print_all_nodes() 340 | 341 | switch_node.given('switch') 342 | print "After switching doors..." 343 | print_all_nodes() 344 | 345 | print "After choosing another color door..." 346 | door_after_choice_node.given('blue') 347 | print_all_nodes() --------------------------------------------------------------------------------