自然語言處理中,語法分析是理解句子結構的關鍵。利用 NLTK 提供的 Penn Treebank 等樹函式庫資源,可以有效地開發語法規則。NLTK 提供了存取和處理樹函式庫資料的介面,方便提取特定語法結構,例如帶有句子補語的動詞。PP 附著語料函式庫則有助於分析動詞的配價特性,進而提升語法規則的精確度。然而,隨著語法規則的複雜化,句子歧義性也隨之增加。為瞭解決這個問題,機率上下文無關文法(PCFG)為每個產生式賦予機率,藉此區分不同解析樹的合理性,並選取最可能的解析結果。文章中提供的程式碼範例,示範瞭如何使用 NLTK 定義和使用 PCFG,以及如何使用 Viterbi 演算法進行解析。此外,文章也探討瞭如何計算解析樹的深度,這對於評估語法分析的複雜度至關重要。

文法開發與樹函式庫的應用

在自然語言處理(NLP)領域中,語法分析是理解句子結構的關鍵步驟。隨著語法規則的擴充套件以涵蓋更廣泛的語言結構,句子的歧義性也會隨之增加。本文將探討如何利用樹函式庫來開發廣泛覆寫的語法,並討論相關的挑戰。

樹函式庫與語法開發

樹函式庫是一種標註了語法結構的語料函式庫,能夠為語法開發提供寶貴的資料支援。NLTK(Natural Language Toolkit)提供了Penn Treebank語料函式庫的存取介面,這是一個廣泛使用的樹函式庫資源。

from nltk.corpus import treebank
t = treebank.parsed_sents('wsj_0001.mrg')[0]
print(t)

內容解密:

此段程式碼展示瞭如何從Penn Treebank語料函式庫中讀取已解析的句子樹。首先,我們匯入了treebank模組,然後讀取了第一個已解析的句子並列印預出來。這段程式碼的作用是展示如何存取樹函式庫中的資料,為語法開發提供基礎。

利用樹函式庫資料進行語法開發

樹函式庫資料可以用於幫助開發和改進語法規則。例如,我們可以利用樹函式庫中的資料來找出帶有句子補語的動詞。

def filter(tree):
    child_nodes = [child.label() for child in tree if isinstance(child, nltk.Tree)]
    return (tree.label() == 'VP') and ('S' in child_nodes)

from nltk.corpus import treebank
[subtree for tree in treebank.parsed_sents() for subtree in tree.subtrees(filter)]

內容解密:

此段程式碼定義了一個filter函式,用於找出那些標籤為VP且包含S子節點的樹結構。然後,我們利用這個函式遍歷Penn Treebank中的所有已解析句子,找出符合條件的樹結構。這有助於我們識別出那些帶有句子補語的動詞,從而改進語法規則。

PP附著語料函式庫的應用

NLTK還提供了PP附著語料函式庫(ppattach),這是另一個有用的資源,能夠幫助我們瞭解特定動詞的配價特性。

entries = nltk.corpus.ppattach.attachments('training')
table = nltk.defaultdict(lambda: nltk.defaultdict(set))
for entry in entries:
    key = entry.noun1 + '-' + entry.prep + '-' + entry.noun2
    table[key][entry.attachment].add(entry.verb)

for key in sorted(table):
    if len(table[key]) > 1:
        print(key, 'N:', sorted(table[key]['N']), 'V:', sorted(table[key]['V']))

內容解密:

此段程式碼展示瞭如何利用PP附著語料函式庫來分析動詞的配價特性。我們首先讀取了訓練資料中的附著例項,然後構建了一個表格來記錄不同名詞、介片語合下的動詞附著情況。最後,我們列印預出了那些具有多種附著可能性的組合,從而幫助我們瞭解特定動詞的語法特性。

語法開發中的挑戰:有害的歧義

隨著語法覆寫範圍的擴大和輸入句子的增長,語法分析樹的數量會迅速增加,從而導致嚴重的歧義問題。

grammar = nltk.CFG.fromstring("""
S -> NP V NP
NP -> NP Sbar | 'fish'
Sbar -> NP V
V -> 'fish'
""")

