1. 程式人生 > >【NLP】分詞演算法綜述

【NLP】分詞演算法綜述

之前總是在看前沿文章,真正落實到工業級任務還是需要實打實的硬核基礎,我司選用了HANLP作為分片語件,在使用的過程中才感受到自己基礎的薄弱,決定最近好好把分詞的底層演算法梳理一下。

1. 簡介

NLP的底層任務由易到難大致可以分為詞法分析、句法分析和語義分析。分詞是詞法分析(還包括詞性標註和命名實體識別)中最基本的任務,可以說既簡單又複雜。說簡單是因為分詞的演算法研究已經很成熟了,大部分的準確率都可以達到95%以上,說複雜是因為剩下的5%很難有突破,主要因為三點:

  1. 粒度,不同應用對粒度的要求不一樣,比如“蘋果手機”可以是一個詞也可以是兩個詞
  2. 歧義,比如“下雨天留人天留我不留”
  3. 未登入詞,比如“skrrr”、“打call”等新興詞語

然而,在真實的應用中往往會因為以上的難點造成分詞效果欠佳,進而影響之後的任務。對於追求演算法表現的童鞋來說,不僅要會調分詞包,也要對這些基礎技術有一定的瞭解,在做真正的工業級應用時有能力對分詞器進行調整。這篇文章不是著重介紹某個SOTA成果,而是對常用的分詞演算法(不僅是機器學習或神經網路,還包括動態規劃等)以及其核心思想進行介紹。

2. 分詞演算法

我認為分詞演算法根據其核心思想主要分為兩種,第一種是基於字典的分詞,先把句子按照字典切分成詞,再尋找詞的最佳組合方式;第二種是基於字的分詞,即由字構詞,先把句子分成一個個字,再將字組合成詞,尋找最優的切分策略,同時也可以轉化成序列標註問題。歸根結底,上述兩種方法都可以歸結為在圖或者概率圖上尋找最短路徑的問題。接下來將以“他說的確實在理

”這句話為例,講解各個不同的分詞演算法核心思想。

2.1 基於詞典的分詞

2.1.1 最大匹配分詞演算法

最大匹配分詞尋找最優組合的方式是將匹配到的最長詞組合在一起。主要的思路是先將詞典構造成一棵Trie樹,也稱為字典樹,如下圖:

Trie樹由詞的公共字首構成節點,降低了儲存空間的同時提升查詢效率。最大匹配分詞將句子與Trie樹進行匹配,在匹配到根結點時重新由下一個字開始進行查詢。比如正向(從左至右)匹配“他說的確實在理”,得出的結果為“他說/的確/實在/理”。如果進行反向最大匹配,則為“他/說/的/確實/在理”。

可見,詞典分詞雖然可以在O(n)時間對句子進行分詞,但是效果很差,在實際情況中基本不使用此種方法。

2.1.2 最短路徑分詞演算法

最短路徑分詞演算法首先將一句話中的所有詞匹配出來,構成詞圖(有向無環圖DAG),之後尋找從起始點到終點的最短路徑作為最佳組合方式,引用《統計自然語言處理》中的圖:

圖中每條邊的權重都為1。

在求解DAG圖的最短路徑問題時,總是要利用到一種性質:即兩點之間的最短路徑也包含了路徑上其他頂點間的最短路徑。比如S->A->B->E為S到E到最短路徑,那S->A->B一定是S到B到最短路徑,否則會存在一點C使得d(S->C->B)<d(S->A->B),那S到E的最短路徑也會變為S->C->B->E,這就與假設矛盾了。利用上述的最優子結構性質,可以利用貪心演算法或動態規劃兩種求解演算法:

  1. 最短路徑分詞演算法

