1. 程式人生 > >資料探勘基礎-2.中文分詞

資料探勘基礎-2.中文分詞

一、中文分詞基礎

• 切開的開始位置對應位是1,否則對應位是0,來表示“有/意見/分歧”的bit內容是:11010,通過識別1後面幾個0,就可以知道有幾個字切在一塊。

• 還可以用一個分詞節點序列來表示切分方案,例如“有/意見/分歧”的分詞節點序列是{0,1,3,5},代表0-1為一起,1-3為一起,3-5為一起,有向無環圖DAG就是採用這種方式。

1.常用方法

​• 最常見的分詞方法是基於詞典匹配

–最大長度查詢(前向查詢,後向查詢)

前向:首先有一個詞典,句子從前往後切分,如果有存在一個最大長度的詞在詞典中,就在這切分。

後向:類似於前向。一般來說後向切分效果會好一點,因為中文重心一般位於句子後面。

資料結構

– 為了提高查詢效率,不要逐個匹配詞典中的詞。否則每個詞都需要遍歷一下詞典。

– 查詢詞典所佔的時間可能佔總分詞時間的1/3,為了保證切分速度,需要優化查詢詞典方法。

Trie常用於加速分詞查詢詞典問題,查到了某個字只需要向下再查有沒有更長的詞,而不需要再遍歷整個詞表看看整個詞是否存在。

2.有向無環圖:(DAG)

例如:0:[0,1,3],表示0到1是個詞,0到3是個詞,自身也可以是個詞。

3.概率語言模型

• 假設需要分出來的詞在語料庫和詞表中都存在,最簡單的方法是按詞計算概率。

• 從統計思想的角度來看,分詞問題的輸入是一個字串C=c1,c2……cn ,輸出是一個詞串S=w1,w2……wm ,其中m<=n。對於一個特定的字串C,會有多個切分方案S對應,分詞的任務就是在這些S中找出一個切分方案S,使得P(S|C)的值最大。

• P(S|C)就是由字串C產生切分S的概率,也就是對輸入字串切分出最有可能的詞序列。

• P(C)只是一個用來歸一化的固定值,即這個句子在語料庫中佔的比例。從詞串恢復到漢字串的概率只有一種可能,所以P(C|S)=1。比較P(S1|C)和P(S2|C)的大小變成比較P(S1)和P(S2) 的大小。

• 例如:對於輸入字串C“南京市長江大橋”,有下面兩種切分可能:

– S1:南京市 / 長江 / 大橋

– S2:南京 / 市長 / 江大橋

• 這兩種切分方法分別叫做S1和S2。計算條件概率P(S1|C)和P(S2|C),然後根據P(S1|C)和P(S2|C)的值來決定選擇S1還是S2。P(C)是字串在語料庫中出現的概率。比如說語料庫中有1萬個句子,其中有一句是 “南京市長江大橋” 那麼P(C)=P(“南京市長江大橋” )=萬分之一。

• 假設每個詞出現的概率互相獨立,因為P(S1)=P(南京市,長江,大橋)=P(南京市)*P(長江)*P(大橋)> P(S2)=P(南京,市長,江大橋),所以選擇切分方案S1。

4.一元模型

假設每個詞之間的概率是上下文無關:

• 對於不同的S,m(分詞的數量)的值是不一樣的,一般來說m越大,P(S)會越小。也就是說,分出的詞越多,概率越小。(但是也不一定,只是有這個傾向)

• 因此:logP(wi ) = log(Freq w ) - logN

• 這個P(S)的計算公式也叫做基於一元模型的計算公式,它綜合考慮了切分出的詞數和詞頻。

P(S) = P(w1,w 2,...,wm ) P(w1)×P(w 2 )×...×P(wm )logP(w1) +logP(w 2 ) +...+ logP(wm )

• 其中,P(w) 就是這個詞出現在語料庫中的概率。因為函式y=log(x),當x增大,y也會增大,所以是單調遞增函式。 ∝是正比符號。因為詞的概率小於1,所以取log後是負數。

• 最後算 logP(w)。取log是為了防止向下溢位,如果一個數太小,例如0.00000000000 0000000000000000001 可能會向下溢位。

• 如果這些對數值事前已經算出來了,則結果直接用加法就可以得到,加法比乘法速度更快。

5.N元模型

• 假設在日本,[和服]也是一個常見的詞。按照一元概率分詞,可能會把“產品和服務”分成[產品][和服][務]。為了切分更準確,要考慮詞所處的上下文。

• N元模型使用n個單片語成的序列來衡量切分方案的合理性:

• 估計單詞w1後出現w2的概率。根據條件概率的定義:

• 可以得到:P(w1,w2)= P(w1)P(w2|w1),同理:P(w1,w2,w3)= P(w1,w2)P(w3|w1,w2)

• 所以有:P(w1,w2,w3)= P(w1)P(w2|w1)P(w3|w1,w2)

• 更加一般的形式:

P(S)=P(w1,w2,...,wn)= P(w1)P(w2|w1)P(w3|w1,w2)…P(wn|w1w2…wn-1)

這叫做概率的鏈規則。

• 如果一個詞的出現不依賴於它前面出現的詞,叫做一元模型(Unigram)

• 如果簡化成一個詞的出現僅依賴於它前面出現的一個詞,那麼就稱為二元模型(Bigram)。

P(S)=P(w1,w2,...,wn)=P(w1) P(w2|w1) P(w3|w1,w2)…P(wn|w1w2…wn-1)≈P(w1) P(w2|w1) P(w3|w2)…P(wn|wn-1)

• 如果簡化成一個詞的出現僅依賴於它前面出現的兩個詞,就稱之為三元模型(Trigram)

二、Jieba分詞

1.介紹

jieba分詞主要是基於統計詞典,構造一個字首詞典;然後利用字首詞典對輸入句子進行切分,得到所有的切分可能,根據切分位置,構造一個有向無環圖;通過動態規劃演算法,計算得到最大概率路徑,也就得到了最終的切分形式。

• 支援三種分詞模式

– 精確模式:將句子最精確的分開,適合文字分析

– 全模式:句子中所有可以成詞的詞語都掃描出來,速度快,不能解決歧義

– 搜尋引擎模式:在精確模式基礎上,對長詞再次切分,提高召回

• 支援繁體分詞

• 支援自定義字典

• 基於Trie樹結構實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖(DAG)

• 採用了動態規劃查詢最大概率路徑, 找出基於詞頻的最大切分組合

對於未登入詞,採用了基於漢字成詞能力的HMM模型,使用了Viterbi演算法

2.Jieba詞典載入

以“去北京大學玩”為例,作為待分詞的輸入文字。

離線統計的詞典形式如下,每一行有三列,第一列是詞,第二列是詞頻,第三列是詞性。

例如:

北京大學 2053 nt

大學 20025 n

去 123402 v

玩 4207 v

北京 34488 ns

3.字首詞典構建

首先是基於統計詞典構造字首詞典,如統計詞典中的詞“北京大學”的字首分別是“北”、“北京”、“北京大”、“北京大學”;詞“大學”的字首是“大”、“大學”。統計詞典中所有的詞形成的字首詞典如下所示,你也許會注意到“北京大”作為“北京大學”的字首,但是它的詞頻卻為0,這是為了便於後面有向無環圖的構建。

北京大學 2053

北京大 0

大學 20025

去 123402

玩 4207

北京 34488

北 17860

京 6583

大 144099

學 17482

4.有向無環圖構建

然後基於字首詞典,對輸入文字進行切分,對於“去”,沒有字首,那麼就只有一種劃分方式;對於“北”,則有“北”、“北京”、“北京大學”三種劃分方式;對於“京”,也只有一種劃分方式;對於“大”,則有“大”、“大學”兩種劃分方式,依次類推,可以得到以每個字開始的字首詞的劃分方式。

在jieba分詞中,對每個字都是通過在文字中的位置來標記的,因此可以構建一個以位置為key,相應劃分的末尾位置構成的列表為value的對映,如下所示:

0: [0]

1: [1,2,4]

2: [2]

3: [3,4]

4: [4]

5: [5]

對於0: [0],表示位置0對應的詞,就是0 ~ 0,就是“去”;對於1: [1,2,4],表示位置1開始,在1,2,4位置都是詞,就是1 ~ 1,1 ~ 2,1 ~ 4,即“北”,“北京”,“北京大學”這三個詞。對於每一種劃分,都將相應的首尾位置相連,例如,對於位置1,可以將它與位置1、位置2、位置4相連線,最終構成一個有向無環圖,如下所示:

https://images2015.cnblogs.com/blog/668850/201611/668850-20161115201058029-666718440.png

5.最大概率路徑計算

在得到所有可能的切分方式構成的有向無環圖後,我們發現從起點到終點存在多條路徑,多條路徑也就意味著存在多種分詞結果,例如:

# 路徑10 -> 1 -> 2 -> 3 -> 4 -> 5# 分詞結果1,去 / / / / /

# 路徑20 -> 1 , 2 -> 3 -> 4 -> 5 # 分詞結果2,去 / 北京 / / /

# 路徑30 -> 1 , 2 -> 3 , 4 -> 5  # 分詞結果3,去 / 北京 / 大學 /

# 路徑40 -> 1 , 2 , 3 , 4 -> 5   # 分詞結果4,去 / 北京大學 /

因此,我們需要計算最大概率路徑,也即按照這種方式切分後的分詞結果的概率最大。在計算最大概率路徑時,jieba分詞采用從後往前這種方式進行計算。為什麼採用從後往前這種方式計算呢?這裡反向是因為漢語句子的重心經常落在後面, 就是落在右邊, 因為通常情況下形容詞太多, 後面的才是主幹, 因此, 從右往左計算, 正確率要高於從左往右計算, 這個類似於逆向最大匹配。

在採用動態規劃計算最大概率路徑時,每到達一個節點,它前面的節點到終點的最大路徑概率已經計算出來。

6.原始碼分析

演算法流程

jieba.__init__.py中實現了jieba分詞介面函式cut(self, sentence, cut_all=False, HMM=True)。

jieba分詞介面主入口函式,會首先將輸入文字解碼為Unicode編碼,然後根據入參,選擇不同的切分方式,本文主要以精確模式進行講解,因此cut_all和HMM這兩個入參均為預設值;

切分方式選擇:

re_han = re_han_default

re_skip = re_skip_default

塊切分方式選擇:

cut_block = self.__cut_DAG

函式__cut_DAG(self, sentence)首先構建字首詞典,其次構建有向無環圖,然後計算最大概率路徑,最後基於最大概率路徑進行分詞,如果遇到未登入詞,則呼叫HMM模型進行切分。

字首詞典構建

get_DAG(self, sentence)函式會首先檢查系統是否初始化,如果沒有初始化,則進行初始化。在初始化的過程中,會構建字首詞典。

構建字首詞典的入口函式是gen_pfdict(self, f),解析離線統計詞典文字檔案,每一行分別對應著詞、詞頻、詞性,將詞和詞頻提取出來,以詞為key,以詞頻為value,加入到字首詞典中。對於每個詞,再分別獲取它的字首詞,如果字首詞已經存在於字首詞典中,則不處理;如果該字首詞不在字首詞典中,則將其詞頻置為0,便於後續構建有向無環圖。

jieba分詞中gen_pfdict函式實現如下:

