《用Python进行自然语言处理》第 9 章 建立基于特征的文法

本文介绍了如何使用特征结构扩展上下文无关文法框架,以实现更细粒度的文法控制。讨论了特征结构的形式化属性及其计算方法,并展示了如何在基于特征的文法中捕捉语言模式和文法结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 我们怎样用特征扩展上下文无关文法框架,以获得更细粒度的对文法类别和产生式的控制?
2. 特征结构的主要形式化属性是什么,我们如何使用它们来计算?

3. 我们现在用基于特征的文法能捕捉到什么语言模式和文法结构

9.1 文法特征

#描述了如何通过检测文本的特征建立分类器。
#那些特征可能非常简单, 如提取一个单词的最后一个字母,或者更复杂一点儿,如分类器自己预测的词性标签。
#在本 章中,我们将探讨在建立基于规则的文法中特征的作用。

#对比特征提取,记录已经自动检测 到的特征,我们现在要声明词和短语的特征
#以一个很简单的例子开始,使用字典存储 特征和它们的值。
kim = {'CAT': 'NP', 'ORTH': 'Kim', 'REF': 'k'}
chase = {'CAT': 'V', 'ORTH': 'chased', 'REL': 'chase'}
#在基于规则的文法上下文中,这样的特征和特征值对被称为特征结构


#特征结构包含各种有关文法实体的信息。
#使 用'sbj'(主语)和'obj'(宾语)作为占位符,它会被填充
chase['AGT'] = 'sbj'
chase['PAT'] = 'obj'
print(chase)

#在动词直接左侧和右侧的 NP 分别是主语和宾语。我们还在例子结尾为 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']
verb['PAT'] = obj['REF']
for k in ['ORTH', 'REL', 'AGT', 'PAT']:
    print("%-5s => %s" % (k, verb[k]))

句法协议

#在英语中,名词通常被标记为单数或复数
#动词的形态属性与主语名词短语的句法属性一起变化。这种一起变 化被称为协议(agreement)
#动 词与它的主语在人称和数量上保持一致。(我们用 3 作为第三人称的缩写,SG 表示单数,PL 表示复数)

使用属性和约束

#N[NUM=pl] 
#它的意思是类别 N 有一个(文法)特征叫做 NU M(“number 数字”的简写),此特征的值是 pl(“plural 复数”的简写)。
#Det[NUM=sg] -> 'this' Det[NUM=pl] -> 'these' 
# N[NUM=sg] -> 'dog'  N[NUM=pl] -> 'dogs' 
# V[NUM=sg] -> 'runs' V[NUM=pl] -> 'run'


# S -> NP[NUM=?n] VP[NUM=?n]
#使用?n 作为 NUM 值上的变量;它可以在给定的产生式中被实例化为 sg 或 pl。
#我 们可以读取第一条产生式就像再说不管 NP 为特征 NUM 取什么值,VP 必须取同样的值。



#基于特征的文法
import nltk
print(nltk.data.show_cfg('grammars/book_grammars/feat0.fcfg'))
#一个句法类别可以有多个特征,例如:V[TENSE=pres, NUM=pl]
#在一般情况下,我们喜欢多少特征就可以添加多少。
#语句%start S。这个“指令”告诉分析器以 S 作为文法的开始符号。



#NLTK中使用 Earley 图表分析器分析基于特征的文法
#为输入分词之后,我们导入 load_parser 函数,以文法文件名为输入,返回一个图表分析器 cp。
#调用分析器的 nbest_parse()方法将返回一个 分析树的 trees 链表;
#如果文法无法分析输入,trees 将为空,否则会包含一个或多个分析树,取决于输入是否有句法歧义。

#跟踪基于特征的图表分析器.
print("=="*30)
tokens = 'Kim likes children'.split()
from nltk import load_parser
cp = load_parser('grammars/book_grammars/feat0.fcfg', trace=2)
#检查生成的分析树
for tree in cp.parse(tokens):
    print(tree)

术语

#像 sg 和 pl 这样的特征值。这些简单的值通常被称为原子—— 也就是,它们不能被分解成更小的部分。
#原子值的一种特殊情况是布尔值,也就是说,值仅仅指定一个属性是真还是假。
#例如:我们可能要用布尔特征 AUX 区分助动词,如:can、may、will 和 do。
#那么产生式 V[TENSE=pres,aux=+] -> 'can'意味着 can 接受 TENSE 的值为 pres 并且 AUX 的值为+或 true。
#有一个广泛采用的约定用缩写表示布尔特征 f;不用 aux=+或 aux=-,我们分别用+aux 和-aux。
# V[TENSE=pres, +aux] -> 'can'
#V[TENSE=pres, -aux] -> 'likes'


#N[NUM=sg]包含词性信息,可以表示为 POS=N。因此,这个类别的替代符号是[POS=N, NUM=sg]。

#除了原子值特征,特征可能需要本身就是特征结构的值。
#例如:我们可以将协议特征组 合在一起(例如:人称、数量和性别)作为一个类别的不同部分,表示为 AGR 的值。
#这种 情况中,我们说 AGR 是一个复杂值。描述的结构,在格式上称为属性值矩阵(attribute value matrix,AVM)。
# [POS = N         ] 
# [                ] 
# [AGR = [PER = 3 ]]
# [      [NUM=pl ]]
# [      [GND= fem ]]

9.2 处理特征结构


#NLTK中的特征结构使用构造函数 FeatStruct()声明。原子特征值可以是字符串或整 数。
fs1 = nltk.FeatStruct(TENSE='past', NUM='sg')
print(fs1)


#一个特征结构实际上只是一种字典,所以我们可以平常的方式通过索引访问它的值
fs1 = nltk.FeatStruct(PER=3, NUM='pl', GND='fem')
print(fs1)
print(fs1['GND'])

fs1['CASE'] = 'acc'
print(fs1)

#为特征结构定义更复杂的值
fs2 = nltk.FeatStruct(POS='N', AGR=fs1)
print(fs2)
print(fs2['AGR']['PER'])



#指定特征结构的另一种方法是使用包含 feature=value 格式的特征-值对的方括号括起的字符串,其中值本身可能是特征结构:
print('='*30)
print(nltk.FeatStruct("[POS='N', AGR=[PER=3, NUM='pl', GND='fem']]"))

#特征结构本身并不依赖于语言对象;它们是表示知识的通用目的的结构。
#例如:我们可以将一个人的信息用特征结构编码:
print('='*30)
print(nltk.FeatStruct(name='Lee', telno='01 27 86 42 96', age=33))


#将特征结构作为图来查看往往是有用的,更具体的,作为有向无环图
#当两条路径具有相同的值时,它们被称为是 等价的。

#为了在我们的矩阵式表示中表示重入,我们将在共享的特征结构第一次出现的地方加一 个括号括起的数字前缀
print('='*30)
print(nltk.FeatStruct("""[NAME='Lee', ADDRESS=(1)[NUMBER=74, STREET='rue Pascal'], 
                          SPOUSE=[NAME='Kim', ADDRESS->(1)]]"""))
#括号内的整数有时也被称为标记或同指标志(coi ndex)。
#整数的选择并不重要。可以有任意数目的标记在一个单独的特征结构中。
print('='*30)
fs3 = nltk.FeatStruct("[A='a', B=(1)[C='c'], D->(1), E->(1)]")
print(fs3)
print(fs3['B'])
print(fs3['D'])
print(fs3['B']==fs3['D'])

包含和统一

#一个更一般的特征结构包含( subsumes)一个 较少一般的。
#合并两个特征结构的信息被称为统一,由方法 unify()支持。
fs1 = nltk.FeatStruct(NUMBER=74, STREET='rule Pascal')
fs2 = nltk.FeatStruct(CITY='Paris')
print(fs1.unify(fs2))



#如果这两个特征结构共享路径π,但在 FS0 中的π值 与在 FS1 中的π值是不同的原子值。这通过设置统一的结果为 None 来实现。
fs0 = nltk.FeatStruct(A='a')
fs1 = nltk.FeatStruct(A='b')
fs2 = fs0.unify(fs1)
print(fs2)



print('='*30)
#统一如何与结构共享相互作用,事情就变得很有趣
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 的整个路径。
print('='*30)
fs1 = nltk.FeatStruct("[SPOUSE = [ADDRESS = [CITY = Paris]]]")
print(fs1.unify(fs0))


#如果 fs1 与 fs2 的结构共享版本统一,结果是非常不同的
print('='*30)
fs2 = nltk.FeatStruct("""[NAME='Lee', 
                          ADDRESS=(1)[NUMBER=74, 
                                      STREET='rue Pascal'], 
                          SPOUSE=[NAME='Kim', 
                                  ADDRESS->(1)]]""")
print(fs1.unify(fs2))




#正如我们已经看到的,结构共享也可以使用变量表示,如?x。
print('='*30)
fs1 = nltk.FeatStruct("[ADDRESS1=[NUMBER=74, STREET='rue Pascal']]")
fs2 = nltk.FeatStruct("[ADDRESS1=?x, ADDRESS2=?x]")
print(fs2)
print(fs2.unify(fs1))

