中文分詞演算法
阿新 • • 發佈:2022-05-05
中文分詞演算法的幾種方法(帶原始碼)
規則分詞
基於規則的分詞是一種機械分詞方法,主要通過維護字典,在切分語句時將語句的每個字串與詞表中的詞進行注意匹配,找到則切分,否則不予切分。
按照切分方式,主要有正向最大匹配法、逆向最大匹配法以及雙向最大匹配法三種
特徵:從左向右切分
示例程式碼:
""" 中文分詞技術 編輯者:饅頭 部落格:https://www.cnblogs.com/mantou0/ """ # 正向最大匹配法 class MM(object): def __init__(self): self.windowSize = 3 # 詞典中最長的詞的詞長 self.dic = ["研究","研究生","生命","命","的","起源"] # 詞典 def cut(self,text): result = [] # 存放分割出的詞 index = 0 # 初始化進行要分割的詞的初始索引 text_length = len(text) while text_length > index: for size in range(self.windowSize+index,index,-1): piece = text[index:size] if piece in self.dic: index = size -1 break index = index + 1 result.append(piece+"---") print(result) if __name__ == '__main__': text = "研究生命的起源" tokenizer = MM() tokenizer.cut(text)
特徵:從右向左切分
示例程式碼:
""" 中文分詞技術 編輯者:饅頭 部落格:https://www.cnblogs.com/mantou0/ """ # 逆向最大匹配法 class RMM(object): def __init__(self): self.windowSize = 3 # 詞典中最長的詞的詞長 self.dic = ["研究","研究生","生命","命","的","起源"] # 詞典 def cut(self,text): result = [] # 存放分割出的詞 index = len(text) # 初始化進行要分割的詞的初始索引 while index > 0: for size in range(index - self.windowSize,index): piece = text[size:index] if piece in self.dic: index = size + 1 break index = index - 1 result.append(piece+"---") result.reverse() print(result) if __name__ == '__main__': text = "研究生命的起源" tokenizer = RMM() tokenizer.cut(text)
特徵:正逆對比,然後按照最大匹配原則
示例程式碼:
""" 中文分詞技術 編輯者:饅頭 部落格:https://www.cnblogs.com/mantou0/ """ class M(object): def __init__(self): self.windowSize = 3 # 詞典中最長的詞的詞長 self.dic = ["研究","研究生","生命","命","的","起源"] # 詞典 #正向最大匹配演算法 def MM(self,text): result = [] # 存放分割出的詞 index = 0 # 初始化進行要分割的詞的初始索引 text_length = len(text) while text_length > index: for size in range(self.windowSize+index,index,-1): piece = text[index:size] if piece in self.dic: index = size -1 break index = index + 1 result.append(piece+"---") return result #逆向最大匹配演算法 def RMM(self, text): result = [] # 存放分割出的詞 index = len(text) # 初始化進行要分割的詞的初始索引 while index > 0: for size in range(index - self.windowSize, index): piece = text[size:index] if piece in self.dic: index = size + 1 break index = index - 1 result.append(piece + "---") result.reverse() return result def BMM(self, MM_result,RMM_result): """ 比較兩個分詞方法分詞的結果 比較方法: 1. 如果正反向分詞結果詞數不同,則取分詞數量較少的那個 2. 如果分詞結果詞數相同: 2.1 分詞結果相同,說明沒有歧義,可返回任意一個 2.2 分詞結果不同,返回其中單字較少的那個 :param MM_result: 正向最大匹配法的分詞結果 :param RMM_result: 逆向最大匹配法的分詞結果 :return: 1.詞數不同返回詞數較少的那個 2.詞典結果相同,返回任意一個(MM_result) 3.詞數相同但是詞典結果不同,返回單字最少的那個 """ if len(MM_result) != len(RMM_result): # 如果兩個結果詞數不同,返回詞數較少的那個 return MM_result if (len(MM_result) < len(RMM_result)) else RMM_result else: if MM_result == RMM_result: # 因為RMM的結果是取反了的,所以可以直接匹配 # 詞典結果相同,返回任意一個 return MM_result else: # 詞數相同但是詞典結果不同,返回單字最少的那個 MM_word_1 = 0 RMM_word_1 = 0 for word in MM_result: # 判斷正向匹配結果中單字出現的詞數 if len(word) == 1: MM_word_1 += 1 for word in RMM_result: # 判斷逆向匹配結果中單字出現的詞數 if len(word) == 1: RMM_word_1 += 1 if (MM_word_1 < RMM_word_1): return MM_result else: return RMM_result if __name__ == '__main__': text = "研究生命的起源" token = M() token.BMM(token.MM(text),token.RMM(text))
統計分詞
建立統計語言模型,對句子進行單詞劃分,然後對劃分的結果進行概率計算,獲得概率最大的分詞方式。這裡就用到了統計學習演算法,隱含馬爾可夫模型(HMM)、條件隨機場(CRF)
需要提前準備好詞典 需要詞典和原始碼可以去我的github下載,例如
示例程式碼:
""" 中文分詞技術 編輯者:饅頭 部落格:https://www.cnblogs.com/mantou0/ """ # encoding=utf-8 class HMM(object): def __init__(self): """ 方法:初始化引數 """ # 主要是用於存取演算法中間結果,不用每次都訓練模型 self.model_file = './data/hmm_model.pkl' # 狀態值集合 self.state_list = ['B', 'M', 'E', 'S'] # 引數載入,用於判斷是否需要重新載入model_file self.load_para = False def try_load_model(self, trained): """ 方法:用於載入已計算的中間結果,當需要重新訓練時,需初始化清空結果 輸入:trained :是否已經訓練好 """ if trained: import pickle with open(self.model_file, 'rb') as f: self.A_dic = pickle.load(f) self.B_dic = pickle.load(f) self.Pi_dic = pickle.load(f) self.load_para = True else: # 狀態轉移概率(狀態->狀態的條件概率) self.A_dic = {} # 發射概率(狀態->詞語的條件概率) self.B_dic = {} # 狀態的初始概率 self.Pi_dic = {} self.load_para = False def train(self, path): """ 方法:計算轉移概率、發射概率以及初始概率 輸入:path:訓練材料路徑 """ # 重置幾個概率矩陣 self.try_load_model(False) # 統計狀態出現次數,求p(o) Count_dic = {} # 初始化引數 def init_parameters(): for state in self.state_list: self.A_dic[state] = {s: 0.0 for s in self.state_list} self.Pi_dic[state] = 0.0 self.B_dic[state] = {} Count_dic[state] = 0 def makeLabel(text): """ 方法:為訓練材料每個詞劃BMES 輸入:text:一個詞 輸出:out_text:劃好的一個BMES列表 """ out_text = [] if len(text) == 1: out_text.append('S') else: out_text += ['B'] + ['M'] * (len(text) - 2) + ['E'] return out_text init_parameters() line_num = -1 # 觀察者集合,主要是字以及標點等 words = set() with open(path, encoding='utf8') as f: for line in f: line_num += 1 line = line.strip() if not line: continue word_list = [i for i in line if i != ' '] words |= set(word_list) # 更新字的集合 linelist = line.split() line_state = [] for w in linelist: line_state.extend(makeLabel(w)) assert len(word_list) == len(line_state) for k, v in enumerate(line_state): Count_dic[v] += 1 if k == 0: self.Pi_dic[v] += 1 # 每個句子的第一個字的狀態,用於計算初始狀態概率 else: self.A_dic[line_state[k - 1]][v] += 1 # 計算轉移概率 self.B_dic[line_state[k]][word_list[k]] = \ self.B_dic[line_state[k]].get( word_list[k], 0) + 1.0 # 計算髮射概率 self.Pi_dic = {k: v * 1.0 / line_num for k, v in self.Pi_dic.items()} self.A_dic = {k: {k1: v1 / Count_dic[k] for k1, v1 in v.items()} for k, v in self.A_dic.items()} # 加1平滑 self.B_dic = {k: {k1: (v1 + 1) / Count_dic[k] for k1, v1 in v.items()} for k, v in self.B_dic.items()} # 序列化 import pickle with open(self.model_file, 'wb') as f: pickle.dump(self.A_dic, f) pickle.dump(self.B_dic, f) pickle.dump(self.Pi_dic, f) return self def viterbi(self, text, states, start_p, trans_p, emit_p): """ 方法:維特比演算法,尋找最優路徑,即最大可能的分詞方案 輸入:text:文字 states:狀態集 start_p:第一個字的各狀態的可能 trans_p:轉移概率 emit_p:發射概率 輸出:prob:概率 path:劃分方案 """ V = [{}] # 路徑圖 path = {} for y in states: # 初始化第一個字的各狀態的可能性 V[0][y] = start_p[y] * emit_p[y].get(text[0], 0) path[y] = [y] for t in range(1, len(text)): # 每一個字 V.append({}) newpath = {} # 檢驗訓練的發射概率矩陣中是否有該字 neverSeen = text[t] not in emit_p['S'].keys() and \ text[t] not in emit_p['M'].keys() and \ text[t] not in emit_p['E'].keys() and \ text[t] not in emit_p['B'].keys() for y in states: # 每個字的每個狀態的可能 emitP = emit_p[y].get( text[t], 0) if not neverSeen else 1.0 # 設定未知字單獨成詞 # y0上一個字可能的狀態,然後算出當前字最可能的狀態,prob則是最大可能,state是上一個字的狀態 (prob, state) = max( [(V[t - 1][y0] * trans_p[y0].get(y, 0) * emitP, y0) for y0 in states if V[t - 1][y0] > 0]) V[t][y] = prob newpath[y] = path[state] + [y] # 更新路徑 path = newpath if emit_p['M'].get(text[-1], 0) > emit_p['S'].get(text[-1], 0): # 最後一個字是詞中的可能大於單獨成詞的可能 (prob, state) = max([(V[len(text) - 1][y], y) for y in ('E', 'M')]) else: # 否則就直接選最大可能的那條路 (prob, state) = max([(V[len(text) - 1][y], y) for y in states]) return (prob, path[state]) # 用維特比演算法分詞,並輸出 def cut(self, text): import os if not self.load_para: self.try_load_model(os.path.exists(self.model_file)) prob, pos_list = self.viterbi( text, self.state_list, self.Pi_dic, self.A_dic, self.B_dic) begin, next = 0, 0 for i, char in enumerate(text): pos = pos_list[i] if pos == 'B': begin = i elif pos == 'E': yield text[begin: i + 1] next = i + 1 elif pos == 'S': yield char next = i + 1 if next < len(text): yield text[next:] hmm = HMM() hmm.train('./data/trainCorpus.txt_utf8') text = '研究生命的起源' res = hmm.cut(text) print(text) print(str(list(res)))
今年來,隨著NLP技術的日益成熟,開源實現的分詞工具越來越多,如,Ansj,盤古分詞等。Jieba分詞結合了基於規則和基於統計這兩類方法。
Jieba提供了三種分詞模式:
精準模式:
試圖將橘子最精準的切開,適合文字分析。
全模式:
把句子中所有可以成詞的詞語都掃描出來,速度非常快,但是不能解決歧義。
搜尋引擎模式:
在精準模式的基礎上,對長詞再次切分,提高召回率,適合用於搜尋引擎分詞。
示例程式碼:
""" 中文分詞技術 編輯者:饅頭 部落格:https://www.cnblogs.com/mantou0/ """ import jieba sent = '中文分詞工具是文字處理不可或缺的一步!' seg_list = jieba.cut(sent,cut_all=True) print('全模式:','/'.join(seg_list)) seg_list = jieba.cut(sent,cut_all=False) print('精準模式:','/'.join(seg_list)) seg_list = jieba.cut(sent) print('預設精準模式:','/'.join(seg_list)) seg_list = jieba.cut_for_search(sent) print('搜尋引擎模式:','/'.join(seg_list))