# f是離線統計的詞典檔案控制代碼,檔案控制代碼對於開啟的檔案是唯一的識別依據

def gen_pfdict(self, f):

    # 初始化字首詞典

    lfreq = {}

    ltotal = 0應該是統計總共的詞數,用來計算詞頻

    f_name = resolve_filename(f) #獲得檔案的名字,即dict.txt

    for lineno, line in enumerate(f, 1):

        try:

            # 解析離線詞典文字檔案

            line = line.strip().decode('utf-8')

            # 詞和對應的詞頻

            word, freq = line.split(' ')[:2]

            freq = int(freq)

            lfreq[word] = freq

            ltotal += freq

            # 獲取該詞所有的字首詞

            for ch in xrange(len(word)):

                wfrag = word[:ch + 1]

                # 如果某字首詞不在字首詞典中,則將對應詞頻設定為0,

                # 如第2章中的例子“北京大”

                if wfrag not in lfreq:

                    lfreq[wfrag] = 0

        except ValueError:

            raise ValueError(

                'invalid dictionary entry in %s at Line %s: %s' % (f_name, lineno, line))

    f.close()

    return lfreq, ltotal

為什麼jieba沒有使用trie樹作為字首詞典儲存的資料結構?

對於get_DAG()函式來說,用Trie資料結構,特別是在Python環境,記憶體使用量過大。經實驗,可構造一個字首集合解決問題。

該集合儲存詞語及其字首,如set(['數', '資料', '資料結', '資料結構'])。在句子中按字正向查詢詞語,在字首列表中就繼續查詢,直到不在字首列表中或超出句子範圍。大約比原詞庫增加40%詞條。

有向無環圖構建

有向無環圖,directed acyclic graphs,簡稱DAG,是一種圖的資料結構,顧名思義,就是沒有環的有向圖。DAG在分詞中的應用很廣,無論是最大概率路徑,還是其它做法,DAG都廣泛存在於分詞中。因為DAG本身也是有向圖,所以用鄰接矩陣來表示是可行的,但是jieba採用了Python的dict結構,可以更方便的表示DAG。最終的DAG是以{k : [k , j , ..] , m : [m , p , q] , ...}的字典結構儲存,其中k和m為詞在文字sentence中的位置,k對應的列表存放的是文字中以k開始且詞sentence[k: j + 1]在字首詞典中的 以k開始j結尾的詞的列表,即列表存放的是sentence中以k開始的可能的詞語的結束位置,這樣通過查詢字首詞典就可以得到詞。

get_DAG(self, sentence)函式進行對系統初始化完畢後,會構建有向無環圖。

從前往後依次遍歷文字的每個位置,對於位置k,首先形成一個片段,這個片段開始只包含位置k的字,然後就判斷該片段是否在字首詞典中:

1.如果這個片段在字首詞典中

1.1 如果詞頻大於0,就將這個位置i追加到以kkey的一個列表中;

1.2 如果詞頻等於0,如同第2章中提到的“北京大”,則表明字首詞典存在這個字首,但是統計詞典並沒有這個詞,繼續迴圈;

2.如果這個片段不在字首詞典中,則表明這個片段已經超出統計詞典中該詞的範圍,則終止迴圈;

3.然後該位置加1,然後就形成一個新的片段,該片段在文字的索引為[k:i+1],繼續判斷下一個片段是否在字首詞典中。

jieba分詞中get_DAG函式實現如下,

# 有向無環圖構建主函式

def get_DAG(self, sentence):

    # 檢查系統是否已經初始化

    self.check_initialized()

    # DAG儲存向無環圖的資料,資料結構是dict

    DAG = {}

    N = len(sentence)

    # 依次遍歷文字中的每個位置

    for k in xrange(N):

        tmplist = []

        i = k

        # 位置k形成的片段

        frag = sentence[k]

        # 判斷片段是否在字首詞典中

        # 如果片段不在字首詞典中,則跳出本迴圈

        # 也即該片段已經超出統計詞典中該詞的長度

        while i < N and frag in self.FREQ:

            # 如果該片段的詞頻大於0

            # 將該片段加入到有向無環圖中

            # 否則,繼續迴圈

            if self.FREQ[frag]:

                tmplist.append(i)

            # 片段末尾位置加1

            i += 1

            # 新的片段較舊的片段右邊新增一個字

            frag = sentence[k:i + 1]

        if not tmplist:

            tmplist.append(k)

        DAG[k] = tmplist

    return DAG

以“去北京大學玩”為例,最終形成的有向無環圖為:

{0: [0], 1: [1,2,4], 2: [2], 3: [3,4], 4: [4], 5: [5]}

最大概率路徑計算

有向無環圖DAG的每個節點,都是帶權的,對於在字首詞典裡面的詞語,其權重就是它的詞頻;我們想要求得route = (w1,w2,w3,...,wn),使得 ∑weight(wi) 最大。

如果需要使用動態規劃求解,需要滿足兩個條件,重複子問題、最優子結構

重複子問題

對於節點wi和其可能存在的多個後繼節點Wj和Wk:

任意通過Wi到達Wj的路徑的權重 = 該路徑通過Wi的路徑權重 + Wj的權重,也即{Ri -> j} = {Ri + weight(j)}

任意通過Wi到達Wk的路徑的權重 = 該路徑通過Wi的路徑權重 + Wk的權重,也即{Ri -> k} = {Ri + weight(k)}

即對於擁有公共前驅節點Wi的節點Wj和Wk,需要重複計算達到Wi的路徑的概率。

最優子結構

對於整個句子的最優路徑Rmax和一個末端節點Wx,對於其可能存在的多個前驅Wi,Wj,Wk...,設到達Wi,Wj,Wk的最大路徑分別是Rmaxi,Rmaxj,Rmaxk,有,

