# 9\. 構建基于特征的語法
自然語言具有范圍廣泛的語法結構,用[8.](./ch08.html#chap-parse)中所描述的簡單的方法很難處理的如此廣泛的語法結構。為了獲得更大的靈活性,我們改變我們對待語法類別如`S`、`NP`和`V`的方式。我們將這些原子標簽分解為類似字典的結構,其特征可以為一個范圍的值。
本章的目的是要回答下列問題:
1. 我們怎樣用特征擴展上下文無關語法框架,以獲得更細粒度的對語法類別和產生式的控制?
2. 特征結構的主要形式化屬性是什么,我們如何使用它們來計算?
3. 我們現在用基于特征的語法能捕捉到什么語言模式和語法結構?
一路上,我們將介紹更多的英語句法主題,包括約定、子類別和無限制依賴成分等現象。
## 1 語法特征
在[chap-data-intensive](./ch06.html#chap-data-intensive)中,我們描述了如何建立基于檢測文本特征的分類器。那些特征可能非常簡單,如提取一個單詞的最后一個字母,或者更復雜一點兒,如分類器自己預測的詞性標簽。在本章中,我們將探討特征在建立基于規則的語法中的作用。對比特征提取,記錄已經自動檢測到的特征,我們現在要 _ 聲明 _ 詞和短語的特征。我們以一個很簡單的例子開始,使用字典存儲特征和它們的值。
```py
>>> kim = {'CAT': 'NP', 'ORTH': 'Kim', 'REF': 'k'}
>>> chase = {'CAT': 'V', 'ORTH': 'chased', 'REL': 'chase'}
```
對象`kim`和`chase`有幾個共同的特征,`CAT`(語法類別)和`ORTH`(正字法,即拼寫)。此外,每一個還有更面向語義的特征:`kim['REF']`意在給出`kim`的指示物,而`chase['REL']`給出`chase`表示的關系。在基于規則的語法上下文中,這樣的特征和特征值對被稱為特征結構,我們將很快看到它們的替代符號。
特征結構包含各種有關語法實體的信息。這些信息不需要詳盡無遺,我們可能要進一步增加屬性。例如,對于一個動詞,根據動詞的參數知道它扮演的“語義角色”往往很有用。對于 chase,主語扮演“施事”的角色,而賓語扮演“受事”角色。讓我們添加這些信息,使用`'sbj'`和`'obj'`作為占位符,它會被填充,當動詞和它的語法參數結合時:
```py
>>> chase['AGT'] = 'sbj'
>>> chase['PAT'] = 'obj'
```
如果我們現在處理句子<cite>Kim chased Lee</cite>,我們要“綁定”動詞的施事角色和主語,受事角色和賓語。我們可以通過鏈接到相關的`NP`的`REF`特征做到這個。在下面的例子中,我們做一個簡單的假設:在動詞直接左側和右側的`NP`分別是主語和賓語。我們還在例子結尾為 Lee 添加了一個特征結構。
```py
>>> 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]))
ORTH => chased
REL => chase
AGT => k
PAT => l
```
同樣的方法可以適用不同的動詞,例如 surprise,雖然在這種情況下,主語將扮演“源事”(`SRC`)的角色,賓語扮演“體驗者”(`EXP`)的角色:
```py
>>> surprise = {'CAT': 'V', 'ORTH': 'surprised', 'REL': 'surprise',
... 'SRC': 'sbj', 'EXP': 'obj'}
```
特征結構是非常強大的,但我們操縱它們的方式是極其 _ad hoc_。我們本章接下來的任務是,顯示上下文無關語法和分析如何能擴展到合適的特征結構,使我們可以一種更通用的和有原則的方式建立像這樣的分析。我們將通過查看句法協議的現象作為開始;我們將展示如何使用特征典雅的表示協議約束,并在一個簡單的語法中說明它們的用法。
由于特征結構是表示任何形式的信息的通用的數據結構,我們將從更形式化的視點簡要地看著它們,并演示 NLTK 提供的特征結構的支持。在本章的最后一部分,我們將表明,特征的額外表現力開辟了一個用于描述語言結構的復雜性的廣泛的可能性。
## 1.1 句法協議
下面的例子展示詞序列對,其中第一個是符合語法的而第二個不是。(我們在詞序列的開頭用星號表示它是不符合語法的。)
```py
S -> NP VP
NP -> Det N
VP -> V
Det -> 'this'
N -> 'dog'
V -> 'runs'
```
## 1.2 使用屬性和約束
我們說過非正式的語言類別具有 _ 屬性 _;例如,名詞具有復數的屬性。讓我們把這個弄的更明確:
```py
N[NUM=pl]
```
注意一個句法類別可以有多個特征,例如`V[TENSE=pres, NUM=pl]`。在一般情況下,我們喜歡多少特征就可以添加多少。
關于[1.1](./ch09.html#code-feat0cfg)的最后的細節是語句`%start S`。這個“指令”告訴分析器以`S`作為文法的開始符號。
一般情況下,即使我們正在嘗試開發很小的語法,把產生式放在一個文件中我們可以編輯、測試和修改是很方便的。我們將[1.1](./ch09.html#code-feat0cfg)以 NLTK 的數據格式保存為文件`'feat0.fcfg'`。你可以使用`nltk.data.load()`制作你自己的副本進行進一步的實驗。
[1.2](./ch09.html#code-featurecharttrace) 說明了基于特征的語法圖表解析的操作。為輸入分詞之后,我們導入`load_parser`函數[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch09.html#load_parser1),以語法文件名為輸入,返回一個圖表分析器`cp` [![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch09.html#load_parser2)。調用分析器的`parse()`方法將迭代生成的分析樹;如果文法無法分析輸入,`trees`將為空,并將會包含一個或多個分析樹,取決于輸入是否有句法歧義。
```py
>>> tokens = 'Kim likes children'.split()
>>> from nltk import load_parser ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
>>> cp = load_parser('grammars/book_grammars/feat0.fcfg', trace=2) ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
>>> for tree in cp.parse(tokens):
... print(tree)
...
|.Kim .like.chil.|
Leaf Init Rule:
|[----] . .| [0:1] 'Kim'
|. [----] .| [1:2] 'likes'
|. . [----]| [2:3] 'children'
Feature Bottom Up Predict Combine Rule:
|[----] . .| [0:1] PropN[NUM='sg'] -> 'Kim' *
Feature Bottom Up Predict Combine Rule:
|[----] . .| [0:1] NP[NUM='sg'] -> PropN[NUM='sg'] *
Feature Bottom Up Predict Combine Rule:
|[----> . .| [0:1] S[] -> NP[NUM=?n] * VP[NUM=?n] {?n: 'sg'}
Feature Bottom Up Predict Combine Rule:
|. [----] .| [1:2] TV[NUM='sg', TENSE='pres'] -> 'likes' *
Feature Bottom Up Predict Combine Rule:
|. [----> .| [1:2] VP[NUM=?n, TENSE=?t] -> TV[NUM=?n, TENSE=?t] * NP[] {?n: 'sg', ?t: 'pres'}
Feature Bottom Up Predict Combine Rule:
|. . [----]| [2:3] N[NUM='pl'] -> 'children' *
Feature Bottom Up Predict Combine Rule:
|. . [----]| [2:3] NP[NUM='pl'] -> N[NUM='pl'] *
Feature Bottom Up Predict Combine Rule:
|. . [---->| [2:3] S[] -> NP[NUM=?n] * VP[NUM=?n] {?n: 'pl'}
Feature Single Edge Fundamental Rule:
|. [---------]| [1:3] VP[NUM='sg', TENSE='pres'] -> TV[NUM='sg', TENSE='pres'] NP[] *
Feature Single Edge Fundamental Rule:
|[==============]| [0:3] S[] -> NP[NUM='sg'] VP[NUM='sg'] *
(S[]
(NP[NUM='sg'] (PropN[NUM='sg'] Kim))
(VP[NUM='sg', TENSE='pres']
(TV[NUM='sg', TENSE='pres'] likes)
(NP[NUM='pl'] (N[NUM='pl'] children))))
```
分析過程中的細節對于當前的目標并不重要。然而,有一個實施上的問題與我們前面的討論語法的大小有關。分析包含特征限制的產生式的一種可行的方法是編譯出問題中特征的所有可接受的值,是我們最終得到一個大的完全指定的[(6)](./ch09.html#ex-agcfg1)中那樣的 CFG。相比之下,前面例子中顯示的分析器過程直接與給定語法的未指定的產生式一起運作。特征值從詞匯條目“向上流動”,變量值于是通過如`{?n: 'sg', ?t: 'pres'}`這樣的綁定(即字典)與那些值關聯起來。當分析器裝配有關它正在建立的樹的節點的信息時,這些變量綁定被用來實例化這些節點中的值;從而通過查找綁定中`?n`和`?t`的值,未指定的`VP[NUM=?n, TENSE=?t] -> TV[NUM=?n, TENSE=?t] NP[]`實例化為`VP[NUM='sg', TENSE='pres'] -> TV[NUM='sg', TENSE='pres'] NP[]`。
最后,我們可以檢查生成的分析樹(在這種情況下,只有一個)。
```py
>>> for tree in trees: print(tree)
(S[]
(NP[NUM='sg'] (PropN[NUM='sg'] Kim))
(VP[NUM='sg', TENSE='pres']
(TV[NUM='sg', TENSE='pres'] likes)
(NP[NUM='pl'] (N[NUM='pl'] children))))
```
## 1.3 術語
到目前為止,我們只看到像`sg`和`pl`這樣的特征值。這些簡單的值通常被稱為原子——也就是,它們不能被分解成更小的部分。原子值的一種特殊情況是布爾值,也就是說,值僅僅指定一個屬性是真還是假。例如,我們可能要用布爾特征`AUX`區分助動詞,如 can,may,will 和 do。例如,產生式`V[TENSE=pres, AUX=+] -> 'can'`意味著 can 接受`TENSE`的值為`pres`,并且`AUX`的值為`+`或`true`。有一個廣泛采用的約定用縮寫表示布爾特征`f`;不用`AUX=+`或`AUX=-`,我們分別用`+AUX`和`-AUX`。這些都是縮寫,然而,分析器就像`+`和`-`是其他原子值一樣解釋它們。[(15)](./ch09.html#ex-lex)顯示了一些有代表性的產生式:
```py
V[TENSE=pres, +AUX] -> 'can'
V[TENSE=pres, +AUX] -> 'may'
V[TENSE=pres, -AUX] -> 'walks'
V[TENSE=pres, -AUX] -> 'likes'
```
在傳遞中,我們應該指出有顯示 AVM 的替代方法;[1.3](./ch09.html#fig-avm1)顯示了一個例子。雖然特征結構呈現的[(16)](./ch09.html#ex-agr0)中的風格不太悅目,我們將堅持用這種格式,因為它對應我們將會從 NLTK 得到的輸出。
關于表示,我們也注意到特征結構,像字典,對特征的 _ 順序 _ 沒有指定特別的意義。所以[(16)](./ch09.html#ex-agr0)等同于︰
```py
[AGR = [NUM = pl ]]
[ [PER = 3 ]]
[ [GND = fem ]]
[ ]
[POS = N ]
```
## 2 處理特征結構
在本節中,我們將展示如何在 NLTK 中構建和操作特征結構。我們還將討論統一的基本操作,這使我們能夠結合兩個不同的特征結構中的信息。
NLTK 中的特征結構使用構造函數`FeatStruct()`聲明。原子特征值可以是字符串或整數。
```py
>>> fs1 = nltk.FeatStruct(TENSE='past', NUM='sg')
>>> print(fs1)
[ NUM = 'sg' ]
[ TENSE = 'past' ]
```
一個特征結構實際上只是一種字典,所以我們可以平常的方式通過索引訪問它的值。我們可以用我們熟悉的方式 _ 賦 _ 值給特征:
```py
>>> fs1 = nltk.FeatStruct(PER=3, NUM='pl', GND='fem')
>>> print(fs1['GND'])
fem
>>> fs1['CASE'] = 'acc'
```
我們還可以為特征結構定義更復雜的值,如前面所討論的。
```py
>>> fs2 = nltk.FeatStruct(POS='N', AGR=fs1)
>>> print(fs2)
[ [ CASE = 'acc' ] ]
[ AGR = [ GND = 'fem' ] ]
[ [ NUM = 'pl' ] ]
[ [ PER = 3 ] ]
[ ]
[ POS = 'N' ]
>>> print(fs2['AGR'])
[ CASE = 'acc' ]
[ GND = 'fem' ]
[ NUM = 'pl' ]
[ PER = 3 ]
>>> print(fs2['AGR']['PER'])
3
```
指定特征結構的另一種方法是使用包含`feature=value`格式的特征-值對的方括號括起的字符串,其中值本身可能是特征結構:
```py
>>> print(nltk.FeatStruct("[POS='N', AGR=[PER=3, NUM='pl', GND='fem']]"))
[ [ GND = 'fem' ] ]
[ AGR = [ NUM = 'pl' ] ]
[ [ PER = 3 ] ]
[ ]
[ POS = 'N' ]
```
特征結構本身并不依賴于語言對象;它們是表示知識的通用目的的結構。例如,我們可以將一個人的信息用特征結構編碼:
```py
>>> print(nltk.FeatStruct(NAME='Lee', TELNO='01 27 86 42 96', AGE=33))
[ AGE = 33 ]
[ NAME = 'Lee' ]
[ TELNO = '01 27 86 42 96' ]
```
在接下來的幾頁中,我們會使用這樣的例子來探討特征結構的標準操作。這將使我們暫時從自然語言處理轉移,因為在我們回來談論語法之前需要打下基礎。堅持!
將特征結構作為圖來查看往往是有用的;更具體的,作為有向無環圖(DAG)。[(19)](./ch09.html#ex-dag01)等同于上面的 AVM。
```py
>>> print(nltk.FeatStruct("""[NAME='Lee', ADDRESS=(1)[NUMBER=74, STREET='rue Pascal'],
... SPOUSE=[NAME='Kim', ADDRESS->(1)]]"""))
[ ADDRESS = (1) [ NUMBER = 74 ] ]
[ [ STREET = 'rue Pascal' ] ]
[ ]
[ NAME = 'Lee' ]
[ ]
[ SPOUSE = [ ADDRESS -> (1) ] ]
[ [ NAME = 'Kim' ] ]
```
括號內的整數有時也被稱為標記或同指標志。整數的選擇并不重要。可以有任意數目的標記在一個單獨的特征結構中。
```py
>>> print(nltk.FeatStruct("[A='a', B=(1)[C='c'], D->(1), E->(1)]"))
[ A = 'a' ]
[ ]
[ B = (1) [ C = 'c' ] ]
[ ]
[ D -> (1) ]
[ E -> (1) ]
```
## 2.1 包含和統一
認為特征結構提供一些對象的部分信息是很正常的,在這個意義上,我們可以根據它們通用的程度給特征結構排序。例如,[(23a)](./ch09.html#ex-fs01)比[(23b)](./ch09.html#ex-fs02)具有更少特征,(23b)比[(23c)](./ch09.html#ex-fs03)具有更少特征。
```py
[NUMBER = 74]
```
統一被正式定義為一個(部分)二元操作: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 中也是如此:
```py
>>> print(fs2.unify(fs1))
[ CITY = 'Paris' ]
[ NUMBER = 74 ]
[ STREET = 'rue Pascal' ]
```
如果我們統一兩個具有包含關系的特征結構,那么統一的結果是兩個中更具體的那個:
```py
>>> fs0 = nltk.FeatStruct(A='a')
>>> fs1 = nltk.FeatStruct(A='b')
>>> fs2 = fs0.unify(fs1)
>>> print(fs2)
None
```
現在,如果我們看一下統一如何與結構共享相互作用,事情就變得很有趣。首先,讓我們在 Python 中定義[(21)](./ch09.html#ex-dag04):
```py
>>> fs0 = nltk.FeatStruct("""[NAME=Lee,
... ADDRESS=[NUMBER=74,
... STREET='rue Pascal'],
... SPOUSE= [NAME=Kim,
... ADDRESS=[NUMBER=74,
... STREET='rue Pascal']]]""")
>>> print(fs0)
[ ADDRESS = [ NUMBER = 74 ] ]
[ [ STREET = 'rue Pascal' ] ]
[ ]
[ NAME = 'Lee' ]
[ ]
[ [ ADDRESS = [ NUMBER = 74 ] ] ]
[ SPOUSE = [ [ STREET = 'rue Pascal' ] ] ]
[ [ ] ]
[ [ NAME = 'Kim' ] ]
```
我們為 Kim 的地址指定一個`CITY`作為參數會發生什么?請注意,`fs1`需要包括從特征結構的根到`CITY`的整個路徑。
```py
>>> fs1 = nltk.FeatStruct("[SPOUSE = [ADDRESS = [CITY = Paris]]]")
>>> print(fs1.unify(fs0))
[ ADDRESS = [ NUMBER = 74 ] ]
[ [ STREET = 'rue Pascal' ] ]
[ ]
[ NAME = 'Lee' ]
[ ]
[ [ [ CITY = 'Paris' ] ] ]
[ [ ADDRESS = [ NUMBER = 74 ] ] ]
[ SPOUSE = [ [ STREET = 'rue Pascal' ] ] ]
[ [ ] ]
[ [ NAME = 'Kim' ] ]
```
通過對比,如果`fs1`與`fs2`的結構共享版本統一,結果是非常不同的(如圖[(22)](./ch09.html#ex-dag03)所示):
```py
>>> fs2 = nltk.FeatStruct("""[NAME=Lee, ADDRESS=(1)[NUMBER=74, STREET='rue Pascal'],
... SPOUSE=[NAME=Kim, ADDRESS->(1)]]""")
>>> print(fs1.unify(fs2))
[ [ CITY = 'Paris' ] ]
[ ADDRESS = (1) [ NUMBER = 74 ] ]
[ [ STREET = 'rue Pascal' ] ]
[ ]
[ NAME = 'Lee' ]
[ ]
[ SPOUSE = [ ADDRESS -> (1) ] ]
[ [ NAME = 'Kim' ] ]
```
不是僅僅更新 Kim 的 Lee 的地址的“副本”,我們現在同時更新他們兩個的地址。更一般的,如果統一包含指定一些路徑π的值,那么統一同時更新等價于π的任何路徑的值。
正如我們已經看到的,結構共享也可以使用變量表示,如`?x`。
```py
>>> fs1 = nltk.FeatStruct("[ADDRESS1=[NUMBER=74, STREET='rue Pascal']]")
>>> fs2 = nltk.FeatStruct("[ADDRESS1=?x, ADDRESS2=?x]")
>>> print(fs2)
[ ADDRESS1 = ?x ]
[ ADDRESS2 = ?x ]
>>> print(fs2.unify(fs1))
[ ADDRESS1 = (1) [ NUMBER = 74 ] ]
[ [ STREET = 'rue Pascal' ] ]
[ ]
[ ADDRESS2 -> (1) ]
```
## 3 擴展基于特征的語法
在本節中,我們回到基于特征的語法,探索各種語言問題,并展示將特征納入語法的好處。
## 3.1 子類別
第[8.](./ch08.html#chap-parse)中,我們增強了類別標簽表示不同類別的動詞,分別用標簽`IV`和`TV`表示不及物動詞和及物動詞。這使我們能編寫如下的產生式:
```py
VP -> IV
VP -> TV NP
```
## 3.2 核心詞回顧
我們注意到,在上一節中,通過從主類別標簽分解出子類別信息,我們可以表達有關動詞屬性的更多概括。類似的另一個屬性如下:`V`類的表達式是`VP`類的短語的核心。同樣,`N`是`NP`的核心詞,`A`(即形容詞)是`AP`的核心詞,`P`(即介詞)是`PP`的核心詞。并非所有的短語都有核心詞——例如,一般認為連詞短語(如 the book and the bell)缺乏核心詞——然而,我們希望我們的語法形式能表達它所持有的父母/核心子女關系。現在,`V`和`VP`只是原子符號,我們需要找到一種方法用特征將它們關聯起來(就像我們以前關聯`IV`和`TV`那樣)。
X-bar 句法通過抽象出短語級別的概念,解決了這個問題。它通常認為有三個這樣的級別。如果`N`表示詞匯級別,那么`N`'表示更高一層級別,對應較傳統的級別<cite>Nom</cite>,`N`''表示短語級別,對應類別`NP`。[(34a)](./ch09.html#ex-xbar0)演示了這種表示結構,而[(34b)](./ch09.html#ex-xbar01)是更傳統的對應。
```py
S -> N[BAR=2] V[BAR=2]
N[BAR=2] -> Det N[BAR=1]
N[BAR=1] -> N[BAR=1] P[BAR=2]
N[BAR=1] -> N[BAR=0] P[BAR=2]
N[BAR=1] -> N[BAR=0]XS
```
## 3.3 助動詞與倒裝
倒裝從句——其中的主語和動詞順序互換——出現在英語疑問句,也出現在“否定”副詞之后:
```py
S[+INV] -> V[+AUX] NP VP
```
## 3.4 無限制依賴成分
考慮下面的對比:
```py
>>> nltk.data.show_cfg('grammars/book_grammars/feat1.fcfg')
% start S
# ###################
# Grammar Productions
# ###################
S[-INV] -> NP VP
S[-INV]/?x -> NP VP/?x
S[-INV] -> NP S/NP
S[-INV] -> Adv[+NEG] S[+INV]
S[+INV] -> V[+AUX] NP VP
S[+INV]/?x -> V[+AUX] NP VP/?x
SBar -> Comp S[-INV]
SBar/?x -> Comp S[-INV]/?x
VP -> V[SUBCAT=intrans, -AUX]
VP -> V[SUBCAT=trans, -AUX] NP
VP/?x -> V[SUBCAT=trans, -AUX] NP/?x
VP -> V[SUBCAT=clause, -AUX] SBar
VP/?x -> V[SUBCAT=clause, -AUX] SBar/?x
VP -> V[+AUX] VP
VP/?x -> V[+AUX] VP/?x
# ###################
# Lexical Productions
# ###################
V[SUBCAT=intrans, -AUX] -> 'walk' | 'sing'
V[SUBCAT=trans, -AUX] -> 'see' | 'like'
V[SUBCAT=clause, -AUX] -> 'say' | 'claim'
V[+AUX] -> 'do' | 'can'
NP[-WH] -> 'you' | 'cats'
NP[+WH] -> 'who'
Adv[+NEG] -> 'rarely' | 'never'
NP/NP ->
Comp -> 'that'
```
[3.1](./ch09.html#code-slashcfg)中的語法包含一個“缺口引進”產生式,即`S[-INV] -> NP S/NP`。為了正確的預填充斜線特征,我們需要為擴展`S`,`VP`和`NP`的產生式中箭頭兩側的斜線添加變量值。例如,`VP/?x -> V SBar/?x`是`VP -> V SBar`的斜線版本,也就是說,可以為一個成分的父母`VP`指定斜線值,只要也為孩子`SBar`指定同樣的值。最后,`NP/NP ->`允許`NP`上的斜線信息為空字符串。使用[3.1](./ch09.html#code-slashcfg)中的語法,我們可以分析序列 who do you claim that you like
```py
>>> 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)
(S[-INV]
(NP[+WH] who)
(S[+INV]/NP[]
(V[+AUX] do)
(NP[-WH] you)
(VP[]/NP[]
(V[-AUX, SUBCAT='clause'] claim)
(SBar[]/NP[]
(Comp[] that)
(S[-INV]/NP[]
(NP[-WH] you)
(VP[]/NP[] (V[-AUX, SUBCAT='trans'] like) (NP[]/NP[] )))))))
```
這棵樹的一個更易讀的版本如[(52)](./ch09.html#ex-gapparse)所示。
```py
>>> tokens = 'you claim that you like cats'.split()
>>> for tree in cp.parse(tokens):
... print(tree)
(S[-INV]
(NP[-WH] you)
(VP[]
(V[-AUX, SUBCAT='clause'] claim)
(SBar[]
(Comp[] that)
(S[-INV]
(NP[-WH] you)
(VP[] (V[-AUX, SUBCAT='trans'] like) (NP[-WH] cats))))))
```
此外,它還允許沒有 wh 結構的倒裝句:
```py
>>> tokens = 'rarely do you sing'.split()
>>> for tree in cp.parse(tokens):
... print(tree)
(S[-INV]
(Adv[+NEG] rarely)
(S[+INV]
(V[+AUX] do)
(NP[-WH] you)
(VP[] (V[-AUX, SUBCAT='intrans'] sing))))
```
## 3.5 德語中的格和性別
與英語相比,德語的協議具有相對豐富的形態。例如,在德語中定冠詞根據格、性別和數量變化,如[3.1](./ch09.html#tab-german-def-art)所示。
表 3.1:
德語定冠詞的形態范式
```py
>>> nltk.data.show_cfg('grammars/book_grammars/german.fcfg')
% start S
# Grammar Productions
S -> NP[CASE=nom, AGR=?a] VP[AGR=?a]
NP[CASE=?c, AGR=?a] -> PRO[CASE=?c, AGR=?a]
NP[CASE=?c, AGR=?a] -> Det[CASE=?c, AGR=?a] N[CASE=?c, AGR=?a]
VP[AGR=?a] -> IV[AGR=?a]
VP[AGR=?a] -> TV[OBJCASE=?c, AGR=?a] NP[CASE=?c]
# Lexical Productions
# Singular determiners
# masc
Det[CASE=nom, AGR=[GND=masc,PER=3,NUM=sg]] -> 'der'
Det[CASE=dat, AGR=[GND=masc,PER=3,NUM=sg]] -> 'dem'
Det[CASE=acc, AGR=[GND=masc,PER=3,NUM=sg]] -> 'den'
# fem
Det[CASE=nom, AGR=[GND=fem,PER=3,NUM=sg]] -> 'die'
Det[CASE=dat, AGR=[GND=fem,PER=3,NUM=sg]] -> 'der'
Det[CASE=acc, AGR=[GND=fem,PER=3,NUM=sg]] -> 'die'
# Plural determiners
Det[CASE=nom, AGR=[PER=3,NUM=pl]] -> 'die'
Det[CASE=dat, AGR=[PER=3,NUM=pl]] -> 'den'
Det[CASE=acc, AGR=[PER=3,NUM=pl]] -> 'die'
# Nouns
N[AGR=[GND=masc,PER=3,NUM=sg]] -> 'Hund'
N[CASE=nom, AGR=[GND=masc,PER=3,NUM=pl]] -> 'Hunde'
N[CASE=dat, AGR=[GND=masc,PER=3,NUM=pl]] -> 'Hunden'
N[CASE=acc, AGR=[GND=masc,PER=3,NUM=pl]] -> 'Hunde'
N[AGR=[GND=fem,PER=3,NUM=sg]] -> 'Katze'
N[AGR=[GND=fem,PER=3,NUM=pl]] -> 'Katzen'
# Pronouns
PRO[CASE=nom, AGR=[PER=1,NUM=sg]] -> 'ich'
PRO[CASE=acc, AGR=[PER=1,NUM=sg]] -> 'mich'
PRO[CASE=dat, AGR=[PER=1,NUM=sg]] -> 'mir'
PRO[CASE=nom, AGR=[PER=2,NUM=sg]] -> 'du'
PRO[CASE=nom, AGR=[PER=3,NUM=sg]] -> 'er' | 'sie' | 'es'
PRO[CASE=nom, AGR=[PER=1,NUM=pl]] -> 'wir'
PRO[CASE=acc, AGR=[PER=1,NUM=pl]] -> 'uns'
PRO[CASE=dat, AGR=[PER=1,NUM=pl]] -> 'uns'
PRO[CASE=nom, AGR=[PER=2,NUM=pl]] -> 'ihr'
PRO[CASE=nom, AGR=[PER=3,NUM=pl]] -> 'sie'
# Verbs
IV[AGR=[NUM=sg,PER=1]] -> 'komme'
IV[AGR=[NUM=sg,PER=2]] -> 'kommst'
IV[AGR=[NUM=sg,PER=3]] -> 'kommt'
IV[AGR=[NUM=pl, PER=1]] -> 'kommen'
IV[AGR=[NUM=pl, PER=2]] -> 'kommt'
IV[AGR=[NUM=pl, PER=3]] -> 'kommen'
TV[OBJCASE=acc, AGR=[NUM=sg,PER=1]] -> 'sehe' | 'mag'
TV[OBJCASE=acc, AGR=[NUM=sg,PER=2]] -> 'siehst' | 'magst'
TV[OBJCASE=acc, AGR=[NUM=sg,PER=3]] -> 'sieht' | 'mag'
TV[OBJCASE=dat, AGR=[NUM=sg,PER=1]] -> 'folge' | 'helfe'
TV[OBJCASE=dat, AGR=[NUM=sg,PER=2]] -> 'folgst' | 'hilfst'
TV[OBJCASE=dat, AGR=[NUM=sg,PER=3]] -> 'folgt' | 'hilft'
TV[OBJCASE=acc, AGR=[NUM=pl,PER=1]] -> 'sehen' | 'moegen'
TV[OBJCASE=acc, AGR=[NUM=pl,PER=2]] -> 'sieht' | 'moegt'
TV[OBJCASE=acc, AGR=[NUM=pl,PER=3]] -> 'sehen' | 'moegen'
TV[OBJCASE=dat, AGR=[NUM=pl,PER=1]] -> 'folgen' | 'helfen'
TV[OBJCASE=dat, AGR=[NUM=pl,PER=2]] -> 'folgt' | 'helft'
TV[OBJCASE=dat, AGR=[NUM=pl,PER=3]] -> 'folgen' | 'helfen'
```
正如你可以看到的,特征<cite>objcase</cite>被用來指定動詞支配它的對象的格。下一個例子演示了包含支配與格的動詞的句子的分析樹。
```py
>>> tokens = 'ich folge den Katzen'.split()
>>> cp = load_parser('grammars/book_grammars/german.fcfg')
>>> for tree in cp.parse(tokens):
... print(tree)
(S[]
(NP[AGR=[NUM='sg', PER=1], CASE='nom']
(PRO[AGR=[NUM='sg', PER=1], CASE='nom'] ich))
(VP[AGR=[NUM='sg', PER=1]]
(TV[AGR=[NUM='sg', PER=1], OBJCASE='dat'] folge)
(NP[AGR=[GND='fem', NUM='pl', PER=3], CASE='dat']
(Det[AGR=[NUM='pl', PER=3], CASE='dat'] den)
(N[AGR=[GND='fem', NUM='pl', PER=3]] Katzen))))
```
在開發語法時,排除不符合語法的詞序列往往與分析符合語法的詞序列一樣具有挑戰性。為了能知道在哪里和為什么序列分析失敗,設置`load_parser()`方法的`trace`參數可能是至關重要的。思考下面的分析故障:
```py
>>> tokens = 'ich folge den Katze'.split()
>>> cp = load_parser('grammars/book_grammars/german.fcfg', trace=2)
>>> for tree in cp.parse(tokens):
... print(tree)
|.ich.fol.den.Kat.|
Leaf Init Rule:
|[---] . . .| [0:1] 'ich'
|. [---] . .| [1:2] 'folge'
|. . [---] .| [2:3] 'den'
|. . . [---]| [3:4] 'Katze'
Feature Bottom Up Predict Combine Rule:
|[---] . . .| [0:1] PRO[AGR=[NUM='sg', PER=1], CASE='nom']
-> 'ich' *
Feature Bottom Up Predict Combine Rule:
|[---] . . .| [0:1] NP[AGR=[NUM='sg', PER=1], CASE='nom'] -> PRO[AGR=[NUM='sg', PER=1], CASE='nom'] *
Feature Bottom Up Predict Combine Rule:
|[---> . . .| [0:1] S[] -> NP[AGR=?a, CASE='nom'] * VP[AGR=?a] {?a: [NUM='sg', PER=1]}
Feature Bottom Up Predict Combine Rule:
|. [---] . .| [1:2] TV[AGR=[NUM='sg', PER=1], OBJCASE='dat'] -> 'folge' *
Feature Bottom Up Predict Combine Rule:
|. [---> . .| [1:2] VP[AGR=?a] -> TV[AGR=?a, OBJCASE=?c] * NP[CASE=?c] {?a: [NUM='sg', PER=1], ?c: 'dat'}
Feature Bottom Up Predict Combine Rule:
|. . [---] .| [2:3] Det[AGR=[GND='masc', NUM='sg', PER=3], CASE='acc'] -> 'den' *
|. . [---] .| [2:3] Det[AGR=[NUM='pl', PER=3], CASE='dat'] -> 'den' *
Feature Bottom Up Predict Combine Rule:
|. . [---> .| [2:3] NP[AGR=?a, CASE=?c] -> Det[AGR=?a, CASE=?c] * N[AGR=?a, CASE=?c] {?a: [NUM='pl', PER=3], ?c: 'dat'}
Feature Bottom Up Predict Combine Rule:
|. . [---> .| [2:3] NP[AGR=?a, CASE=?c] -> Det[AGR=?a, CASE=?c] * N[AGR=?a, CASE=?c] {?a: [GND='masc', NUM='sg', PER=3], ?c: 'acc'}
Feature Bottom Up Predict Combine Rule:
|. . . [---]| [3:4] N[AGR=[GND='fem', NUM='sg', PER=3]] -> 'Katze' *
```
跟蹤中的最后兩個`Scanner`行顯示 den 被識別為兩個可能的類別:`Det[AGR=[GND='masc', NUM='sg', PER=3], CASE='acc']`和`Det[AGR=[NUM='pl', PER=3], CASE='dat']`。我們從[3.2](./ch09.html#code-germancfg)中的語法知道`Katze`的類別是`N[AGR=[GND=fem, NUM=sg, PER=3]]`。因而,產生式`NP[CASE=?c, AGR=?a] -> Det[CASE=?c, AGR=?a] N[CASE=?c, AGR=?a]`中沒有變量`?a`的綁定,這將滿足這些限制,因為`Katze`的`AGR`值將不與 den 的任何一個`AGR`值統一,也就是`[GND='masc', NUM='sg', PER=3]`或`[NUM='pl', PER=3]`。
## 4 小結
* 上下文無關語法的傳統分類是原子符號。特征結構的一個重要的作用是捕捉精細的區分,否則將需要數量翻倍的原子類別。
* 通過使用特征值上的變量,我們可以表達語法產生式中的限制,允許不同的特征規格的實現可以相互依賴。
* 通常情況下,我們在詞匯層面指定固定的特征值,限制短語中的特征值與它們的孩子中的對應值統一。
* 特征值可以是原子的或復雜的。原子值的一個特定類別是布爾值,按照慣例用[+/- `f`]表示。
* 兩個特征可以共享一個值(原子的或復雜的)。具有共享值的結構被稱為重入。共享的值被表示為 AVM 中的數字索引(或標記)。
* 一個特征結構中的路徑是一個特征的元組,對應從圖的根開始的弧的序列上的標簽。
* 兩條路徑是等價的,如果它們共享一個值。
* 包含的特征結構是偏序的。FS<sub>0</sub>包含 FS<sub>1</sub>,當包含在 FS<sub>0</sub>中的所有信息也出現在 FS<sub>1</sub>中。
* 兩種結構 FS<sub>0</sub>和 FS<sub>1</sub>的統一,如果成功,就是包含 FS<sub>0</sub>和 FS<sub>1</sub>的合并信息的特征結構 FS<sub>2</sub>。
* 如果統一在 FS 中指定一條路徑π,那么它也指定等效與π的每個路徑π'。
* 我們可以使用特征結構建立對大量廣泛語言學現象的簡潔的分析,包括動詞子類別,倒裝結構,無限制依賴結構和格支配。
## 5 深入閱讀
本章進一步的材料請參考`http://nltk.org/`,包括特征結構、特征語法和語法測試套件。
X-bar 句法:[(Jacobs & Rosenbaum, 1970)](./bibliography.html#chomsky1970rn), [(Jackendoff, 1977)](./bibliography.html#jackendoff1977xs)(The primes we use replace Chomsky's typographically more demanding horizontal bars)。
協議現象的一個很好的介紹,請參閱[(Corbett, 2006)](./bibliography.html#corbett2006a)。
理論語言學中最初使用特征的目的是捕捉語音的音素特性。例如,音/**b**/可能會被分解成結構`[+labial, +voice]`。一個重要的動機是捕捉分割的類別之間的一般性;例如/**n**/在任一`+labial`輔音前面被讀作/**m**/。在喬姆斯基語法中,對一些現象,如協議,使用原子特征是很標準的,原子特征也用來捕捉跨句法類別的概括,通過類比與音韻。句法理論中使用特征的一個激進的擴展是廣義短語結構語法(GPSG; [(Gazdar, Klein, & and, 1985)](./bibliography.html#gazdar1985gps)),特別是在使用帶有復雜值的特征。
從計算語言學的角度來看,[(Dahl & Saint-Dizier, 1985)](./bibliography.html#kay1984ug)提出語言的功能方面可以被屬性-值結構的統一捕獲,一個類似的方法由[(Grosz & Stickel, 1983)](./bibliography.html#shieber1983fip)在 PATR-II 形式體系中精心設計完成。詞匯功能語法(LFG; [(Bresnan, 1982)](./bibliography.html#kaplan1982lfg))的早期工作介紹了 f-structure 概念,它的主要目的是表示語法關系和與成分結構短語關聯的謂詞參數結構。[(Shieber, 1986)](./bibliography.html#shieber1986iub)提供了研究基于特征語法方面的一個極好的介紹。
當研究人員試圖為反面例子建模時,特征結構的代數方法的一個概念上的困難出現了。另一種觀點,由[(Kasper & Rounds, 1986)](./bibliography.html#kasper1986lsf)和[(Johnson, 1988)](./bibliography.html#johnson1988avl)開創,認為語法涉及結構功能的描述而不是結構本身。這些描述使用邏輯操作如合取相結合,而否定僅僅是特征描述上的普通的邏輯運算。這種面向描述的觀點對 LFG 從一開始就是不可或缺的(參見[(Huang & Chen, 1989)](./bibliography.html#kaplan1989fal)),也被中心詞驅動短語結構語法的較高版本采用(HPSG; [(Sag & Wasow, 1999)](./bibliography.html#sag1999st))。`http://www.cl.uni-bremen.de/HPSG-Bib/`上有 HPSG 文獻的全面的參考書目。
本章介紹的特征結構無法捕捉語言信息中重要的限制。例如,有沒有辦法表達`NUM`的值只允許是`sg`和`pl`,而指定`[NUM=masc]`是反常的。同樣地,我們不能說`AGR`的復合值必須包含特征`PER`,`NUM`和`gnd`的指定,但不能包含如`[SUBCAT=trans]`這樣的指定。指定類型的特征結構被開發出來彌補這方面的不足。開始,我們規定總是鍵入特征值。對于原子值,值就是類型。例如,我們可以說`NUM`的值是類型`num`。此外,`num`是`NUM`最一般類型的值。由于類型按層次結構組織,通過指定`NUM`的值為`num`的子類型,即要么是`sg`要么是`pl`,我們可以更富含信息。
In the case of complex values, we say that feature structures are themselves typed. So for example the value of `AGR` will be a feature structure of type `AGR`. We also stipulate that all and only `PER`, `NUM` and `GND` are appropriate features for a structure of type `AGR`. 一個早期的關于指定類型的特征結構的很好的總結是[(Emele & Zajac, 1990)](./bibliography.html#emele1990tug)。一個形式化基礎的更全面的檢查可以在[(Carpenter, 1992)](./bibliography.html#carpenter1992ltf)中找到,[(Copestake, 2002)](./bibliography.html#copestake2002itf)重點關注為面向 HPSG 的方法實現指定類型的特征結構。
有很多著作是關于德語的基于特征語法框架上的分析的。[(Nerbonne, Netter, & Pollard, 1994)](./bibliography.html#nerbonne1994ghd)是這個主題的 HPSG 著作的一個好的起點,而[(M{\"u}ller, 2002)](./bibliography.html#mueller2002cp)給出 HPSG 中的德語句法非常廣泛和詳細的分析。
[(Jurafsky & Martin, 2008)](./bibliography.html#jurafskymartin2008)的第 15 章討論了特征結構、統一的算法和將統一整合到分析算法中。
## 6 練習
1. ? 需要什么樣的限制才能正確分析詞序列,如 I am happy 和 she is happy 而不是*you is happy 或*they am happy?實現英語中動詞 be 的現在時態范例的兩個解決方案,首先以語法[(6)](./ch09.html#ex-agcfg1)作為起點,然后以語法 [(18)](./ch09.html#ex-agr2)為起點。
2. ? 開發[1.1](./ch09.html#code-feat0cfg)中語法的變體,使用特征<cite>count</cite>來區分下面顯示的句子:
```py
fs1 = nltk.FeatStruct("[A = ?x, B= [C = ?x]]")
fs2 = nltk.FeatStruct("[B = [D = d]]")
fs3 = nltk.FeatStruct("[B = [C = d]]")
fs4 = nltk.FeatStruct("[A = (1)[B = b], C->(1)]")
fs5 = nltk.FeatStruct("[A = (1)[D = ?x], C = [E -> (1), F = ?x] ]")
fs6 = nltk.FeatStruct("[A = [D = d]]")
fs7 = nltk.FeatStruct("[A = [D = d], C = [F = [D = d]]]")
fs8 = nltk.FeatStruct("[A = (1)[D = ?x, G = ?x], C = [B = ?x, E -> (1)] ]")
fs9 = nltk.FeatStruct("[A = [B = b], C = [E = [G = e]]]")
fs10 = nltk.FeatStruct("[A = (1)[B = b], C -> (1)]")
```
在紙上計算下面的統一的結果是什么。(提示:你可能會發現繪制圖結構很有用。)
1. `fs1` and `fs2`
2. `fs1` and `fs3`
3. `fs4` and `fs5`
4. `fs5` and `fs6`
5. `fs5` and `fs7`
6. `fs8` and `fs9`
7. `fs8` and `fs10`
用 Python 檢查你的答案。
3. ? 列出兩個包含[A=?x, B=?x]的特征結構。
4. ? 忽略結構共享,給出一個統一兩個特征結構的非正式算法。
5. ? 擴展[3.2](./ch09.html#code-germancfg)中的德語語法,使它能處理所謂的動詞第二順位結構,如下所示:
| (58) | | Heute sieht der Hund die Katze. |
6. ? 同義動詞的句法屬性看上去略有不同[(Levin, 1993)](./bibliography.html#levin1993)。思考下面的動詞 loaded、filled 和 dumped 的語法模式。你能寫語法產生式處理這些數據嗎?
| (59) | |
| a. | | The farmer _loaded_ the cart with sand |
| b. | | The farmer _loaded_ sand into the cart |
| c. | | The farmer _filled_ the cart with sand |
| d. | | *The farmer _filled_ sand into the cart |
| e. | | *The farmer _dumped_ the cart with sand |
| f. | | The farmer _dumped_ sand into the cart |
|
7. ★ 形態范例很少是完全正規的,矩陣中的每個單元的意義有不同的實現。例如,詞位 walk 的現在時態詞性變化只有兩種不同形式:第三人稱單數的 walks 和所有其他人稱和數量的組合的 walk。一個成功的分析不應該額外要求 6 個可能的形態組合中有 5 個有相同的實現。設計和實施一個方法處理這個問題。
8. ★ 所謂的核心特征在父節點和核心孩子節點之間共享。例如,`TENSE`是核心特征,在一個`VP`和它的核心孩子`V`之間共享。更多細節見[(Gazdar, Klein, & and, 1985)](./bibliography.html#gazdar1985gps)。我們看到的結構中大部分是核心結構——除了`SUBCAT`和`SLASH`。由于核心特征的共享是可以預見的,它不需要在語法產生式中明確表示。開發一種方法自動計算核心結構的這種規則行為的比重。
9. ★ 擴展 NLTK 中特征結構的處理,允許統一值為列表的特征,使用這個來實現一個 HPSG 風格的子類別分析,核心類別的`SUBCAT`是它的補語的類別和它直接父母的`SUBCAT`值的連結。
10. ★ 擴展 NLTK 的特征結構處理,允許帶未指定類別的產生式,例如`S[-INV] --> ?x S/?x`。
11. ★ 擴展 NLTK 的特征結構處理,允許指定類型的特征結構。
12. ★ 挑選一些[(Huddleston & Pullum, 2002)](./bibliography.html#huddleston2002cge)中描述的文法結構,建立一個基于特征的語法計算它們的比例。
關于本文檔...
針對 NLTK 3.0 進行更新。本章來自于 _Natural Language Processing with Python_,[Steven Bird](http://estive.net/), [Ewan Klein](http://homepages.inf.ed.ac.uk/ewan/) 和[Edward Loper](http://ed.loper.org/),Copyright ? 2014 作者所有。本章依據 _Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License_ [[http://creativecommons.org/licenses/by-nc-nd/3.0/us/](http://creativecommons.org/licenses/by-nc-nd/3.0/us/)] 條款,與 _ 自然語言工具包 _ [`http://nltk.org/`] 3.0 版一起發行。
本文檔構建于星期三 2015 年 7 月 1 日 12:30:05 AEST