中文分詞預處理之N最短路徑法小結

 本文算法來自《基於N-最短路徑方法的中文詞語粗分模型》(張華平、劉羣,中文信息學報,16卷5期)。凡有不解處,當參考原文。


       漢語之魅力在於整齊而富有音律美。不像英文,單詞間長短不一,字與字之間還用空格隔開。話雖如此,可計算機處理起來,天然的空格有助於計算機迅速識別單詞間邊界。而中文,美則美矣,卻讓機器頗爲困惑。所以,中文分詞就自然而然的成了一切中文信息處理的必要前提。但是,由於中文往往會出現歧義、人(地)名、譯名等多種計算機難於處理的語言現象,所以一個最爲妥善的辦法便是先進行預處理,將所有可能的分詞結果一併給出。然後讓後續的程序來進一步處理這些結果。此預處理過程便是本文討論的重點。


       通常思維能夠想到的最直接的分詞方案是對着詞典挨個找。比如:老師說明天下午休息。我們通過跟詞典比較,能夠找到以下詞:
老師 / 說 / 明天 / 下午 / 休息
老師 / 說明 / 天下 / 午休 / 息
那麼我們的程序如何實現這樣一個想法呢?這並不簡單。這裏我們不對詞典結構做具體展開,僅僅討論如何在有詞典的情況下找出最佳的前幾個分詞可能性。這裏說的最佳是指,在所有最終給出的N個(比如說10個)最終分詞方案中,要儘可能地含有正確的分詞方案。這是一個很自然的要求,如果你預處理的結果集中沒有正確的答案,那後續的處理只能是錯上加錯。這就是所謂召回率的意思。


       我們說過,漢語很美,其實古漢語更美。他總是力圖使用最少的字來表達最豐富的含義。現代漢語傳承了古漢語的這一特點(雖然他比古漢語要醜很多)。所以,你會看到,一箇中文句子,總是詞越少越好(那些蹩腳翻譯家從那些從句套從句的英語句子翻譯出來的也是從句套從句的中文除外!不過,當然,這些句子也能用我們這裏的方法分詞就是。)這就是這個算法尋求最短路徑的依據所在。對於一個給定的句子,他試圖找出具有最少個數詞語的分詞結果。


       上面這段文字換一種說法就是,我們要找出第一個字到最後一個字之間的最短詞語路徑。這有點拗口,什麼叫詞語路徑?我們通常說的路徑是指從一個點到另一個點,而我們這裏說的詞語路徑,意思無非就是指從一個詞到另一個詞。理解我這裏說的意思便好,叫什麼無所謂。至此爲止,一個基於圖搜索的方案已經浮出水面,不過具體的點的意思可能和剛纔所說的並不完全一致。在這個圖中,點是詞(能夠從字典中找到的)之間的邊界,而相鄰點之間的路徑實質上就是詞首到詞尾連成的一個通路。


       還是以前面的例子爲例。我們首先認爲每一個字都可能是一個詞。所以,前面那句話共九個字,所以我們要有十個點。我們用更爲學術的方法來標記。用c[i]來表示各個字,i從1到9。這樣c[1]=老,c[2]=師,如此等等。再用v[j]來表示點,j從0到9,並且v[j - 1]到v[j]之間的路徑表示字c[j]。注意,我們還需要定義每條路徑的長度作爲其權重。簡單地,我們設每條路徑的長度都是1。這個原因很簡單,1表示這個詞詞典裏是有的,它是一個可能的方案。而對於那些詞典裏沒有的詞,它的長度是0,也就是,沒有這條路徑。這樣,我們就部分完成了這個圖的建立。


       但是,這個圖還沒有建好。我們還需要真正地把所有可能的詞找出來,並且把它們的路徑連起來(注意到,到目前爲止,我們的圖上只有相鄰的點之間有通路,任意其他點之間還沒有通路,等待我們建立)。比如,上面的例子中,老師是一個詞,那麼v[0]和v[2]兩個點之間應該有一條連線。如此一來,我們說,這個圖真正建成功了。


       當然,你完全可以質疑,爲什麼要把每條連線的權重都設爲1。這個質疑是很合理的,因爲沒有任何理由不能把各路徑的值設爲小數。另外一個更有力的質疑理由是:如果句子的長度增長,那麼我們將得到的結果集中可能會是一個很大的集合。雖然我們只找排名前N的結果,但具有相同最短路徑長度的結果我們視爲排名一樣,並且他們都擠在一起。比如,如果有兩個長度一樣的分詞方案都是排名第二,那麼他們並列第二名,並且不影響下面的成爲第三名。如果句子長度過長,並且每條連線的權重都是1,那麼每一個名次上都有很多種分詞方案堆積在一起,結果我們就得到一大堆結果。就比如,2分成兩個正整數之和只能是1+1,可如果是100分成兩個正整數之和呢?我們會得到一堆方案。所以,我們需要爲每條路徑設置不同的權重。至於如何設權重,以及具體尋找排名前N的最短路徑,我姑且留待下回分享。