基於Dijkstra演算法求解最短路徑。該演算法適用於所有帶權有向圖,求解源節點到其他所有節點的最短路徑,並可以求得全域性最優解。Dijkstra本質為貪心演算法,在每一步走到當前路徑最短的節點,遞推地更新原節點到其他節點的距離。但應用於分詞問題時,由於詞圖是帶權有向無環圖,無法求得全域性最優解。不過針對當前問題,Dijkstra演算法的計算結果為:“他/說/的/確實/在理“。可見最短路徑分詞演算法可以滿足部分分詞要求。

2. N-最短路徑分詞演算法

N-最短路徑分詞是對Dijkstra演算法的擴充套件,在每一步儲存最短的N條路徑,並記錄這些路徑上當前節點的前驅,在最後求得最優解時回溯得到最短路徑。該方法的準確率優於Dijkstra演算法,但在時間和空間複雜度上都更大。

2.1.3 基於n-gram model的分詞演算法

在前文的詞圖中,邊的權重都為1。而現實中卻不一樣,常用詞的出現頻率/概率肯定比罕見詞要大。因此可以將求解詞圖最短路徑的問題轉化為求解最大概率路徑的問題,即分詞結果為“最有可能的詞的組合“。計算詞出現的概率,僅有詞典是不夠的,還需要有充足的語料。因此分詞任務已經從單純的“演算法”上升到了“建模”,即利用統計學方法結合大資料探勘,對“語言”進行建模。

語言模型的目的是構建一句話出現的概率p(s),根據條件概率公式我們知道:

p(他說的確實在理)=p(他)p(說|他)p(的|他說)p(確|他說的)...p(理|他說的確實在)

而要真正計算“他說的確實在理”出現的概率,就必須計算出上述所有形如 p(w_n|w_1...w_{n-1}) n=1,...,6 的概率,計算量太過龐大,因此我們近似地認為:

p(s) = \prod_{i=1}^{l}p(w_i|w_1...w_{i-1})\approx \prod_{i=1}^{l}p(w_i|w_{i-1}) \\

其中 s=w_1w_2...w_lw_i 為字或單詞。我們將上述模型成為二元語言模型(2-gram model)。類似的,如果只對詞頻進行統計,則為一元語言模型。由於計算量的限制,在實際應用中n一般取3。

我們將基於詞的語言模型所統計出的概率分佈應用到詞圖中,可以得到詞的概率圖

對該詞圖用2.1.2中的演算法求解最大概率的路徑,即可得到分詞結果。

2.2 基於字的分詞

與基於詞典的分詞不同的是,基於字的分詞事先不對句子進行詞的匹配,而是將分詞看成序列標註問題,把一個字標記成B(Begin), I(Inside), O(Outside), E(End), S(Single)。因此也可以看成是每個字的分類問題,輸入為每個字及其前後字所構成的特徵,輸出為分類標記。對於分類問題,可以用統計機器學習或神經網路的方法求解。

統計機器學習方法通過一系列演算法對問題進行抽象,進而得到模型,再用得到的模型去解決相似的問題。也可以將模型看成一個函式,輸入X,得到f(X)=Y。另外,機器學習中一般將模型分為兩類:生成式模型和判別式模型,兩者的本質區別在於X和Y的生成關係。生成式模型以“輸出Y按照一定的規律生成輸入X”為假設對P(X,Y)聯合概率進行建模;判別式模型認為Y由X決定,直接對後驗概率P(Y|X)進行建模。兩者各有利弊,生成模型對變數的關係描述更加清晰,而判別式模型容易建立和學習。下面對幾種序列標註方法做簡要介紹。

2.2.1 生成式模型分詞演算法

生成式模型主要有n-gram模型、HMM隱馬爾可夫模型、樸素貝葉斯分類等。在分詞中應用比較多的是n-gram模型和HMM模型。如果將2.1.3中的節點由詞改成字,則可基於字的n-gram模型進行分詞,不過這種方法的效果沒有基於詞的效果要好。

HMM模型是常用的分詞模型,基於Python的jieba分詞器和基於Java的HanLP分詞器都使用了HMM。要注意的是,該模型建立的概率圖與上文中的DAG圖並不同,因為節點具有觀測概率,所以不能再用上文中的演算法求解,而應該使用Viterbi演算法求解最大概率的路徑。