tokens = ["fish"] * 5
cp = nltk.ChartParser(grammar)
for tree in cp.nbest_parse(tokens):
    print(tree)

內容解密:

此段程式碼展示了一個簡單的語法規則,用於解析類別似fish fish fish fish fish這樣的句子。我們定義了一個上下文無關語法(CFG),然後利用NLTK的圖表解析器對句子進行解析。結果顯示,這個句子有多種可能的解析樹,反映了句子的歧義性。

解決歧義問題的必要性

隨著句子長度的增加,解析樹的數量會按照卡塔蘭數字的速度增長。對於一個長度為23的句子,可能會有超過208,012種解析樹;對於長度為50的句子,這個數字更是超過了$10^{12}$。這種級別的歧義性對於任何實用的NLP系統來說都是無法接受的。因此,如何有效地解決歧義問題是語法開發中的一個重要挑戰。

隨著NLP技術的不斷進步,我們可以預見未來在語法開發領域會有更多的創新和突破。一些可能的發展方向包括:

  1. 更先進的語法理論:開發更為精確和全面的語法理論,以更好地描述和解析自然語言。
  2. 更有效的解析演算法:研究和開發更為高效的解析演算法,以應對日益增長的語法複雜性和句子長度。
  3. 跨語言的語法開發:將語法開發擴充套件到更多的語言,以滿足全球範圍內的NLP需求。
  4. 結合深度學習技術:利用深度學習技術來改進語法解析的準確性和效率。

透過這些努力,我們可以期待在NLP領域看到更多的進步和創新。

機率上下文無關文法(PCFG)在自然語言處理中的應用

在自然語言處理(NLP)領域,語法分析(parsing)是一項基本任務。傳統的上下文無關文法(CFG)能夠有效地解析句子的結構,但在處理具有多重歧義的句子時會遇到效率問題。機率上下文無關文法(PCFG)透過為每個產生式分配機率,解決了這一問題,並在語法分析中取得了顯著成效。

為何需要機率上下文無關文法(PCFG)

自然語言的複雜性使得語法分析變得困難。一個句子可能有多種解析樹(parse trees),而傳統的CFG無法區分這些解析樹的合理性。PCFG透過引入機率,允許我們對不同的解析樹進行排序,從而選擇最合理的解析結果。

範例:動詞 “give” 的不同用法

動詞 “give” 可以有不同的補語順序,例如:

  • 介詞與格(prepositional dative):Kim gave a bone to the dog.
  • 雙賓語結構(double object):Kim gave the dog a bone.

當間接賓語是代詞時,雙賓語結構更為常見,例如:

  • Kim gives me the heebie-jeebies.

透過分析Penn Treebank語料函式庫中的例項,我們可以觀察到 “give” 的不同用法及其機率分佈。這種分析有助於我們理解語言中的機率特性,並為PCFG的構建提供依據。

如何定義機率上下文無關文法(PCFG)

PCFG可以透過加權產生式來定義,每個產生式都有一個對應的機率。這些機率反映了該產生式在特定上下文中被使用的可能性。

範例:定義一個簡單的PCFG

grammar = nltk.parse_pcfg("""
S -> NP VP [1.0]
VP -> TV NP [0.4]
VP -> IV [0.3]
VP -> DatV NP NP [0.3]
TV -> 'saw' [1.0]
IV -> 'ate' [1.0]
DatV -> 'gave' [1.0]
NP -> 'telescopes' [0.8]
NP -> 'Jack' [0.2]
""")

在這個例子中,我們定義了一個簡單的PCFG,其中每個非終端符號都有對應的產生式和機率。例如,VP 有三種可能的展開方式,其機率分別為 0.4、0.3 和 0.3。

PCFG的優點與應用

  1. 處理歧義: PCFG能夠有效地處理自然語言中的歧義,透過為不同的解析樹分配機率,選擇最合理的解析結果。
  2. 提高效率: 透過機率排序,PCFG可以優先處理最可能的解析樹,從而提高語法分析的效率。
  3. 廣泛應用: PCFG在語音識別、機器翻譯、資訊抽取等NLP任務中都有廣泛的應用。

