34、自然语言处理中的语法分析与特征结构

自然语言处理中的语法分析与特征结构

1. 概率解析与维特比解析器

在自然语言处理中,解析句子是一项重要任务。当为解析树分配概率后,对于给定句子可能存在大量可能的解析这一情况就不再是问题了,因为解析器的任务是找到最可能的解析。

1.1 维特比解析器示例

以下是使用维特比解析器对句子 “Jack saw telescopes” 进行解析的示例代码:

viterbi_parser = nltk.ViterbiParser(grammar)
print viterbi_parser.parse(['Jack', 'saw', 'telescopes'])

输出结果为:

(S (NP Jack) (VP (TV saw) (NP telescopes))) (p=0.064)

这表明该句子的解析树结构为:句子 (S) 由名词短语 (NP) “Jack” 和动词短语 (VP) 组成,动词短语又由及物动词 (TV) “saw” 和名词短语 (NP) “telescopes” 构成,并且该解析的概率为 0.064。

2. 语法分析基础概念

2.1 句子结构与语法

  • 句子结构 :句子具有内部组织,可以用树来表示。成分结构的显著特征包括递归、中心语、补语和修饰语。
  • 语法定义 :语法是对潜在无限句子集合的紧凑描述。我们说一棵树根据语法是形式良好的,或者说语法允许这样的树存在。语法是一种形式模型,用于描述给定短语是否可以分配特定的成分或依赖结构。

2.2 上下文无关语法和依赖语法

  • 上下文无关语法(CFG) :给定一组句法类别,上下文无关语法使用一组产生式来说明某个类别 A 的短语如何分析为更小部分的序列 α1 … αn。
  • 依赖语法 :依赖语法使用产生式指定给定词汇中心语的依赖项。

2.3 句法歧义

句法歧义是指一个句子有不止一种句法分析,例如介词短语附着歧义。

2.4 解析器

解析器是一种用于找到与语法正确的句子对应的一个或多个树的程序。常见的解析器包括递归下降解析器和移进 - 归约解析器。

2.4.1 递归下降解析器

递归下降解析器是一种简单的自顶向下解析器,它借助语法产生式递归地扩展起始符号(通常是 S),并尝试匹配输入句子。然而,它存在一些问题:
- 无法处理左递归产生式,例如 “NP -> NP PP” 这样的产生式。
- 效率低下,它盲目地扩展类别而不检查它们是否与输入字符串兼容,并且会反复扩展相同的非终结符并丢弃结果。

2.4.2 移进 - 归约解析器

移进 - 归约解析器是一种简单的自底向上解析器,它将输入移到栈上,并尝试将栈顶的项与语法产生式的右侧进行匹配。该解析器存在以下问题:
- 即使存在有效的解析,也不能保证找到输入的有效解析。
- 在构建子结构时不检查其是否与语法全局一致。

以下是两种解析器的对比表格:
| 解析器类型 | 工作方式 | 优点 | 缺点 |
| ---- | ---- | ---- | ---- |
| 递归下降解析器 | 自顶向下,递归扩展起始符号 | 实现简单 | 无法处理左递归,效率低 |
| 移进 - 归约解析器 | 自底向上,移进输入到栈并匹配产生式 | 简单直观 | 不一定能找到有效解析,不检查全局一致性 |

2.5 更多学习资源

此外,还有许多关于句法的入门书籍,例如:
- (O’Grady et al., 2004) 是对语言学的一般介绍。
- (Radford, 1988) 提供了对转换生成语法的温和介绍,尤其在无界依赖结构的转换方法方面值得推荐。

还有一些正在进行的构建大规模基于规则的语法的项目,如:
- LFG Pargram 项目: http://www2.parc.com/istl/groups/nltt/pargram/
- HPSG LinGO Matrix 框架: http://www.delph-in.net/matrix/
- XTAG 项目: http://www.cis.upenn.edu/~xtag/

3. 练习题

