Skip to content

Implementation: Current Best Learning #591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Here is a table of algorithms, the figure, name of the code in the book and in t
| 18.11 | Decision-List-Learning | `DecisionListLearner` | [`learning.py`][learning] |
| 18.24 | Back-Prop-Learning | `BackPropagationLearner` | [`learning.py`][learning] |
| 18.34 | AdaBoost | `AdaBoost` | [`learning.py`][learning] |
| 19.2 | Current-Best-Learning | |
| 19.2 | Current-Best-Learning | `current_best_learning` | [`knowledge.py`](knowledge.py) |
| 19.3 | Version-Space-Learning | |
| 19.8 | Minimal-Consistent-Det | |
| 19.12 | FOIL | |
Expand Down
186 changes: 186 additions & 0 deletions knowledge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
"""Knowledge in learning, Chapter 19"""

from random import shuffle
from utils import powerset


def current_best_learning(examples, h, examples_so_far=[]):
""" [Figure 19.2]
The hypothesis is a dictionary in the following form:
* The keys are the attribute names.
* The NOT operation is denoted by an exclamation mark (!) in front of a value.
* A special key (GOAL) for the goal/target attribute. Its value is boolean.
* A special key (|) for the OR operation. The value for that key is another
dictionary/hypothesis, in a recursive structure."""
if not examples:
return h

e = examples[0]
if is_consistent(e, h):
return current_best_learning(examples[1:], h, examples_so_far + [e])
elif false_positive(e, h):
for h2 in specializations(examples_so_far + [e], h):
h3 = current_best_learning(examples[1:], h2, examples_so_far + [e])
if h3 != 'FAIL':
return h3
elif false_negative(e, h):
for h2 in generalizations(examples_so_far + [e], h):
h3 = current_best_learning(examples[1:], h2, examples_so_far + [e])
if h3 != 'FAIL':
return h3

return 'FAIL'


def specializations(examples_so_far, h):
"""Specialize the hypothesis. First add AND operations, then
recursively specialize in the OR part of the hypothesis."""
hypotheses = []

# Add AND operations
for e in examples_so_far:
mod = ''
if not e['GOAL']:
mod = '!'

for k, v in e.items():
if k in h or k == 'GOAL' or k == '|':
continue

h2 = h.copy()
h2[k] = mod + v
if check_all_consistency(examples_so_far, h2):
hypotheses += [h2]


# Specialize in OR
if '|' in h.keys():
for h_or in specializations(examples_so_far, h['|']):
h2 = h.copy()
h2['|'] = h_or
hypotheses += [h2]

shuffle(hypotheses)
return hypotheses


def generalizations(examples_so_far, h):
"""Generalize the hypothesis. First delete operations (including OR) from
the hypothesis. Then, add OR operations if one doesn't already exist.
If an OR operation exists, recursively generalize in that."""
hypotheses = []
h_powerset = powerset(h.keys())

# Delete operations
for deletions in h_powerset:
h2 = h.copy()
for d in deletions:
del h2[d]

if check_all_consistency(examples_so_far, h2):
hypotheses += [h2]

if '|' not in h:
# Add OR
hypotheses.extend(add_or(examples_so_far, h))
else:
# Generalize in OR
for h_or in generalizations(examples_so_far, h['|']):
if check_negative_consistency(examples_so_far, h2):
h2 = h.copy()
h2['|'] = h_or
hypotheses += [h2]

shuffle(hypotheses)
return hypotheses


def add_or(examples_so_far, h):
"""Adds an OR operation to the hypothesis. The AND operations inside the OR
are generated by the last example (which is the problematic one). Note that
the function adds just one OR operation."""
hypotheses = []
e = examples_so_far[-1]
mod = '!'
if e['GOAL']:
mod = ''

attrs = {k: mod + v for k, v in e.items() if k != 'GOAL'}
a_powerset = powerset(attrs.keys())

for c in a_powerset:
h2 = {}
for k in c:
h2[k] = attrs[k]

if check_negative_consistency(examples_so_far, h2):
h3 = h.copy()
h3['|'] = h2
hypotheses += [h3]

return hypotheses


def check_all_consistency(examples, h):
for e in examples:
if not is_consistent(e, h):
return False

return True


def check_negative_consistency(examples, h):
"""Check if the negative examples are consistent under h."""
for e in examples:
if e['GOAL']:
continue

if not is_consistent(e, h):
return False

return True


def guess_value(e, h):
"""Guess value of example e under hypothesis h."""
for k, v in h.items():
if k == '|':
if guess_value(e, v):
return True
continue

if v[0] == '!':
# v is a NOT expression
# e[k], thus, should not be equal to v
if e[k] == v[1:]:
if '|' not in h:
return False
else:
return guess_value(e, h['|'])
elif e[k] != v:
if '|' not in h:
return False
else:
return guess_value(e, h['|'])

return True


def is_consistent(e, h):
return e["GOAL"] == guess_value(e, h)


def false_positive(e, h):
if e["GOAL"] == False:
if guess_value(e, h):
return True

return False


def false_negative(e, h):
if e["GOAL"] == True:
if not guess_value(e, h):
return True

