中文分詞:最大匹配算法
(一)引言
分詞是自然語言處理中非常常見的操作,也是必不可少的文本數據預處理步驟。各國語言的表達方式和書寫方式截然不同,因此分詞的方式和難度也不同。英文分詞是最簡單的,因為每個單詞已經用空格自動分詞了,比如"I like Chinese" 這個句子已經被分成了三個單詞。當然,英文分詞也是有難點的,比如單詞大小寫所代表的含義不同以及各種符號的用法,這里暫不討論。中文是漢字為基本書寫單位,詞語甚至句子之間並沒有明顯的區分標記,並且不同的詞組合容易產生歧義。比如:“結婚的和尚未結婚的”,計算機很難判斷是分成“結婚/的/和/尚未/結婚/的”還是“結婚/的/和尚/未/結婚/的”。因此,中文分詞是一項非常具有挑戰性的工作。
現今,中文分詞方法一般被分為三類:
(1)基於字典的分詞方法
(2)基於統計的分詞方法
(3)基於機器學習的分詞方法
本篇文章主要介紹最直接粗暴的方法----基於詞典的分詞方法。
(二)正向最大匹配算法
顧名思義,正向最大匹配是從左到右掃描字符串,在一個給定的詞典中尋找詞的最大匹配。先看一個例子:
給定一個句子:“他是研究生物化學的”。
給定一個詞典:["他","是","研究","研究生","生物","物化","化學","學","的"]。
思路:先獲得詞典中詞的最大長度m,這個例子中m為3;給字符串初始位置一個指針pi,即在“他”的位置;從當前指針起取m個字作為詞(也可以直接到字符串末尾作為詞,但是效率低),即“他是研”;選出的詞如果在詞典中,就在詞的后面進行划分,然后指針移動到這個詞后面的一個字,如果選出的詞不在詞典中,就把選出的詞長度減一(即m-1),“他是研”就變成了“他是”,然后在進行此步驟的操作。
算法流程:
輸入:字符串 s
過程:
- 令指針 pi 指向 s 的初始位置
- repeat
- 計算當前指針 pi 到字串末端的字數(即未被切分字串的長度) n
- 令 m=詞典中最長單詞的字數,如果 n<m, 令 m=n
- 從當前 pi 起取 m 個漢字作為詞 wi
- **if wi **在詞典中
- then 在 wi 后添加一個切分標志,根據 wi 的長度修改指針 pi
- ** else**
- 將 wi 從右端去掉一個字
- until pi 指向字串末端
輸出: 添加切分標志后的字符串 s
示例代碼:
text = "他是研究生物化學的"
Dict = ["他","是","研究","研究生","生物","物化","化學","學","的"]
def forword_Match(text, Dict):
'''前向最大匹配'''
word_list = []
pi = 0 #初始位置
#找出字典中的最長的詞的長度
m = max([len(word) for word in Dict])
while pi != len(text):
n = len(text[pi:]) #當前指針到字符串末尾的長度
if n < m:
m = n
for index in range(m,0,-1): #從當前 pi 起取 m 個漢字作為詞
if text[pi:pi+index] in Dict:
word_list.append(text[pi:pi+index])
pi = pi + index # 根據詞的長度修改指針pi
break
print('/'.join(word_list))
forword_Match(text, Dict)
## 輸出: 他/是/研究生/物化/學/的
(三)逆向最大匹配算法
可以想到,逆向最大匹配是從右到左掃描字符串,在一個給定的詞典中尋找詞的最大匹配。先看一個例子:
給定一個句子:“他是研究生物化學的”。
給定一個詞典:["他","是","研究","研究生","生物","物化","化學","學","的"]。
思路:先獲得詞典中詞的最大長度m,這個例子中m為3;給字符串末尾位置一個指針pi,即在“的”的位置;從當前指針向左取m個字作為詞(也可以直接到字符串開頭作為詞,但是效率低),即“化學的”;選出的詞如果在詞典中,就在詞的前面進行划分,然后指針移動到這個詞前面的一個字,如果選出的詞不在詞典中,就把選出的詞長度減一(即m-1),“化學的”就變成了“學的”,然后在進行此步驟的操作。
算法流程:
輸入:字符串 s
過程:
- 令指針 pi 指向 s 的末尾位置
- repeat
- 計算當前指針 pi 到字串開頭的字數(即未被切分字串的長度) n
- 令 m=詞典中最長單詞的字數,如果 n<m, 令 m=n
- 從當前 pi 起取往左取m個漢字作為詞 wi
- **if wi **在詞典中
- then 在 wi 前面添加一個切分標志,根據 wi 的長度修改指針 pi
- ** else**
- 將 wi 從左端去掉一個字
- until pi 指向字串開頭
輸出: 添加切分標志后的字符串 s
示例代碼:
text = "他是研究生物化學的"
Dict = ["他","是","研究","研究生","生物","物化","化學","學","的"]
def back_Match(text, Dict):
'''逆向最大匹配'''
word_list = []
pi = len(text) - 1
m = max(len(word) for word in Dict)
while pi >= 0:
n = len(text[0:pi+1])
if n < m:
m = n
for index in range(m-1,-1,-1):
if text[pi-index:pi+1] in Dict:
word_list.append(text[pi-index:pi+1])
pi = pi - index -1
break
print('/'.join(word_list[::-1]))
back_Match(text, Dict)
## 輸出: 他/是/研究/生物/化學/的
至此就完成了基於詞典的中文分詞方法,但是這種方法過度依賴於詞典。如果詞典質量不高(比如容量小、記錄不全等)會影響分類效果,還有一些會產生歧義的句子也不適合用這種方法。接下來我們會一起學習基於統計的分詞方法......