特徵語法能提升語法分析的精確度,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'

內容解密:

此範例展示了一個根據特徵的語法系統,主要特點如下:

  1. 特徵變數的使用:使用 ?n?t 變數來表示數目(NUM)和時態(TENSE)的統一約束,確保主語和謂語動詞的一致性。
  2. 詞彙與語法規則的結合:透過特徵結構來定義詞彙類別(如 Det、N、IV、TV)的屬性,實作更精確的語法控制。
  3. 語法規則的層次結構:將語法規則(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)

剖析過程解析

  1. 詞彙分析:將輸入句子分解為詞彙單元 ['Kim', 'likes', 'children']
  2. 語法分析:使用 Earley 圖表剖析法進行語法樹構建,過程中動態約束特徵變數。
  3. 結果輸出:輸出可能的語法樹結構,並顯示剖析過程中的詳細步驟。

語法樹結果展示

# 輸出語法樹結果
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. 語法結構:句子 Kim likes children 被解析為一個包含主語(NP)和謂語(VP)的結構。
  2. 特徵標註:NP 和 VP 節點均帶有特徵標註,如 NUM='sg'TENSE='pres',確保語法一致性。
  3. 詞性標註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]]

特徵結構解析

  1. 布林特徵:使用 +aux-aux 表示動詞是否為助動詞。
  2. 複雜特徵:將一致性特徵(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 的語法結構樹,主要包含:

  1. S 節點分解為 NP 和 VP。
  2. NP 節點進一步分解為專有名詞(PropN)。
  3. VP 節點包含及物動詞(TV)和名詞短語(NP)。

技術特點與優勢

  1. 精確的語法控制:透過特徵結構實作語法一致性檢查。
  2. 靈活的特徵表示:支援原子特徵、布林特徵及複雜特徵結構。
  3. 高效的剖析技術:採用 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' ]

#### 內容解密:

在上述程式碼範例中,我們首先建立了兩個特徵結構 fs1fs2fs1 包含地址的街道號碼和街道名稱,而 fs2 包含城市名稱。透過 unify() 方法,我們將 fs1fs2 合併,生成了一個新的特徵結構,該結構包含了 fs1fs2 中的所有資訊。這展示瞭如何在 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' ]

內容解密:

  1. 統一運算:特徵結構的統一是一種合併兩個特徵結構的運算。
  2. FS_0 \⊔ FS_1:表示將 $FS_0$ 和 $FS_1$ 進行統一。
  3. 對稱性:統一運算的結果與輸入順序無關,即 $FS_0 \⊔ FS_1 = FS_1 \⊔ FS_0$。
  4. 範例程式碼:展示了在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' ] ]

內容解密:

  1. fs0定義:建立了一個包含NAMEADDRESSSPOUSE等特徵的結構。
  2. ADDRESS結構分享Lee和其配偶Kim分享相同的ADDRESS結構。
  3. print fs0:輸出特徵結構的內容,顯示了結構的層次和分享情況。

當我們嘗試統一兩個具有相同路徑但不同原子值的特徵結構時,統一將失敗並傳回None

>>> fs0 = nltk.FeatStruct(A='a')
>>> fs1 = nltk.FeatStruct(A='b')
>>> fs2 = fs0.unify(fs1)
>>> print fs2
None

內容解密:

  1. fs0fs1定義:兩個特徵結構在A這個特徵上有不同的原子值。
  2. 統一失敗:由於A的值不同,統一運算失敗,傳回None
  3. 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' ] ]

內容解密:

  1. fs1fs2定義fs1指定了配偶的地址城市為Paris,而fs2定義了LeeKim的地址結構分享。
  2. 統一結果:統一後,LeeKim的地址都被更新為包含CITY = 'Paris'
  3. 結構分享效果:由於LeeKim的地址結構分享,對Kim的地址修改同時影響了Lee的地址。

根據特徵的語法擴充套件

在根據特徵的語法(Feature-Based Grammar)中,我們進一步探討了子分類別(subcategorization)的問題。在第8章中,我們擴充套件了類別標籤以表示不同型別的動詞,例如不及物動詞(IV)和及物動詞(TV)。然而,這些標籤只是CFG中的原子非終端符號,無法表達動詞之間的共同特性。

子分類別與SUBCAT特徵

為了改進這一點,我們引入了SUBCAT特徵來表示動詞的子分類別資訊。SUBCAT特徵的值可以是intranstransclause等,用於區分不同型別的動詞。

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'

內容解密:

  1. SUBCAT特徵:用於區分不同子分類別的動詞,例如不及物動詞(intrans)、及物動詞(trans)和帶從句的動詞(clause)。
  2. VP規則:根據動詞的SUBCAT值,決定VP的構成方式。
  3. 詞彙規則:根據動詞的SUBCATTENSENUM特徵,決定具體的動詞詞形。

這種方法允許我們更精確地描述動詞的語法行為,同時保持詞類別的一致性。例如,walklike都屬於V類別,但根據其SUBCAT值,它們會出現在不同的VP結構中。