python自然语言处理 | 构建基于特征的语法

本文探讨了如何通过特征扩展文法框架,对文法类别和产生式进行细化控制,如子类别、核心词、助动词倒装及无限制依赖。通过实例解析和NLTK库,展示了如何使用特征结构和统一操作来处理英语句法协议,如动词协议和德语格和性别。
摘要由CSDN通过智能技术生成

自然语言具有广泛的文法结构,用第8章中所描述的简单方法很难处理如此广泛的文法结构。为了获得更大的灵活性,可改变对待文法类别如S、NP和V的方式。我们将这些原子标签分解为类似字典的结构,以便可以提取一系列的值作为特征。

  • 本章回答下列问题:
  1. 怎样用特征扩展无关上下文文法的框架,以获得对文法类别和产生式的更细粒度的控制?
  2. 特征结构的主要形式化属于是什么,如何使用它们来计算?
  3. 我们现在用基于特征的文法能获得哪些语言模式和文法结构?

在此过程中,我们将介绍更多的英语句法主题,包括:约定、子类别和无限制依赖成分等现象

1 文法特征

声明词和短语的特征–> 使用字典存储特征和值

"""
# 文法实体的信息
# CAT(文法类别) ORTH(拼写) REF(指示物) REL(关系)
"""
kim = {'CAT': 'NP', 'ORTH': 'Kim', 'REF': 'k'}
chase = {'CAT': 'V', 'ORTH': 'chased', 'REL': 'chase'}

