Popper is an inductive logic programming (ILP) system.
If you use Popper, please cite the paper: Andrew Cropper and Rolf Morel. Learning programs by learning from failures. Mach. Learn. 110(4): 801-856 (2021)
- pyswip (You must install pyswip from the master branch! with the command:
pip install git+https://github.com/yuce/pyswip@master#egg=pyswip) - SWI-Prolog (8.4.2 or above)
- Clingo (5.5.0 or above)
To install the master branch, run the command:
pip install git+https://github.com/logic-and-learning-lab/Popper@main
Run Popper with the command python popper.py <input dir>. For instance, the command python popper.py examples/dropk produces:
********** SOLUTION **********
Precision:1.00 Recall:1.00 TP:10 FN:0 TN:10 FP:0 Size:7
f(A,B,C):- tail(A,C),one(B).
f(A,B,C):- decrement(B,E),tail(A,D),f(D,E,C).
******************************The command python popper.py examples/trains1 produces:
********** SOLUTION **********
Precision:1.00 Recall:1.00 TP:394 FN:0 TN:606 FP:0 Size:6
f(A):- has_car(A,C),has_car(A,B),long(B),three_wheels(C),roof_closed(B).
******************************Look at the examples for guidance.
Popper requires three files:
- an examples file
- a background knowledge (BK) file
- a bias file
An examples file contains positive and negative examples of the relation you want to learn:
pos(grandparent(ann,amelia)).
pos(grandparent(steve,amelia)).
pos(grandparent(ann,spongebob)).
pos(grandparent(steve,spongebob)).
pos(grandparent(linda,amelia)).
neg(grandparent(amy,amelia)).A BK file contains other information about the problem:
mother(ann,amy).
mother(ann,andy).
mother(amy,amelia).
mother(linda,gavin).
father(steve,amy).
father(steve,andy).
father(gavin,amelia).
father(andy,spongebob).A bias file contains the information necessary to restrict the search space of Popper. Predicate declarations tell Popper which predicate symbols it can use in the head or body of a rule, such as:
head_pred(grandparent,2).
body_pred(mother,2).
body_pred(father,2).These declarations say that each rule in a program must have the symbol grandparent with arity two in the head and mother and/or father in the body, also with arity two. If we call Popper with these three files it will produce the output:
Popper is an anytime algorithm. By default, it shows intermediate solutions. For instance, the command python popper.py examples/dropk produces:
08:08:54 Num. pos examples: 10
08:08:54 Num. neg examples: 10
08:08:54 Searching programs of size: 3
08:08:54 Searching programs of size: 4
08:08:54 Searching programs of size: 5
08:08:54 Searching programs of size: 6
08:08:54 ********************
08:08:54 New best hypothesis:
08:08:54 tp:1 fn:9 size:6
08:08:54 f(A,B,C):- tail(E,C),tail(D,F),tail(F,E),even(B),tail(A,D).
08:08:54 ********************
08:08:56 Searching programs of size: 7
08:08:57 ********************
08:08:57 New best hypothesis:
08:08:57 tp:10 fn:0 size:13
08:08:57 f(A,B,C):- tail(A,C),element(A,B).
08:08:57 f(A,B,C):- tail(E,C),tail(D,F),tail(F,E),even(B),tail(A,D).
08:08:57 f(A,B,C):- decrement(B,D),tail(E,C),f(A,D,E).
08:08:57 ********************
08:08:58 ********************
08:08:58 New best hypothesis:
08:08:58 tp:10 fn:0 size:7
08:08:58 f(A,B,C):- tail(A,C),one(B).
08:08:58 f(A,B,C):- f(A,E,D),tail(D,C),decrement(B,E).
08:08:58 ********************
********** SOLUTION **********
Precision:1.00 Recall:1.00 TP:10 FN:0 TN:10 FP:0 Size:7
f(A,B,C):- tail(A,C),one(B).
f(A,B,C):- decrement(B,E),f(A,E,D),tail(D,C).
******************************To suppress intermediate solutions, run Popper with the --quiet (-q) flag.
To enable recursion add enable_recursion. to the bias file. Recursion allows Popper to learn programs where a predicate symbol appears in both the head and body of a rule, such as to find a duplicate element (python popper.py examples/find-dupl) in a list:
f(A,B):-head(A,B),tail(A,C),element(C,B).
f(A,B):-tail(A,C),f(C,B).Or to remove (python popper.py examples/filter) non-even elements from a list:
f(A,B):-empty(A),empty(B).
f(A,B):-tail(A,D),head(A,C),odd(C),f(D,B).
f(A,B):-head(A,E),even(E),tail(A,C),f(C,D),prepend(E,D,B).Recursion is expensive, so it is best to try without it first.
Popper supports optional type annotations in the bias file.A type annotation is of the form type(p,(t1,t2,...,tk) for a predicate symbol p with arity k, such as:
type(f,(list,list)).
type(head,(list,element)).
type(tail,(list,list)).
type(empty,(list,)).
type(odd,(element,)).
type(even,(element,)).
type(prepend,(element,list,list)).These types are optional but can help reduce learning times.
Prolog often requires arguments to be ground. For instance, when asking Prolog to answer the query:
X is 3+K.It throws an error:
ERROR: Arguments are not sufficiently instantiatedTo avoid theses issues, Popper supports optional direction annotations. A direction annotation is of the form direction(p,(d1,d2,...,dk) for a predicate symbol p with arity k, where each di is either in or out.
An in variable must be ground when calling the relation. By contrast, an out variable need not be ground. Here are example directions:
direction(head,(in,out)).
direction(tail,(in,out)).
direction(length,(in,out)).
direction(prepend,(in,in,out)).
direction(geq,(in,in)).Directions are optional but can substantially reduce learning times.
Popper has three important bias settings:
max_vars(N)sets the maximum number of variables in a rule toN(default: 6)max_body(N)sets the maximum number of body literals in a rule toN(default: 6)max_clauses(N)sets the maximum number of rules/clauses toN(default: 1 or 2 ifenable_recursionis set)
These settings greatly influence performance. If the values are too high then Popper might struggle to learn a solution. If the settings are too low then the search space might be too small to contain a good solution. You can set these settings in the bias file or through the command line (see --help).
IMPORTANT: do not supply max_clauses if you are learning non-recursive programs.
Popper supports automatic predicate invention (PI). To enable PI, add the setting enable_pi. to the bias file. With PI enabled, Popper (python popper.py examples/kinship-pi) learns the following program:
grandparent(A,B):-inv1(C,B),inv1(A,C).
inv1(A,B):-mother(A,B).
inv1(A,B):-father(A,B).
% Precision:1.00, Recall:1.00, TP:5, FN:0, TN:1, FP:0Predicate invention is currently very expensive so it is best to avoid it if possible.
--stats(default: false) shows runtime statistics--debug(default: false) runs in debug mode--quiet(default: False) runs in quiet mode--bkcons(default: False) allows Popper to discover constraints from the BK. This flag only works with Datalog programs.--datalog(default: False) allows Popper to test programs more quickly by ordering the literals on them. This flag only works with Datalog programs.--timeout(default: 600 seconds) sets a maximum learning time--eval-timeout(default: 0.001 seconds) sets a maximum example testing time. This flag only applies when learning recursive programs.
- If possible, transform your BK to sets of facts and use Popper with the
--bkconsand--datalogflags.
You can import Popper and use it in your Python code like so:
from popper.util import Settings, print_prog_score
from popper.loop import learn_solution
settings = Settings(kbpath='input_dir')
prog, score, stats = learn_solution(settings)
if prog != None:
print_prog_score(prog, score)