2.2.2 判別式模型分詞演算法

判別式模型主要有感知機、SVM支援向量機、CRF條件隨機場、最大熵模型等。在分詞中常用的有感知機模型和CRF模型:

  1. 平均感知機分詞演算法

感知機是一種簡單的二分類線性模型,通過構造超平面,將特徵空間(輸入空間)中的樣本分為正負兩類。通過組合,感知機也可以處理多分類問題。但由於每次迭代都會更新模型的所有權重,被誤分類的樣本會造成很大影響,因此採用平均的方法,在處理完一部分樣本後對更新的權重進行平均。

2. CRF分詞演算法

CRF可以說是目前最常用的分詞、詞性標註和實體識別演算法,它對未登陸詞有很好的識別能力,但開銷較大。

2.2.3 神經網路分詞演算法

目前對於序列標註任務,公認效果最好的模型是BiLSTM+CRF。結構如圖:


利用雙向迴圈神經網路BiLSTM,相比於上述其它模型,可以更好的編碼當前字等上下文資訊,並在最終增加CRF層,核心是用Viterbi演算法進行解碼,以得到全域性最優解,避免B,S,E這種標記結果的出現。

3. 分詞演算法中的資料結構

前文主要講了分詞任務中所用到的演算法和模型,但在實際的工業級應用中,僅僅有演算法是不夠的,還需要高效的資料結構進行輔助。

3.1 詞典

中文有7000多個常用字,56000多個常用詞,要將這些資料載入到記憶體雖然容易,但進行高併發毫秒級運算是困難的,這就需要設計巧妙的資料結構和儲存方式。前文提到的Trie樹只可以在O(n)時間完成單模式匹配,識別出“的確”後到達Trie樹對也節點,句子指標接著指向“實”,再識別“實在”,而無法識別“確實”這個詞。如果要在O(n)時間完成多模式匹配,構建詞圖,就需要用到Aho-Corasick演算法將模式串預處理為有限狀態自動機,如模式串是he/she/his/hers,文字為“ushers”。構建的自動機如圖:

這樣,在第一次到葉節點5時,下一步的匹配可以直接從節點2開始,一次遍歷就可以識別出所有的模式串。

對於資料結構的儲存,一般可以用連結串列或者陣列,兩者在查詢、插入和刪除操作的複雜度上各有千秋。在基於Java的高效能分詞器HanLP中,作者使用雙陣列完成了Trie樹和自動機的儲存。

3.2 詞圖

圖作為一種常見的資料結構,其儲存方式一般有兩種:

  1. 鄰接矩陣

鄰接矩陣用陣列下標代表節點,值代表邊的權重,即d[i][j]=v代表節點i和節點j間的邊權重為v。如下圖:


用矩陣儲存圖的空間複雜度較高,在儲存稀疏圖時不建議使用。

2. 鄰接表

鄰接表對圖中的每個節點建立一個單鏈表,對於稀疏圖可以極大地節省儲存空間。第i個單鏈表中的節點表示依附於頂點i的邊,如下圖:


在實際應用中,尤其是用Viterbi演算法求解最優路徑時,由於是按照廣度優先的策略對圖進行遍歷,最好是使用鄰接表對圖進行儲存,便於訪問某個節點下的所有節點。

4. 總結

分詞作為NLP底層任務之一,既簡單又重要,很多時候上層演算法的錯誤都是由分詞結果導致的。因此,對於底層實現的演算法工程師,不僅需要深入理解分詞演算法,更需要懂得如何高效地實現。而對於上層應用的演算法工程師,在實際分詞時,需要根據業務場景有選擇地應用上述演算法,比如在搜尋引擎對大規模網頁進行內容解析時,對分詞對速度要求大於精度,而在智慧問答中由於句子較短,對分詞的精度要求大於速度。