Rmax = max(Rmaxi,Rmaxj,Rmaxk,...) + weight(Wx)

於是,問題轉化為,求解Rmaxi,Rmaxj,Rmaxk,...等,組成了最優子結構,子結構裡面的最優解是全域性的最優解的一部分。

狀態轉移方程為:Rmax = max{(Rmaxi,Rmaxj,Rmaxk,...) + weight(Wx)}

jieba分詞中計算最大概率路徑的主函式是calc(self, sentence, DAG, route),函式根據已經構建好的有向無環圖計算最大概率路徑。函式是一個自底向上的動態規劃問題,它從sentence的最後一個字(N-1)開始倒序遍歷sentence的每個字(idx)的方式,計運算元句sentence[idx ~ N-1]的概率對數得分。然後將概率對數得分最高的情況以(概率對數,詞語最後一個位置)這樣的元組儲存在route中。函式中,logtotal為構建字首詞頻時所有的詞頻之和的對數值,這裡的計算都是使用概率對數值,可以有效防止下溢問題。

jieba分詞中calc函式實現如下,

def calc(self, sentence, DAG, route):

    N = len(sentence)

    # 初始化末尾為0

    route[N] = (0, 0)

    logtotal = log(self.total)

    # 從後到前計算

    for idx in xrange(N - 1, -1, -1):#遍歷0-N-1,-1代表遞減的方式

        route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -

                          logtotal + route[x + 1][0], x) for x in DAG[idx])

                  print "root:",route

DAG :{0: [0, 1], 1: [1], 2: [2, 3, 5], 3: [3], 4: [4, 5], 5: [5], 6: [6, 7], 7: [7]}

計算過程:

root: {8: (0, 0), 7: (-8.702713881905304, 7)}

root: {8: (0, 0), 6: (-8.682096638586806, 7), 7: (-8.702713881905304, 7)}

root: {8: (0, 0), 5: (-18.81251125649701, 5), 6: (-8.682096638586806, 7), 7: (-8.702713881905304, 7)}

root: {8: (0, 0), 4: (-25.495037477673915, 5), 5: (-18.81251125649701, 5), 6: (-8.682096638586806, 7), 7: (-8.702713881905304, 7)}

root: {3: (-34.70541057789988, 3), 4: (-25.495037477673915, 5), 5: (-18.81251125649701, 5), 6: (-8.682096638586806, 7), 7: (-8.702713881905304, 7), 8: (0, 0)}

root: {2: (-25.495037477673915, 5), 3: (-34.70541057789988, 3), 4: (-25.495037477673915, 5), 5: (-18.81251125649701, 5), 6: (-8.682096638586806, 7), 7: (-8.702713881905304, 7), 8: (0, 0)}

root: {1: (-33.72931380325669, 1), 2: (-25.495037477673915, 5), 3: (-34.70541057789988, 3), 4: (-25.495037477673915, 5), 5: (-18.81251125649701, 5), 6: (-8.682096638586806, 7), 7: (-8.702713881905304, 7), 8: (0, 0)}

root: {0: (-34.76895126093703, 1), 1: (-33.72931380325669, 1), 2: (-25.495037477673915, 5), 3: (-34.70541057789988, 3), 4: (-25.495037477673915, 5), 5: (-18.81251125649701, 5), 6: (-8.682096638586806, 7), 7: (-8.702713881905304, 7), 8: (0, 0)}

三、馬爾科夫模型

1.馬爾科夫

• 每個狀態只依賴之前有限個狀態

– N階馬爾科夫:依賴之前n個狀態

p(w1,w2,w3,w4…wn) = p(w1)p(w2|w1)p(w3|w1,w2)……p(wn|w1,w2,……,wn-1) =p(w1) p(w2|w1) p(w3|w2)……p(wn|wn-1)

– 1階馬爾科夫:僅僅依賴前一個狀態

p(w1=今天,w2=我,w3=寫,w4=了,w5=一個,w6=程式)=p(w1=今天)p(w2=我|w1=今天)p(w3=寫|w2=我)……p(w6=程式|w5=一個)

• 引數

– 狀態,由數字表示,假設共有M個(有多少字就有多少個M)

– 初始概率,由πk表示

– 狀態轉移概率,由表示ak,l表示,詞k變換到詞l

這些引數值用統計的方法來獲得,即最大似然估計法。

2.最大似然法

最大似然估計,就是利用已知的樣本結果,反推最有可能(最大概率)導致這樣結果的引數值,在這裡就是根據樣本的情況,近似地得到概率值,所以下面的等號嚴格來說是約等號。

– 狀態轉移概率ak,l

• P(St+1=l|St=k)=l緊跟k出現的次數/k出現的總次數

– 初始概率πk

• P(S1=k)=k作為序列開始的次數/觀測序列總數

馬爾科夫模型是對一個序列資料建模,但有時我們需要對兩個序列資料建模,所以需要隱馬爾可夫模型。

– 例如:

• 機器翻譯:源語言序列 <-> 目標語言序列

• 語音識別:語音訊號序列 <-> 文字序列

• 詞性標註:文字序列 <-> 詞性序列

– 寫/一個/程式

– Verb/Num/Noun

四、隱馬爾科夫模型

1.觀察序列和隱藏序列

• 通常其中一個序列是我們觀察到的,背後隱藏的序列是我們要尋找的

– 把觀察到的序列表示為O,隱藏的序列表示為S

• 觀察序列O中的資料通常是由對應的隱藏序列資料決定的,聲波訊號彼此間相互獨立

隱藏序列資料間相互依賴,通常構成了馬爾科夫序列

– 例如,語音識別中聲波訊號每段訊號都是相互獨立的,有對應的文字決定

