《自然語言處理入門》12.依存句法分析--提取用戶評論

筆記轉載於GitHub項目https://github.com/NLP-LOVE/Introduction-NLP

12. 依存句法分析

語法分析(syntactic parsing )是自然語言處理中一個重要的任務,其目標是分析句子的語法結構並將其表示爲容易理解的結構(通常是樹形結構)。同時,語法分析也是所有工具性NLP任務中較爲高級、較爲複雜的一種任務。 通過掌握語法分析的原理、實現和應用,我們將在NLP工程師之路上跨越一道分水嶺。 本章將會介紹短語結構樹依存句法樹兩種語法形式,並且着重介紹依存句法分析的原理和實現。

12.1 短語結構樹

語言其實具備自頂而下的層級關係,固定數量的語法結構能夠生成無數句子。比如,僅僅利用下列兩個語法規律,我們就能夠生成所有名詞短語。

  • 名詞短語可以由名詞和名詞短語組成。
  • 名詞短語還可以由名詞和名詞組成。

例如,“上海+浦東+機場+航站樓”,所以,漢語中大部分句子都可以通過這樣的語法來生成。

在語言學中,這樣的語法被稱爲上下文無關文法,它由如下組件構成:

  • 終結符結合 Σ,比如漢語的一個詞表。
  • 非終結符集合 V,比如“名詞短語”“動詞短語”等短語結構組成的集合。V 中至少包含一個特殊的非終結符,即句子符或初始符,計作 S。
  • 推到規則 R,即推到非終結符的一系列規則: V -> V U Σ。

基於上下文無關文法理論,我們可以從 S 出發,逐步推導非終結符。一個非終結符至少產生一個下級符號,如此一層一層地遞推下去,我們就得到了一棵語法樹。但在NLP中,我們稱其爲短語結構樹。也就是說,計算機科學中的術語“上下文無關文法”在語言學中被稱作“短語結構語法”。

  1. 短語結構樹

    短語結構語法描述瞭如何自頂而下的生成一個句子,反過來,句子也可以用短語結構語法來遞歸的分解。層級結構其實是一種樹形結構,例如這句話“上海 浦東 開發 與 法制 建設 同步”,分解成如下圖的短語結構樹:

    這樣的樹形結構稱爲短語結構樹,相應的語法稱爲*短語結構語法**或上下文無關文法。至於樹中的字母下面開始介紹。

  2. 賓州樹庫和中文樹庫

    語言學家制定短語結構語法規範,將大量句子人工分解爲樹形結構,形成了一種語料庫,稱爲樹庫( treebank )。常見的英文樹庫有賓州樹庫,相應地,中文領域有CTB。上圖中葉子節點(詞語)的上級節點爲詞性,詞性是非終結符的一種,滿足“詞性生成詞語”的推導規則。

    常見的標記如下:

    標記 釋義
    IP-HLN 單句-標題
    NP-SBJ 名詞短語-主語
    NP-PN 名詞短語-代詞
    NP 名詞短語
    VP 動詞短語

    但是由於短語結構語法比較複雜,相應句法分析器的準確率並不高,現在研究者絕大部分轉向了另一種語法形式。

12.2 依存句法樹

不同於短語結構樹,依存句法樹並不關注如何生成句子這種宏大的命題。依存句法樹關注的是句子中詞語之間的語法聯繫,並且將其約束爲樹形結構。

  1. 依存句法理論

    依存語法理論認爲詞與詞之間存在主從關係,這是一種二元不等價的關係。在句子中,如果一個詞修飾另一個詞,則稱修飾詞爲從屬詞( dependent ),被修飾的詞語稱爲支配詞(head),兩者之間的語法關係稱爲依存關係( dependency relation)。比如句子“大夢想”中形容詞“大”與名詞“夢想"之間的依存關係如圖所示:

    圖中的箭頭方向由支配詞指向從屬詞,這是可視化時的習慣。將一個句子中所有詞語的依存關係以有向邊的形式表示出來,就會得到一棵樹,稱爲依存句法樹( dependency parse tree)。比如句子“弱小的我也有大夢想”的依存句法樹如圖所示。

    現代依存語法中,語言學家 Robinson 對依存句法樹提了 4 個約束性的公理。

    • 有且只有一個詞語(ROOT,虛擬根節點,簡稱虛根)不依存於其他詞語。
    • 除此之外所有單詞必須依存於其他單詞。
    • 每個單詞不能依存於多個單詞。
    • 如果單詞 A 依存於 B,那麼位置處於 A 和 B 之間的單詞 C 只能依存於 A、B 或 AB 之間的單詞。

    這 4 條公理分別約束了依存句法樹(圖的特例)的根節點唯一性、 連通、無環和投射性( projective )。這些約束對語料庫的標註以及依存句法分析器的設計奠定了基礎。

  2. 中文依存句法樹庫

    目前最有名的開源自由的依存樹庫當屬UD ( Universal Dependencies),它以“署名-非商業性使用-相同方式共享4.0”等類似協議免費向公衆授權。UD是個跨語種的語法標註項目,一共有 200 多名貢獻者爲 70 多種語言標註了 100 多個樹庫。具體到中文,存在4個不同領域的樹庫。本章選取其中規模最大的 UD_ Chinese GSD 作爲示例。該樹庫的語種爲繁體中文,將其轉換爲簡體中文後,供大家下載使用。

    http://file.hankcs.com/corpus/chs-gsd-ud.zip

    該樹庫的格式爲 CoNLL-U,這是一種以製表符分隔的表格格式。CoNLL-U 文件有10列,每行都是一個單詞, 空白行表示句子結束。單元中的下劃線 _ 表示空白, 結合其中一句樣例,解釋如表所示。

    詞性標註集合依存關係標註集請參考 UD 的官方網站:

    http://niversaldependencies.org/guidelines.html

    另一份著名的語料庫依然是 CTB,只不過需要額外利用一些工具將短語結構樹轉換爲依存句法樹。讀者可以直接下載轉換後的 CTB 依存句法樹庫,其格式是類似於 CoNLl-U 的 CoNLL。

  3. 依存句法樹的可視化

    工具如下:

    • 南京大學湯光超開發的 Dependency Viewer。導入 .conll 擴展名的樹庫文件即可。
    • brat 標註工具。

    可視化工具可以幫助我們理解句法樹的結構,比較句子之間的不同。

