diff --git a/README.md b/README.md index 8b1d8650a..a48b0453d 100644 --- a/README.md +++ b/README.md @@ -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 | | diff --git a/knowledge.py b/knowledge.py new file mode 100644 index 000000000..b0d5cf0a2 --- /dev/null +++ b/knowledge.py @@ -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 diff --git a/learning.py b/learning.py index 11abaf420..7d5aef302 100644 --- a/learning.py +++ b/learning.py @@ -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, diff --git a/tests/test_knowledge.py b/tests/test_knowledge.py new file mode 100644 index 000000000..bec27ba3a --- /dev/null +++ b/tests/test_knowledge.py @@ -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} +] diff --git a/tests/test_utils.py b/tests/test_utils.py index 3785b762b..47d65220a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -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 diff --git a/utils.py b/utils.py index 3ba0b202b..86617100c 100644 --- a/utils.py +++ b/utils.py @@ -8,6 +8,7 @@ import random import math import functools +from itertools import chain, combinations # ______________________________________________________________________________ # Functions on Sequences and Iterables @@ -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 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