prolog for linguists symbolic systems 139p/239p john dowding week 8, november 26, 2001...
TRANSCRIPT
Prolog for LinguistsSymbolic Systems 139P/239P
John Dowding
Week 8, November 26, 2001
Office Hours
We have reserved 4 workstations in the Unix Cluster in Meyer library, fables 1-4
Office Hours this Thursday, 4:30-5:30
Contact me to make other arrangements
Course Schedule
1. Oct. 82. Oct. 153. Oct. 224. Oct. 295. Nov. 5 (double up)6. Nov. 127. Nov. 26 (double up) – Iterative Deepening and Logic8. Dec. 3
No class on Nov. 19
Review of Last Week
Last week we covered two techniques in DCGs Syntactic Gaps Quasi-Logical Form
Instead of just reviewing that material, I did the homework exercises starting from the grammar including gaps and QLFs.
Examples:
The man likes the cats
qterm(the, X, man(X)), qterm(the,Y,cat(Y)), likes(X, Y)
The man that likes the cats sleeps
qterm(the,X,man(X), qterm(the,Y,cat(Y)), likes(X,Y), sleeps(X)
The man that the cats like sleeps
qterm(the,X, man(X)), qterm(the,Y, cat(Y)), likes(Y,X), sleeps(X)
QLF bits
qterm(Quantifier, Variable, Predicate)
1-place predicates for nouns:
cat(X)
man(X)
1- and 2- place predicates for verbs
likes(X,Y)
sleeps(X)
Problem 1: Optional Determiners
det(plural, qterm(null, Var, Pred), Pred, Var) -->[].
det(Number, qterm(Det, Var, Pred), Pred, Var) --> [Det], {det(Det, Number)}.
det(a, singular).det(all, plural).det(an, singular).det(each, singular).det(every, singular).det(the, _).
Problem 2: Noun Phrase Adjectives
% Adjective Phrase Rulesadjp_star(_, true) -->
[].adjp_star(Var, ResultQLF) -->
adjp(Var, AdjpQLF),adjp_star(Var, RestQLF),{conjoin(AdjpQLF, RestQLF, ResultQLF)}.
% conjoin two LFsconjoin(true, LF, LF):-
!.conjoin(LF, true, LF):-
!.conjoin(LF1, LF2, (LF1,LF2)).
Problem 2 (cont)
adjp(Var, ResultQLF) -->advp_star(AdjQLF, ResultQLF),[Adj],{adj(Adj),AdjQLF =.. [Adj, Var]}.
advp_star(AdjQLF, AdjQLF) -->[].
advp_star(AdjQLF, ResultQLF) -->advp(AdjQLF, AdvpQLF),advp_star(AdjQLF, RestQLF),{conjoin(AdvpQLF, RestQLF, ResultQLF)}.
Problem 2 (cont)
advp(QLF, AdvQLF) -->[Adv], {adv(Adv),
AdvQLF =.. [Adv, QLF]}.
adv(very).adv(surprisingly).
adj(big).adj(fat).adj(fluffy).adj(furry).adj(old).adj(young).
Problem 2 (cont)
np_base(Case, third, Number, GapsIn, GapsOut, ResultQLF, Var) -->
det(Number, QLF, Pred, Var),
adjp_star(Var, AdjpQLF),
n(Number, Pred, Var),
{conjoin(QLF, AdjpQLF, NextQLF)},
np_right_modifiers(Case, third, Number, GapsIn, GapsOut, Var, RMQLF),
{conjoin(NextQLF, RMQLF, ResultQLF)}.
Problem 3: Proper Names
np_base(_Case, third, singular, Gaps, Gaps, true, ProperName) -->
[ProperName],
{proper_name(ProperName)}.
proper_name(john).
proper_name(mary).
proper_name(molly).
proper_name(me).
Problem 4: Pronouns
np_base(Case, Person, Number, Gaps, Gaps, qterm(pro, Pronoun, ProVar), ProVar) -->[Pronoun],{pronoun(Pronoun, Person, Number, Case)}.
pronoun(i, first, singular, nom).pronoun(me, first, singular, acc).pronoun(we, first, plural, nom).pronoun(us, first, plural, acc).pronoun(you, second, plural, _Case).pronoun(he, third, singular, nom).pronoun(him, third, singular, acc).pronoun(she, third, singular, nom).pronoun(her, third, singular, acc).pronoun(it, third, singular, _Case).pronoun(they, third, plural, nom).pronoun(them, third, plural, acc).
Problem 5: PP modifiers to NP and VP
np_right_modifiers(_Case, _Person, _Number, Gaps, Gaps, _Var, true) -->
[].
np_right_modifiers(Case, Person, Number, GapsIn, GapsOut, Var, QLF) -->
np_right_modifier(Case, Person, Number, GapsIn, GapsNext, Var, ThisQLF),
np_right_modifiers(Case, Person, Number, GapsNext, GapsOut, Var, RestQLF),
{conjoin(ThisQLF, RestQLF, QLF)}.
np_right_modifier(Case, Person, Number, GapsIn, GapsOut, Var, QLF) -->
relative_clause(Case, Person, Number, GapsIn, GapsOut, Var, QLF).
np_right_modifier(_Case, _Person, _Number, GapsIn, GapsOut, Var, QLF) -->
pp(Var, GapsIn, GapsOut, QLF).
Problem 5: PP
pp_base(Var, GapsIn, GapsOut, ResultQLF) -->
[Prep],
{preposition(Prep)},
np(acc, _Person, _Number, GapsIn, GapsOut, QLF, NPVar),
{Rel =.. [Prep, Var, NPVar],
conjoin(QLF, Rel, ResultQLF)}.
Problem 5 – PP modifying VP
vp_right_modifiers(Pred, Gaps, Gaps, Pred) -->
[].
vp_right_modifiers(Pred, GapsIn, GapsOut, QLF) -->
vp_right_modifier(Pred, GapsIn, GapsNext, ThisQLF),
vp_right_modifiers(Pred, GapsNext, GapsOut, RestQLF),
{conjoin(ThisQLF,RestQLF, QLF)}.
vp_right_modifier(Pred, GapsIn, GapsOut, QLF) -->
pp(Pred, GapsIn, GapsOut, QLF).
Problem 5 (cont)
vp_base(Person, Number, GapsIn, GapsOut, SubjVar, ResultQLF) -->
v(Person, Number, intransitive, SubjVar^Pred),
vp_right_modifiers(Pred, GapsIn, GapsOut, ResultQLF).
vp_base(Person, Number, GapsIn, GapsOut, SubjVar, QLF) -->
v(Person, Number, transitive, SubjVar^ObjVar^Pred),
np(acc, _, _Number, GapsIn, GapsNext, ObjQLF, ObjVar),
vp_right_modifiers(Pred, GapsNext, GapsOut, PredQLF),
{conjoin(ObjQLF, PredQLF, QLF)}.
Problem 6: Copula
vp_base(Person, Number, GapsIn, GapsOut, SubjVar, QLF) --> [Copula],{copula(Copula, Person, Number, _)},predicates(SubjVar, Number, GapsIn, GapsOut, QLF).
predicate(SubjVar, Number, GapsIn, GapsOut, ResultQLF ) -->np(acc, _Person, Number, GapsIn, GapsOut, QLF, PredVar),{conjoin(QLF, equals(SubjVar, PredVar), ResultQLF)}.
predicate(SubjVar, _Number, Gaps, Gaps, QLF ) -->adjp(SubjVar, QLF).
predicate(SubjVar, _Number, GapsIn, GapsOut, QLF ) -->pp(SubjVar, GapsIn, GapsOut, QLF).
Problem 7: Indirect Objects
vp_base(Person, Number, GapsIn, GapsOut, SubjVar, QLF) --> v(Person, Number, indirect, SubjVar^ObjVar^IndirObjVar^Pred),np(acc, _, _, GapsIn, Gaps1, ObjQLF, ObjVar),[to],np(acc, _, _, Gaps1, Gaps2, IndirObj, IndirObjVar),{conjoin(ObjQLF, IndirObj, ObjQLFs)},vp_right_modifiers(Pred, Gaps2, GapsOut, PredQLF),{conjoin(ObjQLFs, PredQLF, QLF)}.
v(gives, singular, indirect, X^Y^Z^gives(X,Y,Z)).v(give, plural, indirect, X^Y^Z^gives(X,Y,Z)).v(sells, singular, indirect, X^Y^Z^sells(X,Y,Z)).v(sell, plural, indirect, X^Y^Z^sells(X,Y,Z)).
Problem 8: Dative Transformation
vp_base(Person, Number, GapsIn, GapsOut, SubjVar, QLF) -->
v(Person, Number, indirect, SubjVar^ObjVar^IndirObjVar^Pred),
np(acc, _, _, GapsIn, Gaps1, IndirObj, IndirObjVar),
np(acc, _, _, Gaps1, Gaps2, ObjQLF, ObjVar),
{conjoin(ObjQLF, IndirObj, ObjQLFs)},
vp_right_modifiers(Pred, Gaps2, GapsOut, PredQLF),
{conjoin(ObjQLFs, PredQLF, QLF)}.
Problem 9: Conjunction
Handled conjoined s, np, n, and vps(GapsIn, GapsOut, QLF) -->
s_base(GapsIn, GapsTemp, SQLF),conjunction,s(GapsTemp, GapsOut, RestQLF),{conjoin(SQLF, RestQLF, QLF)}.
s(GapsIn, GapsOut, QLF) -->s_base(GapsIn, GapsOut, QLF).
s_base(GapsIn, GapsOut, QLF) --> np(nom, Person, Number, GapsIn, GapsNext, NP, NPVar), vp(Person, Number, GapsNext, GapsOut, NPVar, VP),{conjoin(NP, VP, QLF)}.
Problem 9 (cont)
np(Case, Person, Number, GapsIn, GapsOut, QLF, SemanticVar) -->np_base(Case, Person, Number, GapsIn, GapsOut, QLF, SemanticVar).
np(Case, Person, plural, GapsIn, GapsOut, QLF, SemanticVar) -->np_base(Case, Person, _, GapsIn, GapsTemp, QLF1, Sem1),[and],np(Case, Person, _, GapsTemp, GapsOut, QLF2, Sem2),{conjoin(QLF1, QLF2, TempQLF), conjoin(TempQLF, and(Sem1, Sem2, SemanticVar), QLF)}.
np(Case, Person, Number, GapsIn, GapsOut, QLF, SemanticVar) -->np_base(Case, Person, Number, GapsIn, GapsTemp, QLF1, Sem1),[or],np(Case, Person, Number, GapsTemp, GapsOut, QLF2, Sem2),{conjoin(QLF1, QLF2, TempQLF), conjoin(TempQLF, or(Sem1, Sem2, SemanticVar), QLF)}.
Iterative Deepening
Addresses depth-first run-away Or exploring an infinite part of the search tree first
If the program has a solution, ID will help you find it
Puts a depth bound on the computation
Increases depth bound when no solutions are found
Successor Arithmetic
Can we do arithmetic, fully reversible, in pure Prolog?
Add a depth bound to each predicate:is_number(0).
is_number(s(Number)):-
is_number(Number).
Becomes
is_number(0, s(_Bound)).
is_number(s(Number), s(Bound)):-
is_number(Number, Bound).
Successor Arithmetic (cont)
add(0, Sum, Sum):-
is_number(Sum).
add(s(Addend1), Addend2, s(Sum)):-
add(Addend1, Addend2, Sum).
Becomes
add(0, Sum, Sum, s(Bound)):-
is_number(Sum, Bound).
add(s(Addend1), Addend2, s(Sum), s(Bound)):-
add(Addend1, Addend2, Sum, Bound).
Successor Arithmetic (cont)
% mult(?Term1, ?Term2, ?Product)mult(Term1, Term2, Product):- start(Start), mult_id(Term1, Term2, Product, Start).
mult_id(Term1, Term2, Product, DepthBound):- mult(Term1, Term2, Product, DepthBound).mult_id(Term1, Term2, Product, DepthBound):- increment(DepthBound, Increment), add(DepthBound, Increment, NewDepthBound), mult_id(Term1, Term2, Product, NewDepthBound).
start(s(0)).increment(Bound, s(Bound)).
Problems
Wastes a lot of work
Returns duplicate results
Never terminates Bounded computations that terminate without ID aren’t
terminating now
mult(s(s(0)), s(s(0)), X)
We can address the returning duplicate results issue, and stay in ‘pure Prolog’
Counting deductions
Put a bound not only on depth, also on number of computational steps
Use threading to keep track of how many steps have been used
Only return a solution if it was found for the first time during this iteration That is, is required Depth < Steps < NextDepth steps, Or, RemainingDepth < Increment
mult/3 (again)
% mult(?Term1, ?Term2, ?Product)mult(Term1, Term2, Product):- start(Start), mult_id(Term1, Term2, Product, Start).
mult_id(Term1, Term2, Product, DepthBound):- mult(Term1, Term2, Product, DepthBound, RemainingBound), increment(Increment), less_than(RemainingBound, Increment).mult_id(Term1, Term2, Product, DepthBound):- increment(Increment), add(DepthBound, Increment, NewDepthBound), mult_id(Term1, Term2, Product, NewDepthBound).
mult/3 (cont)
% mult(+Term1, +Term2, ?Product, +Bound)mult(0, Term, 0, s(Bound), RemainingBound):- is_number(Term, Bound, RemainingBound).mult(s(Term1), Term2, Product, s(Bound), RemainingBound):- mult(Term1, Term2, Partial, Bound, NextBound), add(Term2, Partial, Product, NextBound, RemainingBound).
% add(?Addend1, ?Addend2, ?Sum, +Bound, -RemainingBound)add(0, Sum, Sum, s(Bound), RemainingBound):- is_number(Sum, Bound, RemainingBound).add(s(Addend1), Addend2, s(Sum), s(Bound), RemainingBound):- add(Addend1, Addend2, Sum, Bound, RemainingBound).
How much wasted work?
Assuming an increment of 1
Assuming a balanced binary search tree, then
Searching a depth of N should take O(2^N) steps
The prior N-1 passes should takeO(2^N-1 + 2^N-2 + … + 2^1) steps ~= O(2^N) steps
Under these assumptions, finding a solution using iterative deepening would take roughly twice as many steps
To reduce the wasted work, increase the increment
Termination?
Can we also solve the termination problem in pure Prolog?
No, because we can’t tell the difference between a genuine failure, and a failure due to the depth bound
To do this, we need to save some information across failure For instance, using assert/1.
Iterative Deepening Pure Prolog Interpreter
We can solve this in a general way by writing a specialized Prolog interpreter in Prolog.Using a new Prolog built-in clause/2: clause(Goal, Body)
When (Goal :- Body) is in the Prolog database,and is declared dynamic.
The Body for a fact is defined to be true.
For now, we will only worry about interpreting pure Prolog, no Prolog built-ins or cut.
id_call/[1,3]
id_call(Goal):-start(Start),increment(Increment),id_call(Goal, Start, Increment).
id_call(Goal, Bound, Increment):-retractall(more_search),id_interpret(Goal, Bound, Remaining),Remaining < Increment.
id_call(Goal, Bound, Increment):-more_search,NextBound is Bound + Increment,id_call(Goal, NextBound, Increment).
id_interpret/3
id_interpret(_Goal, 0, _Remaining):-!,assert_once(more_search),fail.
id_interpret(true, Bound, Remaining):-!,Remaining is Bound - 1.
id_interpret((Goal1,Goal2), Bound, Remaining):-!,Bound1 is Bound - 1,id_interpret(Goal1, Bound1, Bound2),id_interpret(Goal2, Bound2, Remaining).
id_interpret(Goal, Bound, Remaining):-NextBound is Bound - 1,clause(Goal, Body),id_interpret(Body, NextBound, Remaining).
Assignment
3rd assignment to be handed in
Modify consult_file/1 to create id_consult_file/1, which adds iterative deepening to the pure Prolog programs as they are being read in.
Provide a definition for id_call/1 which calls the iterative deepening versions of these predicates.
Be sure to handle both regular Prolog clauses with :- and DCG rules with .Test it by using your DCG from assignment 2 for generation.
More on Iterative Deepening
Iterative Deepening, and techniques like it, can be used to implement various other sorts of search problems Shortest path though a graph Minimal edit distance
Shortest Path through a DAG
edge(a,b).
edge(b,c).
edge(c,i).
edge(b,d).
edge(i,h).
edge(d,h).
edge(a,e).
edge(f,d).
edge(e,f).
edge(f,g).
edge(g,h).
path(Start, Finish, [Start,Finish]):-
edge(Start,Finish).
path(Start, Finish, [Start|Rest]):-
edge(Start, Next),
path(Next, Finish, Rest).
Minimal Edit Distance
The minimal edit distance between two sequences of symbols is the smallest number of insertions, deletions, and substitutions required to make the sequences identical.
The set of edits may not be unique
minimal_edit_distance([a,b,c,d],[e,b,c,f,g], Edits).
Edits = [substitute(a,e), insert(f), substitute(d,g)]
minimal_edit_distance/3
minimal_edit_distance(List1, List2, Edits):-
minimal_edit_distance(List1, List2, 0, Edits).
minimal_edit_distance(List1, List2, Cost, Edits):-
edit_distance(List1, List2, Cost, Edits),
!.
minimal_edit_distance(List1, List2, Cost, Edits):-
NextCost is Cost + 1,
minimal_edit_distance(List1, List2, NextCost, Edits).
edit_distance/4
edit_distance([], [], _Cost, []).edit_distance(List1, List2, Cost, Edits):-
Cost >= 0,edit(List1, List2, NewList1, NewList2, ThisCost, ThisEdit),append(ThisEdit, NextEdits, Edits),NewCost is Cost - ThisCost,edit_distance(NewList1, NewList2, NewCost, NextEdits).
edit([Head|Tail1], [Head|Tail2], Tail1, Tail2, 0, []).edit([Head|Tail1], Tail2, Tail1, Tail2, 1, [delete(Head)]).edit(Tail1, [Head|Tail2], Tail1, Tail2, 1, [insert(Head)]).edit([Head1|Tail1], [Head2|Tail2], Tail1, Tail2, 1, [substitute(Head1, Head2)]).
Logic and Prolog
Review Propositional and Predicate Logic
Introduce Clausal Form
Resolution Rule and Theorem Proving
This material is from Ch. 10 in Clocksin and Mellish
Propositional Logic
The logic of statements
Lower case letters (p, q, r, s…) represent propositions, that may be either true or false.
A formula is either a proposition, or a combination of propositions combined using connectives like and, or, and not:
p qp q p
p q
p q
Theories and Theorems
A Theory is a set of axioms plus a set of inference rulesA formula is a theorem in the theory if it can be derived from the axioms using the inference rules
Modus Ponens:
p p q
q
Resolution Rule for Propositional Logic
A1 …Ai AN A B1 … BN Ai
A1 … B1 … BN … AN A
Only really need , , and
The connectives and can be reduced to , , and
( p q ) is the same as (p q)
(p q) is the same as (p q) (p q)
Predicate Logic
Logic of TermsInstead of atomic propositions, we now have predicates applied to 1st order termsA 1st order term is either a constant a variable a function symbol and N arguments, which are 1st order terms
An atomic formula is a predicate symbol followed by N arguments,which are 1st order termsA formula can be an atomic formula, or built up using connectives , , , and and quantifiers and
Clausal Form
A 1st order formula can be expressed in a normal form clausal form, of the form( (A1 A2 … An) :- (B1 B2 … Bn) (C1 C2 … Cn) :- (D1 D2 … Dn) …)
We’ll derive clausal form in a series of steps1. Removing implications
2. Move negations inwards
3. Remove existential quantifiers
4. Moving universal quantifiers outwards
5. Distributing over 6. Putting into clauses
Step 1: Removing implications
Since we know that and can be expressed using only , , and , rewrite the formula to remove them
x.man(x) human(x)
becomes
x. man(x) human(x)
Step 2: Moving negations inwards
We can use these equalities to move negations inwards such that we only negate atomic formulas
() = () = x. = x.
x. = x.
We will refer to negated atomic formulas as negative literals, and unnegated atomic formulas as positive literals.
Step 3: Removing existential quantifiers
We can remove quantifiers by introducing new constant and function symbols
This technique is called Skolemization, and the new symbols are called skolem constants and skolem functions.
x.female(x) mother(x, eve)
female(g197) mother(g197, eve) for some new constant g197
Step 3:
When both universal and existential quantifiers are present, things get trickier:x.human(x) y.motherof(x,y)
x.human(x) motherof(x,g198)
That would incorrectly mean that every human has the same mother
We use a function instead (a function of the universally bound variables)x.human(x) motherof(x,g198(x))
Step 3:
Quoting Clocksin and Mellish (pg. 226):Skolemizing does more damage to the logical properties of a formula
than the other transformations we discuss. Nevertheless, it has the following important property. There is an interpretation for the symbols of a formula that makes the formula true if and only if there is an interpretation for the Skolemized version of the formula.
Step 4: Moving universal quantifiers outwards
We can move any universal quantifiers to the outside of the formula:x.man(x) y.woman(y) likes(x,y)
x. y.man(x) woman(y) likes(x,y)
Since all variables are universally bound, we can leave out the quantifier altogether.
At this point, our formulas are just positive and negative literals, connected by and
Step 5: Distributing over
We can use these equivalences to put the formula in conjunctive normal form, where no is inside a .(A B) C = (A C) (B C)
(A B) C = (A C) (B C)
Our formulas are now simply a conjunction of disjunctions
Step 6: Putting into Clauses
Our formulas are now of the form:A1 A2 A3 …, where each Ai is a disjunction of positive and
negative literals.
Call each such disjunction a clause
Since is associative and commutative, we can treat the formula as a set of clauses, with implicit conjunction.
Each clause is now just a disjunction of positive and negative literals.
Since is associative and commutative, we can treat each clause as a set of literals
Step 6 (cont)
We can rewrite each clause with all the positive literals on one side, and all the negative literals on the other:A1 A2 … An B1 B2 … Bm
A1 A2 … An (B1 B2 … Bm)
(A1 A2 … An) :- (B1 B2 … Bm)
Keep in mind either (or both) n or m might be 0.
When n=1, we call this a definite clause
Clausal Form and Prolog
(A1 A2 … An) :- (B1 B2 … Bm)
Prolog is restricted to clauses where N <= 1. That is, Prolog cannot prove disjunctions
When M=0, we call this a fact.When N=0, we call this a goal.If M=0 and N=0, we have an empty clause, which is false.The restriction to definite clauses means Prolog cannot represent all first order theories. But, definite clauses are still powerful enough to prove theorems about all computable functions.
Prolog and Theorem Proving
Suppose that we want to prove that a formula B is a logical consequence of a consistent set of clauses S: S = {A1 … AN}
Then, adding B to S should make it inconsistentS’= {A1 … AN, :- B}
This is how Prolog proves theorems, it adds the negation of the Goal to the set of clauses, and proves that it is inconsistent.
Resolution Rule for Predicate Logic
A1 …Ai AN A B1 … BN Ai’
(A1 … B1 … BN … AN A)
If Ai unifies with Ai’, yielding the substitution function
Prolog Theorem Proving
Prolog uses a restricted form of the Resolution Rule as it’s only inference ruleStarting with a Goal (a disjunction of negative literals):- B1 … BN
One application of the resolution rule to the left-most negative literal, and a rule of the formB1’ :- C1 … CM, where B1 unifies with B1’, with substitution
Yields:- (C1 … CM B2 … BN)
This continues until the the empty clause (false) is derivedThe history of substitution functions is used to produce an “answer”, effectively a counter-example to the claim that B1 … BN is inconsistent.
Final Projects
Any questions on final projects?
Due date?
Next Week
Still open to suggestions about what to cover next week
If any topics come up relevant to your final projects that you want help on, email me this week.
Tentatively planning on some programming tips for speeding up Prolog programs.