12.3 依存句法分析

依存句法分析( dependency parsing )指的是分析句子的依存語法的一種中高級 NLP任務,其輸人通常是詞語和詞性,輸出則是一棵依存句法樹。 本節介紹實現依存句法分析的兩種宏觀方法,以及依存句法分析的評價指標。

  1. 基於圖的依存句法分析

    正如樹是圖的特例一樣,依存句法樹其實是完全圖的一個子圖。如果爲完全圖中的每條邊是否屬於句法樹的可能性打分,然後就可以利用 Prim 之類的算法找出最大生成樹( MST )作爲依存句法樹了。這樣將整棵樹的分數分解( factorize )爲每條邊上的分數之和,然後在圖上搜索最優解的方法統稱爲基於圖的算法。

    在傳統機器學習時代,基於圖的依存句法分析器往往面臨運行開銷大的問題。這是由於傳統機器學習所依賴的特徵過於稀疏,訓練算法需要在整個圖上進行全局的結構化預測等。考慮到這些問題,另一種基於轉移的路線在傳統機器學習框架下顯得更加實用。

  2. 基於轉移的依存句法分析

    我們以“人 吃 魚”這個句子爲例子,手動構建依存句法樹。

    • 從“吃”連線到“人”建立依存關係,主謂關係。
    • 從“吃”連線到“魚”建立依存關係,動賓關係。

    如此,我們將一棵依存句法樹的構建過程表示爲兩個動作。如果機器學習模型能夠根據句子的某些特徵準確地預測這些動作,那麼計算機就能夠根據這些動作拼裝出正確的依存句法樹了。這種拼裝動作稱爲轉移( transition),而這類算法統稱爲基於轉移的依存句法分析

12.4 基於轉移的依存句法分析

  1. Arc-Eager 轉移系統

    一個轉移系統 S 由 4 個部件構成: S = (C,T,Cs,Ct),其中:

    • C 是系統狀態的集合
    • T 是所有可執行的轉移動作的集合。
    • Cs 是一個初始化函數
    • Ct 爲一系列終止狀態,系統進入該狀態後即可停機輸出最終的動作序列。

    而系統狀態又由 3 元祖構成: C = (σ,β,A) 其中:

    • σ 爲一個存儲單詞的棧。
    • β 爲存儲單詞的隊列
    • A 爲已確定的依存弧的集合。

    Arc-Eager 轉移系統的轉移動作集合詳見下表:

    動作名稱 條件 解釋
    Shift 隊列 β 非空 將隊首單詞 i 壓棧
    LeftArc 棧頂單詞 i 沒有支配詞 將棧頂單詞 i 的支配詞設爲隊首單詞 j,即 i 作爲 j 的子節點
    RightArc 隊首單詞 j 沒有支配詞 將隊首單詞 j 的支配詞設爲棧頂單詞 i,即 j 作爲 i 的子節點
    Reduce 棧頂單詞 i 已有支配詞 將棧頂單詞 i 出棧

    對於上面的“人 吃 魚”案例,Arc-Eager 的執行步驟如下:

    裝填編號 σ 轉移動作 β A
    0 [] 初始化 [人,吃,魚,虛根] {}
    1 [人] Shift [吃,魚,虛根] {}
    2 [] LeftArc(主謂) [吃,魚,虛根] {}\{人\xleftarrow{主謂}吃\}
    3 [吃] Shift [魚,虛根] {}\{人\xleftarrow{主謂}吃\}
    4 [吃,魚] RightArc(動賓) [虛根] {,}\{人\xleftarrow{主謂}吃,吃\xrightarrow{動賓}魚\}
    5 [吃] Reduce [虛根] {,}\{人\xleftarrow{主謂}吃,吃\xrightarrow{動賓}魚\}
    6 [] LeftArc(核心) [虛根] {,,}\{人\xleftarrow{主謂}吃,吃\xrightarrow{動賓}魚,吃\xleftarrow{核心}虛根\}

    此時集合 A 中的依存弧爲一顆依存句法樹。

  2. 訓練原理

    對基於轉移的依存句法分析器而言,它學習和預測的對象是一系列轉移動作。然而依存句法樹庫是一棵樹,並不是現成的轉移動作序列。這時候就需要一個算法將語料庫中的依存句法樹轉移爲正確地轉移動作序列。

    這裏可以使用感知機進行訓練得到轉移動作序列,原理詳見:

    5. 感知機分類與序列標註

    訓練句法分析器時,結構化感知機算法迭代式的優化線性模型,目標是使其將最高的分值賦予可抵達正確句法樹的轉移序列。

    訓練分爲以下幾個步驟:

    • 讀入一個訓練樣本,提取特徵,創建 ArcEager 的初始狀態 c。
    • 若 c 不是終止狀態,反覆進行轉移序列,修正參數。
    • 算法終止,返回返回模型參數 w。

12.5 依存句法分析 API

  1. 訓練模型

    本節使用的語料庫是 CTB8.0,運行代碼的時候會自動下載語料庫: train_parser.py

    https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch12/train_parser.py

    訓練時間比較長,結果如下:

    1 人 人 N NN _ 2 nsubj _ _
    2 吃 吃 V VV _ 0 ROOT _ _
    3 魚 魚 N NN _ 2 dobj _ _
    UAS=83.3% LAS=81.0%
    
  2. 標準化評測

    給定兩棵樹,一棵樹爲標準答案(來自測試集),一棵樹爲預測結果,評測的目標是衡量這兩棵樹的差異。如果將樹的節點編號,拆解爲依存弧並分別存入兩個集合 A ( 標準答案)和 B (預測結果),則可以利用分類任務的 F1 評價指標。

    依存句法分析任務採用的評測指標爲 UAS (unlabeled atachment score) 和 LAS (labeled attachment score ),分別對應忽略標籤和包括標籤的 F1 值。以 LAS 爲例,具體計算方式如下:
    P=ABBR=ABALAS=2PRP+R P=\frac{|A\cap B|}{|B|}\\ R=\frac{|A\cap B|}{|A|}\\ LAS=\frac{2*P*R}{P+R}
    UAS 的計算也是同理,只不過將每條依存弧上的標籤去掉後放人集合參與運算即可。相較於 LAS, UAS 僅僅衡量支配詞的預測準確率,不衡量依存關係的準確率,一般分數更高。

    在上面的訓練模型中已經做了評測

    UAS=83.3% LAS=81.0%
    

    這個分數說明,在測試集上有 83% 的支配詞被準確預測,有 81% 的依存弧被準確預測。

12.6 案例: 基於依存句法分析的意見抽取

其實許多人都有一個疑問,依存句法分析究竟可以用來幹什麼。本節就來利用依存句法分析實現一個意見抽取的例子,提取下列商品評論中的屬性和買家評價。

電池非常棒,機身不長,長的是待機,但是屏幕分辨率不高。

爲了提取“電池”“機身”“待機”和“分辨率”所對應的意見,樸素的處理方式是在分司和詞性標註之後編寫正則表達式,提取名詞後面的形容詞。然而正則表達式無法處理“長的是待機”這樣句式靈活的例子。

這時就可以對這句話進行依存句法分析,分析代碼如下:

from pyhanlp import *

CoNLLSentence = JClass('com.hankcs.hanlp.corpus.dependency.CoNll.CoNLLSentence')
CoNLLWord = JClass('com.hankcs.hanlp.corpus.dependency.CoNll.CoNLLWord')
IDependencyParser = JClass('com.hankcs.hanlp.dependency.IDependencyParser')
KBeamArcEagerDependencyParser = JClass('com.hankcs.hanlp.dependency.perceptron.parser.KBeamArcEagerDependencyParser')

parser = KBeamArcEagerDependencyParser()
tree = parser.parse("電池非常棒,機身不長,長的是待機,但是屏幕分辨率不高。")
print(tree)

運行結果如下:

1	電池	電池	N	NN	_	3	nsubj	_	_
2	非常	非常	A	AD	_	3	advmod	_	_
3	棒	棒	V	VA	_	0	ROOT	_	_
4	,	,	P	PU	_	3	punct	_	_
5	機身	機身	N	NN	_	7	nsubj	_	_
6	不	不	A	AD	_	7	neg	_	_
7	長	長	V	VA	_	3	conj	_	_
8	,	,	P	PU	_	7	punct	_	_
9	長	長	V	VA	_	11	top	_	_
10	的	的	D	DEC	_	9	cpm	_	_
11	是	是	V	VC	_	7	conj	_	_
12	待機	待機	N	NN	_	11	attr	_	_
13	,	,	P	PU	_	3	punct	_	_
14	但是	但是	A	AD	_	18	advmod	_	_
15	屏幕	屏幕	N	NN	_	16	nn	_	_
16	分辨率	分辨率	N	NN	_	18	nsubj	_	_
17	不	不	A	AD	_	18	neg	_	_
18	高	高	V	VA	_	3	conj	_	_
19	。	。	P	PU	_	3	punct	_	_

進行可視化後:

仔細觀察,不難發現“電池”與“棒”、“機身”與“長”、“分辨率”與“高”之間的依存關係都是 nsubj (名詞性主語)。

  1. 利用這一規律, 不難寫出第一版遍歷算法, 也就是用個for 循環去遍歷樹中的每個節點。對於算法遍歷樹中的每一個詞語, 如果其詞性爲名詞且作爲某個形容詞的名詞性主語,則認爲該名詞是屬性,而形容詞是意見。運行代碼如下:

    def extactOpinion1(tree):
        for word in tree.iterator():
            if word.POSTAG == "NN" and word.DEPREL == "nsubj":
                print("%s = %s" % (word.LEMMA, word.HEAD.LEMMA))
    
    print("第一版")
    extactOpinion1(tree)
    

    結果如下:

    第一版
    電池 = 棒
    機身 = 長
    分辨率 = 高
    
  2. 雖然的確提取出了一些意見,然而後兩個都是錯誤的。這一版算法存在的問題之一是沒有考慮到“機身不長””“分辨率不高"等否定修飾關係。否定修飾關係在依存句法中的標記爲 neg,於是我們只需檢查形容詞是否存在否定修飾的支配詞即可。於是得出第二版算法:

    def extactOpinion2(tree):
        for word in tree.iterator():
            if word.POSTAG == "NN" and word.DEPREL == "nsubj":
                if tree.findChildren(word.HEAD, "neg").isEmpty():
                    print("%s = %s" % (word.LEMMA, word.HEAD.LEMMA))
                else:
                    print("%s = 不%s" % (word.LEMMA, word.HEAD.LEMMA))
    
    print("第二版")
    extactOpinion2(tree)
    

    結果如下:

    第二版
    電池 = 棒
    機身 = 不長
    分辨率 = 不高
    
  3. 接下來思考如何提取“待機”的意見,“待機”與“長”之間的公共父節點爲“是”,於是我們得到第三版算法如下:

    def extactOpinion3(tree):
        for word in tree.iterator():
            if word.POSTAG == "NN":
              
              	# 檢測名詞詞語的依存弧是否是“屬性關係”,
                # 如果是,則尋找支配詞的子節點中的主題詞
                # 以該主題詞作爲名詞的意見。
                if word.DEPREL == "nsubj":  # ①屬性
    
                    if tree.findChildren(word.HEAD, "neg").isEmpty():
                        print("%s = %s" % (word.LEMMA, word.HEAD.LEMMA))
                    else:
                        print("%s = 不%s" % (word.LEMMA, word.HEAD.LEMMA))
                elif word.DEPREL == "attr":
                    top = tree.findChildren(word.HEAD, "top")  # ②主題
    
                    if not top.isEmpty():
                        print("%s = %s" % (word.LEMMA, top.get(0).LEMMA))
    
    print("第三版")
    extactOpinion3(tree)
    

    結果如下:

    第三版
    電池 = 棒
    機身 = 不長
    待機 = 長
    分辨率 = 不高
    

至此,4 個屬性被完整正確地提取出來了,讀者可以嘗試蒐集更多的句子,通過分析句法結構總結更多的提取規則。

12.7 GitHub

HanLP何晗–《自然語言處理入門》筆記:

https://github.com/NLP-LOVE/Introduction-NLP

項目持續更新中…

目錄


章節
第 1 章:新手上路
第 2 章:詞典分詞
第 3 章:二元語法與中文分詞
第 4 章:隱馬爾可夫模型與序列標註
第 5 章:感知機分類與序列標註
第 6 章:條件隨機場與序列標註
第 7 章:詞性標註
第 8 章:命名實體識別
第 9 章:信息抽取
第 10 章:文本聚類
第 11 章:文本分類
第 12 章:依存句法分析
第 13 章:深度學習與自然語言處理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章