NLTK學習筆記(八):文法--詞關系研究的工具


對於一門語言來說,一句話有無限可能。問題是我們只能通過有限的程序來分析結構和含義。嘗試將“語言”理解為:僅僅是所有合乎文法的句子的大集合。在這個思路的基礎上,類似於 word -> word and/or/... word 就成立,這種式子叫做遞歸產生式。理論上,句子可以無限擴充。

文法

自定義文法

寫法上與上一篇博文的分類規則思路基本一致,並且更簡單、更直觀,可以和之前的對比着看。

import nltk
from nltk import CFG

grammar = nltk.CFG.fromstring("""
  S -> NP VP
  VP -> V NP | V NP PP
  PP -> P NP
  V -> "saw" | "ate" | "walked"
  NP -> "John" | "Mary" | "Bob" | Det N | Det N PP
  Det -> "a" | "an" | "the" | "my"
  N -> "man" | "dog" | "cat" | "telescope" | "park"
  P -> "in" | "on" | "by" | "with"
  """)
sent = 'Mary saw Bob'.split()
rd_parser = nltk.RecursiveDescentParser(grammar)
for i in rd_parser.parse(sent):
    print(i)

在定義文法的時候,NP->'New York'應該寫成 NP->'New_York',連接作用的空格應該用_代替。

文法用途

語言基本可以說是由修飾結構和並列結構拼接而成(不喜勿噴)。比如下面這樣不斷的擴充:

  1. he ran
  2. he ran there
  3. he saw it there
  4. the bear saw the font in it

顯然正常的句子是第四句,如果將上述過程倒過來從4->1。最終就可以得到兩個元素。也就是說:再復合語法規則句子中的詞序列可以被一個更小的且不會導致句子不符合語法規則的序列代替。下面兩張圖,第一張代表詞序列的替換。第二張是根據文法規則畫出的圖。(附截圖*2)

詞序列的替換

文法規則

開發文法

下面程序展示了利用簡單的過濾器,找出帶句子補語的動詞

from nltk.corpus import treebank
t = treebank.parsed_sents('wsj_0001.mrg')[0]
print(t) #查看封裝好的文法

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)#找出帶句子補語的動詞

[subtree for tree in treebank.parsed_sents() \
        for subtree in tree.subtrees(filter)]

分析文法的算法

  1. 下降遞歸分析:自上而下
  2. 移進-歸約分析:自下而上
  3. 左角落分析:自下而上過濾的自上而下的方法
  4. 圖表法:動態規划技術

以下是前兩種分析算法對應的解析器。

遞歸下降解析器

三個主要缺點:

  • 左遞歸產生式:NP-> NP PP 會陷入死循環
  • 處理不符合句子的詞和結構時候浪費時間
  • 回溯過程過重會丟掉計算過的分析,重新計算
import nltk
from nltk import CFG
grammar1 = nltk.CFG.fromstring("""
  S -> NP VP
  VP -> V NP | V NP PP
  PP -> P NP
  V -> "saw" | "ate" | "walked"
  NP -> "John" | "Mary" | "Bob" | Det N | Det N PP
  Det -> "a" | "an" | "the" | "my"
  N -> "man" | "dog" | "cat" | "telescope" | "park"
  P -> "in" | "on" | "by" | "with"
  """)
rd_parser = nltk.RecursiveDescentParser(grammar1)
sent = 'Mary saw a dog'.split()
for t in rd_parser.parse(sent):
    print(t)

可以調用nltk.app.rdparser()來查看分析過程

移進-歸約解析器

此解析器反復將下個輸入詞push進堆棧,成為移位操作。如果堆棧前n項,匹配表達式右側的n個項目,彈出棧,並且將產生式左邊項目壓如棧,稱為歸約操作。

兩個缺點:

  • 由於堆棧的特殊性,只能找到一種解析
  • 不能保證一定能找到解析
sr_parse = nltk.ShiftReduceParser(grammar1)
for t in sr_parse.parse(sent):
    print(t)

基於特征的文法

怎么對文法進行更細微的控制,用什么結構來表示?可以將標簽分解為類似字典的結構,提取一系列的值作為特征。

