FOIL (#625) · TonyJava/aima-python@718224a · GitHub
Skip to content

Commit 718224a

Browse files
Chipe1norvig
authored andcommitted
* Added predicate_symbols * Added FOIL * Updated README
1 parent a065c3b commit 718224a

5 files changed

Lines changed: 335 additions & 18 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion

knowledge.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""Knowledge in learning, Chapter 19"""
22

33
from random import shuffle
4+
from math import log
45
from utils import powerset
56
from collections import defaultdict
6-
from itertools import combinations
7+
from itertools import combinations, product
8+
from logic import (FolKB, constant_symbols, predicate_symbols, standardize_variables,
9+
variables, is_definite_clause, subst, expr, Expr)
710

811
# ______________________________________________________________________________
912

@@ -231,6 +234,117 @@ def consistent_det(A, E):
231234
# ______________________________________________________________________________
232235

233236

237+
class FOIL_container(FolKB):
238+
"""Holds the kb and other necessary elements required by FOIL"""
239+
240+
def __init__(self, clauses=[]):
241+
self.const_syms = set()
242+
self.pred_syms = set()
243+
FolKB.__init__(self, clauses)
244+
245+
def tell(self, sentence):
246+
if is_definite_clause(sentence):
247+
self.clauses.append(sentence)
248+
self.const_syms.update(constant_symbols(sentence))
249+
self.pred_syms.update(predicate_symbols(sentence))
250+
else:
251+
raise Exception("Not a definite clause: {}".format(sentence))
252+
253+
def foil(self, examples, target):
254+
"""Learns a list of first-order horn clauses
255+
'examples' is a tuple: (positive_examples, negative_examples).
256+
positive_examples and negative_examples are both lists which contain substitutions."""
257+
clauses = []
258+
259+
pos_examples = examples[0]
260+
neg_examples = examples[1]
261+
262+
while pos_examples:
263+
clause, extended_pos_examples = self.new_clause((pos_examples, neg_examples), target)
264+
# remove positive examples covered by clause
265+
pos_examples = self.update_examples(target, pos_examples, extended_pos_examples)
266+
clauses.append(clause)
267+
268+
return clauses
269+
270+
def new_clause(self, examples, target):
271+
"""Finds a horn clause which satisfies part of the positive
272+
examples but none of the negative examples.
273+
The horn clause is specified as [consequent, list of antecedents]
274+
Return value is the tuple (horn_clause, extended_positive_examples)"""
275+
clause = [target, []]
276+
# [positive_examples, negative_examples]
277+
extended_examples = examples
278+
while extended_examples[1]:
279+
l = self.choose_literal(self.new_literals(clause), extended_examples)
280+
clause[1].append(l)
281+
extended_examples = [sum([list(self.extend_example(example, l)) for example in
282+
extended_examples[i]], []) for i in range(2)]
283+
284+
return (clause, extended_examples[0])
285+
286+
def extend_example(self, example, literal):
287+
"""Generates extended examples which satisfy the literal"""
288+
# find all substitutions that satisfy literal
289+
for s in self.ask_generator(subst(example, literal)):
290+
s.update(example)
291+
yield s
292+
293+
def new_literals(self, clause):
294+
"""Generates new literals based on known predicate symbols.
295+
Generated literal must share atleast one variable with clause"""
296+
share_vars = variables(clause[0])
297+
for l in clause[1]:
298+
share_vars.update(variables(l))
299+
300+
for pred, arity in self.pred_syms:
301+
new_vars = {standardize_variables(expr('x')) for _ in range(arity - 1)}
302+
for args in product(share_vars.union(new_vars), repeat=arity):
303+
if any(var in share_vars for var in args):
304+
yield Expr(pred, *[var for var in args])
305+
306+
def choose_literal(self, literals, examples):
307+
"""Chooses the best literal based on the information gain"""
308+
def gain(l):
309+
pre_pos = len(examples[0])
310+
pre_neg = len(examples[1])
311+
extended_examples = [sum([list(self.extend_example(example, l)) for example in
312+
examples[i]], []) for i in range(2)]
313+
post_pos = len(extended_examples[0])
314+
post_neg = len(extended_examples[1])
315+
if pre_pos + pre_neg == 0 or post_pos + post_neg == 0:
316+
return -1
317+
318+
# number of positive example that are represented in extended_examples
319+
T = 0
320+
for example in examples[0]:
321+
def represents(d):
322+
return all(d[x] == example[x] for x in example)
323+
if any(represents(l_) for l_ in extended_examples[0]):
324+
T += 1
325+
326+
return T * log((post_pos*(pre_pos + pre_neg) + 1e-4) / ((post_pos + post_neg)*pre_pos))
327+
328+
return max(literals, key=gain)
329+
330+
def update_examples(self, target, examples, extended_examples):
331+
"""Adds to the kb those examples what are represented in extended_examples
332+
List of omitted examples is returned"""
333+
uncovered = []
334+
for example in examples:
335+
def represents(d):
336+
return all(d[x] == example[x] for x in example)
337+
if any(represents(l) for l in extended_examples):
338+
self.tell(subst(example, target))
339+
else:
340+
uncovered.append(example)
341+
342+
return uncovered
343+
344+
345+
# ______________________________________________________________________________
346+
347+
234348
def check_all_consistency(examples, h):
235349
"""Check for the consistency of all examples under h"""
236350
for e in examples:

logic.py

Lines changed: 22 additions & 12 deletions

0 commit comments

Comments
 (0)