3.1 基础练习

  1. 能否想出可能从未被说过的语法正确的句子?这对人类语言有什么启示?
  2. 回顾 Strunk 和 White 禁止在句首使用 “however” 表示 “although” 的规则,在网络上搜索句首使用 “however” 的情况,这种用法有多普遍?
  3. 考虑句子 “Kim arrived or Dana left and everyone cheered”,写出括号形式以显示 “and” 和 “or” 的相对范围,并生成对应这两种解释的树结构。
  4. Tree 类实现了多种有用的方法,查看 Tree 帮助文档以获取更多详细信息(即导入 Tree 类,然后输入 help(Tree) )。
  5. 手动构建一些解析树:
    • 编写代码为短语 “old men and women” 的两种理解分别生成一棵树。
    • 将本章中呈现的任何树编码为带标签的括号形式,并使用 nltk.Tree() 检查其是否形式良好,然后使用 draw() 显示树。
    • 像 (a) 一样,为句子 “The woman saw a man last Thursday” 绘制一棵树。
  6. 编写一个递归函数来遍历树并返回树的深度,使得只有一个节点的树深度为零。(提示:子树的深度是其所有子节点的最大深度加一。)
  7. 分析关于 Piglet 的 A.A. Milne 句子,下划线标注其中包含的所有句子,然后用 S 替换这些句子(例如,第一个句子变为 “S when S”),为这个 “压缩” 句子绘制树结构,构建这样长句子使用的主要句法结构是什么?
  8. 在递归下降解析器演示中,通过编辑菜单中的 “Edit Text” 选项更改要解析的句子进行实验。
  9. 语法 grammar1 (示例 8 - 1)能否用于描述长度超过 20 个单词的句子?
  10. 使用图形图表解析器界面试验不同的规则调用策略,想出自己可以手动使用图形界面执行的策略,描述步骤并报告其效率改进(例如,从生成的图表大小方面)。这些改进是否依赖于语法的结构?你如何看待通过更巧妙的规则调用策略实现显著性能提升的前景?
  11. 用铅笔和纸手动跟踪递归下降解析器和移进 - 归约解析器对已经见过的上下文无关语法或自己设计的语法的执行过程。
  12. 我们已经看到图表解析器只添加而从不从图表中移除边,为什么?
  13. 考虑句子 “Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo”,这是一个语法正确的句子,参考 http://en.wikipedia.org/wiki/Buffalo_buffalo_Buffalo_buffalo_buffalo_buffalo_Buffalo_buffalo 上的树图,写出合适的语法。将大小写规范化为小写,以模拟听众听到这个句子时的问题。能否找到这个句子的其他解析?随着句子变长,解析树的数量如何增长?(更多此类句子的示例可在 http://en.wikipedia.org/wiki/List_of_homophonous_phrases 找到。)