屬性和約束

首先看一個例子,通過nltk.data.show_cfg('grammars/book_grammars/feat0.fcfg')

% start S
# ###################
# Grammar Productions
# ###################
# S expansion productions
S -> NP[NUM=?n] VP[NUM=?n]
# NP expansion productions
NP[NUM=?n] -> N[NUM=?n] 
NP[NUM=?n] -> PropN[NUM=?n] 
NP[NUM=?n] -> Det[NUM=?n] N[NUM=?n]
NP[NUM=pl] -> N[NUM=pl] 
# VP expansion productions
VP[TENSE=?t, NUM=?n] -> IV[TENSE=?t, NUM=?n]
VP[TENSE=?t, NUM=?n] -> TV[TENSE=?t, NUM=?n] NP
# ###################
# Lexical Productions
# ###################
Det[NUM=sg] -> 'this' | 'every'
Det[NUM=pl] -> 'these' | 'all'
Det -> 'the' | 'some' | 'several'
PropN[NUM=sg]-> 'Kim' | 'Jody'
N[NUM=sg] -> 'dog' | 'girl' | 'car' | 'child'
N[NUM=pl] -> 'dogs' | 'girls' | 'cars' | 'children' 
IV[TENSE=pres,  NUM=sg] -> 'disappears' | 'walks'
TV[TENSE=pres, NUM=sg] -> 'sees' | 'likes'
IV[TENSE=pres,  NUM=pl] -> 'disappear' | 'walk'
TV[TENSE=pres, NUM=pl] -> 'see' | 'like'
IV[TENSE=past] -> 'disappeared' | 'walked'
TV[TENSE=past] -> 'saw' | 'liked'

類似於字典的規則,NUM,TENSE等就是屬性,press,sg,pl就是約束。這樣我們就能顯示指明'the some dogs'這樣的句子,而不是'the some dog'。

其中,sg代表單數,pl代表復數,?n代表不確定(皆可)。類似sg,pl這樣的特征值稱為原子,原子也可以是bool值,並且用+aux-aux分別表示True 和 False。

處理特征結構

NLTK的特征結構使用構造函數FeatStuct()來進行聲明,原子特征值可以是字符串或者整數。簡單示例:

fs1 = nltk.FeatStruct("[TENSE = 'past',NUM = 'sg',AGR=[NUM='pl',GND = 'fem']]")
print(fs1)
print(fs1['NUM'])#可以像字典那樣進行訪問

打印出來發現是矩陣形式。為了在矩陣中表示重入,可以在共享特征結構的地方加一個括號包圍的數字前綴,例如(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' ]           ]

結果可以看成一個圖結構,如果沒有這種(num),就是有向無環圖;有的話,就有環了。(附截圖)

有向有環圖

包含和統一

如果有兩種結構:

a.[num = 74]
b.[num = 74]
  [street = 'BeiJing']

那么b包含a。類似於集合運算,這個順序稱為包含

統一就是合並兩個結構,但是如果相同的屬性有不同的值,那么會返回None類型。

fs1 = nltk.FeatStruct(NUMBER = 74)
fs2 = nltk.FeatStruct(City = 'BeiJint')
#fs2 = nltk.FeatStruct(NUMBER= 45)#返回None
print(fs2.unify(fs1))

總結

在NLP中中,簡單的說:文法=語法=詞法+句法。

   它是語言學的一個分支,研究按確定用法來運用的"詞類"、"詞"的曲折變化或表示相互關系的其他手段以及詞在句中的功能和關系。包含詞的構詞、構形的規則和組詞成句的規則。

   由於,不同的文法框架在寫法上也有差異,所以在構造的時候需要具體查看相關文檔和庫的要求。這方面的編程,更多的是在規則的基礎上對詞和詞類的關系進行研究,並且不斷完善文法規則。


歡迎進一步交流本博文相關內容:

博客園地址 : http://www.cnblogs.com/AsuraDong/

CSDN地址 : http://blog.csdn.net/asuradong

也可以致信進行交流 : xiaochiyijiu@163.com

歡迎轉載 , 但請指明出處  :  )



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM