Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b022791

Browse files
antmarakisnorvig
authored andcommitted
Implementation: Current Best Learning (aimacode#593)
* Update README.md * add powerset to utils * add powerset test * Create knowledge.py * Create test_knowledge.py * add header docstring to knowledge.py * update learning docstring * minor edits in knowledge.py
1 parent b561299 commit b022791

File tree

6 files changed

+275
-2
lines changed

6 files changed

+275
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Here is a table of algorithms, the figure, name of the code in the book and in t
108108
| 18.11 | Decision-List-Learning | `DecisionListLearner` | [`learning.py`][learning] |
109109
| 18.24 | Back-Prop-Learning | `BackPropagationLearner` | [`learning.py`][learning] |
110110
| 18.34 | AdaBoost | `AdaBoost` | [`learning.py`][learning] |
111-
| 19.2 | Current-Best-Learning | |
111+
| 19.2 | Current-Best-Learning | `current_best_learning` | [`knowledge.py`](knowledge.py) |
112112
| 19.3 | Version-Space-Learning | |
113113
| 19.8 | Minimal-Consistent-Det | |
114114
| 19.12 | FOIL | |
@@ -146,6 +146,7 @@ Many thanks for contributions over the years. I got bug reports, corrected code,
146146
[csp]:../master/csp.py
147147
[games]:../master/games.py
148148
[grid]:../master/grid.py
149+
[knowledge]:../master/knowledge.py
149150
[learning]:../master/learning.py
150151
[logic]:../master/logic.py
151152
[mdp]:../master/mdp.py

knowledge.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""Knowledge in learning, Chapter 19"""
2+
3+
from random import shuffle
4+
from utils import powerset
5+
from collections import defaultdict
6+
7+
# ______________________________________________________________________________
8+
9+
10+
def current_best_learning(examples, h, examples_so_far=[]):
11+
""" [Figure 19.2]
12+
The hypothesis is a list of dictionaries, with each dictionary representing
13+
a disjunction."""
14+
if not examples:
15+
return h
16+
17+
e = examples[0]
18+
if is_consistent(e, h):
19+
return current_best_learning(examples[1:], h, examples_so_far + [e])
20+
elif false_positive(e, h):
21+
for h2 in specializations(examples_so_far + [e], h):
22+
h3 = current_best_learning(examples[1:], h2, examples_so_far + [e])
23+
if h3 != 'FAIL':
24+
return h3
25+
elif false_negative(e, h):
26+
for h2 in generalizations(examples_so_far + [e], h):
27+
h3 = current_best_learning(examples[1:], h2, examples_so_far + [e])
28+
if h3 != 'FAIL':
29+
return h3
30+
31+
return 'FAIL'
32+
33+
34+
def specializations(examples_so_far, h):
35+
"""Specialize the hypothesis by adding AND operations to the disjunctions"""
36+
hypotheses = []
37+
38+
for i, disj in enumerate(h):
39+
for e in examples_so_far:
40+
for k, v in e.items():
41+
if k in disj or k == 'GOAL':
42+
continue
43+
44+
h2 = h[i].copy()
45+
h2[k] = '!' + v
46+
h3 = h.copy()
47+
h3[i] = h2
48+
if check_all_consistency(examples_so_far, h3):
49+
hypotheses.append(h3)
50+
51+
shuffle(hypotheses)
52+
return hypotheses
53+
54+
55+
def generalizations(examples_so_far, h):
56+
"""Generalize the hypothesis. First delete operations
57+
(including disjunctions) from the hypothesis. Then, add OR operations."""
58+
hypotheses = []
59+
60+
# Delete disjunctions
61+
disj_powerset = powerset(range(len(h)))
62+
for disjs in disj_powerset:
63+
h2 = h.copy()
64+
for d in reversed(list(disjs)):
65+
del h2[d]
66+
67+
if check_all_consistency(examples_so_far, h2):
68+
hypotheses += h2
69+
70+
# Delete AND operations in disjunctions
71+
for i, disj in enumerate(h):
72+
a_powerset = powerset(disj.keys())
73+
for attrs in a_powerset:
74+
h2 = h[i].copy()
75+
for a in attrs:
76+
del h2[a]
77+
78+
if check_all_consistency(examples_so_far, [h2]):
79+
h3 = h.copy()
80+
h3[i] = h2.copy()
81+
hypotheses += h3
82+
83+
# Add OR operations
84+
hypotheses.extend(add_or(examples_so_far, h))
85+
86+
shuffle(hypotheses)
87+
return hypotheses
88+
89+
90+
def add_or(examples_so_far, h):
91+
"""Adds an OR operation to the hypothesis. The AND operations in the disjunction
92+
are generated by the last example (which is the problematic one)."""
93+
ors = []
94+
e = examples_so_far[-1]
95+
96+
attrs = {k: v for k, v in e.items() if k != 'GOAL'}
97+
a_powerset = powerset(attrs.keys())
98+
99+
for c in a_powerset:
100+
h2 = {}
101+
for k in c:
102+
h2[k] = attrs[k]
103+
104+
if check_negative_consistency(examples_so_far, h2):
105+
h3 = h.copy()
106+
h3.append(h2)
107+
ors.append(h3)
108+
109+
return ors
110+
111+
# ______________________________________________________________________________
112+
113+
114+
def check_all_consistency(examples, h):
115+
"""Check for the consistency of all examples under h"""
116+
for e in examples:
117+
if not is_consistent(e, h):
118+
return False
119+
120+
return True
121+
122+
123+
def check_negative_consistency(examples, h):
124+
"""Check if the negative examples are consistent under h"""
125+
for e in examples:
126+
if e['GOAL']:
127+
continue
128+
129+
if not is_consistent(e, [h]):
130+
return False
131+
132+
return True
133+
134+
135+
def disjunction_value(e, d):
136+
"""The value of example e under disjunction d"""
137+
for k, v in d.items():
138+
if v[0] == '!':
139+
# v is a NOT expression
140+
# e[k], thus, should not be equal to v
141+
if e[k] == v[1:]:
142+
return False
143+
elif e[k] != v:
144+
return False
145+
146+
return True
147+
148+
149+
def guess_value(e, h):
150+
"""Guess value of example e under hypothesis h"""
151+
for d in h:
152+
if disjunction_value(e, d):
153+
return True
154+
155+
return False
156+
157+
158+
def is_consistent(e, h):
159+
return e["GOAL"] == guess_value(e, h)
160+
161+
162+
def false_positive(e, h):
163+
if e["GOAL"] == False:
164+
if guess_value(e, h):
165+
return True
166+
167+
return False
168+
169+
170+
def false_negative(e, h):
171+
if e["GOAL"] == True:
172+
if not guess_value(e, h):
173+
return True
174+
175+
return False

learning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Learn to estimate functions from examples. (Chapters 18-20)"""
1+
"""Learn to estimate functions from examples. (Chapters 18, 20)"""
22

33
from utils import (
44
removeall, unique, product, mode, argmax, argmax_random_tie, isclose, gaussian,

tests/test_knowledge.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from knowledge import *
2+
import random
3+
4+
random.seed("aima-python")
5+
6+
7+
def test_current_best_learning():
8+
examples = restaurant
9+
hypothesis = [{'Alt': 'Yes'}]
10+
h = current_best_learning(examples, hypothesis)
11+
values = []
12+
for e in examples:
13+
values.append(guess_value(e, h))
14+
15+
assert values == [True, False, True, True, False, True, False, True, False, False, False, True]
16+
17+
examples = animals_umbrellas
18+
initial_h = [{'Species': 'Cat'}]
19+
h = current_best_learning(examples, initial_h)
20+
values = []
21+
for e in examples:
22+
values.append(guess_value(e, h))
23+
24+
assert values == [True, True, True, False, False, False, True]
25+
26+
27+
animals_umbrellas = [
28+
{'Species': 'Cat', 'Rain': 'Yes', 'Coat': 'No', 'GOAL': True},
29+
{'Species': 'Cat', 'Rain': 'Yes', 'Coat': 'Yes', 'GOAL': True},
30+
{'Species': 'Dog', 'Rain': 'Yes', 'Coat': 'Yes', 'GOAL': True},
31+
{'Species': 'Dog', 'Rain': 'Yes', 'Coat': 'No', 'GOAL': False},
32+
{'Species': 'Dog', 'Rain': 'No', 'Coat': 'No', 'GOAL': False},
33+
{'Species': 'Cat', 'Rain': 'No', 'Coat': 'No', 'GOAL': False},
34+
{'Species': 'Cat', 'Rain': 'No', 'Coat': 'Yes', 'GOAL': True}
35+
]
36+
37+
restaurant = [
38+
{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Some',
39+
'Price': '$$$', 'Rain': 'No', 'Res': 'Yes', 'Type': 'French', 'Est': '0-10',
40+
'GOAL': True},
41+
42+
{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Full',
43+
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Thai', 'Est': '30-60',
44+
'GOAL': False},
45+
46+
{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'No', 'Hun': 'No', 'Pat': 'Some',
47+
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Burger', 'Est': '0-10',
48+
'GOAL': True},
49+
50+
{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'Yes', 'Hun': 'Yes', 'Pat': 'Full',
51+
'Price': '$', 'Rain': 'Yes', 'Res': 'No', 'Type': 'Thai', 'Est': '10-30',
52+
'GOAL': True},
53+
54+
{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'Yes', 'Hun': 'No', 'Pat': 'Full',
55+
'Price': '$$$', 'Rain': 'No', 'Res': 'Yes', 'Type': 'French', 'Est': '>60',
56+
'GOAL': False},
57+
58+
{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Some',
59+
'Price': '$$', 'Rain': 'Yes', 'Res': 'Yes', 'Type': 'Italian', 'Est': '0-10',
60+
'GOAL': True},
61+
62+
{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'No', 'Hun': 'No', 'Pat': 'None',
63+
'Price': '$', 'Rain': 'Yes', 'Res': 'No', 'Type': 'Burger', 'Est': '0-10',
64+
'GOAL': False},
65+
66+
{'Alt': 'No', 'Bar': 'No', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Some',
67+
'Price': '$$', 'Rain': 'Yes', 'Res': 'Yes', 'Type': 'Thai', 'Est': '0-10',
68+
'GOAL': True},
69+
70+
{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'Yes', 'Hun': 'No', 'Pat': 'Full',
71+
'Price': '$', 'Rain': 'Yes', 'Res': 'No', 'Type': 'Burger', 'Est': '>60',
72+
'GOAL': False},
73+
74+
{'Alt': 'Yes', 'Bar': 'Yes', 'Fri': 'Yes', 'Hun': 'Yes', 'Pat': 'Full',
75+
'Price': '$$$', 'Rain': 'No', 'Res': 'Yes', 'Type': 'Italian', 'Est': '10-30',
76+
'GOAL': False},
77+
78+
{'Alt': 'No', 'Bar': 'No', 'Fri': 'No', 'Hun': 'No', 'Pat': 'None',
79+
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Thai', 'Est': '0-10',
80+
'GOAL': False},
81+
82+
{'Alt': 'Yes', 'Bar': 'Yes', 'Fri': 'Yes', 'Hun': 'Yes', 'Pat': 'Full',
83+
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Burger', 'Est': '30-60',
84+
'GOAL': True}
85+
]

tests/test_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ def test_mode():
5151
assert mode("absndkwoajfkalwpdlsdlfllalsflfdslgflal") == 'l'
5252

5353

54+
def test_powerset():
55+
assert powerset([1, 2, 3]) == [(1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
56+
57+
5458
def test_argminmax():
5559
assert argmin([-2, 1], key=abs) == 1
5660
assert argmax([-2, 1], key=abs) == -2

utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import random
99
import math
1010
import functools
11+
from itertools import chain, combinations
12+
1113

1214
# ______________________________________________________________________________
1315
# Functions on Sequences and Iterables
@@ -66,6 +68,12 @@ def mode(data):
6668
return item
6769

6870

71+
def powerset(iterable):
72+
"""powerset([1,2,3]) --> (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"""
73+
s = list(iterable)
74+
return list(chain.from_iterable(combinations(s, r) for r in range(len(s)+1)))[1:]
75+
76+
6977
# ______________________________________________________________________________
7078
# argmin and argmax
7179

0 commit comments

Comments
 (0)