– 對應的文字序列中相鄰的字相互依賴,構成Markov鏈。

• HMM引數

– 狀態s,由數字表示,假設共有M個

– 觀測o,由數字表示,假設共有N個

– 初始概率,由πk表示

– 狀態轉移概率,由ak,l表示

• ak,l = P (st+1=l|st=k) k,l = 1,2, … , M

– 發射概率,由bk(u) 表示

2.初始概率

在5_codes/word_seg/jieba/jieba/posseg中,可以看到jieba的各個概率值

('S', 'uv'): -8.15808672228609,這個代表某單個詞屬於’uv’詞性的概率是-8.15808672228609

3.轉移概率

4.發射概率

5.HMM生成過程

生成第一個狀態,然後依次由當前狀態生成下一個狀態,最後每個狀態發射出一個觀察值。

6.隱馬模型例項

觀測細胞在不同時刻的健康程度(cell state)細胞健康程度不易直接觀測,不過可以通過觀測一種熒光蛋白( GFP)的密集程度對健康程度有一個間接瞭解。

cell state: { Healthy, OK, Sick}

GFP intensity: {High, Average, Low}

cell state 就是隱藏狀態,GFP intensity 就是觀測狀態

P(Healthy)=0.5,P(OK)=0.3,P(Sick)=0.2

那給定這麼一個 HMM,能做什麼?假設我們觀測到這麼一個 GFP intensity 序列:

出現這麼一個觀測序列的概率是多少?P(Hi, Av, Av, Lo, Lo)= ?

這個觀測序列背後最有可能的 cell state 序列是什麼?

三個基本問題

– 模型引數估計,需要現有以下個θ概率值

在jieba模型中,已經有了θ值,現在考慮如何根據θ計算最大概率的S。

7.前後向演算法

前向概率是聯合概率,後向概率是條件概率。

前向概率-簡單演算法

簡單演算法:將t之前所有的情況全部列出來,並將概率相加。

對於剛才的例子:

前向概率-改進演算法 動態規劃

後向概率

其他概率

8.隱馬爾科夫模型引數估計

完全資料:

9.HMM應用

基於字首詞典和動態規劃方法可以實現分詞,但是如果沒有字首詞典或者有些詞不在字首詞典中,jieba分詞一樣可以分詞,基於漢字成詞能力的HMM模型識別未登入詞。利用HMM模型進行分詞,主要是將分詞問題視為一個序列標註(sequence labeling)問題,其中,句子為觀測序列,分詞結果為狀態序列。首先通過語料訓練出HMM相關的模型,然後利用Viterbi演算法進行求解,最終得到最優的狀態序列,然後再根據狀態序列,輸出分詞結果。

序列標註

序列標註,就是將輸入句子和分詞結果當作兩個序列,句子為觀測序列,分詞結果為狀態序列,當完成狀態序列的標註,也就得到了分詞結果。

以“去北京大學玩”為例,我們知道“去北京大學玩”的分詞結果是“去 / 北京大學 / 玩”。對於分詞狀態,由於jieba分詞中使用的是4-tag,因此我們以4-tag進行計算。4-tag,也就是每個字處在詞語中的4種可能狀態,B、M、E、S,分別表示Begin(這個字處於詞的開始位置)、Middle(這個字處於詞的中間位置)、End(這個字處於詞的結束位置)、Single(這個字是單字成詞)。具體如下圖所示,“去”和“玩”都是單字成詞,因此狀態就是S,“北京大學”是多字組合成的詞,因此“北”、“京”、“大”、“學”分別位於“北京大學”中的B、M、M、E。

https://images2015.cnblogs.com/blog/668850/201611/668850-20161118123105545-1599810853.png

HMM模型作的兩個基本假設:

1.齊次馬爾科夫性假設,即假設隱藏的馬爾科夫鏈在任意時刻t的狀態只依賴於其前一時刻的狀態,與其它時刻的狀態及觀測無關,也與時刻t無關;

2.觀測獨立性假設,即假設任意時刻的觀測只依賴於該時刻的馬爾科夫鏈的狀態,與其它觀測和狀態無關,

HMM模型有三個基本問題:

1.概率計算問題,給定模型 λ=(A,B,π)和觀測序列 O=(o1,o2,...,oT),怎樣計算在模型λ下觀測序列O出現的概率 P(O|λ),也就是前向(Forward-backward)演算法;

2.學習問題,已知觀測序列 O=(o1,o2,...,oT)O=(o1,o2,...,oT) ,估計模型 λ=(A,B,π)λ=(A,B,π) ,使得在該模型下觀測序列的概率 P(O|λ)P(O|λ) 儘可能的大,即用極大似然估計的方法估計引數;

3.預測問題,也稱為解碼問題,已知模型 λ=(A,B,π)和觀測序列 O=(o1,o2,...,oT),求對給定觀測序列條件概率 P(S|O)P(S|O) 最大的狀態序列 I=(s1,s2,...,sT),即給定觀測序列,求最有可能的對應的狀態序列;

其中,jieba分詞主要涉及第三個問題,也即預測問題。

這裡仍然以“去北京大學玩”為例,那麼“去北京大學玩”就是觀測序列。而“去北京大學玩”對應的“SBMMES”則是隱藏狀態序列,我們將會注意到B後面只能接(M或者E),不可能接(B或者S);而M後面也只能接(M或者E),不可能接(B或者S)。

狀態初始概率表示,每個詞初始狀態的概率;jieba分詞訓練出的狀態初始概率模型如下所示。

P={'B': -0.26268660809250016, 'E': -3.14e+100, 'M': -3.14e+100, 'S': -1.4652633398537678}

