第八章-分析句子结构

前面的章节侧重于单词:如何识别它们,分析它们的结构,将它们分配到词汇类别,以及访问它们的含义。我们还看到了如何识别单词序列或n-gram中的模式。然而,这些方法只是划分了控制句子的复杂约束的表面。我们需要一种方法来处理自然语言闻名的模糊性。我们还需要能够应对无限数量的可能句子这一事实,我们只能编写有限的程序来分析它们的结构并发现它们的含义。
本章的目标是回答以下问题:
我们如何使用正式语法来描述无限制句子的结构?
我们如何使用语法树来表示句子的结构?
解析器如何分析句子并自动构建语法树?
在此过程中,我们将介绍英语语法的基础知识,并且一旦我们确定了句子的结构,就会发现有一些系统方面的意义更容易被捕获。

8.1一些语法困境

8.1.1语言数据和无限可能性

前面的章节向您展示了如何处理和分析文本语料库,我们已经强调了NLP在处理日常增长的大量电子语言数据时面临的挑战。让我们更仔细地考虑这些数据,并进行思想实验,我们有一个巨大的语料库,包括过去50年来用英语说出或写的所有内容。我们是否有理由将这种语料称为“现代英语的语言”?我们可以回答没有问题的原因有很多。回想一下,在第三章中,我们要求您在网上搜索模式的实例。虽然在网上很容易找到包含这个单词序列的例子,比如"New man at the of IMG"说英语的人会说大多数这样的例子都是错误的,因此毕竟不是英语的一部分。因此,我们可以争辩说,“现代英语”并不等同于我们想象中的语料库中非常大的单词序列。说英语的人可以对这些序列做出判断,并拒绝其中一些作为不合语法的序列。
同样,很容易撰写一个新的句子,并让说英语的人同意它是非常好的英语。例如,句子有一个有趣的属性,它们可以嵌入更大的句子中。请考虑以下句子:

如果我们用符号S替换整个句子,我们会看到像Andre这样的模式S和我认为S.这些都是以一个句子,构建一个更大的句子模板。还有其他我们可以使用的模板,比如S,但是S,和S.当S时,我们可以使用这些模板构建一些非常长的句子。这是一个令人印象深刻的例子,来自A.A.的小熊维尼故事。 米尔恩,其中小猪完全被水包围:

这个长句子实际上有一个简单的结构,当S开始S但是S.从这个例子我们可以看出,语言为我们提供了似乎允许我们无限期地扩展句子的结构。同样引人注目的是,我们可以理解我们以前从未听过的任意长度的句子:编写一个完全新颖的句子并不难,这句话在语言历史上可能以前从未使用过,但所有的使用这个语言的人都会理解它。

语法的目的是给出语言的明确描述。但是我们对语法的思考方式与我们认为的语言密切相关。它是一个庞大但有限的观测表达和书面文本吗?它是否像精通这种语言的人那样在关于语法的句子包含隐含的知识那样更抽象吗?或者它是两者的某种组合?我们不会在这个问题上采取立场,而是将介绍主要方法。

在本章中,我们将采用“生成语法”的正式框架,其中“语言”被认为只是所有语法句子的大量集合,语法是一种正式的符号,可用于“生成”该集合的成员。语法使用S→S和S形式的递归式制作,我们将在第三节中进行探讨。在第十章中,我们将对此进行扩展,以便从其各部分的含义中自动建立句子的含义。

8.1.2无处不在的歧义

让我们仔细看看这句话中的含糊之处:I shot an elephant in my pajamas.
首先,我们需要定义一个简单的语法:

这种语法允许以两种方式分析句子,这取决于我的睡衣中的介词短语是描述大象还是射击事件

该程序产生两个括号内的结构,我们可以将其描绘成树,如图所示

请注意,任何单词的含义都没有歧义;例如单词shot不是指在第一句中使用枪,在第二句中使用相机。

本章介绍了语法和解析,对我们一直在讨论的语言现象研究并建模,形成正式并可计算的方法。正如我们将要看到的,由单词组成的序列无论是否符合语法,根据短语结构和依赖关系,我们可以理解其模式。我们可以使用语法和解析器开发这些结构的正式模型。和以前一样,关键动机是自然语言理解。当我们可以可靠地识别它包含的语言结构时,我们可以访问多少文本的含义?阅读完文本后,程序是否可以“理解”它足以回答关于“发生了什么”或“谁对谁做了什么”的简单问题?和以前一样,我们将开发简单的程序来处理带注释的语料库并执行有用的任务。

