本次實驗內容是基於詞典的雙向匹配算法的中文分詞算法的實現。使用正向和反向最大匹配算法對給定句子進行分詞,對得到的結果進行比較,從而決定正確的分詞方法。
算法描述
正向最大匹配算法
先設定掃描的窗口大小maxLen(最好是字典最長的單詞長度),從左向右取待切分漢語句的maxLen個字符作為匹配字段。查找詞典並進行匹配。若匹配成功,則將這個匹配字段作為一個詞切分出來,並將窗口向右移動這個單詞的長度。若匹配不成功,則將這個匹配字段的最后一個字去掉,剩下的字符串作為新的匹配字段,進行再次匹配,重復以上過程,直到切分出所有詞為止。
反向最大匹配算法
該算法是正向的逆向算法,區別是窗口是從后向左掃描,若匹配不成功,則去掉第一個字符,重復上述的匹配步驟。
雙向最大匹配算法
雙向最大匹配法是將正向最大匹配法得到的分詞結果和逆向最大匹配法的到的結果進行比較,從而決定正確的分詞方法。定義的匹配規則如下:
如果正反向匹配算法得到的結果相同,我們則認為分詞正確,返回任意一個結果即可。
如果正反向匹配算法得到的結果不同,則考慮單字詞、非字典詞、總詞數數量的數量,三者的數量越少,認為分詞的效果越好。我們設定一個懲罰分數(score_fmm / score_bmm = 0),例如:正向匹配中單字詞數量多於反向匹配,則正向匹配的分值score_fmm += 1。其他兩個條件相同。可以根據實際的分詞效果調整懲罰分數的大小,但由於沒有正確分詞的數據,因此懲罰分數都設為1。最后比較懲罰分數,返回較小的匹配結果。
詳例描述
以“對外經濟技術合作與交流不斷擴大。”為例,詳細描述算法如下:
窗口大小設為4,句子長度為16,分詞列表words = []。
首先是正向匹配。sub_str = ‘對外經濟’與詞典進行匹配,匹配失敗,窗口大小減一。
sub_str = ‘對外經’與詞典進行匹配,匹配失敗,窗口大小減一。
sub_str = ‘對外’與詞典進行匹配,匹配成功,窗口大小恢復為4,向右移動之前匹配詞的長度,此時sub_str = ‘經濟技術’,將其添加至列表words中。重復上述步驟。
當匹配到最后一個詞時,算法停止。
正向匹配結果如下:[‘對外’, ‘經濟’, ‘技術’, ‘合作’, ‘與’, ‘交流’, ‘不斷’, ‘擴大’, ‘。’]
反向匹配如法炮制,結果如下:[‘對外’, ‘經濟’, ‘技術’, ‘合作’, ‘與’, ‘交流’, ‘不斷’, ‘擴大’, ‘。’]
正向與反向匹配結果相同,返回任意一個。
代碼:
加載字典
def read_dict(path): words_dict = [] with open(path, 'r') as r: line = r.readlines() # print(line) for i in line: word = i.split(',') words_dict.append(word[0]) return words_dict window_size = 4
正向匹配算法
def fmm(source, words_dict): len_source = len(source) # 原句長度 index = 0 words = [] # 分詞后句子每個詞的列表 while index < len_source: # 如果下標未超過句子長度 match = False for i in range(window_size, 0, -1): sub_str = source[index: index+i] if sub_str in words_dict: match = True words.append(sub_str) index += i break if not match: words.append(source[index]) index += 1 return words
反向匹配算法
def bmm(source, word_dict): len_source = len(source) # 原句長度 index = len_source words = [] # 分詞后句子每個詞的列表 while index > 0: match = False for i in range(window_size, 0, -1): sub_str = source[index-i: index] if sub_str in words_dict: match = True words.append(sub_str) index -= i break if not match: words.append(source[index-1]) index -= 1 words.reverse() # 得到的列表倒序 return words
雙向匹配算法
def bi_mm(source, word_dict): forward = fmm(source, words_dict) backward = bmm(source, words_dict) # 正反向分詞結果 print("FMM: ", forward) print("BMM: ", backward) # 單字詞個數 f_single_word = 0 b_single_word = 0 # 總詞數 tot_fmm = len(forward) tot_bmm = len(backward) # 非字典詞數 oov_fmm = 0 oov_bmm = 0 # 罰分,罰分值越低越好 score_fmm = 0 score_bmm = 0 # 如果正向和反向結果一樣,返回任意一個 if forward == backward: return backward # print(backward) else: # 分詞結果不同,返回單字數、非字典詞、總詞數少的那一個 for each in forward: if len(each) == 1: f_single_word += 1 for each in backward: if len(each) == 1: b_single_word += 1 for each in forward: if each not in words_dict: oov_fmm += 1 for each in backward: if each not in backward: oov_bmm += 1 # 可以根據實際情況調整懲罰分值 # 這里都罰分都為1分 # 非字典詞越少越好 if oov_fmm > oov_bmm: score_bmm += 1 elif oov_fmm < oov_bmm: score_fmm += 1 # 總詞數越少越好 if tot_fmm > tot_bmm: score_bmm += 1 elif tot_fmm < tot_bmm: score_fmm += 1 # 單字詞越少越好 if f_single_word > b_single_word: score_bmm += 1 elif f_single_word < b_single_word: score_fmm += 1 # 返回罰分少的那個 if score_fmm < score_bmm: return forward else: return backward
主函數,測試了分詞的時間,包括加載詞典的時間
if __name__ == '__main__': start = time.time() words_dict = read_dict('chineseDic.txt') # print(bmm("我正在上自然語言處理課。", words_dict)) # print("result: ", result) print("BiMM: ", bi_mm("對外經濟技術合作與交流不斷擴大。", words_dict)) end = time.time() print("running time: " + str(end - start) + "s")