return False
2 changes: 1 addition & 1 deletion learning.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Learn to estimate functions from examples. (Chapters 18-20)"""
"""Learn to estimate functions from examples. (Chapters 18, 20)"""

from utils import (
removeall, unique, product, mode, argmax, argmax_random_tie, isclose, gaussian,
Expand Down
97 changes: 97 additions & 0 deletions tests/test_knowledge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from knowledge import *
import random

random.seed("aima-python")


# def test_current_best_learning_full():
# """ !!! This takes too long !!! """
# examples = restaurant
# hypothesis = {'Alt': 'Yes'}
# h = current_best_learning(examples, hypothesis)
# values = []
# for e in examples:
# values.append(guess_value(e, h))
#
# assert values == [True, False, True, True, False, True, False, True, False, False, False, True]


def test_current_best_learning():
examples = restaurant[:3]
initial_h = {'Alt': 'Yes'}
h = current_best_learning(examples, initial_h)
values = []
for e in examples:
values.append(guess_value(e, h))

assert values == [True, False, True]

examples = animals_umbrellas
initial_h = {'Species': 'Cat'}
h = current_best_learning(examples, initial_h)
values = []
for e in examples:
values.append(guess_value(e, h))

assert values == [True, True, True, False, False, False, True]


animals_umbrellas = [
{'Species': 'Cat', 'Rain': 'Yes', 'Coat': 'No', 'GOAL': True},
{'Species': 'Cat', 'Rain': 'Yes', 'Coat': 'Yes', 'GOAL': True},
{'Species': 'Dog', 'Rain': 'Yes', 'Coat': 'Yes', 'GOAL': True},
{'Species': 'Dog', 'Rain': 'Yes', 'Coat': 'No', 'GOAL': False},
{'Species': 'Dog', 'Rain': 'No', 'Coat': 'No', 'GOAL': False},
{'Species': 'Cat', 'Rain': 'No', 'Coat': 'No', 'GOAL': False},
{'Species': 'Cat', 'Rain': 'No', 'Coat': 'Yes', 'GOAL': True}
]

restaurant = [
{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Some',
'Price': '$$$', 'Rain': 'No', 'Res': 'Yes', 'Type': 'French', 'Est': '0-10',
'GOAL': True},

{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Full',
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Thai', 'Est': '30-60',
'GOAL': False},

{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'No', 'Hun': 'No', 'Pat': 'Some',
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Burger', 'Est': '0-10',
'GOAL': True},

{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'Yes', 'Hun': 'Yes', 'Pat': 'Full',
'Price': '$', 'Rain': 'Yes', 'Res': 'No', 'Type': 'Thai', 'Est': '10-30',
'GOAL': True},

{'Alt': 'Yes', 'Bar': 'No', 'Fri': 'Yes', 'Hun': 'No', 'Pat': 'Full',
'Price': '$$$', 'Rain': 'No', 'Res': 'Yes', 'Type': 'French', 'Est': '>60',
'GOAL': False},

{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Some',
'Price': '$$', 'Rain': 'Yes', 'Res': 'Yes', 'Type': 'Italian', 'Est': '0-10',
'GOAL': True},

{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'No', 'Hun': 'No', 'Pat': 'None',
'Price': '$', 'Rain': 'Yes', 'Res': 'No', 'Type': 'Burger', 'Est': '0-10',
'GOAL': False},

{'Alt': 'No', 'Bar': 'No', 'Fri': 'No', 'Hun': 'Yes', 'Pat': 'Some',
'Price': '$$', 'Rain': 'Yes', 'Res': 'Yes', 'Type': 'Thai', 'Est': '0-10',
'GOAL': True},

{'Alt': 'No', 'Bar': 'Yes', 'Fri': 'Yes', 'Hun': 'No', 'Pat': 'Full',
'Price': '$', 'Rain': 'Yes', 'Res': 'No', 'Type': 'Burger', 'Est': '>60',
'GOAL': False},

{'Alt': 'Yes', 'Bar': 'Yes', 'Fri': 'Yes', 'Hun': 'Yes', 'Pat': 'Full',
'Price': '$$$', 'Rain': 'No', 'Res': 'Yes', 'Type': 'Italian', 'Est': '10-30',
'GOAL': False},

{'Alt': 'No', 'Bar': 'No', 'Fri': 'No', 'Hun': 'No', 'Pat': 'None',
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Thai', 'Est': '0-10',
'GOAL': False},

{'Alt': 'Yes', 'Bar': 'Yes', 'Fri': 'Yes', 'Hun': 'Yes', 'Pat': 'Full',
'Price': '$', 'Rain': 'No', 'Res': 'No', 'Type': 'Burger', 'Est': '30-60',
'GOAL': True}
]
4 changes: 4 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def test_mode():
assert mode("absndkwoajfkalwpdlsdlfllalsflfdslgflal") == 'l'


def test_powerset():
assert powerset([1, 2, 3]) == [(1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]


def test_argminmax():
assert argmin([-2, 1], key=abs) == 1
assert argmax([-2, 1], key=abs) == -2
Expand Down
7 changes: 7 additions & 0 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import random
import math
import functools
from itertools import chain, combinations

# ______________________________________________________________________________
# Functions on Sequences and Iterables
Expand Down Expand Up @@ -66,6 +67,12 @@ def mode(data):
return item


def powerset(iterable):
"""powerset([1,2,3]) --> (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"""
s = list(iterable)
return list(chain.from_iterable(combinations(s, r) for r in range(len(s)+1)))[1:]


# ______________________________________________________________________________
# argmin and argmax

Expand Down
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