中文分词算法
中文分词算法的几种方法(带源码)
规则分词
基于规则的分词是一种机械分词方法,主要通过维护字典,在切分语句时将语句的每个字符串与词表中的词进行注意匹配,找到则切分,否则不予切分。
按照切分方式,主要有正向最大匹配法、逆向最大匹配法以及双向最大匹配法三种
特征:从左向右切分
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | "" " 中文分词技术 编辑者:馒头 博客: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) |
特征:从右向左切分
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "" " 中文分词技术 编辑者:馒头 博客: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) |
特征:正逆对比,然后按照最大匹配原则
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | """ 中文分词技术 编辑者:馒头 博客: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下载,例如
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | """ 中文分词技术 编辑者:馒头 博客: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提供了三种分词模式:
精准模式:
试图将橘子最精准的切开,适合文本分析。
全模式:
把句子中所有可以成词的词语都扫描出来,速度非常快,但是不能解决歧义。
搜索引擎模式:
在精准模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | """ 中文分词技术 编辑者:馒头 博客: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)) |
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
本文作者:mt0u的Blog
本文链接:https://www.cnblogs.com/mt0u/p/16226532.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步