其中的概率值都是取對數之後的結果(可以讓概率相乘轉變為概率相加),其中-3.14e+100代表負無窮,對應的概率值就是0。這個概率表說明一個詞中的第一個字屬於{B、M、E、S}這四種狀態的概率,如下可以看出,E和M的概率都是0,這也和實際相符合:開頭的第一個字只可能是每個詞的首字(B),或者單字成詞(S)。這部分對應jieba/finaseg/ prob_start.py,具體可以進入原始碼檢視。

狀態轉移概率是馬爾科夫鏈中很重要的一個知識點,一階的馬爾科夫鏈最大的特點就是當前時刻T = i的狀態states(i),只和T = i時刻之前的n個狀態有關,即{states(i-1),states(i-2),...,states(i-n)}。再看jieba中的狀態轉移概率,其實就是一個巢狀的詞典,數值是概率值求對數後的值,如下所示,

P={'B': {'E': -0.510825623765990, 'M': -0.916290731874155}, 'E': {'B': -0.5897149736854513, 'S': -0.8085250474669937}, 'M': {'E': -0.33344856811948514, 'M': -1.2603623820268226}, 'S': {'B': -0.7211965654669841, 'S': -0.6658631448798212}}

P['B']['E']代表的含義就是從狀態B轉移到狀態E的概率,由P['B']['E'] = -0.58971497368-54513,表示當前狀態是B,下一個狀態是E的概率對數是-0.5897149736854513,對應的概率值是0.6,相應的,當前狀態是B,下一個狀態是M的概率是0.4,說明當我們處於一個詞的開頭時,下一個字是結尾的概率要遠高於下一個字是中間字的概率,符合我們的直覺,因為二個字的詞比多個字的詞更常見。這部分對應jieba/finaseg/prob_trans.py,具體可以檢視原始碼。

狀態發射概率,根據HMM模型中觀測獨立性假設,發射概率,即觀測值只取決於當前狀態值,也就如下所示,

P(observed[i],states[j]) = P(states[j]) * P(observed[i] | states[j])

其中,P(observed[i] | states[j])就是從狀態發射概率中獲得的。

