分詞概述
目前中文的分詞可分爲三大類:基於詞典的方法、基於統計的方法和混合方
法。基於詞典的方法需要分詞的源字符串,如果能夠找到對應的字符串將成功匹配。這是一種很原始且效率相對低效的分詞策略。舉個簡單案例,在“我要認真看論文”句子中查找關鍵詞“論文”,無論採用何種匹配方式,它都需要從左往右或者從右往左一個字或一個詞的查找(長度取決於對分詞的粒度控制),直到經過幾個輪迴之後找到“論文”這個詞組,這樣纔算成功。對於這類分詞,根據解析方向的不同,可以分爲正向匹配和逆向匹配;按照不同長度優先原則,可以分爲最長匹配和最短匹配。
- 正向最大匹配法( Maximum Match Method)。該方法就是從左往右通過詞典進行匹配時,盡最大可能去匹配一個語義完整的詞彙。例如有這樣一個句子:“現在開始進行畢業論文的查重檢測了”。對這個句子進行匹配時,方向爲從左到右,匹配長度循環遞減。假設最大匹配長度爲5,那麼首先匹配結果是“現在開始進”,不符合要求。接下來是“現在開始”,然後是“現在開”,再然後是“現在”,這是一個符合要求的詞,提取出來。同理接下來5個字,“開始進行畢”,提取出“開始”,按此進行不斷匹配,最後通過正向最大匹配法得到我們想要的分詞結果:“現在/開始/進行/畢業/論文/的/查重/檢測/了”。
- 逆向最大匹配法( Reverse Maximum Match Method )。顧名思義,它的匹配方向與正向匹配法相反,從右邊取N個字符進行匹配。若失敗,則去掉匹配字段最前面的一個字,繼續匹配。在實際應用中,通常通過正向匹配法來實現逆向匹配,先將源文件進行倒排,得到逆序文件,然後根據逆序詞典(即按照相反的順序存儲詞條),對逆序文件使用正向最大匹配法進行處理,這樣就得到理論上逆向匹配法得到的結果文件。
- 雙向最大匹配法。雙向最大匹配法( Bi-directction Matching method) 是將正向最大匹配法得到的分詞結果和逆向最大匹配法得到的結果進行比較,然後按照最大匹配原則,選取詞數切分最少的作爲結果。 據 SunM.S. 和 Benjamin K.T. ( 1995 )的研究表明 ,中文中 90.0% 左右的句子,正向最大匹配法和逆向最大匹配法完全重合且正確,只有大概 9.0% 的句子兩種切分方法得到的結果不一樣,但其中必有一個是正確的 (歧義檢測成功),只有不到 1.0%的句子,使用正向最大匹配法和逆向最大匹配法的切分雖重合卻是錯的,或者正向最大匹配法和逆向最大匹配法切分不同但兩個都不對 (歧義檢測失敗) 。 這正是雙向最大匹配
法在實用中文信息處理系統中得以廣泛使用的原因。
python代碼實現
import os
import sys
import time
class IMM(object):
def __init__(self, *dic_path):
self.dictionary = set()
self.maximum = 0
# 加載詞典
for path in dic_path:
self.load_dic(path)
# 加載字典
def load_dic(self, dic_path):
with open(dic_path, 'r', encoding='utf-8') as fp:
for line in fp:
line = line.strip().split()[0]
if not line:
continue
self.dictionary.add(line)
self.maximum = max(self.maximum, len(line))
# 正向最大匹配
def FMM_cut(self, text):
result = []
index = 0
while index < len(text): # 小標未超過句子長度
match = False
for size in range(self.maximum, 0, -1):
if index + size > len(text):
continue
piece = text[index:(index + size)]
if piece in self.dictionary:
match = True
result.append(piece)
index += size
break
if not match:
result.append(text[index])
index += 1
return result
# 逆向最大匹配
def RMM_cut(self, text):
result = []
index = len(text)
while index > 0:
match = False
for size in range(self.maximum, 0, -1):
if index - size < 0:
continue
piece = text[(index - size):index] # 切分單詞
# 匹配成功,index向前移動word長度
if piece in self.dictionary:
match = True
result.append(piece)
index -= size
break
if not match:
result.append(text[index - 1])
index -= 1
return result[::-1]
# 雙向最大匹配
def BMM_cut(self, text):
words_FMM = self.FMM_cut(text)
words_RMM = self.RMM_cut(text)
print("FMM:", words_FMM)
print("RMM:", words_RMM)
# 如果正向和反向結果一樣,返回任意一個
if words_FMM == words_RMM:
return words_FMM
# 單字詞個數
f_single_word = 0
r_single_word = 0
# 總次數
fmm_count = len(words_FMM)
rmm_count = len(words_RMM)
# 非字典數
fmm_oov = 0
rmm_oov = 0
# 罰分都爲1分,分值越低越好
fmm_score = 0
rmm_score = 0
# 分詞結果不同,返回單字數、非字典詞、總詞數少的那一個
for each_word in words_FMM:
if len(each_word) == 1:
f_single_word += 1
if each_word not in self.dictionary:
fmm_oov += 1
for each_word in words_RMM:
if len(each_word) == 1:
r_single_word += 1
if each_word not in self.dictionary:
rmm_oov += 1
# 非字典詞越少越好
fmm_score = fmm_oov + fmm_count + f_single_word
rmm_score = rmm_oov + rmm_count + r_single_word
# 返回罰分少的那個
if fmm_score < rmm_score:
return words_FMM
else:
return words_RMM
def main():
dict1_path = os.path.join(sys.path[0], r'.\data\dict.txt.big')
dict2_path = os.path.join(sys.path[0], r'.\data\THUOCL_animal.txt')
test_path = os.path.join(sys.path[0], r'.\data\CTBtestingset.txt')
output_path = os.path.join(sys.path[0], r'.\data\output.txt')
tokenizer = IMM(dict1_path, dict2_path)
# tokenizer = IMM(r'./data/THUOCL_animal.txt')
try:
with open(test_path, 'r',
encoding='utf-8') as input_text, open(output_path,
'w',
encoding='utf-8',
newline='') as output:
for line in input_text:
line = tokenizer.BMM_cut(line.strip())
print(line)
line = ' '.join(line) + os.linesep
print(line)
output.write(line)
except Exception:
print(sys.stderr, "文件打開錯誤")
raise Exception
sys.exit(1)
if __name__ == "__main__":
start = time.time()
main()
end = time.time()
print("運行時間:", end - start)