實作細節與程式碼範例

在NLTK函式庫中,我們可以使用parse_pcfg函式來定義和解析PCFG。下面是一個完整的程式碼範例,展示如何使用PCFG進行語法分析:

import nltk
from nltk import parse_pcfg

# 定義PCFG
grammar = nltk.parse_pcfg("""
S -> NP VP [1.0]
VP -> TV NP [0.4]
VP -> IV [0.3]
VP -> DatV NP NP [0.3]
TV -> 'saw' [1.0]
IV -> 'ate' [1.0]
DatV -> 'gave' [1.0]
NP -> 'telescopes' [0.8]
NP -> 'Jack' [0.2]
""")

# 列印PCFG
print(grammar)

# 使用PCFG進行語法分析
parser = nltk.ViterbiParser(grammar)
sentence = "Jack saw telescopes".split()
for tree in parser.parse(sentence):
    print(tree)

內容解密:

此範例展示如何使用NLTK函式庫定義一個簡單的PCFG並對句子進行語法分析。首先,我們定義了一個包含多個產生式的PCFG,然後使用ViterbiParser對句子"Jack saw telescopes"進行解析,最後列印出解析樹及其對應的機率。

隨著深度學習技術的發展,將PCFG與神經網路模型相結合,可能會進一步提高語法分析的效能。此外,如何更好地處理長距離依賴和複雜句法結構,也是未來研究的重要方向。

PCFG解析流程圖示

  graph LR;
    A[輸入句子] --> B[詞法分析];
    B --> C[語法分析];
    C --> D[生成解析樹];
    D --> E[機率計算];
    E --> F[輸出最佳解析樹];

圖表翻譯: 此圖示展示了使用PCFG進行語法分析的基本流程。首先對輸入句子進行詞法分析,然後進行語法分析生成解析樹,接著計算每個解析樹的機率,最終輸出機率最高的解析樹作為結果。

8.8 進一步閱讀與資源

本章的額外資料已釋出在 http://www.nltk.org/,包括與本章相關的免費網路資源。有關使用NLTK進行語法分析的更多範例,請參閱 http://www.nltk.org/howto 的Parsing HOWTO。

語法分析相關文獻

有很多關於語法分析的入門書籍可供參考。(O’Grady et al., 2004) 是一本通用的語言學入門書,而 (Radford, 1988) 則提供了一個關於轉換語法的溫和介紹,並且因其對無界依賴結構的轉換方法而值得推薦。語言學中最廣泛使用的正式語法術語是生成語法(generative grammar),儘管它與生成(generation)無關(Chomsky, 1965)。

(Burton-Roberts, 1997) 是一本實用性強的教材,專注於分析英語的構成結構,包含大量的範例和練習。(Huddleston & Pullum, 2002) 提供了一個最新的、全面分析英語語法現象的著作。

(Jurafsky & Martin, 2008) 的第12章涵蓋了英語的形式語法;第13.1-3節涵蓋了簡單的語法分析演算法和處理歧義的技術;第14章涵蓋了統計語法分析;第16章涵蓋了Chomsky層級和自然語言的形式複雜度。(Levin, 1993) 根據句法特性將英語動詞分類別為細粒度類別。