前一篇我們已經根據詞典建立了一個圖,但是一個缺陷就是,我們主觀地設圖上各個路徑的長度都是1,這不是一個好的選擇。一個很自然的想法是,我們應當將一條路徑(其實也就是一個詞)的權重(或者說長度)與這個詞在這裏應該成爲一個詞的概率聯繫起來。比如說:今天下雨。我們看這樣一種分詞方案:


       (今)(天下)(雨)


這一條通路其實就是V[0] ---> V[1] ---> V[3] ---> V[4]。我們現在要考察V[1] ---> V[3] 這條路徑的權重,這實際上應該與(天下)這個詞在這裏成爲一個詞的概率(這樣的問題對於人腦而言的確過於小兒科,概率直接就是0。但是計算機並沒辦法知道這一點)聯繫在一起。說到概率,我們自然想到應當有一個充分大的語料庫,然後考察各個詞出現的頻率。這些都對,但是,事情其實還很複雜,因爲(天下)是否成爲一個詞,他關係到的不僅是(天)和(下)兩個字,他直接關係到整個句子的分詞。所以我們不能僅僅根據(天下)一詞出現的概率來給V[1] ---> V[3]這條路徑設置權重。


        我們還是按照通用的邏輯來描述這個事情,但這涉及一點數學基礎。接着前面的例子,我們的問題歸根到底就是得到字符串“今天下雨”被分解成(今)(天下)(雨)的概率。或者更學術一點,我們的問題就是已知一個字符串C,本來這個字符串是一個詞串,就是由一個一個詞組成的這麼一串,但是我們現在已經沒有了這個詞串的分解,就是說,這個詞串W已經退化成了原字符串C,我們的任務就是找到這個W,使得概率P(W | C)最大。因爲概率最大意思就是這種分詞方案是最可能的分詞方案。按照Bayes公式:P(W | C) = P(W) * P(C | W) / P(C)。由於對所有分詞方案而言,P(C)都是一樣的,所以我們不予考慮。又由於從詞串W恢復到字符串C的方式是唯一的,所以P(C | W) = 1。於是,我們只剩下P(W),我們就是要求他的最大概率。如果我們假設詞與詞之間是獨立的,那麼P(W)就等於各個詞w[j]的概率P(w[j])的乘積。


        我們再來集中討論P(w[j])的計算方法。我們這裏用的方法叫n-grams,千萬別被這個名字嚇到,其實他很簡單。當然,如果你願意通過查看原文獻來了解更多的細節,那麼你可以參考《Foundations of Statistical Natrual Language Processing》一書的6.2節。這個算法的核心思想就是,當前這個詞他能夠成爲一個詞,只與在他前面的n個詞相關。繼續前面的例子,如果我們使用的是2-grams(也叫bigrams),那麼以(雨)舉例。這裏(雨)究竟該不該以一個單獨的詞出現,他的概率就等於在語料庫中在(今)(天下)之後找到(雨)的概率。這裏有必要對語料庫稍作介紹。我們的語料庫不僅有着充分大的語句資源,而且這些句子都是分詞好了的,並且還經過了詞性標註。所以我們尋找前面的概率才成爲可能。如果我們用更學術的方法寫出來,就是P(w[j]) = P(w[j] | w[j - n + 1], . . . , w[j - 1])。這個表達方式可能會讓人困惑,因爲前面一個P和後面一個P的含義是不一樣的:前面的表示w[j]這個詞分的成功率,後面的P表示這些情況在語料庫中出現的比例。進一步地,因爲我們做過條件獨立假設,所以,P(w[j] | w[j - n + 1], . . . , w[j - 1]) = P(w[j - n + 1] , . . . , w[j] | w[j - n + 1], . . . , w[j - 1]) = P(w[j - n + 1] , . . . , w[j]) / P(w[j - n + 1] , . . . , w[j - 1])。所以,(雨)在這裏成爲一個詞,他的概率等於語料庫中(今)(天下)(雨)出現的機率除以(今)(天下)出現的機率。


        當然,明眼人一看就看出來了。我們這裏面臨一個問題就是,如果n很大,非常有可能的是,這n個詞串在語料庫中一次都沒出現過,這樣其值是0,而這個0值因爲乘法關係被傳遞到最終的結果中。那豈不是很糟糕?這的確是一個問題。在n-最短路徑中文分詞算法中,作者採用了分子分母同時加1的方法來做一個參數平滑處理。更多的參數平滑處理方法可以參考黃建中、王肖雷:Katz平滑算法在中文分詞系統中的應用(計算機工程,30卷)。


        還有最後一點:我們這裏用的是乘法,而最後各個路徑的和我們需要的是加法,我們需要將乘法轉換爲加法。這個很簡單,做對數操作就可以實現。所以,最後,一條路徑的權重應該是ln(((P(w[j - n + 1] , . . . , w[j]) + 1) / (P(w[j - n + 1] , . . . , w[j - 1]) + 1))。不過這與N-最短路徑的原文並不符合,這是比較奇怪的地方。但包括這篇原文引用的原文獻以及其他文獻中都是我們這裏給出的式子,所以我這裏暫且這麼寫,但不代表這個就是正確的。總之,這樣一個系統需要自己來實現一下,在自己實現的過程中,我們可以更清楚地瞭解各個細節。


        至於Dijkstra算法求最短路徑,這個比較簡單。我姑且留作下回分享。


現在,我們只剩下最後一步:求出一個圖中兩個點的最短N個路徑。教科書上已經有非常成熟的算法求解單向圖上兩點之間的最短路徑,這就是Dijkstra法。這個算法的想法是兩步走:


        第一步:計算各點到起始點的最短距離。這個思路就是先設各點到起始點的最短距離是無窮,當然,起始點本身是0。然後,從最接近起始點的點開個逐個計算。每計算一個點時,就是計算這個點的所有前面一個緊鄰的點加上該點與目前的這個點的距離,在所有這些求和值中找一個最小值。這個最小值就是該點距離起始點的最短距離。當然,很有可能在計算一個點時,這個點已經被賦了一個值。這個時候就把這個值與那些求和值一起比較,找出一個最小值,這個過程稱爲鬆弛技術。這個算法的思路是很自然的。不過,在教科書上,他有個不太優雅的名字:貪心算法。意思就是,每次都做最優的一個選擇,最後的問題解也必然是最優的。這有個前提:就是最優問題必須具備最優子問題,並且子問題不會相互影響。更詳細的介紹請參考教科書。


        第二步:從終點逐點回溯。這個過程很簡單,我們在鬆弛時記錄下每個點的前溯點即可。《算法導論》上給出了這些操作能夠得到最短路徑的詳細證明。有興趣的朋友可以閱讀一下。


         以上步驟是求出一條最短路徑,但我們現在的目標是找出N條最短路徑。這在操作上要麻煩一點,但想法還是很簡單的,同樣是上面的思路,只是每個點我們都找出前N個(而不是前1個)最短距離,並存儲N個前溯點。如此一來,我們的整個算法就設計完工了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章