9.3 扩展基于特征的文法


#回到基于特征的文法,探索各种语言问题,并展示将特征纳入文法的好处。

子类别

#一个简单的方法,最初为文法框架开发的称为广义短语结构文法(Generalized Phrase Structure Grammar,GPSG),
#通过允许词汇类别支持子类别特征尝试解决这个问题,它告诉 我们该项目所属的子类别。

核心词回顾

#V 类的表达式是 VP 类的短语的核心。
#同样, N 是 NP 的核心词,A(即形容词)是 AP 的核心词,P(即介词)是 PP 的核心词。
#并非所 有的短语都有核心词

#然而,我们希望我们的文法形式能表达它所持有的父母/核心子女关系。
#现在,V 和 VP 只是原子符号,我们需要找到一种方法用特征将它们关联起来(就像我们以前关联 IV 和 T V 那样)。

# X-bar 句法通过抽象出短语级别的概念,解决了这个问题。
# 它通常认为有三个这样的级 别。如果 N 表示的词汇级别,
# 那么 N'表示更高一层级别,对应较传统的级别 Nom,N''表 示短语级别,对应类别 NP。

#核心词是 N,而 N'和 N''被称为 N 的(短语的)投影。N''是最大的投影, N 有时也被称为零投影。
#X-bar 文法一个中心思想是所有成分都有结构的类似性

助动词与 倒装

#倒装从句——其中的主语和动词顺序互换——出现在英语疑问句,也出现在“否定” 副词 之后:
#可以放置在倒装的从句开头的动词术语叫做助动词的类别,
#如:do、can 和 have,也包 括 be、will 和 shall。捕捉这种结构的方法之一是使用以下产生式:
#S[+INV] -> V[+AUX] NP VP
#标记有[+inv]的从句包含一个助动词,其后跟着一个 VP 。

无限制依赖成分

#有些上下文中强制性的补语可 以省略:Kim knows who you like.
#也就是说,一个强制性补语可以被省略,如果句子中有是适当的填充
#句子包含一个缺口,在那里强制性补语被省略了


#具有倒装从句和长距离依赖的产生式的文法,使用斜线类别。
print( nltk.data.show_cfg('grammars/book_grammars/feat1.fcfg'))



print('='*30)
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)
    
    
print('='*30)    
#可以分析没有缺口的句子:
tokens = 'you claim that you like cats'.split()
for tree in cp.parse(tokens):
    print(tree)

德语中的格和性别

#英语相比,德语的协议具有相对丰富的形态。

#文法演示带格的协议(包括人称、数量和性别)的相互作用。 例9-4. 基于特征的文法的例子。
#基于特征的文法的例子。
print(nltk.data.show_cfg('grammars/book_grammars/german.fcfg'))
#特征 objcase 被用来指定动词支配它的对象的格。


#包含支配与格的动词的句子的分析树:
print('='*30)
tokens = 'ich folge den Katzen'.split()
cp = load_parser('grammars/book_grammars/german.fcfg')
for tree in cp.parse(tokens):
    print(tree)

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

9.4 小结

上下文无关文法的传统分类是原子符号。特征结构的一个重要的作用是捕捉精细的区 分,否则将需要数量翻倍的原子类别。

通过使用特征值上的变量,我们可以表达文法产生式中的限制,允许不同的特征规格的 实现可以相互依赖。

通常情况下,我们在词汇层面指定固定的特征值,限制短语中的特征值与它们的孩子中 的对应值统一。

特征值可以是原子的或复杂的。原子值的一个特定类别是布尔值,按照惯例用[+/- fe at]表示。

两个特征可以共享一个值(原子的或复杂的)。具有共享值的结构被称为重入。共享的 值被表示为 AVM中的数字索引(或标记)。

一个特征结构中的路径是一个特征的元组,对应从图的根开始的弧的序列上的标签。

两条路径是等价的,如果它们共享一个值。

包含的特征结构是偏序的。FS0 包含 FS1,当 FS0 比 FS1 更一般(较少信息)。

两种结构 FS0 和 FS1 的统一,如果成功,就是包含 FS0 和 FS1 的合并信息的特征结构FS2 。

如果统一在 FS 中指定一条路径π,那么它也指定等效与π的每个路径π'。

我们可以使用特征结构建立对大量广泛语言学现象的简洁的分析,包括动词子类别,倒装结构,无限制依赖结构和格支配。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值