P={'B': {'': -3.6544978750449433, '': -8.125041941842026, '': -7.817392401429855, ... 'S': {':': -15.828865681131282, '': -4.92368982120877, '': -9.024528361347633,

P['B']['一']代表的含義就是狀態處於'B',而觀測的字是‘一’的概率對數值為P['B']['一'] = -3.6544978750449433。這部分對應jieba/finaseg/prob_emit.py,具體可以檢視原始碼。

viterbi演算法

Viterbi演算法實際上是用動態規劃求解HMM模型預測問題,即用動態規劃求概率路徑最大(最優路徑)。一條路徑對應著一個狀態序列。根據動態規劃原理,最優路徑具有這樣的特性:如果最優路徑在時刻t通過結點 it ,那麼這一路徑從結點 it 到終點 iT 的部分路徑,對於從 it  iT的所有可能的部分路徑來說,必須是最優的。因為假如不是這樣,那麼it  iT就有另一條更好的部分路徑存在,如果把它和從 it 到達 iT的部分路徑連線起來,就會形成一條比原來的路徑更優的路徑,這是矛盾的。依據這個原理,我們只需要從時刻t=1開始,遞推地計算在時刻t狀態i的各條部分路徑的最大概率,直至得到時刻t=T狀態為i的各條路徑的最大概率。時刻t=T的最大概率就是最優路徑的概率 P,最優路徑的終結點 iT也同時得到。之後,為了找出最優路徑的各個結點,從終結點 iT開始,由後向前逐步求得結點 iT−1,...,...,i1 ,最終得到最優路徑 I=(i1,i2,...,iT)I=(i1,i2,...,iT) 。(不理解)

首先先定義兩個變數,δ,ψ,定義在時刻t狀態i的所有單個路徑 (i1,i2,...,it)(i1,i2,...,it) 中概率最大值為

def viterbi(obs, states, start_p, trans_p, emit_p):

    V = [{}]  # tabular

    path = {}

    # 時刻t = 0,初始狀態

    for y in states:  # init

        V[0][y] = start_p[y] + emit_p[y].get(obs[0], MIN_FLOAT)

        path[y] = [y]

    # 時刻t = 1,...,len(obs) - 1

    for t in xrange(1, len(obs)):

        V.append({})

        newpath = {}

        # 當前時刻所處的各種可能的狀態

        for y in states:

            # 獲取發射概率對數

            em_p = emit_p[y].get(obs[t], MIN_FLOAT)

            # 分別獲取上一時刻的狀態的概率對數,該狀態到本時刻的狀態的轉移概率對數,本時刻的狀態的發射概率對數

            # 其中,PrevStatus[y]是當前時刻的狀態所對應上一時刻可能的狀態

            (prob, state) = max(

                [(V[t - 1][y0] + trans_p[y0].get(y, MIN_FLOAT) + em_p, y0) for y0 in PrevStatus[y]])

            V[t][y] = prob

            # 將上一時刻最優的狀態 + 這一時刻的狀態

            newpath[y] = path[state] + [y]

        path = newpath

    # 最後一個時刻

    (prob, state) = max((V[len(obs) - 1][y], y) for y in 'ES')

    # 返回最大概率對數和最優路徑

    return (prob, path[state])

輸出分詞結果

Viterbi演算法得到狀態序列,根據狀態序列得到分詞結果。其中狀態以B開頭,離它最近的以E結尾的一個子狀態序列或者單獨為S的子狀態序列,就是一個分詞。以去北京大學玩的隱藏狀態序列”SBMMES“為例,則分詞為”S / BMME / S“,對應觀測序列,也就是 / 北京大學 /

10.原始碼分析

jieba分詞中HMM模型識別未登入詞的原始碼目錄在jieba/finalseg/下,

__init__.py 實現了HMM模型識別未登入詞;

prob_start.py 儲存了已經訓練好的HMM模型的狀態初始概率表;

prob_trans.py 儲存了已經訓練好的HMM模型的狀態轉移概率表;

prob_emit.py 儲存了已經訓練好的HMM模型的狀態發射概率表;

HMM模型引數訓練

來源主要有兩個,一個是網上能下載到的1998人民日報的切分語料還有一個msr的切分語料。另一個是我自己收集的一些txt小說,用ictclas把他們切分(可能有一定誤差),然後用python指令碼統計詞頻。

要統計的主要有三個概率表:1)位置轉換概率,即B(開頭),M(中間),E(結尾),S(獨立成詞)四種狀態的轉移概率;2)位置到單字的發射概率,比如P("和"|M)表示一個詞的中間出現”和"這個字的概率;3) 詞語以某種狀態開頭的概率,其實只有兩種,要麼是B,要麼是S。

基於HMM模型的分詞流程

jieba分詞會首先呼叫函式cut(sentence),cut函式會先將輸入句子進行解碼,然後呼叫__cut函式進行處理。__cut函式就是jieba分詞中實現HMM模型分詞的主函式。__cut函式會首先呼叫viterbi演算法,求出輸入句子的隱藏狀態,然後基於隱藏狀態進行分詞。

def __cut(sentence):

    global emit_P

    # 通過viterbi演算法求出隱藏狀態序列

    prob, pos_list = viterbi(sentence, 'BMES', start_P, trans_P, emit_P)

    begin, nexti = 0, 0

    # print pos_list, sentence

    # 基於隱藏狀態序列進行分詞

    for i, char in enumerate(sentence):

        pos = pos_list[i]

        # 字所處的位置是開始位置

        if pos == 'B':

            begin = i

        # 字所處的位置是結束位置

        elif pos == 'E':

            # 這個子序列就是一個分詞

            yield sentence[begin:i + 1]

            nexti = i + 1

        # 單獨成字

        elif pos == 'S':

            yield char

            nexti = i + 1

    # 剩餘的直接作為一個分詞,返回

    if nexti < len(sentence):

        yield sentence[nexti:]

五、實踐

案例1:中文分詞和webserver

1. yum install git(如果自己電腦沒有git的話)

3. [[email protected] test]# python demo.py進入test資料夾後,隨便執行一個

4. [[email protected] test]# cp demo.py badou.py在此模板上修改

#encoding=utf-8

from __future__ import unicode_literals

import sys

sys.path.append("../")//jieba模組載入進來,這裡的jieba模組是jieba資料夾下的jieba資料夾

import jieba

import jieba.posseg

import jieba.analyse

s = "體專案。2013年,實現營業收入0萬元,實現淨利潤-139.13萬元。"

for x, w in jieba.analyse.extract_tags(s, withWeight=True):

    print('%s %s' % (x, w)) //分出單詞和詞的權重

5. [[email protected] word_seg]# cd segment/,這裡的web是開源工具,編輯word_seg.py

#encoding=utf-8

import web

import sys

sys.path.append("./")//這裡需要把jieba裡面的jieba模組載入進去,所以要注意路徑問題。

import jieba

import jieba.posseg

import jieba.analyse

urls = (

    '/', 'index',預設的方式

    '/test', 'test',

)

app = web.application(urls, globals())

class index:

    def GET(self):

        params = web.input()

        context = params.get('context', '')//自定義輸入http://192.168.101.10:9999/? context=語句

        seg_list = jieba.cut(context)

        result = ", ".join(seg_list)

        print("=====>", result)列印結果

        return result

    def GET(self):

        print web.input()

        return '222'

if __name__ == "__main__":

app.run()

[[email protected] segment]# python web_seg.py 9999

http://192.168.101.10:9998/?context=加入購物車,結果為亂碼,需要在瀏覽器修改編碼方式,在選項卡里面工具,修改為自動檢測或者utf-8等。

結果:加入, 購物車

案例2:jieba和mapreduce結合

1.run.sh

#HADOOP_CMD="/usr/local/src/hadoop-1.2.1/bin/hadoop"

#STREAM_JAR_PATH="/usr/local/src/hadoop-1.2.1/contrib/streaming/hadoop-streaming-1.2.1.jar"

HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop"

STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar"

INPUT_FILE_PATH_1="/music_meta.txt.small"

其中music_meta.txt.small 資料如下

8920791333    天路MV-韓紅

8920845333    初音未來

相關推薦

資料基礎-2.中文

一、中文分詞基礎 • 切開的開始位置對應位是1,否則對應位是0,來表示“有/意見/分歧”的bit內容是:11010,通過識別1後面幾個0,就可以知道有幾個字切在一塊。 • 還可以用一個分詞節點序列來表示切分方案,例如“有/意見/分歧”的分詞節點序列是{0,1,3,5}

利用Python學習資料2

本文結合程式碼例項待你上手python資料探勘和機器學習技術。   本文包含了五個知識點: 1. 資料探勘與機器學習技術簡介   2. Python資料預處理實戰   3. 常見分類演算法介紹  

資料基礎之統計學的分佈函式

本部落格根據非常好的excel資料而編寫,使用python語言操作,預計使用一週的時間更新完成。需要《非常好的excel資料》word文件,歡迎發郵件給[email protected],免費發放。 1、幾種常見的統計函式 2、分佈函式 ① ② ,求P{X=5

學習筆記:從0開始學習大資料-29. solr增加ik中文器並匯入doc,pdf文件全文檢索

環境 centos7,solr7.5.0 1. 新建core 從  solr-7.5.0/example/files/conf 作為配置檔案模板,建立core,名為mycore 2.下載分詞器 從https://search.maven.org/search?q=g:com

資料基礎之統計學的假設檢驗實驗

本部落格根據非常好的excel資料而編寫,使用python語言操作,預計使用一週的時間更新完成。需要《非常好的excel資料》word文件,歡迎發郵件給[email protected],免費發放。這篇部落格對應《非常好的excel資料》裡的第3章節。 1.假設檢驗實驗 1

資料基礎導論學習筆記(五)

第五章 分類 其他分類 貝葉斯分類器 貝葉斯定理:把類的先驗知識和從資料中收集的新證據相結合的統計原理。 公式: P(Y|X)=P(X|Y)*P(Y)/P(X) X是屬性集,Y是類變數 把X和Y看成隨機變數,用P(Y|X)以概率的方式捕捉二者之間的關係,這個條件

《Python資料分析與挖掘實戰》筆記(一):資料基礎

一、資料探勘的基本任務 利用分類與預測、聚類分析、關聯規則、時序模式、偏差檢測、智慧推薦等方法,幫助企業提取資料中蘊含的商業價值,提升企業的競爭力。 二、資料探勘建模過程 定義挖掘目標:任務目標和完

資料基礎-1.文字相似度

一、文字相似度 相似度度量指的是計算個體間相似程度,一般使用距離來度量,相似度值越小,距離越大,相似度值越大,距離越小。在說明文字相似度概念和計算方式之前,先回顧下餘弦相似度。 1.餘弦相似度 衡量文字相似度最常用的方法是使用餘弦相似度。  – 空間中,兩個向量夾角的

資料2)關聯規則FpGrowth演算法

介紹了關聯規則挖掘的一些基本概念和經典的Apriori演算法,Aprori演算法利用頻繁集的兩個特性,過濾了很多無關的集合,效率提高不少,但是我們發現Apriori演算法是一個候選消除演算法,每一次消除都需要掃描一次所有資料記錄,造成整個演算法在面臨大資料集時顯得無能

python 資料基礎 入門

  一. 基於Python的資料探勘 基本架構   1. matplotlib, 圖形化 2. pandas,資料探勘的關鍵, 提供各種挖掘分析的演算法 3. numpy, 提供基本的統計    scipy, 提供各種數學公式 4. python common lib,py

資料基礎知識-矩陣(分解)

1. 矩陣知識: //特徵值,行列式,秩,對稱矩陣,單位矩陣,正定半正定,雅可比等等!! 正交矩陣: 如果:AA'=E(E為單位矩陣,A'表示“矩陣A的轉置矩陣”。)或A′A=E,則n階實矩陣A稱為正交矩陣, 若A為正交陣,則滿足以下條件: 1) AT是正交矩陣 2)(

NLP&資料基礎知識

Basis(基礎): SSE(Sum of Squared Error, 平方誤差和) SAE(Sum of Absolute Error, 絕對誤差和) SRE(Sum of Relative Error, 相對誤差和) MSE(Mean Squared Error, 均方誤差) RMSE(R

資料 文字分類(二)蒐集中文語料庫與ICTCLAS

           在上一篇部落格中簡單介紹了實驗環境和流程,這一篇我們繼續。            第一步,下載搜狗中文語料庫。連結:http://www.sogou.com/labs/dl/c.html                            我們下載

資料01---文字分析(jieba雲繪製)

一、定義: 文字挖掘:從大量文字資料中抽取出有價值的知識,並且利用這些知識重新組織資訊的過程。 二、語料庫(Corpus) 語料庫是我們要分析的所有文件的集合。 import os import os.path filePaths = [] #定義一個數組變數 #再用

【結巴資料彙編】結巴中文原始碼分析(2)

如下演算法實現分詞: 1. 基於字首詞典實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖 (DAG); 作者這個版本中使用字首字典實現了詞庫的儲存(即dict.txt檔案中的內容),而棄用之前版本的trie樹儲存詞庫,想想也是,python中實現的trie樹是基於dict型

資料乾貨總結(二)--NLP進階-詳解Jieba工具

NLP進階-詳解Jieba分詞工具 一、Jieba分詞工具 1. 三種模式 • 精確模式:將句子最精確的分開,適合文字分析 • 全模式:句子中所有可以成詞的詞語都掃描出來,速度快,不能解決歧義 • 搜尋引擎模式:在精確模式基礎上,對長詞再次切分,提高召回 2.實現的演算法 • 基於Tri

python基礎===jieba模塊,Python 中文組件

word cut 用法 地址 api mas 精確 == com api參考地址:https://github.com/fxsjy/jieba/blob/master/README.md 安裝自行百度 基本用法: import jieba #全模式 word = jie

開源中文工具析(六):Stanford CoreNLP

inf git deb seq 效果 analysis stream fix sps CoreNLP是由斯坦福大學開源的一套Java NLP工具,提供諸如:詞性標註(part-of-speech (POS) tagger)、命名實體識別(named entity recog

Solr6.2搭建和配置ik中文

首先需要的準備好ik分詞器,因為Solr6.0版本比較高,所以ik分詞器的版本最好高一點,我是用ikanalyzer-solr5來搭建的. 第一步 解壓ikanalyzer-solr5. 第二步 複製ik-analyzer-solr5-5.x.jar並將其放在solr-6.2.1\se

基礎學習大資料的33個知識點整理

摘要: 下面是一些關於大資料探勘的知識點,今天和大家一起來學習一下。1. 資料、資訊和知識是廣義資料表現的不同形式。2. 主要知識模式型別有:廣義知識,關聯知識,類知識,預測型知識,特異型知識3. web挖掘研究的主要流派有:Web結構挖掘、Web使用挖掘、Web內容挖掘4. 一般地說,KD