3.2 进阶练习

  1. 可以通过编辑菜单中的 “Edit Grammar” 选项修改递归下降解析器演示中的语法,将第一个扩展产生式 “NP -> Det N PP” 改为 “NP -> NP PP”,使用 “Step” 按钮尝试构建解析树,会发生什么?
  2. 扩展语法 grammar2 ,添加将介词扩展为不及物、及物和需要介词短语补语的产生式,基于这些产生式,使用前面练习的方法为句子 “Lee ran away home” 绘制树。
  3. 选择一些常见动词并完成以下任务:
    • 编写程序在 PP 附着语料库 nltk.corpus.ppattach 中查找这些动词,找到同一个动词表现出两种不同附着情况,但第一个名词、第二个名词或介词保持不变的情况(如在句法歧义讨论中所见)。
    • 设计上下文无关语法产生式来涵盖其中一些情况。
  4. 编写程序比较自顶向下图表解析器和递归下降解析器的效率(第 8.4 节),对两者使用相同的语法和输入句子,使用 timeit 模块比较它们的性能(有关如何操作的示例,请参阅第 4.7 节)。
  5. 使用相同的语法和三个语法正确的测试句子比较自顶向下、自底向上和左角解析器的性能,使用 timeit 记录每个解析器处理相同句子所需的时间,编写一个函数对所有三个句子运行所有三个解析器,并打印一个 3×3 的时间网格以及行和列的总和,讨论结果。
  6. 了解 “花园路径” 句子,解析器的计算工作与人类处理这些句子的困难有何关系?(请参阅 http://en.wikipedia.org/wiki/Garden_path_sentence 。)
  7. 为了在单个窗口中比较多个树,可以使用 draw_trees() 方法,定义一些树并尝试使用:
from nltk.draw.tree import draw_trees
draw_trees(tree1, tree2, tree3)
  1. 使用树位置列出宾州树库中前 100 个句子的主语,为了使结果更易于查看,将提取的主语限制为高度最多为 2 的子树。
  2. 检查 PP 附着语料库并尝试提出影响介词短语附着的一些因素。
  3. 在第 8.2 节中,我们声称有些语言规律不能简单地用 n - 元组来描述。考虑以下句子,特别是 “in his turn” 短语的位置,这是否说明了基于 n - 元组的方法存在问题?
What was more, the in his turn somewhat youngish Nikolay Parfenovich also turned out to be the only person in the entire world to acquire a sincere liking to our “discriminated - against” public procurator. (Dostoevsky: The Brothers Karamazov)
  1. 编写一个递归函数,为树生成嵌套括号形式,省略叶节点,并在子树后显示非终结符标签。例如,第 8.6 节中关于 “Pierre Vinken” 的示例将生成: [[[NNP NNP]NP , [ADJP [CD NNS]NP JJ]ADJP ,]NP - SBJ MD [VB [DT NN]NP [IN [DT JJ NN]NP]PP - CLR [NNP CD]NP - TMP]VP .]S ,连续的类别应该用空格分隔。
  2. 从 Project Gutenberg 下载几本电子书,编写程序扫描这些文本中的超长句子,能找到的最长句子是什么?导致这样长句子的句法结构是什么?
  3. 修改 init_wfst() complete_wfst() 函数,使加权有限状态转移器(WFST)中每个单元格的内容是一组非终结符符号,而不是单个非终结符。
  4. 考虑示例 8 - 3 中的算法,能否解释为什么解析上下文无关语法的时间复杂度与输入句子的长度 n 的三次方成正比?
  5. 处理宾州树库语料库样本 nltk.corpus.treebank 中的每棵树,并使用 Tree.productions() 提取产生式,丢弃只出现一次的产生式。具有相同左部和相似右部的产生式可以合并,从而得到一组等效但更紧凑的规则,编写代码输出紧凑的语法。

3.3 高级练习

  1. 在英语中,定义句子 S 的主语的一种常见方法是作为 S 的子节点且是 VP 的兄弟节点的名词短语。编写一个函数,接受句子的树作为输入,并返回对应句子主语的子树。如果传递给该函数的树的根节点不是 S,或者缺少主语,该函数应该怎么做?
  2. 编写一个函数,接受一个语法(如示例 8 - 1 中定义的语法),并返回由该语法生成的随机句子。(使用 grammar.start() 找到语法的起始符号, grammar.productions(lhs) 获取语法中具有指定左部的产生式列表, production.rhs() 获取产生式的右部。)
  3. 实现一个使用回溯的移进 - 归约解析器版本,使其能够找到句子的所有可能解析,这可能被称为 “递归上升解析器”。参考 http://en.wikipedia.org/wiki/Backtracking 上的回溯相关内容。
  4. 如第 7 章所见,可以将组块压缩为它们的组块标签。当对包含 “gave” 一词的句子进行这样的操作时,我们会发现以下模式:
    • gave NP
    • gave up NP in NP
    • gave NP up
    • gave NP NP
    • gave NP to NP
    • 使用这种方法研究感兴趣的动词的补语模式,并编写合适的语法产生式。(这项任务有时被称为词汇获取。)
    • 识别一些近义的英语动词,如第 9 章 (64) 中的 “dumped/filled/loaded” 示例,使用组块方法研究这些动词的补语模式,创建一个语法来涵盖这些情况,这些动词可以相互自由替换吗?是否存在限制?讨论结果。
  5. 基于递归下降解析器开发一个左角解析器,并继承自 ParseI
  6. 扩展 NLTK 的移进 - 归约解析器以纳入回溯,确保它能找到所有存在的解析(即它是完备的)。
  7. 修改 init_wfst() complete_wfst() 函数,使得当非终结符符号添加到 WFST 的单元格中时,它包含该符号所源自的单元格的记录,实现一个函数将这种形式的 WFST 转换为解析树。

4. 特征结构与基于特征的语法

4.1 特征结构的引入

自然语言有广泛的语法结构,简单的方法难以处理。为了获得更多灵活性,我们改变对语法类别(如 S、NP 和 V)的处理方式,将它们分解为类似字典的结构,其中特征可以取一系列值。

4.2 语法特征示例

我们从一个非常简单的例子开始,使用字典来存储特征及其值。

kim = {'CAT': 'NP', 'ORTH': 'Kim', 'REF': 'k'}
chase = {'CAT': 'V', 'ORTH': 'chased', 'REL': 'chase'}

对象 kim chase 都有一些共享特征,如 CAT (语法类别)和 ORTH (正字法,即拼写)。此外,每个对象还有一个更具语义导向的特征: kim['REF'] 表示 kim 的指称, chase['REL'] 表示 chase 所表达的关系。在基于规则的语法上下文中,这种特征和值的配对被称为特征结构。

4.3 特征结构的扩展

对于动词,了解其参数所扮演的 “语义角色” 通常很有用。以 chase 为例,主语扮演 “施事” 角色,宾语扮演 “受事” 角色。我们可以添加这些信息:

chase['AGT'] = 'sbj'
chase['PAT'] = 'obj'

当处理句子 “Kim chased Lee” 时,我们希望将动词的施事角色绑定到主语,受事角色绑定到宾语。以下是具体实现代码:

sent = "Kim chased Lee"
tokens = sent.split()
lee = {'CAT': 'NP', 'ORTH': 'Lee', 'REF': 'l'}

def lex2fs(word):
    for fs in [kim, lee, chase]:
        if fs['ORTH'] == word:
            return fs

subj, verb, obj = lex2fs(tokens[0]), lex2fs(tokens[1]), lex2fs(tokens[2])
verb['AGT'] = subj['REF']  # 'chase' 的施事是 Kim
verb['PAT'] = obj['REF']   # 'chase' 的受事是 Lee

for k in ['ORTH', 'REL', 'AGT', 'PAT']:  # 检查 'chase' 的特征结构
    print "%-5s => %s" % (k, verb[k])

输出结果为:

ORTH  => chased
REL   => chase
AGT   => k
PAT   => l

4.4 不同动词的特征结构

对于不同的动词,如 “surprise”,主语扮演 “源”(SRC)角色,宾语扮演 “体验者”(EXP)角色:

surprise = {'CAT': 'V', 'ORTH': 'surprised', 'REL': 'surprise',
            'SRC': 'sbj', 'EXP': 'obj'}

4.5 基于特征的语法扩展

特征结构很强大,但我们目前操作它们的方式非常临时。接下来的任务是展示如何扩展上下文无关语法和解析的框架以适应特征结构,从而以更通用和有原则的方式构建这样的分析。

以下是特征结构示例的总结表格:
| 对象 | 特征 | 值 |
| ---- | ---- | ---- |
| kim | CAT | NP |
| kim | ORTH | Kim |
| kim | REF | k |
| chase | CAT | V |
| chase | ORTH | chased |
| chase | REL | chase |
| chase | AGT | sbj |
| chase | PAT | obj |
| surprise | CAT | V |
| surprise | ORTH | surprised |
| surprise | REL | surprise |
| surprise | SRC | sbj |
| surprise | EXP | obj |

通过以上内容,我们对自然语言处理中的语法分析和特征结构有了更深入的了解,从基本的解析器到复杂的特征结构处理,这些知识为进一步研究自然语言处理提供了坚实的基础。在实际应用中,我们可以根据具体需求选择合适的解析器和方法,利用特征结构来更精确地描述语言现象。同时,练习题也为我们提供了实践和巩固知识的机会,帮助我们更好地掌握这些概念和技术。

5. 特征结构在语法分析中的应用与优势

5.1 处理语法一致性

自然语言中存在许多语法一致性的要求,例如主谓一致。特征结构可以很好地处理这类问题。以英语为例,动词的形式需要与主语的单复数和人称保持一致。我们可以通过特征结构来实现这种一致性检查。

假设我们有以下特征结构表示主语和动词:

# 单数第三人称主语
he = {'CAT': 'NP', 'NUM': 'sg', 'PER': 3, 'ORTH': 'he', 'REF': 'h'}
# 对应的动词形式
runs = {'CAT': 'V', 'NUM': 'sg', 'PER': 3, 'ORTH': 'runs', 'REL': 'run'}

在构建句子 “He runs” 时,我们可以检查主语和动词的 NUM (数)和 PER (人称)特征是否匹配:

sent = "He runs"
tokens = sent.split()

def lex2fs(word):
    for fs in [he, runs]:
        if fs['ORTH'] == word:
            return fs

subj, verb = lex2fs(tokens[0]), lex2fs(tokens[1])

if subj['NUM'] == verb['NUM'] and subj['PER'] == verb['PER']:
    print("主谓一致,句子合法")
else:
    print("主谓不一致,句子不合法")

通过这种方式,特征结构可以帮助我们在语法分析过程中确保句子的语法正确性。

5.2 处理次范畴化

次范畴化是指动词对其论元的类型和数量有特定的要求。例如,有些动词需要一个宾语,而有些动词则不需要。特征结构可以用来表示动词的次范畴化信息。

# 需要宾语的动词
eat = {'CAT': 'V', 'ORTH': 'eat', 'REL': 'eat', 'SUBCAT': ['NP']}
# 不需要宾语的动词
sleep = {'CAT': 'V', 'ORTH': 'sleep', 'REL': 'sleep', 'SUBCAT': []}

# 句子 "He eats an apple"
sent1 = "He eats an apple"
tokens1 = sent1.split()

# 句子 "He sleeps"
sent2 = "He sleeps"
tokens2 = sent2.split()

def lex2fs(word):
    for fs in [he, eat, sleep]:
        if fs['ORTH'] == word:
            return fs

# 处理句子 1
subj1, verb1, obj1 = lex2fs(tokens1[0]), lex2fs(tokens1[1]), lex2fs(tokens1[2])
if len(verb1['SUBCAT']) == 1 and obj1['CAT'] == verb1['SUBCAT'][0]:
    print("句子 1 动词次范畴化匹配")
else:
    print("句子 1 动词次范畴化不匹配")

# 处理句子 2
subj2, verb2 = lex2fs(tokens2[0]), lex2fs(tokens2[1])
if len(verb2['SUBCAT']) == 0:
    print("句子 2 动词次范畴化匹配")
else:
    print("句子 2 动词次范畴化不匹配")

通过特征结构中的 SUBCAT (次范畴化)特征,我们可以检查动词和其论元是否符合次范畴化要求,从而判断句子的合法性。

5.3 处理无界依赖结构

无界依赖结构是自然语言中一种较为复杂的语法现象,例如疑问词移位。特征结构可以帮助我们处理这类结构。

考虑句子 “What did he eat?”,我们可以使用特征结构来表示疑问词和动词的关系。

what = {'CAT': 'NP', 'ORTH': 'what', 'REF': 'w', 'DEPTYPE': 'wh'}
did = {'CAT': 'V', 'ORTH': 'did', 'REL': 'do', 'SUBCAT': ['V', 'NP']}
eat = {'CAT': 'V', 'ORTH': 'eat', 'REL': 'eat', 'SUBCAT': ['NP']}

sent = "What did he eat?"
tokens = sent.split()

def lex2fs(word):
    for fs in [what, did, he, eat]:
        if fs['ORTH'] == word:
            return fs

wh, aux, subj, verb = lex2fs(tokens[0]), lex2fs(tokens[1]), lex2fs(tokens[2]), lex2fs(tokens[3])

# 检查疑问词和动词的依赖关系
if wh['DEPTYPE'] == 'wh' and len(verb['SUBCAT']) == 1:
    print("可以处理无界依赖结构,疑问词和动词关系匹配")
else:
    print("无法处理无界依赖结构,疑问词和动词关系不匹配")

通过特征结构中的 DEPTYPE (依赖类型)特征,我们可以跟踪疑问词的移位,并确保其与动词的依赖关系正确。

5.4 特征结构的优势总结

特征结构在语法分析中具有以下优势:
- 灵活性 :可以根据需要定义各种特征,以适应不同的语法现象。
- 精确性 :能够更精确地描述语言实体的属性和关系,提高语法分析的准确性。
- 可扩展性 :可以方便地添加新的特征和规则,以处理更复杂的语言结构。

以下是特征结构在不同语法现象处理中的应用总结表格:
| 语法现象 | 特征结构的应用方式 | 示例 |
| ---- | ---- | ---- |
| 主谓一致 | 检查主语和动词的数和人称特征是否匹配 | “He runs” |
| 次范畴化 | 检查动词的次范畴化要求和论元的类型是否匹配 | “He eats an apple” |
| 无界依赖结构 | 跟踪疑问词的移位和与动词的依赖关系 | “What did he eat?” |

6. 总结与展望

6.1 知识总结

通过前面的介绍,我们全面了解了自然语言处理中的语法分析和特征结构。从基本的解析器(如递归下降解析器和移进 - 归约解析器)到复杂的特征结构处理,我们掌握了不同的语法分析方法和技术。

解析器是语法分析的基础工具,不同的解析器有各自的优缺点。递归下降解析器实现简单,但存在无法处理左递归和效率低下的问题;移进 - 归约解析器简单直观,但不一定能找到有效解析且不检查全局一致性。

特征结构是一种强大的工具,它可以将语法类别分解为更细致的特征和值,从而处理语法一致性、次范畴化和无界依赖结构等复杂的语法现象。通过特征结构,我们可以更精确地描述语言实体的属性和关系,提高语法分析的准确性和灵活性。

6.2 未来研究方向

虽然我们已经取得了很多进展,但自然语言处理中的语法分析仍然面临许多挑战。未来的研究可以从以下几个方向展开:

6.2.1 更复杂的特征结构表示

目前我们使用的特征结构主要是基于字典的简单表示,未来可以探索更复杂的特征结构表示方法,例如图结构或层次结构,以处理更复杂的语言现象。

6.2.2 结合机器学习方法

将特征结构与机器学习方法相结合,可以提高语法分析的性能。例如,使用深度学习模型来学习特征结构之间的关系,从而自动发现更有效的语法规则。

6.2.3 多语言处理

目前的研究主要集中在英语等少数语言上,未来可以扩展到更多的语言,研究不同语言之间的语法差异和共性,开发更通用的语法分析方法。

6.3 实践建议

对于想要深入学习自然语言处理语法分析的读者,以下是一些实践建议:
- 多做练习题 :通过练习题可以巩固所学的知识,加深对概念和技术的理解。
- 参与开源项目 :参与开源的自然语言处理项目,与其他开发者交流和合作,学习他们的经验和技巧。
- 关注最新研究成果 :自然语言处理领域发展迅速,关注最新的研究成果可以了解行业的最新动态和趋势。

以下是自然语言处理语法分析的知识体系和未来研究方向的 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([自然语言处理语法分析]):::startend --> B(解析器):::process
    A --> C(特征结构):::process
    B --> B1(递归下降解析器):::process
    B --> B2(移进 - 归约解析器):::process
    C --> C1(处理语法一致性):::process
    C --> C2(处理次范畴化):::process
    C --> C3(处理无界依赖结构):::process
    D(未来研究方向):::process --> D1(更复杂的特征结构表示):::process
    D --> D2(结合机器学习方法):::process
    D --> D3(多语言处理):::process

通过对自然语言处理中语法分析和特征结构的深入学习和研究,可以更好地理解自然语言的结构和规律,为自然语言处理的各种应用(如机器翻译、信息检索、问答系统等)提供更坚实的基础。同时,不断探索和创新,将有助于推动自然语言处理技术的发展,使其在更多领域发挥更大的作用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值