特徵語法能提升語法分析的精確度,Python 的 NLTK 函式庫提供建構和操作特徵結構的工具。特徵結構以字典形式呈現,能以索引方式存取值,並支援複雜的巢狀結構。統一操作能合併不同特徵結構的資訊,有助於處理語言學物件的多重屬性。特徵結構也能以圖形方式理解,其中的節點和邊緣分別代表特徵值和特徵名稱,結構分享則以整數標籤表示。包含關係定義了特徵結構的泛化程度,統一操作的結果會偏向更具體的結構。在語法分析中,特徵結構能更精確地描述動詞的語法行為,例如使用 SUBCAT 特徵區分不同型別的動詞,並據此建構不同的 VP 結構。
特徵語法與剖析技術
特徵語法(Feature-Based Grammar)是自然語言處理中的關鍵技術,透過引入特徵(Feature)來豐富語法規定的表達能力,使語法分析更加精確。以下將探討特徵語法的基本概念、實作方式及其在自然語言處理中的應用。
特徵語法範例分析
範例程式碼與解析
# 特徵語法範例
% start S
# ###################
# 語法規則
# ###################
S -> NP[NUM=?n] VP[NUM=?n]
NP[NUM=?n] -> N[NUM=?n]
NP[NUM=?n] -> PropN[NUM=?n]
NP[NUM=?n] -> Det[NUM=?n] N[NUM=?n]
VP[TENSE=?t, NUM=?n] -> IV[TENSE=?t, NUM=?n]
VP[TENSE=?t, NUM=?n] -> TV[TENSE=?t, NUM=?n] NP
# ###################
# 詞彙規則
# ###################
Det[NUM=sg] -> 'this' | 'every'
Det[NUM=pl] -> 'these' | 'all'
PropN[NUM=sg]-> 'Kim' | 'Jody'
N[NUM=sg] -> 'dog' | 'girl' | 'car' | 'child'
IV[TENSE=pres, NUM=sg] -> 'disappears' | 'walks'
TV[TENSE=pres, NUM=sg] -> 'sees' | 'likes'
內容解密:
此範例展示了一個根據特徵的語法系統,主要特點如下:
- 特徵變數的使用:使用
?n
和?t
變數來表示數目(NUM)和時態(TENSE)的統一約束,確保主語和謂語動詞的一致性。 - 詞彙與語法規則的結合:透過特徵結構來定義詞彙類別(如 Det、N、IV、TV)的屬性,實作更精確的語法控制。
- 語法規則的層次結構:將語法規則(S、NP、VP)與詞彙規則(Det、N、IV、TV)分開定義,增強可讀性和可維護性。
特徵語法剖析流程
剖析器實作範例
# 載入剖析器並進行語法分析
tokens = 'Kim likes children'.split()
from nltk import load_parser
cp = load_parser('grammars/book_grammars/feat0.fcfg', trace=2)
trees = cp.nbest_parse(tokens)
剖析過程解析
- 詞彙分析:將輸入句子分解為詞彙單元
['Kim', 'likes', 'children']
。 - 語法分析:使用 Earley 圖表剖析法進行語法樹構建,過程中動態約束特徵變數。
- 結果輸出:輸出可能的語法樹結構,並顯示剖析過程中的詳細步驟。
語法樹結果展示
# 輸出語法樹結果
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))))
結果解密:
- 語法結構:句子
Kim likes children
被解析為一個包含主語(NP)和謂語(VP)的結構。 - 特徵標註:NP 和 VP 節點均帶有特徵標註,如
NUM='sg'
和TENSE='pres'
,確保語法一致性。 - 詞性標註:
Kim
被標註為專有名詞(PropN),likes
為及物動詞(TV),children
為名詞(N)。
布林特徵與複雜特徵結構
布林特徵表示法
# 布林特徵範例
V[TENSE=pres, +aux] -> 'can'
V[TENSE=pres, -aux] -> 'walks'
複雜特徵結構範例
# 複雜特徵結構表示
[POS = N ]
[AGR = [PER = 3]]
[ [NUM = pl]]
[ [GND = fem]]
特徵結構解析
- 布林特徵:使用
+aux
和-aux
表示動詞是否為助動詞。 - 複雜特徵:將一致性特徵(AGR)表示為包含人稱(PER)、數目(NUM)和性別(GND)的複雜結構。
圖表展示技術
graph LR A[句子結構] --> B[NP 主語] A --> C[VP 謂語] B --> D[PropN] C --> E[TV] C --> F[NP 賓語] E --> G[likes] F --> H[N]
圖表翻譯:
此圖表展示了句子 Kim likes children
的語法結構樹,主要包含:
- S 節點分解為 NP 和 VP。
- NP 節點進一步分解為專有名詞(PropN)。
- VP 節點包含及物動詞(TV)和名詞短語(NP)。
技術特點與優勢
- 精確的語法控制:透過特徵結構實作語法一致性檢查。
- 靈活的特徵表示:支援原子特徵、布林特徵及複雜特徵結構。
- 高效的剖析技術:採用 Earley 圖表剖析法進行高效語法分析。
9.2 處理特徵結構
在這一節中,我們將展示如何在 NLTK 中構建和操作特徵結構。我們還將討論統一(unification)的基本操作,它允許我們合併兩個不同特徵結構中的資訊。
在 NLTK 中使用特徵結構
NLTK 中的特徵結構是透過 FeatStruct()
建構函式宣告的。原子特徵值可以是字串或整數。
>>> fs1 = nltk.FeatStruct(TENSE='past', NUM='sg')
>>> print(fs1)
[ NUM = 'sg' ]
[ TENSE = 'past' ]
特徵結構實際上是一種字典,因此我們可以透過索引來存取其值,就像存取普通字典一樣。我們可以使用熟悉的語法為特徵指定:
>>> fs1 = nltk.FeatStruct(PER=3, NUM='pl', GND='fem')
>>> print(fs1['GND'])
fem
>>> fs1['CASE'] = 'acc'
複雜特徵值的定義
我們還可以定義具有複雜值的特徵結構,如前所述。
>>> fs2 = nltk.FeatStruct(POS='N', AGR=fs1)
>>> print(fs2)
[ [ CASE = 'acc' ] ]
[ AGR = [ GND = 'fem' ] ]
[ [ NUM = 'pl' ] ]
[ [ PER = 3 ] ]
[ ]
[ POS = 'N' ]
特徵結構的另一種表示方法
指定特徵結構的另一種方法是使用括號內的字串,該字串由特徵值對組成,格式為 feature=value
,其中值本身可以是特徵結構。
>>> print(nltk.FeatStruct("[POS='N', AGR=[PER=3, NUM='pl', GND='fem']]"))
[ [ PER = 3 ] ]
[ AGR = [ GND = 'fem' ] ]
[ [ NUM = 'pl' ] ]
[ ]
[ POS = 'N' ]
將特徵結構視為圖
將特徵結構視為圖(更具體地說,是有向無環圖,DAGs)通常很有幫助。特徵名稱出現在有向弧上的標籤,而特徵值則出現在弧所指向的節點上的標籤。
圖的路徑與結構分享
特徵路徑是一系列從根節點開始的弧的序列。我們將路徑表示為弧標籤的元組。例如,在某個特徵結構中,('ADDRESS', 'STREET')
是一個特徵路徑,其值是標有 ‘rue Pascal’ 的節點。
結構分享的表示
在矩陣式表示法中,我們使用整數標籤來表示結構分享。例如,(1)
表示首次出現的分享特徵結構,而 ->(1)
則表示對該結構的參照。
>>> 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' ] ]
包含與統一
特徵結構之間的包含關係(subsumption)定義了一個偏序關係。一個更一般的特徵結構包含一個更具體的特徵結構。如果 FS0
包含 FS1
(形式上寫作 FS0 ⊑ FS1
),則 FS1
必須具有 FS0
的所有路徑和路徑等價,並且可能具有額外的路徑和等價。
統一操作
統一(unification)是一種合併兩個特徵結構資訊的操作。NLTK 透過 unify()
方法支援統一。
>>> fs1 = nltk.FeatStruct(NUMBER=74, STREET='rue Pascal')
>>> fs2 = nltk.FeatStruct(CITY='Paris')
>>> print(fs1.unify(fs2))
[ CITY = 'Paris' ]
[ NUMBER = 74 ]
[ STREET = 'rue Pascal' ]
#### 內容解密:
在上述程式碼範例中,我們首先建立了兩個特徵結構 fs1
和 fs2
。fs1
包含地址的街道號碼和街道名稱,而 fs2
包含城市名稱。透過 unify()
方法,我們將 fs1
和 fs2
合併,生成了一個新的特徵結構,該結構包含了 fs1
和 fs2
中的所有資訊。這展示瞭如何在 NLTK 中使用統一操作來合併特徵結構。
特徵結構的應用
特徵結構不僅可以用於語言學物件的表示,還可以用於表示任何型別的知識。例如,我們可以用特徵結構來表示一個人的資訊:
>>> print(nltk.FeatStruct(name='Lee', telno='0127 8642 96', age=33))
[ age = 33 ]
[ name = 'Lee' ]
[ telno = '0127 8642 96' ]
這種靈活性使得特徵結構成為一種強大的知識表示工具。
特徵結構的統一操作
graph TD A[特徵結構1] -->|統一|> C[統一結果] B[特徵結構2] -->|統一|> C C --> D[包含所有特徵資訊的結果]
圖表翻譯:
此圖表展示了兩個特徵結構透過統一操作合併成一個新的特徵結構的過程。統一操作將兩個特徵結構的資訊合併,生成一個包含所有特徵資訊的結果結構。這種操作在自然語言處理中非常有用,特別是在處理具有多個屬性的語言學物件時。
特徵結構的統一與子分類別
特徵結構統一的定義與特性
特徵結構(Feature Structure, FS)的統一(Unification)是一種二元運算,記作 $FS_0 \⊔ FS_1$。這種運算具有對稱性,即 $FS_0 \⊔ FS_1 = FS_1 \⊔ FS_0$。在Python的NLTK函式庫中,我們可以觀察到這種對稱性:
>>> print fs2.unify(fs1)
[ CITY = 'Paris' ]
[ NUMBER =74 ]
[ STREET = 'rue Pascal' ]
內容解密:
- 統一運算:特徵結構的統一是一種合併兩個特徵結構的運算。
FS_0 \⊔ FS_1
:表示將 $FS_0$ 和 $FS_1$ 進行統一。- 對稱性:統一運算的結果與輸入順序無關,即 $FS_0 \⊔ FS_1 = FS_1 \⊔ FS_0$。
- 範例程式碼:展示了在NLTK中進行特徵結構統一的結果。
如果兩個特徵結構之間存在包含關係(subsumption relationship),那麼統一的結果是較為具體的那一個:
(1) 若 $FS_0 \⊑ FS_1$,則 $FS_0 \⊔ FS_1 = FS_1$
>>> 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' ] ]
內容解密:
fs0
定義:建立了一個包含NAME
、ADDRESS
和SPOUSE
等特徵的結構。ADDRESS
結構分享:Lee
和其配偶Kim
分享相同的ADDRESS
結構。print fs0
:輸出特徵結構的內容,顯示了結構的層次和分享情況。
當我們嘗試統一兩個具有相同路徑但不同原子值的特徵結構時,統一將失敗並傳回None
:
>>> fs0 = nltk.FeatStruct(A='a')
>>> fs1 = nltk.FeatStruct(A='b')
>>> fs2 = fs0.unify(fs1)
>>> print fs2
None
內容解密:
fs0
和fs1
定義:兩個特徵結構在A
這個特徵上有不同的原子值。- 統一失敗:由於
A
的值不同,統一運算失敗,傳回None
。 print fs2
:輸出統一的結果,顯示為None
。
結構分享與統一
結構分享(structure-sharing)在特徵結構統一中扮演重要角色。當兩個特徵結構分享某些結構時,對其中一個的修改可能會影響另一個。
>>> fs1 = nltk.FeatStruct("[SPOUSE = [ADDRESS = [CITY = Paris]]]")
>>> 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' ] ]
內容解密:
fs1
和fs2
定義:fs1
指定了配偶的地址城市為Paris,而fs2
定義了Lee
和Kim
的地址結構分享。- 統一結果:統一後,
Lee
和Kim
的地址都被更新為包含CITY = 'Paris'
。 - 結構分享效果:由於
Lee
和Kim
的地址結構分享,對Kim
的地址修改同時影響了Lee
的地址。
根據特徵的語法擴充套件
在根據特徵的語法(Feature-Based Grammar)中,我們進一步探討了子分類別(subcategorization)的問題。在第8章中,我們擴充套件了類別標籤以表示不同型別的動詞,例如不及物動詞(IV)和及物動詞(TV)。然而,這些標籤只是CFG中的原子非終端符號,無法表達動詞之間的共同特性。
子分類別與SUBCAT特徵
為了改進這一點,我們引入了SUBCAT
特徵來表示動詞的子分類別資訊。SUBCAT
特徵的值可以是intrans
、trans
或clause
等,用於區分不同型別的動詞。
VP[TENSE=?t, NUM=?n] -> V[SUBCAT=intrans, TENSE=?t, NUM=?n]
VP[TENSE=?t, NUM=?n] -> V[SUBCAT=trans, TENSE=?t, NUM=?n] NP
VP[TENSE=?t, NUM=?n] -> V[SUBCAT=clause, TENSE=?t, NUM=?n] SBar
V[SUBCAT=intrans, TENSE=pres, NUM=sg] -> 'disappears' | 'walks'
V[SUBCAT=trans, TENSE=pres, NUM=sg] -> 'sees' | 'likes'
V[SUBCAT=clause, TENSE=pres, NUM=sg] -> 'says' | 'claims'
內容解密:
SUBCAT
特徵:用於區分不同子分類別的動詞,例如不及物動詞(intrans
)、及物動詞(trans
)和帶從句的動詞(clause
)。VP
規則:根據動詞的SUBCAT
值,決定VP
的構成方式。- 詞彙規則:根據動詞的
SUBCAT
、TENSE
和NUM
特徵,決定具體的動詞詞形。
這種方法允許我們更精確地描述動詞的語法行為,同時保持詞類別的一致性。例如,walk
和like
都屬於V
類別,但根據其SUBCAT
值,它們會出現在不同的VP
結構中。