8.2语法的用途是什么?

8.2.1超过n-gram

学习文法的一个好处是,它提供了一个概念框架和词汇表能拼凑出这些直觉。
在一个符合语法规则的句子中的词序列可以被一个更小的序列替代而不会导致句子不符合语法规则。
你直观地知道这些序列是“word-salad”,但你可能会发现很难确定它们的错误。
学习语法的一个好处是它提供了一个概念框架和词汇来拼出这些直觉。
让我们仔细看看最糟糕的部分和笨拙的序列。

这看起来像一个坐标结构,其中两个短语通过协调连接连接,例如和,但是或者或。
这是一个关于协调如何在语法上工作的非正式(和简化)陈述:
并列结构:

这里有几个例子。

在第一个中,两个NP(名词短语)被联合起来制作NP,而在第二个中,两个AP(形容词短语)被联合起来作为一个AP

我们不能做的是连接NP和AP,这就是为什么
the worst part and clumsy looking
是不合语法的。
在我们将这些想法形式化之前,我们需要理解组成结构的概念。

组成结构基于观察词与其他词组合形成单位。一系列单词构成这样一个单元的证据是由可替代性给出的 - 也就是说,一个格式良好的句子中的一系列单词可以用一个较短的序列代替,而不会使句子形成错误。
为澄清这一想法,请考虑以下句子:

(6)	The little bear saw the fine fat trout in the brook.
我们可以用He代替The little bear表明后一个序列是一个单元。
相比之下,我们不能以同样的方式取代little bear saw

在2.1中,我们系统地用较短的序列替换较长的序列,以保持语法。

事实上,形成一个单元的每个序列都可以被一个单词替换,最后我们只得到两个元素。

在2.2中,我们在前面的图中看到的单词中添加了语法类别标签。

标签NP,VP和PP分别代表名词短语,动词短语和介词短语。

如果我们现在删除除最顶行之外的单词,添加一个S节点,并翻转图形,我们最终得到一个标准的短语结构树,如(8)所示。 该树中的每个节点(包括单词)称为成分。 S的直接成分是NP和VP。

我们将在下一节中看到的,语法指定句子如何细分为其直接成分,以及如何将这些细分进一步细分,直到达到单个单词的水平。

正如我们在8.1中看到的,句子可以有任意长度。因此,短语结构树可以具有任意深度。
我们在7.4中看到的级联块解析器只能生成有界深度的结构,因此这里的分块方法不适用。

8.3上下文无关语法

8.3.1一个简单的语法

让我们从一个简单的无上下文语法开始。
按照惯例,第一个产品的左侧是语法的起始符号,通常是S,并且所有符合语法规则的树必须以此符号作为其根标签。
在NLTK中,无上下文语法在nltk.grammar模块中定义。
在3.1中,我们定义了一个语法,并展示了如何解析语法允许的简单句子。

grammar1 = nltk.CFG.fromstring("""
  S -> NP VP
  VP -> V NP | V NP PP
  PP -> P NP
  V -> "saw" | "ate" | "walked"
  NP -> "John" | "Mary" | "Bob" | Det N | Det N PP
  Det -> "a" | "an" | "the" | "my"
  N -> "man" | "dog" | "cat" | "telescope" | "park"
  P -> "in" | "on" | "by" | "with"
  """)

sent = "Mary saw Bob".split() 
rd_parser = nltk.RecursiveDescentParser(grammar1)
for tree in rd_parser.parse(sent):
  print(tree)

句法分类
table3.1

如果我们用3.1的语法解析“The dog saw a man in the park ”这个句子,


根据语法,这句话可以被画成两种树,就是说这句话在结构上含糊不清。模糊性被称为介词短语附着歧义,
正如我们在本章前面所看到的那样。您可能还记得,由于公园中的PP需要连接到树中的两个位置之一,因此附着的含糊不清:无论是作为动词短语的孩子,还是作为名词短语的孩子。当PP连接到VP时,预期的解释是观看事件发生在公园。但是,如果PP附着在NP上,然后那个人在公园里,狗可能坐在俯瞰公园的公寓的阳台上。

8.3.2写你自己的语法

如果您有兴趣尝试编写CFG,你会发现在文本文件中创建和编辑你的语法很有帮助,比如mygrammar.cfg。然后,您可以将其加载到NLTK并使用它进行解析,如下所示:

grammar1 = nltk.data.load('file:mygrammar.cfg')
sent = "Mary saw Bob".split()
rd_parser = nltk.RecursiveDescentParser(grammar1,trace=2)
for tree in rd_parser.parse(sent):
     print(tree)
for p in grammar1.productions():
     print(p)

确保在文件名上加上.cfg后缀,
并且字符串’file:mygrammar.cfg’中没有空格。
如果命令print(tree)没有产生输出,
这可能是因为您的语句不接受您发送的句子。
在这种情况下,请使用跟踪设置为on来调用解析器:

rd_parser =nltk.RecursiveDescentParser(grammar1,trace = 2)

您还可以使用p命令检查语法中的产生式

for p in grammar1.productions(): print(p)

当您在NLTK中编写用于解析的CFG时,你不能将语法类别与同一作品右侧的词汇项目结合起来。
因此,不允许生产诸如PP -> ‘of’ NP,此外,您不得在制作的右侧放置多字词词项。
因此,不是写NP -> ‘New York’,而是必须采用像这样的NP -> 'New_York’替代。

8.3.3句法结构中的递归

如果一个类别出现在产生式的左侧也出现在产生式的右侧,则说法语是递归的,如3.3所示。

a中是直接递归

b中是间接递归
我们这里只展示了两个级别的递归,但深度没有上限。您可以尝试解析涉及更深层嵌套结构的句子。
请注意,递归下降解析器无法处理X-> X Y形式的左递归产生式;
我们将在4中回到这一点。

8.4 上下文无关文法分析

分析器根据文法产生式处理输入的句子,并建立一个或多个符合文法的组成结构。
文法 是一个格式良好的声明规范——它实际上只是一个字符串,而不是程序。
分析器是文法的解释程序。它搜索符合文法的所有树的空间找出一棵边缘有所需句子的树。
分析器允许使用一组测试句子评估一个文法,帮助语言学家发现在他们的文法分析中存在的错误。
分析器可以作为心理语言处理模型,帮助解释人类处理某些句法结构的困难

许多自然语言应用程序在某种程度上涉及解析;例如,我们希望将提交给问答系统的自然语言问题的第一步是进行解析。

在本节中,我们将看到两种简单的解析算法,一种称为递归下降解析的自上向而下方法,以及一种称为shift-reduce解析的自下而上方法。我们还看到一些更复杂的算法,自上而下的方法,自下而上过滤称为 左角解析,以及一种称为图表解析的动态编程技术。

8.4.1 递归下降解析

最简单的解析器将语法解释为如何将高级目标分解为几个较低级别子目标的规范。最高级别的目标是找到一个S.S→NP VP生产允许解析器用两个子目标替换此目标:找到NP,然后找到VP。这些子目标中的每一个都可以通过子目标的子目标依次替换,使用在左侧具有NP和VP的产生式。最终,这个扩展过程导致了子目标,例如:找到望远镜这个词。这些子目标可以直接与输入序列进行比较,如果下一个字匹配则成功。
如果没有匹配,解析器必须备份并尝试不同的替代方案。

递归下降解析器在上述过程中构建解析树。通过初始目标(找到S),创建S根节点。当上述过程使用语法的产生式递归地扩展其目标时,解析树向下扩展(因此名称递归下降)。我们可以使用图形演示nltk.app.rdparser()看到这一点。 4.1中显示了执行此解析器的六个阶段。


在此过程中,解析器经常被迫在几种可能的产生式之间进行选择。例如,从第3步到第4步,它试图在左侧找到带有N的产生式。第一个是N→man。如果这不起作用,它会回溯,并按顺序尝试其他N个制作,直到它到达N→dog,它匹配输入句子中的下一个单词。很久以后,如步骤5所示,它找到了一个完整的解析。这是一棵覆盖整个句子的树,没有任何悬垂的边缘。找到解析后,我们可以让解析器查找其他解析。它将再次回溯并探索产生式的其他选择,以防它们中的任何一个导致解析。

注意:RecursiveDescentParser()采用可选参数跟踪。 如果trace大于零,则解析器将报告解析文本时所采取的步骤。

NLTK提供了一个递归下降解析器

递归下降解析有三个关键缺点

