|
|
— |
pl:prolog:pllib:hyper_hypothesis_refiner [2019/06/27 15:50] (aktualna) |
| ====== Hyper hypothesis refiner ====== |
| {{tag>learning hypothesis}} |
| ===== Description ===== |
| Program HYPER (Hypothesis Refiner) for learning in logicn |
| |
| **Source**: PROLOG programming for artificial intelligence, 3rd Edition, Harlow, 2001, ISBN 0-201-40375-7. |
| ===== Download ===== |
| Program source code: {{hyper_hypothesis_refiner.pl}} |
| ===== Listing ===== |
| <code prolog> |
| % Figure 19.7 The HYPER program. |
| |
| % Program HYPER (Hypothesis Refiner) for learning in logic |
| |
| :- op( 500, xfx, :). |
| :- op( 900, fy, not). |
| |
| % not Goal): negation as failure; |
| % Note: This is often available as a built-in predicate, |
| % often written as prefix operator "\+", e.g. \+ likes(mary,snakes) |
| |
| not Goal :- |
| Goal, !, fail |
| ; |
| true. |
| |
| % induce( Hyp): |
| % induce a consistent and complete hypothesis Hyp by gradually |
| % refining start hypotheses |
| |
| induce( Hyp) :- |
| init_counts, !, % Initialise counters of hypotheses |
| start_hyps( Hyps), % Get starting hypotheses |
| best_search( Hyps, _:Hyp). % Specialised best-first search |
| |
| % best_search( CandidateHyps, FinalHypothesis) |
| |
| best_search( [Hyp | Hyps], Hyp) :- |
| show_counts, % Display counters of hypotheses |
| Hyp = 0:H, % cost = 0: H doesn't cover any neg. examples |
| complete(H). % H covers all positive examples |
| |
| best_search( [C0:H0 | Hyps0], H) :- |
| write('Refining hypo with cost '), write( C0), |
| write(:), nl, show_hyp(H0), nl, |
| all_refinements( H0, NewHs), % All refinements of H0 |
| add_hyps( NewHs, Hyps0, Hyps), !, |
| add1( refined), % Count refined hypos |
| best_search( Hyps, H). |
| |
| all_refinements( H0, Hyps) :- |
| findall( C:H, |
| ( refine_hyp(H0,H), % H new hypothesis |
| once(( add1( generated), % Count generated hypos |
| complete(H), % H covers all pos. exampl. |
| add1( complete), % Count complete hypos |
| eval(H,C) % C is cost of H |
| )) ), |
| Hyps). |
| |
| % add_hyps( Hyps1, Hyps2, Hyps): |
| % merge Hyps1 and Hyps2 in order of costs, giving Hyps |
| |
| add_hyps( Hyps1, Hyps2, Hyps) :- |
| mergesort( Hyps1, OrderedHyps1), |
| merge( Hyps2, OrderedHyps1, Hyps). |
| |
| complete( Hyp) :- % Hyp covers all positive examples |
| not ( ex( P), % A positive example |
| once( prove( P, Hyp, Answ)), % Prove it with Hyp |
| Answ \== yes). % Possibly not proved |
| |
| % eval( Hypothesis, Cost): |
| % Cost of Hypothesis = Size + 10 * # covered negative examples |
| |
| eval( Hyp, Cost) :- |
| size( Hyp, S), % Size of hypothesis |
| covers_neg( Hyp, N), % Number of covered neg. examples |
| ( N = 0, !, Cost is 0; % No covered neg. examples |
| Cost is S + 10*N). |
| |
| % size( Hyp, Size): |
| % Size = k1*#literals + k2*#variables in hypothesis; |
| % Settings of parameters: k1=10, k2=1 |
| |
| size( [], 0). |
| |
| size( [Cs0/Vs0 | RestHyp], Size) :- |
| length(Cs0, L0), |
| length( Vs0, N0), |
| size( RestHyp, SizeRest), |
| Size is 10*L0 + N0 + SizeRest. |
| |
| % covers_neg( H, N): |
| % N is number of neg. examples possibly covered by H |
| % Example possibly covered if prove/3 returns 'yes' or 'maybe' |
| |
| covers_neg( Hyp, N) :- % Hyp covers N negative examples |
| findall( 1, (nex(E), once(prove(E,Hyp,Answ)), Answ \== no), L), |
| length( L, N). |
| |
| % unsatisfiable( Clause, Hyp): |
| % Clause can never be used in any proof, that is: |
| % Clause's body cannot be proved from Hyp |
| |
| unsatisfiable( [Head | Body], Hyp) :- |
| once( prove( Body, Hyp, Answ)), Answ = no. |
| |
| start_hyps( Hyps) :- % Set of starting hypotheses |
| max_clauses( M), |
| setof( C:H, |
| (start_hyp(H,M), add1(generated), |
| complete(H), add1(complete), eval(H,C)), |
| Hyps). |
| |
| % start_hyp( Hyp, MaxClauses): |
| % A starting hypothesis with no more than MaxClauses |
| |
| start_hyp( [], _). |
| |
| start_hyp( [C | Cs], M) :- |
| M > 0, M1 is M-1, |
| start_clause( C), % A user-defined start clause |
| start_hyp( Cs, M1). |
| |
| % refine_hyp( Hyp0, Hyp): |
| % refine hypothesis Hyp0 into Hyp |
| |
| refine_hyp( Hyp0, Hyp) :- |
| choose_clause( Hyp0, Clause0/Vars0, Clauses1, Clauses2), % Choose a clause |
| conc( Clauses1, [Clause/Vars | Clauses2], Hyp), % New hypothesis |
| refine( Clause0, Vars0, Clause, Vars), % Refine chosen clause |
| non_redundant( Clause), % No redundancy in Clause |
| not unsatisfiable( Clause, Hyp). % Clause not unsatisfiable |
| |
| choose_clause( Hyp, Clause, Clauses1, Clauses2) :- % Choose Clause from Hyp |
| conc( Clauses1, [Clause | Clauses2], Hyp), % Choose a clause |
| nex(E), % A negative example E |
| prove( E, [Clause], yes), % Clause itself covers E |
| ! % Clause must be refined |
| ; |
| conc( Clauses1, [Clause | Clauses2], Hyp). % Otherwise choose any clause |
| |
| % refine( Clause, Args, NewClause, NewArgs): |
| % refine Clause with variables Args giving NewClause with NewArgs |
| |
| % Refine by unifying arguments |
| |
| refine( Clause, Args, Clause, NewArgs) :- |
| conc( Args1, [A | Args2], Args), % Select a variable A |
| member( A, Args2), % Match it with another one |
| conc( Args1, Args2, NewArgs). |
| |
| |
| % Refine a variable to a term |
| |
| refine( Clause, Args0, Clause, Args) :- |
| del( Var:Type, Args0, Args1), % Delete Var:Type from Args0 |
| term( Type, Var, Vars), % Var becomes term of type Type |
| conc( Args1, Vars, Args). % Add variables in the new term |
| |
| % Refine by adding a literal |
| |
| refine( Clause, Args, NewClause, NewArgs) :- |
| length( Clause, L), |
| max_clause_length( MaxL), |
| L < MaxL, |
| backliteral( Lit, InArgs, RestArgs), % Background knowledge literal |
| conc( Clause, [Lit], NewClause), % Add literal to body of clause |
| connect_inputs( Args, InArgs), % Connect literal's inputs to other args. |
| conc( Args, RestArgs, NewArgs). % Add rest of literal's arguments |
| |
| % non_redundant( Clause): Clause has no obviously redundant literals |
| |
| non_redundant( [_]). % Single literal clause |
| |
| non_redundant( [Lit1 | Lits]) :- |
| not literal_member( Lit1, Lits), |
| non_redundant( Lits). |
| |
| literal_member( X, [X1 | Xs]) :- % X literally equal to member of list |
| X == X1, ! |
| ; |
| literal_member( X, Xs). |
| |
| % show_hyp( Hypothesis): |
| % Write out Hypothesis in readable form with variables names A, B, ... |
| |
| show_hyp( []) :- nl. |
| |
| show_hyp( [C/Vars | Cs]) :- nl, |
| copy_term( C/Vars, C1/Vars1), |
| name_vars( Vars1, ['A','B','C','D','E','F','G','H','I','J','K','L','M','N']), |
| show_clause( C1), |
| show_hyp( Cs), !. |
| |
| show_clause( [Head | Body]) :- |
| write( Head), |
| ( Body = []; write( ':-' ), nl ), |
| write_body( Body). |
| |
| write_body( []) :- |
| write('.'), !. |
| |
| write_body( [G | Gs]) :- !, |
| tab( 2), write( G), |
| ( Gs = [], !, write('.'), nl |
| ; |
| write(','), nl, |
| write_body( Gs) |
| ). |
| |
| name_vars( [], _). |
| |
| name_vars( [Name:Type | Xs], [Name | Names]) :- |
| name_vars( Xs, Names). |
| |
| % connect_inputs( Vars, Inputs): |
| % Match each variable in list Outputs with a variable in list Vars |
| |
| connect_inputs( _, []). |
| |
| connect_inputs( S, [X | Xs]) :- |
| member( X, S), |
| connect_inputs( S, Xs). |
| |
| % merge( L1, L2, L3), all lists sorted |
| |
| merge( [], L, L) :- !. |
| |
| merge( L, [], L) :- !. |
| |
| merge( [X1|L1], [X2|L2], [X1|L3]) :- |
| X1 @=< X2, !, % X1 "lexicographically precedes" X2 (built-in predicate) |
| merge( L1, [X2|L2], L3). |
| |
| merge( L1, [X2|L2], [X2|L3]) :- |
| merge( L1, L2, L3). |
| |
| % mergesort( L1, L2): sort L1 giving L2 |
| |
| mergesort( [], []) :- !. |
| |
| mergesort( [X], [X]) :- !. |
| |
| mergesort( L, S) :- |
| split( L, L1, L2), |
| mergesort( L1, S1), |
| mergesort( L2, S2), |
| merge( S1, S2, S). |
| |
| % split( L, L1, L2): split L into lists of approx. equal length |
| |
| split( [], [], []). |
| |
| split( [X], [X], []). |
| |
| split( [X1,X2 | L], [X1|L1], [X2|L2]) :- |
| split( L, L1, L2). |
| |
| % Counters of generated, complete and refined hypotheses |
| |
| init_counts :- |
| retract( counter(_,_)), fail % Delete old counters |
| ; |
| assert( counter( generated, 0)), % Init. counter 'generated' |
| assert( counter( complete, 0)), % Init. counter 'complete' |
| assert( counter( refined, 0)). % Init. counter 'refined' |
| |
| add1( Counter) :- |
| retract( counter( Counter, N)), !, N1 is N+1, |
| assert( counter( Counter, N1)). |
| |
| show_counts :- |
| counter(generated, NG), counter( refined, NR), counter( complete, NC), |
| nl, write( 'Hypotheses generated: '), write(NG), |
| nl, write( 'Hypotheses refined: '), write(NR), |
| ToBeRefined is NC - NR, |
| nl, write( 'To be refined: '), write( ToBeRefined), nl. |
| |
| % Parameter settings |
| |
| max_proof_length( 6). % Max. proof length, counting calls to preds. in hypothesis |
| max_clauses( 4). % Max. number of clauses in hypothesis |
| max_clause_length( 5). % Max. number of literals in a clause |
| |
| prove( Goal, Hypo, Answer) :- |
| max_proof_length( D), |
| prove( Goal, Hypo, D, RestD), |
| (RestD >= 0, Answer = yes % Proved |
| ; |
| RestD < 0, !, Answer = maybe % Maybe, but it looks like inf. loop |
| ). |
| |
| prove( Goal, _, no). % Otherwise Goal definitely cannot be proved |
| |
| % prove( Goal, Hyp, MaxD, RestD): |
| % MaxD allowed proof length, RestD 'remaining length' after proof; |
| % Count only proof steps using Hyp |
| |
| prove( G, H, D, D) :- |
| D < 0, !. % Proof length overstepped |
| |
| prove( [], _, D, D) :- !. |
| |
| prove( [G1 | Gs], Hypo, D0, D) :- !, |
| prove( G1, Hypo, D0, D1), |
| prove( Gs, Hypo, D1, D). |
| |
| prove( G, _, D, D) :- |
| prolog_predicate( G), % Background predicate in Prolog? |
| call( G). % Call of background predicate |
| |
| prove( G, Hyp, D0, D) :- |
| D0 =< 0, !, D is D0-1 % Proof too long |
| ; |
| D1 is D0-1, % Remaining proof length |
| member( Clause/Vars, Hyp), % A clause in Hyp |
| copy_term( Clause, [Head | Body] ), % Rename variables in clause |
| G = Head, % Match clause's head with goal |
| prove( Body, Hyp, D1, D). % Prove G using Clause |
| </code> |
| ===== Comments ===== |
| |