对象kim和 chase有几个共同的特征,CAT(文法类别)和ORTH(正字法,即拼写)此外,每一个还有更面向语义的特征:kim[‘REF’]意在给出(kim的指示物,而chase['REL]给出 chase表示的关系。在基于规则的文法上下文中,这样的特征和特征值对被称为特征结构,我们将很快看到它们的替代符号。

# AGT(agent施事的角色) PAT(patient受事角色)
chase['AGT'] = 'sbj'
chase['PAT'] = 'obj'

对于chase,主语扮演“agent(施事)”的角色,而宾语扮演“patient(受事)”角色。

如果我们现在处理句子: Kim chased Lee,我们要**“绑定”动词的施事角色和主语,受事角色和宾语**。

我们可以通过链接到相关的NP的REF特征做到这个。

"""
在下面的例子中,我们做一个简单的假设:在动词直接左侧和右侧的NP分别是主语和宾语。我们还在例子结尾为Lee添加了一个特征结构。

# CAT(文法类别) ORTH(拼写) REF(指示物) REL(关系)
# AGT(agent施事的角色) PAT(patient受事角色)
"""
sent = "Kim chased Lee"
tokens = sent.split()

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


def lex2fs(word):
    for fs in [kim, lee, chase]:
        if fs['ORTH'] ==word:
            return fs  # 返回fs,fs指代kim, lee, chase,指向 subj, verb, obj
        
subj, verb, obj = lex2fs(tokens[0]), lex2fs(tokens[1]), lex2fs(tokens[2])

verb['AGT'] = subj['REF'] # agent of 'chase' is Kim
verb['PAT'] = obj['REF']  # patient of 'chase' is Lee

for k in ['ORTH', 'REL', 'AGT', 'PAT']: # check featstruct of 'chase'
    print("%-5s => %s" % (k, verb[k]))
"""
CAT:文法类别
ORTH:拼写
REL:关系
SRC:source源事,主语
EXP:experiencer 体验者,宾语
"""
surprise = {'CAT': 'V', 'ORTH': 'surprised', 'REL': 'surprise', 'SRC': 'sbj', 'EXP': 'obj'}

在这里插入图片描述

1.1 句法协议

协议(agreement):动词的形态属性同主语名词短语的句法属性一起变化,该过程被称为协议(agreement)
在这里插入图片描述

1.2 使用属性和约束

非正式的语言类别具有属性,例如:名词具有复数的属性。

# 语句%start S。这个“指令”告诉分析器以S作为文法的开始符号。

import nltk
nltk.data.show_cfg('grammars/book_grammars/feat0.fcfg')

NLTK中使用Earley图表分析器分析基于特征的文法(关于这个的更多信息见9.5节),例9-2演示这个是如何实施的。为输入分词之后,我们导入load_parser函数①,以文法文件名为输入,返回一个图表分析器cp②。调用分析器的parse()方法将返回一个分析树的trees链表;如果文法无法分析输入,trees将为空,否则会包含一个或多个分析树,取决于输入是否有句法歧义

# 跟踪基于特征的图表分析器
tokens = 'Kim likes children'.split()

from nltk import load_parser  # ① 导入load_parser函数
cp = load_parser('grammars/book_grammars/feat0.fcfg', trace=2) # 以文法文件名为输入,返回一个图表分析器cp
trees = cp.parse(tokens)  # 调用分析器的parse()方法将返回一个分析树的trees链表
# 可以检查生成的分析树(在这种情况下,只有一个)
for tree in trees:
    print(tree)

1.3 术语

到目前为止,我们只看到像sg和pl这样的特征值。这些简单的值通常被称为原子也就是,它们不能被分解成更小的部分。

原子值的一种特殊情况是布尔值,也就是说,值仅仅指定一个属性是真还是假。例如:我们可能要用布尔特征AUX区分助动词,如: can、may、will和do。那么产生式V[TENSE=pres,aux=+] -> 'can’意味着can接受TENSE的值为pres并且AUX的值为+或true。

有一个广泛采用的约定用缩写表示布尔特征f;不用aux=+或aux=-,我们分别用+aux和-aux。这些都是缩写,然而,分析器就像+和-是其他原子值一样解释它们。

在这里插入图片描述

2 处理特征结构

在本节中,我们将展示如何构建特征结构,并在NLTK中操作。我们还将讨论统一的基本操作,这使我们能够结合两个不同的特征结构中的信息。

  • NLTK中的特征结构使用构造函数FeatStruct()声明。原子特征值可以是字符串或整数
  • 为特征结构定义更复杂的值
  • 指定特征结构的另一种方法是使用包含feature=value,格式的特征-值对的方括号括起的字符串,其中值本身可能是特征结构
fs1 = nltk.FeatStruct(TENSE='past', NUM='sg')
print(fs1)
  • 一个特征结构相当于一个字典
  • 为特征结构定义更复杂的值
  • 指定特征结构的另一种方法是使用包含feature=value,格式的特征-值对的方括号括起的字符串,其中值本身可能是特征结构

可以将特征结构看作是有向无环图
在这里插入图片描述

print(fs2['AGR'])
print(nltk.FeatStruct("[POS='N', AGR=[PER=3, NUM='pl', GND='fem']]"))
print(nltk.FeatStruct(name='Lee', telno='01 27 86 42 96', age=33))

在这里插入图片描述
括号内的整数有时也被称为标记或同指标志(coindex)。整数的选择并不重要。可以有任意数目的标记在一个单独的特征结构中。

print(nltk.FeatStruct("""[NAME='Lee', ADDRESS=(1)[NUMBER=74, STREET='rue Pascal'],
SPOUSE=[NAME='Kim', ADDRESS->(1)]]"""))
print(nltk.FeatStruct("[A='a', B=(1)[C='c'], D->(1), E->(1)]"))

2.1 包含和统一

  • 包含:认为特征结构提供一些对象的部分信息是很正常的,在这个意义上,我们可以根据它们通用的程度给特征结构排序。例如:(25a)比(25b)更一般(更少特征),(25b)比(25c)更一般。

这个顺序被称为包含(subsumptlon);一个更一般的特征结构包含( subsumes)一个较少一般的。如果FSO包含FS1(正式的,我们写成FSO FS1),那么FSI必须具备FSo所有路径和等价路径,也可能有额外的路径和等价路径。因此,(23)包括(24)因为后者有额外的等价路径。包含只提供了特征结构上的偏序,这应该是显而易见的,因为一些特征结构是不可比较的。例如:(26)既不包含(25a)也不被(25a)包含。

  • 统一:合并两个特征结构的信息被称为统一,由方法 unify()支持。
# 统一被正式定义为一个(部分)二元操作:FS<sub>0</sub> ⊔ FS<sub>1</sub>。
# 统一是对称的,所以 FS<sub>0</sub> ⊔ FS<sub>1</sub> = FS<sub>1</sub> ⊔ FS<sub>0</sub>。在 Python 中也是如此
fs1 = nltk.FeatStruct(NUMBER=74, STREET='rue Pascal')
fs2 = nltk.FeatStruct(CITY='Paris')
print(fs1.unify(fs2))
print(fs2.unify(fs1))
# 如果我们统一两个具有包含关系的特征结构,那么统一的结果是两个中更具体的那个
fs0 = nltk.FeatStruct(A='a')
fs1 = nltk.FeatStruct(A='b')
fs2 = fs0.unify(fs1)
print(fs2) # None
# 现在,如果我们看一下统一如何与结构共享相互作用,事情就变得很有趣。首先,让我们在 Python 中定义(21):
fs0 = nltk.FeatStruct("""[NAME=Lee,
ADDRESS=[NUMBER=74,STREET='rue Pascal'],
SPOUSE=[NAME=Kim, ADDRESS=[number=74,STREET='rue Pascal']]]""")
print(fs0)
# 我们为 Kim 的地址指定一个CITY作为参数会发生什么?请注意,fs1需要包括从特征结构的根到CITY的整个路径。
fs1 = nltk.FeatStruct("[SPOUSE=[ADDRESS=[CITY=Paris]]]")
print(fs1.unify(fs0))
# 通过对比,如果fs1与fs2的结构共享版本统一,结果是非常不同的(如图(22)所示):
fs2 = nltk.FeatStruct("""[NAME=Lee, ADDRESS=(1)[NUMBER=74, STREET='rue Pascal'],
SPOUSE=[NAME=Kim, ADDRESS->(1)]]""")
print(fs1.unify(fs2))

"""
不是仅仅更新 Kim 的 Lee 的地址的“副本”,我们现在同时更新他们两个的地址。
更一般的,如果统一包含指定一些路径π的值,那么统一同时更新等价于π的任何路径的值。
"""
# 结构共享也可以使用变量表示,如?x
fs1 = nltk.FeatStruct("[ADDRESS1=[NUMBER=74, STREET='rue Pascal']]")
fs2 = nltk.FeatStruct("[ADDRESS1=?x, ADDRESS2=?x]")
print(fs2)
print(fs2.unify(fs1))

3 扩展基于特征的语法

在本节中,我们回到基于特征的文法,探索各种语言问题,并展示将特征纳入文法的好处。

3.1 子类别

我们能替换如TV和Ⅳ类别标签为带着告诉我们这个动词是否与后面的NP对象结合或者它是否能不带补语等特征的V?
一个简单的方法,最初为文法框架开发的称为广义短语结构文法(Generalized PhraseStructure Grammar,GPSG),通过允许词汇类别支持子类别特征尝试解决这个问题,它告诉我们该项目所属的子类别。相比GPSG使用的整数值表示SUBCAT,下面的例子采用更容易记忆的值,即intrans、trans和 clause:

在这里插入图片描述
在这里插入图片描述

3.2 核心词回顾

从主类别标签分解出子类别信息,我们可以概括更多有关动词属性的内容。
在这里插入图片描述
在这里插入图片描述

3.3 助动词与倒装

  • 倒装从句:倒装从句——其中的主语和动词顺序互换——出现在英语疑问句,也出现在“否定”副词之后:
  • 助动词:
    在这里插入图片描述

3.4 无限制依赖成分

因为可以无限地加深句子补语的递归,所以在整个句子中缺口可以无限远地被填充。这一属性导致无限依赖成分的概念,也就是填充词-缺口依赖,即填充词和缺口之间的距离没有上界。
在这里插入图片描述
已经提出了各种各样的机制处理形式化文法中的无限依赖;在这里我们说明广义短语结构文法中使用的方法,其中包含斜线类别。

一个斜线类别的形式是Y/XP;我们解释为:类别Y的短语缺少一个类别XP的子成分。例如:S/NP是缺少一个NP的S。斜线类别的使用说明如(53)所示。
在这里插入图片描述

# 例9-3 具有倒装从句和长距离依赖的产生式的文法,使用斜线类别
nltk.data.show_cfg('grammars/book_grammars/feat1.fcfg')  # S[-INV ]->NP S/NP
# 使用例9-3中的文法,我们可以分析序列: who do you claim that you like:
tokens = 'who do you claim that you like'.split()
from nltk import load_parser
cp = load_parser('grammars/book_grammars/feat1.fcfg')
for tree in cp.parse(tokens):
    print(tree)
# 例9-3中的文法也可以分析没有缺口的句子:
tokens = 'you claim that you like cats'.split()
for tree in cp.parse(tokens):
    print(tree)
# 此外,它还允许没有wh结构的倒装句:
tokens = 'rarely do you sing'.split()
for tree in cp.parse(tokens):
    print(tree)

3.5 德语中的格和性别

在这里插入图片描述

# 例9-4中的文法演示带格的协议(包括人称、数量和性别)的相互作用。
# 例9-4 基于特征的文法的例子
nltk.data.show_cfg('grammars/book_grammars/german.fcfg')
"""
正如你可以看到的,特征objcase,被用来指定动词支配它的对象的格。
下一个例子演示了包含支配与格的动词的句子的分析树:
"""
tokens = 'ich folge den Katzen'.split()
cp = nltk.load_parser('grammars/book_grammars/german.fcfg')
for tree in cp.parse(tokens):
    print(tree)
"""
为了能知道在哪里和为什么序列分析失败,设置 load_parser()方法的trace参数可能是至关重要的。
思考下面的分析故障:

"""
tokens = 'ich folge den Katze'.split()
cp = nltk.load_parser('grammars/book_grammars/german.fcfg', trace=2)
for tree in cp.parse(tokens):
    print(tree)

在这里插入图片描述

4 小结

在这里插入图片描述

5 练习

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值