Python 自然语言处理(1)中文分词技术
中文分词技术
中文自动分词可主要归纳为“规则分词”“统计分词”和“混合分词”,规则分词主要是通过人工设立词库,按照一定方式进行匹配切分,实现简单高效,但对新词很难进行处理,统计分词能够较好应对新词发现能特殊场景,但太过于依赖语料的质量,因此实践中多是采用两者的结合,即混合分词。
1.1 规则分词
基于规则的分词是一种机械分词方法,主要是通过维护词典,在切分语句时,将语句的每个字符串与词表中的词进行逐一匹配,找到则切分,否则不予切分。
按照匹配切分的方式,主要有正向最大匹配法、逆向最大匹配法以及双向最大匹配法三种方法。
1.1.1 正向最大匹配法
正向最大匹配(Maximum Match Method, MM)法的基本思想为:假定分词词典中的最长词有 i 个汉字字符,则用被处理文档的当前字串中的前 i 个字作为匹配字段,查找字典。若字典中存在这样的一个 i 字词,则匹配成功,匹配字段被作为一个词切分出来。如果字典中找不到这样的一个 i 字词,则匹配失败,将匹配字段中的最后一个字去掉。对剩下的字串重新进行匹配处理。如此进行下去,直到匹配成功,即切分出一个词或剩余字串的长度为零为止,这样就完成了一轮匹配,然后取下一个 i 字字串进行匹配处理,直到文档被扫描完为止。示例代码如下:
- class MM(object):
- def __init__(self, dic_path):
- self.dictionary = set()
- self.maximum = 0
- with open(dic_path, 'r', encoding="utf8") as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- self.dictionary.add(line)
- self.maximum = max(self.maximum, len(line))
- def cut(self, text):
- result = []
- index = 0
- text_length = len(text)
- while text_length > index:
- for size in range(self.maximum + index, index, -1):
- piece = text[index:size]
- if piece in self.dictionary:
- index = size - 1
- break
- index = index + 1
- result.append(piece + '----')
- print(result)
- if __name__ == '__main__':
- text = '研究生命的起源'
- tokenizer = MM('./data/mm_dic.utf8')
- print(tokenizer.cut(text))
1.1.2 逆向最大匹配法
逆向最大匹配(Reverse Maximum Match Method, RMM 法)的基本原理与 MM 法相同,不同的是分词切分的方向与 MM 法相反。逆向最大匹配法从被处理文档的末端开始匹配扫描,每次取最末端的 i 个字符(i 为词典中最长词数)作为匹配字段,若匹配失败,则去掉匹配字段最前面的一个字,继续匹配。相应地,它使用的分词词典是逆序词典,其中的每个词都将按逆序方式存放。在实际处理时,先将文档进行倒排处理,生成逆序文档。然后,根据逆序词典,对逆序文档用正向最大匹配法处理即可。
1.1.3 双向最大匹配法
双向最大匹配法(Bi-direction Matching Method)是将正向最大匹配法得到的分词结果和逆向最大匹配法得到的结果进行比较,然后按照最大匹配原则,选取词数切分最少的作为结果。
双向最大匹配的规则是:
- 如果正反向分词结果词数不同,则取分词数量较少的那个(如“南京市/长江/大桥”的分词数量为3,而“南京市/长江大桥”的分词数量为2,所以返回分词数量为2的)。
- 如果分词结果词数相同:1)分词结果相同,就说明没有歧义,可返回任意一个;2)分词结果不同,返回其中单字较少的那个。
1.2 统计分词
基于统计的中文分词算法主要思想是把每个词看做是由词的最小单位的各个字组成的,如果相连的字在不同的文本中出现的次数越多,就证明这相连的字很可能就是一个词。
- 基于统计的分词,一般要做如下两步操作:建立统计语言模型。
- 对句子进行单词划分,然后对划分结果进行概率计算,获得概率最大的分词方式。这里就用到了统计学习算法,如隐含马尔可夫(HMM)、条件随机场(CRF)等。
1.2.1 HMM 模型
隐含马尔可夫(HMM)是将分词作为字在字串中的序列标注任务来实现的。其基本思路是:每个字在构造一个特定的词语时都占据着一个特定的构词位置,现规定每个字最多只有四个构词位置:即B(词首)、M(词中)、E(词尾)和 S(单独成词),那么下面句子1)的分词结果可以表示成2)所示的逐字标注形式:
1)中文/分词/是/.文本处理/不可或缺/的/一步!
2)中/B文/E分/B词/E是/S文/B本/M处/M理/M不/B可/M或/M缺/E的/S一/B步/E!/S
用数学抽象表示如下:用 λ = λ1λ2...λn 代表输入的句子,n 为句子长度, λi 表示字,ο = ο1ο2...οn 代表输出的标签,那么理想的输出即为:
max = maxP(ο1ο2...οn|λ1λ2...λn)
在分词任务上,ο 即为B、M、E、S 这 4 种标记,λ 为诸如“中”、“文”等句子中的每个字(包括标点等非中文字符)。引入观测独立性假设,即每个字的输出仅仅与当前字有关,于是就能得到下式:
P(ο1ο2...οn|λ1λ2...λn) = P(ο1|λ1)P(ο2|λ2)...P(οn|λn)
通过贝叶斯公式得到:
P(ο|λ) = P(ο, λ)/P(λ) = P(λ|ο)P(ο)/P(λ)
λ 为给定的输入,因此 P(λ) 计算为常数,可以忽略,因此最大化 P(ο|λ) 等价于最大化 P(λ|ο)P(ο)。针对 P(λ|ο)P(ο) 作马尔可夫假设,得到:
P(λ|ο) = P(λ1|ο1)P(λ2|ο2)...P(λn|οn)
对 P(ο) 做齐次马尔可夫假设,每个输出仅仅与上一个输出有关,那么:
P(ο) = P(ο1)P(ο2|ο1)P(ο3|ο2)...P(οn|οn-1)
于是:
P(λ|ο)P(ο) ~ P(λ1|ο1)P(ο2|ο1)P(λ2|ο2)P(ο3|ο2)...P(οn|οn-1)P(λn|οn)
在 HMM 中,将 P(λk|οk) 称为发射概率,P(οk|οk-1) 称为转移概率。通过设置某些 P(οk|οk-1) = 0,可以排除类似 BBB、EM等不合理的组合。
在 HMM 中,求解 maxP(λ|ο)P(ο) 的常用方法是 Veterbi 算法。它是一种动态规划方法,核心思想是:如果最终的最优路径经过某个 οi,那么从初始节点到 οi-1 点的路径必然也是一个最优路径——因为每一个节点 οi 只会影响前后两个 P(οi-1|οi) 和 P(οi|οi+1)。
下面是通过 python 来实现的 HMM,将其封装成一个类:
- class HMM(object):
- def __init__(self):
- self.model_file = './data/hmm_model.pkl'
- self.state_list = ['B', 'M', 'E', 'S']
- self.load_para = False
- def try_load_model(self, 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):
- self.try_load_model(False)
- 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):
- 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 io.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()}
- 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):
- V = [{}]
- path = {}
- for y in states:
- V[0][y] = start_p[y] * emit_p[y].get(text[0], 0)
- path[y] = [y]
- print(V)
- print(path)
- 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
- (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:]
- if __name__ == '__main__':
- hmm = HMM()
- # hmm.train('./data/trainCorpus.txt_utf8')
- text = '这是一个非常棒的方案'
- res = hmm.cut(text)
- print(list(res))
Python 自然语言处理(1)中文分词技术的更多相关文章
- NLP+词法系列(二)︱中文分词技术简述、深度学习分词实践(CIPS2016、超多案例)
摘录自:CIPS2016 中文信息处理报告<第一章 词法和句法分析研究进展.现状及趋势>P4 CIPS2016 中文信息处理报告下载链接:http://cips-upload.bj.bce ...
- 深入浅出Hadoop Mahout数据挖掘实战(算法分析、项目实战、中文分词技术)
Mahout简介 Mahout 是 Apache Software Foundation(ASF) 旗下的一个开源项目, 提供一些可扩展的机器学习领域经典算法的实现,旨在帮助开发人员更加方便快捷地创建 ...
- 学习NLP:《精通Python自然语言处理》中文PDF+英文PDF+代码
自然语言处理是计算语言学和人工智能之中与人机交互相关的领域之一. 推荐学习自然语言处理的一本综合学习指南<精通Python自然语言处理>,介绍了如何用Python实现各种NLP任务,以帮助 ...
- 自然语言处理之中文分词器-jieba分词器详解及python实战
(转https://blog.csdn.net/gzmfxy/article/details/78994396) 中文分词是中文文本处理的一个基础步骤,也是中文人机自然语言交互的基础模块,在进行中文自 ...
- [python] 使用Jieba工具中文分词及文本聚类概念
声明:由于担心CSDN博客丢失,在博客园简单对其进行备份,以后两个地方都会写文章的~感谢CSDN和博客园提供的平台. 前面讲述了很多关于Python爬取本体Ontology.消息盒Inf ...
- NLP自然语言处理 jieba中文分词,关键词提取,词性标注,并行分词,起止位置,文本挖掘,NLP WordEmbedding的概念和实现
1. NLP 走近自然语言处理 概念 Natural Language Processing/Understanding,自然语言处理/理解 日常对话.办公写作.上网浏览 希望机器能像人一样去理解,以 ...
- NLP+词法系列(一)︱中文分词技术小结、几大分词引擎的介绍与比较
笔者想说:觉得英文与中文分词有很大的区别,毕竟中文的表达方式跟英语有很大区别,而且语言组合形式丰富,如果把国外的内容强行搬过来用,不一样是最好的.所以这边看到有几家大牛都在中文分词以及NLP上越走越远 ...
- Go语言 中文分词技术使用技巧(一)
分词技术就是搜索引擎针对用户提交查询的关键词串进行的查询处理后根据用户的关键词串用各种匹配方法进行分词的一种技术. 中文分词(Chinese Word Segmentation)指的是将一个汉字序列( ...
- python利用jieba进行中文分词去停用词
中文分词(Chinese Word Segmentation) 指的是将一个汉字序列切分成一个一个单独的词. 分词模块jieba,它是python比较好用的分词模块.待分词的字符串可以是 unicod ...
随机推荐
- JS JavaScript模块化(ES Module/CommonJS/AMD/CMD)
前言 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了, jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得 ...
- 前序遍历构造已知二叉树(Java)
public BiNode createBiTree() { Scanner input = new Scanner(System.in); int k = input.nextInt(); if(k ...
- Γ(a) 的两种方差与均值
所以 这里是满足 Be(x+1,n-x+1),如果是要服从Be(a,b) 相应的后验概率
- 关于微信小程序分享提示{"errMsg":"config:ok"}
提示 {"errMsg":"config:ok"} {errMsg: "onMenuShareTimeline:ok"} {errMsg: ...
- A Base Class pointer can point to a derived class object. Why is the vice-versa not true?
问题转载自:https://stackoverflow.com/questions/4937180/a-base-class-pointer-can-point-to-a-derived-class- ...
- ElementUI DatePicker 日期选择器控制选择时间范围
选择今天以及今天之后的日期 <el-date-picker v-model="value1" type="date" placeholder=" ...
- java回调函数,看完就懂
java回调函数在网上了看了些例子,比较绕,不够清晰,自己写的一个例子比较通俗,java回调其实很简单. 举个例子我是类B,我有个方法叫b(),现在我要调用类A中的方法a(),写个代码就是: publ ...
- sql server查看表是否死锁
1,查看那个表死锁 select object_name(resource_associated_entity_id) as tableName, request_session_id as pid ...
- Django启动时报错Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试
1.系统环境配置 window 10 + python 3.6 + django 1.11.20 2.报错原因 原因:可以肯定是端口被占用了,但是我只开了一个django,那究竟是哪款软的骚操作占用我 ...
- 你需要Mobx还是Redux?
在过去一年,越来越多的项目继续或者开始使用React和Redux开发,这是目前前端业内很普遍的一种前端项目解决方案,但是随着开发项目越来越多,越来越多样化时,个人又有了不同的感受和想法.是不是因为已经 ...