首先,像NP - > NP PP这样的左递归产生将它发送到无限循环。
其次,解析器浪费了大量时间来考虑与输入句子不对应的单词和结构。
第三,回溯过程可能会丢弃需要稍后重建的已解析成分。
例如,回溯VP - > V NP将丢弃为NP创建的子树。
如果解析器继续使用VP - > V NP PP,则必须再次创建NP子树。
如the dog saw a man in the park

递归下降解析是一种自上而下的解析。
在检查输入之前,自上而下的解析器使用语法来预测输入是什么!
但是,由于输入一直可供解析器使用,因此从一开始就考虑输入句子会更为明智。
这种方法称为自下而上解析,我们将在下一节中看到一个例子。

8.4.2 移位-减少解析

一种简单的自下而上解析器是shift-reduce解析器。
与所有自下而上的解析器一样,
shift-reduce解析器尝试查找与语法生成的右侧对应的单词和短语序列,
并用左侧替换它们,直到整个句子缩小为S.

shift-reduce解析器反​​复将下一个输入字推入堆栈(4.1);这是轮班操作。如果堆栈中的前n个项目与某个生产的右侧的n个项目匹配,则它们全部从堆栈弹出,并且生产的左侧的项目被推入堆栈。使用单个项目替换前n个项目是reduce操作。此操作可能仅适用于堆栈顶部;减少堆栈中较低的项目必须在将后续项目推入堆栈之前完成。当所有输入都被消耗并且堆栈上只剩下一个项目时,解析器结束,一个以S节点为根的解析树。shift-reduce解析器在上述过程中构建一个解析树。每次它从堆栈中弹出n个项目时,它会将它们组合成一个部分解析树,并将其推回堆栈。我们可以使用图形演示nltk.app.srparser()来查看shift-reduce解析算法。执行此解析器的六个阶段显示在4.2中

使用sr_parse = nltk.ShiftReduceParser(grammar1,trace = 2)以跟踪模式运行上述解析器以查看shift和reduce操作的顺序

trace=2的结果
即使输入句子根据语法格式良好,shift-reduce解析器也可以达到死胡同而无法找到任何解析。发生这种情况时,没有输入遗留,并且堆栈包含无法缩减为S的项目。出现这个问题的原因是之前做出的选择无法通过解析器撤消(尽管图形演示的用户可以撤消他们的选择)。解析器有两种选择:(a)当多于一个时可以进行缩减哪个?(b)当两种行为都可以时移位或缩小。

可以扩展shift-reduce解析器以实现用于解决此类冲突的策略。例如,它可以通过仅在不可能减少时移位来解决移位减少冲突,并且可以通过支持从堆栈中移除大多数项的缩减操作来解决减少 - 减少冲突。 (shift-reduce解析器的概括,“前瞻LR解析器”,通常用于编程语言编译器。)

与递归下降解析器相比,shift-reduce解析器的优点是它们只构建与输入中的单词对应的结构。此外,它们仅构建一次每个子结构,例如, NP(Det(the),N(man))仅构建并一次性推入堆栈,无论以后是否会被
VP-> V NP PP减少或NP-> NP PP减少使用

8.4.3左角分析器

递归下降解析器的一个问题是它在遇到左递归生成时进入无限循环。这是因为它盲目地应用语法产生式,而不考虑实际的输入句子。左角解析器是我们看到的自下而上和自上而下的方法之间的混合。grammar1允许我们生成以下语法
tree 11
回想8.3.3中的语法,可以将NP扩充为如下的产生式
tree 12

假如我们让你先看下tree11(上上图)然后决定在递归下降解析器中采用哪种NP产生式-很明显,应该采用12©,那么你怎么知道采用12(a)和12(b)是不可行的呢?因为这两种产生式都不可以推导出一个第一个单词是John的序列,这就是说这些解析器必须要扩展NP,使得能从" John saw Mary"中推导出序列"John a",一般的说,如果A ⇒* B α,我们认为类别B是一个以A为根的树的左角解析器。
tree 13

左角解析器是一种自上而下的解析器,具有自下而上的过滤功能。与普通的递归下降解析器不同,它不会被困在左递归生成中。在开始工作之前,左角解析器预处理无上下文语法以构建一个表,其中每行包含两个单元格,
第一个持有非终端,

8.7总结

句子具有可以使用树来表示的内部组织。组成结构的显着特征是:递归,头部,补充和修饰符。

猜你喜欢

转载自blog.csdn.net/SherryLovesCoding/article/details/90242804