目前有多個正在進行的大規模根據規則的語法構建工作,例如LFG Pargram專案(http://www2.parc.com/istl/groups/nltt/pargram/)、HPSG LinGO Matrix框架(http://www.delph-in.net/matrix/)和XTAG專案(http://www.cis.upenn.edu/~xtag/)。

8.9 練習題

  1. ○ 你能想出一些可能從未被說過的合乎語法的句子嗎?(與夥伴輪流進行。)這對人類語言有什麼啟示?

    • 這個練習旨在讓你思考人類語言的創造性和多樣性。嘗試想出一些新的、合乎語法的句子,並與他人分享。
  2. ○ 回顧Strunk和White禁止在句首使用however來表示“雖然”的規則。在網路上搜尋在句首使用however的例子。這種結構的使用有多廣泛?

    • 進行網路搜尋以瞭解在句首使用however的情況,並分析這種用法在不同語境中的接受度。
  3. ○ 考慮句子“Kim arrived or Dana left and everyone cheered”。寫出表示and和or相對作用域的括號形式。生成對應於這兩種解釋的樹結構。

    import nltk
    from nltk import Tree
    
    # 定義語法
    grammar = nltk.CFG.fromstring("""
        S -> NP VP | S Conj S
        NP -> 'Kim' | 'Dana' | 'everyone'
        VP -> 'arrived' | 'left' | 'cheered'
        Conj -> 'or' | 'and'
    """)
    
    # 解析句子
    sentence = ['Kim', 'arrived', 'or', 'Dana', 'left', 'and', 'everyone', 'cheered']
    parser = nltk.ChartParser(grammar)
    trees = list(parser.parse(sentence))
    
    # 顯示解析樹
    for tree in trees:
        print(tree)
        tree.draw()
    

    內容解密:

    • 上述程式碼展示瞭如何使用NLTK解析具有多重語法結構的句子。
    • 我們定義了一個簡單的CFG語法來表示句子的結構。
    • 使用nltk.ChartParser來解析句子,並生成對應的解析樹。
    • 解析樹顯示了句子的不同語法結構,並視覺化地展示了and和or的作用域。
  4. ○ Tree類別實作了多種其他有用的方法。請參閱Tree幫助檔案以瞭解更多細節(即匯入Tree類別,然後輸入help(Tree))。

    from nltk import Tree
    
    # 建立一個樹
    t = Tree('S', [Tree('NP', ['Kim']), Tree('VP', ['arrived'])])
    
    # 顯示樹的幫助檔案
    help(Tree)
    

    內容解密:

    • nltk.Tree類別提供了多種方法來操作和檢查樹結構。
    • 使用help(Tree)可以檢視Tree類別的詳細檔案。
  5. ○ 在這個練習中,你將手動構建一些解析樹。

    a. 寫程式碼生成兩個樹,分別對應於短語“old men and women”的兩種解讀。

    from nltk import Tree
    
    # 第一種解讀:(old men) and women
    tree1 = Tree('NP', [Tree('ADJP', ['old']), Tree('N', ['men']), 'and', Tree('N', ['women'])])
    print(tree1)
    
    # 第二種解讀:old (men and women)
    tree2 = Tree('NP', [Tree('ADJP', ['old']), Tree('CONJP', ['men', 'and', 'women'])])
    print(tree2)
    

    內容解密:

    • 這個練習展示瞭如何使用NLTK的Tree類別來表示具有不同語法結構的短語。
    • 兩種解讀對應不同的語法樹結構。
  6. ○ 編寫一個遞迴函式來遍歷樹並傳回樹的深度,使得具有單個節點的樹的深度為零。(提示:子樹的深度是其子節點的最大深度加一。)

    def tree_depth(tree):
        if isinstance(tree, str):
            return 0
        else:
            return 1 + max(tree_depth(child) for child in tree)
    
    # 示例樹
    t = Tree('S', [Tree('NP', ['Kim']), Tree('VP', ['arrived'])])
    
    print("Tree depth:", tree_depth(t))
    

    內容解密:

    • 這個函式遞迴地計算樹的深度。
    • 葉節點(字串)的深度為0,非葉節點的深度是其子節點的最大深度加1。

練習題解答與分析

這些練習旨在幫助你深入理解語法分析的基本概念和技術。透過實踐這些練習,你將能夠更好地掌握如何使用NLTK進行語法分析,並理解不同語法結構之間的差異。

  graph LR;
    A[開始] --> B{是否為字串?};
    B -- 是 --> C[傳回0];
    B -- 否 --> D[計運算元樹深度];
    D --> E[傳回1 + 最大子樹深度];

圖表翻譯:

  • 此圖表展示了計算樹深度的遞迴過程。
  • 首先檢查當前節點是否為字串(葉節點)。
  • 如果是字串,則傳回0。
  • 如果不是字串,則遞迴計算所有子節點的深度,並傳回最大深度加1。

這些練習和程式碼示例旨在幫助你深入理解語法分析的相關概念,並提供實踐經驗。透過這些練習,你將能夠更好地理解和應用語法分析技術。