作为事实列出
让我们尝试用一个反例来解释这一点。让我们用简单的事实来指定名词、动词等:
det(the).
det(a).
n(woman).
n(man).
v(shoots).
现在我们可以将名词短语 np
实现为:
np([X,Y]) :-
det(X),
n(Y).
换句话说,我们说“名词短语是一个有两个词的句子,第一个是 a det
,第二个是 a n
”。这将起作用:如果我们查询np([a,woman])
,它将成功等等。
但是现在我们需要做一些更进一步的事情,定义动词短语。有两种可能的动词短语:一个带有动词和名词短语的短语,最初定义为:
vp(X,Z):- v(X,Y),np(Y,Z).
我们可以将其定义为:
vp([X|Y]) :-
v(X),
np(Y).
还有一个只有一个动词:
vp(X,Z):- v(X,Z).
这可以转换为:
vp([X]) :-
v(X).
猜测问题
然而,问题在于两种变体的单词数量不同:动词短语有一个单词和三个单词。这不是一个真正的问题,但现在说 - 我知道这不是正确的英语 - 存在一个定义为vp
后跟的句子np
,所以这将是:
s(X,Z):- vp(X,Y), np(Y,Z).
在原始语法中。
问题是,如果我们想将其转换为新的表示方式,我们需要知道vp
会消耗多少(有多少单词会被 吃掉vp
)。我们无法提前知道这一点:因为此时我们对句子的了解不多,所以我们无法猜测是否vp
会吃掉一个或三个单词。
我们当然可以猜测单词的数量:
s([X|Y]) :-
vp([X]),
np(Y).
s([X,Y,Z|T]) :-
vp([X,Y,Z]),
np(Z).
但我希望你能想象,如果你用 1、3、5 和 7 个词来定义动词短语,事情就会有问题。解决此问题的另一种方法是将其留给 Prolog:
s(S) :-
append(VP,NP,S),
vp(VP),
np(NP).
现在 Prolog 会先猜测如何将句子细分为两部分,然后尝试匹配每个部分。但问题是对于一个有 n 个单词的句子,有n 个断点。
因此,Prolog 将首先将其拆分为:
VP=[],NP=[shoots,the,man,the,woman]
(记住我们交换了动词短语和名词短语的顺序)。如果它得到一个空字符串,显然vp
不会很高兴。所以很容易被拒绝。但接下来它说:
VP=[shoots],NP=[the,man,the,woman]
现在vp
对 only 感到满意shoots
,但要实现这一点需要一些计算工作。np
然而,对这么长的部分并不感兴趣。所以 Prolog 再次回溯:
VP=[shoots,the],NP=[man,the,woman]
现在vp
会再次抱怨它已经被赋予了太多的话。最后 Prolog 将正确拆分它:
VP=[shoots,the,woman],NP=[the,woman]
关键是它需要大量的猜测。对于这些猜测中的每一个vp
,np
也需要工作。对于真正复杂的语法,vp
可能np
会进一步拆分句子,从而导致大量的试错。
真正的原因是append/3
没有“语义”线索如何拆分句子,所以它尝试了所有可能性。然而,人们对一种方法更感兴趣,该方法vp
可以提供有关它真正想要的句子份额的信息。
此外,如果您必须将句子分成 3 个部分,则执行此操作的方法数量甚至会增加到O(n^2)等等。所以猜测不会成功。
您也可以尝试生成一个随机动词短语,然后希望动词短语匹配:
s(S) :-
vp(VP),
append(VP,NP,S),
np(NP).
但在这种情况下,猜测的动词短语的数量将呈指数级增长。当然你可以给出“提示”等来加快这个过程,但这仍然需要一些时间。
解决方案
您要做的是为每个谓词提供句子的一部分,使谓词看起来像:
predicate(Subsentence,Remaining)
Subsentence
是以该谓词开头的单词列表。例如,对于一个名词短语,它可能看起来像[the,woman,shoots,the,man]
. 每个谓词都使用它感兴趣的词:直到某个点的词。在这种情况下,名词短语只对 感兴趣['the','woman']
,因为那是名词短语。为了进行剩余的解析,它返回剩余的部分[shoots,the,woman]
,希望其他谓词可以消耗句子的剩余部分。
对于我们的事实表,这很简单:
det([the|W],W).
det([a|W],W).
n([woman|W],W).
n([man|W],W).
v([shoots|W],W).
因此,这意味着如果您查询一个 setence: [the,woman,shoots,...]
,并询问这det/2
是否是一个限定符,它会说:“是的,the
是一个限定符,但其余部分[woman,shoots,...]
”不是限定符的一部分,请与其他内容匹配。
之所以进行这种匹配,是因为列表表示为链表。[the,woman,shoots,...]
, 实际上表示为[the|[woman|[shoots|...]]]
(因此它指向下一个“子列表”)。如果匹配:
[the|[woman|[shoots|...]]]
det([the|W] ,W)
它将统一[woman|[shoots|...]]
并W
因此导致:
det([the|[woman|[shoots|...]],[woman|[shoots|...]]).
从而返回剩余的列表,它因此消耗了该the
部分。
更高级别的谓词
现在,如果我们定义名词短语:
np(X,Z):- det(X,Y), n(Y,Z).
我们再次调用 with [the,woman,shoots,...]
,它将查询X
与该列表统一。它将首先调用det
将消耗the
,而无需回溯。接下来Y
就等于[woman,shoots,...]
,现在n/2
将消耗女人而归来[shoots,...]
。这也是np
将返回的结果,另一个谓词将不得不使用它。
用处
假设您介绍您的名字作为附加名词:
n([doug,smith|W],W).
(很抱歉使用小案例,但 Prolog 将这些视为变量)。
它只会尝试用doug
and匹配前两个单词smith
,如果成功,则尝试匹配句子的其余部分。所以可以这样写一个句子:([the,doug,smith,shoots,the,woman]
对不起,在英语中,一些名词短语直接映射到一个名词,np(X,Y) :- n(X,Y)
因此the
可以删除更复杂的英语语法)。
猜猜完全消除了?
猜测是否完全消除?没有。消费上仍有重叠的可能。例如,您可以添加:
n([doug,smith|W],W).
n([doug|W],W).
在这种情况下,如果您查询[the,doug,smith,shoots,the,woman]
. 它会首先消费/吃 in det
,然后它会寻找一个名词来消费[doug,smith,...]
。有两个候选人。Prolog 将首先尝试只吃doug
,并匹配[smith,shoots,...]
为一个完整的动词短语,但由于smith
不是动词,它会回溯,重新考虑只吃一个词,因此决定同时吃两者doug
和smith
作为名词。
关键是当使用 append 时,Prolog 也必须回溯。
结论
通过使用差异列表,您可以吃任意数量的单词。返回剩余部分,以便其他句子部分(如动词短语)旨在消耗剩余部分。此外,该列表始终是完全有根据的,因此绝对不会首先使用蛮力生成各种动词短语。