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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy